Skip to content

Choosing an embedding model

There are a lot of embedding models. Most comparisons rank them by benchmark score, which is the least useful way to choose, because the scores are close, they change monthly, and they were measured on somebody else’s documents.

This page organises them by the things that don’t change month to month: who runs them, what they cost you in storage, what languages they handle, and where each one will surprise you.

Almost every model choice comes down to these. Answer them and the field narrows to two or three candidates.

1. Can your text leave your infrastructure? If no, you need a self-hosted open model, and the decision is nearly made. If yes, hosted APIs are less work and generally better.

2. English only, or multilingual? Multilingual models cost a little quality on English in exchange for handling everything else. Getting this wrong is expensive to fix later — it’s a full re-embed.

3. How many vectors will you store? Under a million, dimensions barely matter. Above that, 3,072 versus 768 is the difference between a comfortable machine and an uncomfortable bill.

You send text, you get vectors, somebody else runs the GPUs.

Model Provider Dimensions Max input Notable
text-embedding-3-small OpenAI 1,536 (reducible) 8,191 tokens The sensible default. Cheap, fast, good enough for most work
text-embedding-3-large OpenAI 3,072 (reducible) 8,191 tokens Better on hard retrieval; double the storage
Embed v4 Cohere 256 / 512 / 1,024 / 1,536 128k tokens Multimodal — text and images in one vector space. 100+ languages
voyage-3-large Voyage AI 2,048 / 1,024 / 512 / 256 32k tokens (v3.5) Strong on legal, finance and code. Quantization-aware training
gemini-embedding-001 Google 3,072 (reducible to 1,536 / 768) 2,048 tokens Well integrated with Vertex AI and BigQuery

Download the weights, run them yourself. Nothing leaves your network, and there’s no per-token cost — you’re paying in hardware and operations instead.

Model From Dimensions Max input Licence Notable
BGE-M3 BAAI 1,024 8,192 tokens Apache 2.0 Dense, sparse and multi-vector from one model. 100+ languages
multilingual-e5-large Microsoft 1,024 512 tokens MIT Strong multilingual baseline. Needs prefixes (below)
nomic-embed-text-v1.5 Nomic 768 8,192 tokens Apache 2.0 Fully open: weights, code and training data. Reducible dimensions
all-MiniLM-L6-v2 SBERT 384 ~256 word pieces Apache 2.0 Tiny and fast. What the 5-minute guide uses

BGE-M3 is the interesting one if you were planning hybrid search. It produces dense vectors, sparse (keyword-style) weights and multi-vector representations from a single pass — so you get the hybrid retrieval benefit without running a separate keyword index.

all-MiniLM-L6-v2 is older and weaker than everything else here, and still frequently the right choice for a prototype: it’s 90 MB, runs on a laptop CPU, and gets you working code before you’ve decided anything.

This one silently ruins retrieval, and there is no error message.

Several open models were trained with instruction prefixes, and they expect you to use them. Feed a query in unprefixed and quality quietly drops.

# E5 family — different prefix for queries and documents
document_vector = model.encode("passage: The spare set is in the kitchen drawer.")
query_vector = model.encode("query: where did I put my keys?")
# Nomic — same idea, different words
document_vector = model.encode("search_document: The spare set is in the kitchen drawer.")
query_vector = model.encode("search_query: where did I put my keys?")
# BGE-M3 and the OpenAI models — no prefixes, just pass the text
document_vector = model.encode("The spare set is in the kitchen drawer.")

Note the asymmetry: documents and queries get different prefixes. That’s the point — the model learned that a short question and a long passage play different roles, and the prefix tells it which is which.

Several models now support Matryoshka dimension reduction: they’re trained so the most important information sits at the front of the vector, letting you truncate with less loss than you’d expect.

Supported by OpenAI’s -3 models, Cohere Embed v4, Voyage, Gemini and nomic-embed-text-v1.5.

response = client.embeddings.create(
model="text-embedding-3-large",
input=texts,
dimensions=256, # instead of 3072
)

That’s a twelvefold storage reduction, and truncated -3-large still beats some full-size older models. Whether it beats your current setup is an empirical question — measure it. But measure it before you provision a bigger machine, not after.

Starting out, hosted: text-embedding-3-small. Cheap, fast, well documented, good SDKs in every language on this site. Upgrade when you have a measurement showing the model is your bottleneck, which for most projects never happens.

Starting out, self-hosted: all-MiniLM-L6-v2 to get working, then BGE-M3 when you’re serious. BGE-M3’s hybrid output is worth more in practice than a couple of benchmark points.

Multilingual: BGE-M3 or Cohere Embed v4. Don’t try to make an English-first model work across languages.

Images alongside text: Cohere Embed v4, or CLIP if you’d rather self-host.

Long or specialised documents (legal, finance, code): try Voyage — that’s where it consistently reports its biggest margins.

Whichever you pick, changing your mind means re-embedding everything — vectors from different models are not comparable, and mixing them degrades results with no error message. Plan it as a migration, and keep the source text so you can.