How to choose an embedding model in 2026
Every RAG pipeline, semantic search feature, or recommendation engine I’ve built in the last two years has hinged on one decision that gets made too fast: which embedding model to use. Teams pick whatever the tutorial they read used, ship it, then six months later discover their retrieval quality is mediocre and switching costs a full re-index of everything.
This guide is for developers and small teams building search, RAG, or clustering features who want a repeatable way to pick an embedding model rather than a gut call. By the end you’ll have tested a shortlist against your own data, understood the real cost and storage tradeoffs at your expected scale, and picked one with a plan for what happens when a better model comes out next quarter, because it will.
I’m not going to tell you “just use OpenAI’s model” or “open source is always cheaper.” Both can be true or false depending on your data and your volume. What follows is the process I actually run before committing to a model in production.
what you need
- An API key for at least one commercial embedding provider: OpenAI, Cohere, Voyage AI, or Google’s Gemini embedding API
- Python 3.10 or later with
pip install openai cohere numpy scikit-learn - A place to store vectors for testing: a flat numpy array is fine for evaluation, pgvector or Qdrant for anything you’ll actually query in production
- 100 to 500 real query-document pairs from your own domain (support tickets, product docs, whatever you’re actually searching over). This is the part most people skip and it’s the part that matters most
- A few dollars of budget. Embedding a few thousand documents against three or four models rarely costs more than a coffee
- Optional: a GPU (even a consumer one like an RTX 3060) if you want to test open-source models like BGE-M3, Nomic Embed, or Jina Embeddings v3 locally instead of paying per call
step by step
1. Define what you’re actually embedding
Write down, concretely, what goes into the model: short queries against long documents, code snippets, multilingual support tickets, product titles, whatever it is. This determines which benchmarks and models are even relevant. A model that tops the leaderboard for English news retrieval can be mediocre at code search or Bahasa Indonesia support tickets.
Expected output: a one-paragraph spec, e.g. “English and Mandarin customer support queries against a 40,000-document knowledge base, average doc length 300 words.”
If it breaks: if you can’t describe your data in one paragraph, you don’t have a clear enough use case yet to pick a model. Go collect real examples first.
2. Set your dimension and storage budget
Embedding dimension directly drives storage and query latency. A 1536-dimension vector at float32 is 6KB per document. At 10 million documents that’s 60GB just for vectors, before any index overhead. Some 2026-era models (OpenAI’s text-embedding-3-large, Nomic Embed v2, Voyage 3) support “Matryoshka” style truncation, meaning you can cut the vector to 256 or 512 dimensions and lose only a little retrieval accuracy.
Expected output: a target dimension range (e.g. 512-1024) based on your document count and query latency requirements.
If it breaks: if you don’t know your document count yet, estimate high. Downsizing later is easy; a full re-index because you under-provisioned storage is not.
3. Pull the retrieval-specific leaderboard, not the overall one
Go to the MTEB leaderboard on Hugging Face and filter by the Retrieval task, not the overall average. The overall MTEB score blends classification, clustering, reranking, and STS tasks, most of which don’t resemble what a RAG pipeline does. Also filter by language if you’re not purely English.
Expected output: a ranked list of 15 to 20 models with retrieval nDCG@10 scores for your language.
If it breaks: MTEB scores move as new versions get submitted and old ones get flagged for contamination. If a model’s score looks suspiciously high relative to its size, check the model card for training data disclosure before trusting it.
4. Shortlist three to four candidates, mixing commercial and open source
From the retrieval leaderboard plus your dimension budget, pick a short list. A reasonable 2026 shortlist usually looks like: one OpenAI model (text-embedding-3-small or -large), one Voyage AI model (voyage-3 or voyage-3-lite), one Cohere model (embed-v4), and one open-source model you can self-host (BGE-M3 or Nomic Embed v2). This gives you a price/performance/control spread rather than optimizing one axis.
Expected output: a table with model name, dimension, price per million tokens, and hosting requirement.
If it breaks: if a candidate has no clear published pricing or an unclear license for commercial use, drop it rather than chasing it down later. That friction compounds.
5. Build your own eval set
This is the step that separates a real decision from a leaderboard-guess. Take 100 to 500 real queries from your domain and hand-label which document(s) each one should retrieve. If you already have support ticket resolutions or a FAQ with known answers, this is often half-done already.
# eval_pairs.py
eval_set = [
{"query": "how do I reset my api key", "relevant_doc_ids": ["doc_042", "doc_198"]},
{"query": "billing cycle for annual plans", "relevant_doc_ids": ["doc_017"]},
# ...
]
Expected output: a JSON or CSV file with at least 100 labeled query-to-document pairs.
If it breaks: if you don’t have 100 real queries yet, pull them from search logs, support tickets, or ask 5 colleagues to write 20 realistic questions each. Synthetic queries from an LLM are a fallback, not a first choice, since they tend to be too clean compared to what real users type.
6. Embed everything and measure recall@k
Run your document set and your eval queries through each shortlisted model, then measure recall@5 and recall@10 (does the correct document show up in the top 5 or top 10 results by cosine similarity).
import numpy as np
from openai import OpenAI
client = OpenAI()
def embed(texts, model="text-embedding-3-small"):
resp = client.embeddings.create(input=texts, model=model)
return np.array([r.embedding for r in resp.data])
doc_vectors = embed(documents, model="text-embedding-3-small")
query_vectors = embed([q["query"] for q in eval_set], model="text-embedding-3-small")
def recall_at_k(query_vec, doc_vecs, doc_ids, relevant_ids, k=5):
sims = doc_vecs @ query_vec
top_k = [doc_ids[i] for i in np.argsort(-sims)[:k]]
return int(any(d in relevant_ids for d in top_k))
scores = [
recall_at_k(qv, doc_vectors, doc_ids, eval_set[i]["relevant_doc_ids"])
for i, qv in enumerate(query_vectors)
]
print(f"recall@5: {sum(scores) / len(scores):.2%}")
Expected output: a recall@5 and recall@10 percentage for each of your three or four candidates on your own data.
If it breaks: if all models score under 60% recall@5, the problem is usually chunking strategy or document quality, not the embedding model. Fix chunking first, then re-run this step.
7. Check latency and cost at your real expected volume
Extrapolate from your test run to your production volume. OpenAI’s text-embedding-3-small runs about $0.02 per million tokens as of their pricing page, Voyage and Cohere publish comparable per-token rates on their own docs. Self-hosted models have no per-call cost but need GPU time and ops overhead. Model this at your actual monthly document and query volume, not the small test batch.
Expected output: a monthly cost estimate for each candidate at your projected volume, plus an average embedding latency per request.
If it breaks: if a “free” open-source model turns out more expensive once you price in GPU rental and maintenance time, that’s a legitimate finding, not a reason to force it to work. Write it down and move on.
8. Pick, lock the model, and version your index
Once you’ve picked a model based on steps 6 and 7, store the model name and version alongside every vector you write, not just in a config file somewhere. When the provider ships a new model version (this happens often, OpenAI, Cohere, and Voyage have all shipped major embedding upgrades in the last two years), you need to know which vectors came from which model so you can re-embed cleanly instead of mixing incompatible vector spaces.
Expected output: a schema field like embedding_model_version on every stored vector row.
If it breaks: if you find mixed model versions already in production, the fix is a full re-embed of the older batch, not a partial patch. There’s no reliable way to compare vectors from two different models directly.
common pitfalls
- Trusting the overall MTEB average. It blends tasks that have nothing to do with retrieval. Always filter to the retrieval subset for your language before shortlisting.
- Never testing on your own data. Leaderboards are trained and evaluated on public datasets. Legal contracts, medical notes, and codebases all have vocabulary that general benchmarks don’t represent well.
- Ignoring storage cost until it’s a bill. A 3072-dimension model looks great on a benchmark and then triples your vector database bill at 50 million documents. Decide the dimension budget before you fall in love with a model.
- Mixing embeddings from different models or versions in one index. Cosine similarity between vectors from two different models is meaningless. If you switch models, re-embed everything, don’t append new vectors from a new model into an old index.
- Skipping re-index planning. Providers update embedding models silently sometimes, or deprecate old ones with a migration window. Know your provider’s deprecation policy before you depend on it in production.
scaling this
At 10x your current document count, the process above barely changes. A single commercial API and pgvector or a simple flat index handle it fine. Cost stays low enough that model choice is mostly about quality.
At 100x, batching and rate limits start to matter. You’ll want to embed in batches of a few hundred documents per API call, handle 429 retries, and consider hybrid search (combining BM25 keyword search with vector similarity) because pure vector search starts missing exact-match queries like product codes or names. Dimension reduction (truncating to 512 or using Matryoshka-trained models) becomes worth the small accuracy tradeoff to control storage cost.
At 1000x, the vector database itself becomes the bottleneck, not the embedding model. You’ll likely move to a dedicated system like Qdrant, Milvus, or a managed option, with sharding and approximate nearest neighbor indexes (HNSW) tuned for your recall/latency tradeoff. Self-hosting an open-source model on your own GPUs often becomes cheaper than per-call API pricing at this volume, but now you own the ops burden of keeping that model server up. Re-indexing when you upgrade models becomes a real project with its own runbook, not a side task, since a full re-embed of a billion-vector index can take days.
where to go next
If you’re deciding whether you even need a vector index versus just feeding more context to an LLM, read fine-tuning vs RAG, which do you actually need. If your retrieval results are good but your agent isn’t using them well, see how to build an AI agent that uses tools. And if dimension and token budgets are still fuzzy for you, context windows explained: how big is big enough covers the token side of this same tradeoff.
If you’re building semantic search specifically to improve on-site search or content discovery for SEO purposes, the team at The SEO Desk covers how embeddings and vector search show up in search product work from that angle.
For the full archive of tutorials like this one, check the aitoolgazette.com blog.
Written by Xavier Fok
disclosure: this article may contain affiliate links. if you buy through them we may earn a commission at no extra cost to you. verdicts are independent of payouts. last reviewed by Xavier Fok on 2026-07-16.