MTEB rankings tell you which embedding models are generally strong; they cannot tell you which one retrieves your jargon, your doc formats, in your language mix.
Tier: Core · Workflow area: RAG & Retrieval Engineering
This playbook builds a small labeled retrieval benchmark from your corpus, evaluates an MTEB-informed shortlist on it, and folds in the dimensions everyone forgets: cost per million tokens, vector storage footprint, and latency.
sentence-transformers, mteb (optional, for public-task sanity checks), FAISS/Qdrant for per-model indexes.Expand the labeled set cheaply. Three sources: (a) pb-21's grounding set; (b) mined pairs — take section headings/FAQ questions that naturally point at passages; (c) LLM-generated queries per chunk, filtered by a round-trip check (generate query from chunk → does BM25 rank that chunk top-3 for the query? keep : discard). Target ≥100 pairs, with ≥30% human-written; mark provenance so you can slice by it.
Shortlist 4–6 models spanning the real trade-off axes. From current MTEB retrieval standings, pick across: small-local (bge-small-en-v1.5, 384d), mid-local (bge-base/gte-base, 768d), large-local (bge-m3 for multilingual/long-doc, 1024d), hosted (text-embedding-3-large, voyage-3-class), and — if latency-sensitive — one Matryoshka/truncatable model to test dimension cuts.
Index and evaluate identically. One index per model over the same chunks; same top-k:
for name, model in shortlist.items():
emb = model.encode(chunks, normalize_embeddings=True)
q_emb = model.encode(queries, normalize_embeddings=True) # use query prompts/prefixes where the model requires them!
scores[name] = recall_mrr_ndcg(q_emb, emb, gold, ks=(1, 5, 10))
Respect each model's query/passage prefix conventions ("query: ...", instruction prompts) — omitting them is the most common way benchmarks silently misrank models.
Add the economics columns. For each model: $/1M tokens to embed (0 for local + GPU-hour cost), vector bytes × chunk count (index RAM/storage), query encode latency p95, and re-embedding cost for your full corpus. A model 1 point better at 2.7× the dimension count is often a net loss at scale.
Test dimension truncation where supported. Matryoshka-trained models let you cut 1024→512→256 dims; measure recall at each cut. A 2× storage/latency saving for <1 point of recall is frequently the right trade.
Decide per-slice, then commit. Slice recall by query provenance and document type. Ship the winner; record model name and revision hash in the index metadata (pb-25 will enforce this), because "which embedder built this index" must never be archaeology.
Embedding model changes require full re-embedding — vectors from different models are not comparable, even at equal dimensions. Any migration plan that mixes old and new vectors in one index is wrong by construction.
mteb library: run your shortlist on 2–3 public retrieval tasks nearest your domain as a sanity check that your harness reproduces published orderings — if it doesn't, suspect the prefix handling.