Compare at least one model from each representation family, not just several large dense encoders. Dense models capture meaning and paraphrase; learned sparse models such as SPLADE keep exact terms and identifiers while adding related words; multi-output models such as BGE-M3 produce both from one pass; and compact static models trade some quality for very cheap encoding. Milvus stores each as a different field type, so you can test them side by side and combine the winners with hybrid search.
Shortlist models by the workload. Keep model revisions separate; equal dimensions do not mean equal spaces.
How do the representation families differ?
| Family | What the vector looks like | Strong at | Weak at | Milvus field |
|---|---|---|---|---|
| Dense contextual encoder | A few hundred to a few thousand floats, all non-zero | Paraphrases, synonyms, cross-language meaning | Rare identifiers, codes, exact names | FLOAT_VECTOR (or FLOAT16/BFLOAT16/INT8) |
| BM25 (classical sparse) | Vocabulary-sized vector of term weights from a fixed formula | Exact keywords, no training needed | Synonyms and paraphrase | SPARSE_FLOAT_VECTOR generated by a BM25 Function |
| Learned sparse (SPLADE-style) | Vocabulary-sized vector; a model chooses which terms matter and adds related terms | Exact terms plus expansion; interpretable weights | Larger index than BM25; out-of-vocabulary IDs still hard | SPARSE_FLOAT_VECTOR |
| Multi-output (BGE-M3, mGTE) | Dense vector plus sparse weights, and for BGE-M3 also token vectors | One model feeds hybrid search | Each output still needs its own field and index | Dense + sparse fields, optional embedding list |
| Instruction-conditioned dense | Dense, but the query carries a task instruction or prefix | Adapting one model to several tasks | Wrong or missing prompts quietly hurt quality | FLOAT_VECTOR |
| Static (Model2Vec-style) | Dense vector built from precomputed token embeddings, no transformer at inference | Extremely fast CPU encoding, tiny footprint | Context-dependent meaning, negation, long queries | FLOAT_VECTOR |
Two terms are easy to confuse. A mixture-of-experts encoder activates only part of its network per token, but its output is still a normal dense vector, not a sparse one. And Matryoshka models are dense models trained so the first N dimensions of the output still work on their own, which lets you store shorter vectors; truncate only to the sizes the model card documents.
When should you choose dense, sparse, or both?
Dense retrieval is the right default for natural-language questions over prose: support articles, documentation, chat history. It finds “how do I cancel my plan” when the document says "ending your subscription".
Sparse retrieval matters when users type the exact thing they want: SKUs, error codes, function names, legal citations, drug names. Dense models often blur these because similar-looking codes land near each other. BM25 is the cheapest sparse option and needs no model at all; learned sparse models usually beat BM25 on benchmarks by learning which terms to weight and which related terms to add, at the cost of model inference and a larger inverted index.
In practice the strongest setup for mixed workloads is hybrid: a dense field and a sparse field over the same text, searched together and fused. If your evaluation shows dense-only misses identifiers or sparse-only misses paraphrases, that is the signal to go hybrid. See what hybrid search is and when to use it and how a sparse inverted index differs from dense ANN.
Compact and static models earn a place on the shortlist when encoding cost dominates: billions of short records, edge devices, or a quick baseline that tells you how much a large model actually adds.
Families worth putting on the shortlist
These are well-documented representatives of each family, not a ranking. Check the live MTEB leaderboard for current scores and each model card for prompts, pooling, and license.
| Model | Family | Notes for evaluation | PyMilvus Model route |
|---|---|---|---|
| BAAI/bge-m3 | Dense + sparse + token vectors | Over 100 languages and long inputs; pick which outputs you need | BGEM3EmbeddingFunction |
| Alibaba-NLP/gte-multilingual-base | Dense + sparse | Multilingual; store each output in its own field | MGTEEmbeddingFunction |
| naver/splade-cocondenser-ensembledistil | Learned sparse | Tokenizer vocabulary and model revision define the index; change either and re-encode | SpladeEmbeddingFunction |
| intfloat/multilingual-e5-large-instruct | Instruction-conditioned dense | Follow its query instruction format; test cross-language questions | Generic Sentence Transformers route |
| hkunlp/instructor-xl | Instruction + text | Takes instruction/text pairs, not concatenated strings | InstructorEmbeddingFunction |
| nomic-ai/nomic-embed-text-v1.5 | Task-prefixed dense, Matryoshka | Requires task prefixes; supports documented shorter dimensions | NomicEmbeddingFunction |
| nomic-ai/nomic-embed-text-v2-moe | Multilingual MoE dense | Sparse activation inside, dense vector out | Generic or native Nomic route |
| Snowflake/snowflake-arctic-embed-l-v2.0 | Retrieval-tuned dense | Check its prompt, normalization, and language coverage | Generic Sentence Transformers route |
| KaLM-Embedding/KaLM-embedding-multilingual-mini-instruct-v2.5 | Compact instructed dense | Keep its mean pooling and query instructions | Generic or official implementation |
| maidalun1020/bce-embedding-base_v1 | Chinese/English dense | An established bilingual baseline | Generic or official implementation |
| minishlab/potion-base-8M | Static dense | Very cheap encoding; test hard, context-dependent queries | Model2VecEmbeddingFunction |
| sentence-transformers/all-MiniLM-L6-v2 | Compact dense baseline | Good for plumbing tests; short input limit and English-centric | SentenceTransformerEmbeddingFunction |
A dedicated wrapper means PyMilvus Model knows how to call that family; it still has to match the exact checkpoint’s prompts, arguments, and output. A generic wrapper or a package dependency does not certify every model a provider publishes. The Milvus Model support matrix lists what each wrapper actually handles.
How do you store each family in Milvus?
Dense output goes in a FLOAT_VECTOR field whose dim matches the model, indexed with a metric the model card recommends (usually COSINE or IP for normalized vectors). Learned sparse output goes in a SPARSE_FLOAT_VECTOR field, which needs no dimension, accepts dictionaries like {2: 0.33, 98: 0.72}, and is indexed with SPARSE_INVERTED_INDEX using the IP metric. For BM25 you do not need a model: add a BM25 Function that turns a text field into a sparse field automatically.
A multi-output model such as BGE-M3 fills both fields from one encoding pass:
from pymilvus.model.hybrid import BGEM3EmbeddingFunction
ef = BGEM3EmbeddingFunction(use_fp16=False, device="cpu")
docs_emb = ef(docs) # {"dense": [...], "sparse": ...}
# insert docs_emb["dense"] into a FLOAT_VECTOR field
# and docs_emb["sparse"] into a SPARSE_FLOAT_VECTOR field
At query time, build one AnnSearchRequest per field and call hybrid_search with an RRF or weighted ranker to fuse the two result lists. BGE-M3’s token-level vectors are a third, multi-vector representation; they belong in an embedding list, described in multi-vector retrieval.
Run a fair comparison
Keep the documents, chunking, and relevance judgments fixed, and encode them once per candidate into separate fields or collections. Your query set should deliberately include rare identifiers, synonyms and paraphrases, cross-language questions, and queries with no correct answer, because that is where the families separate. Measure first-stage recall on its own before adding a reranker, and report encoding throughput and memory, vector storage, and filtered query latency next to quality.
Two rules prevent most mistakes: never mix vectors from different models in one field even if the dimensions match, and record the model revision, prompts, and normalization alongside every collection so the comparison can be reproduced.
Related reading
- What are dense and sparse embeddings?
- What is full-text search, and how does BM25 fit?
- Which current embedding models should you evaluate for Milvus?
- How do you read MTEB and compare the current embedding shortlist?
Next step: build a dense-plus-sparse collection with the multi-vector hybrid search guide, or get a local instance running with the Milvus Quickstart.