Fine-tuning embeddings
The retrieval lever people skip. When off-the-shelf embeddings underperform on your corpus, the usual response is a better reranker or a bigger model — fine-tuning the embedder is often cheaper and helps more.
It is worth reaching for when your domain has vocabulary the general model never learned: internal product names, part numbers, legal or clinical terms, or a jargon where two phrases mean the same thing to your users and nothing to a general model.
What you are actually training
Contrastive learning: pull matching pairs together in the vector space, push non-matching ones apart.
query ────┐
├── close (positive)
doc A ────┘
query ────┐
├── far (negative)
doc B ────┘The data you need is (query, relevant passage) pairs. Not labels, not categories — pairs.
Where the pairs come from
This is the whole project; the training is the easy part.
| Source | Quality |
|---|---|
| Search logs | query → clicked doc; best, free |
| Support tickets | question → resolving article |
| Generated questions | one per chunk; synthetic |
| Manual annotation | expensive, small |
Search logs are the gold mine if you have them. A click after a query is a weak relevance label, and weak labels in volume beat a few perfect ones.
If you have none, generate: ask a model to write the questions each chunk answers, then embed the question and pair it with its source chunk. It works, with the usual synthetic-data caveats — see Distillation and synthetic data.
Hard negatives are what make it work
A random negative is easy: the model already knows an unrelated document is unrelated, so it learns nothing. Hard negatives — documents your current retriever ranks highly but which are wrong — are where the signal is.
# Mine hard negatives from the current index
candidates = index.search(query, k=50)
negatives = [
c for c in candidates if c.id not in positives
][:5]Gotcha: mined negatives contain false negatives — genuinely relevant documents that simply were not labelled. Training on those teaches the model that a correct answer is wrong. Skip the very top ranks when mining, or use a judge to filter.
The training loop
from sentence_transformers import (
SentenceTransformer, losses, InputExample,
)
model = SentenceTransformer("base-model")
examples = [
InputExample(texts=[q, pos, neg])
for q, pos, neg in triples
]
loss = losses.MultipleNegativesRankingLoss(model)MultipleNegativesRankingLoss is the workhorse: it treats the other passages
in the batch as negatives, so you get many negatives for free and only need
positive pairs. Larger batches give more in-batch negatives and better
results, which makes batch size the parameter that matters most.
A few thousand pairs and an hour on one GPU is a realistic scale. This is not a large training job.
Measure retrieval, not loss
The loss going down means very little. Evaluate the thing you care about: recall@k and MRR on a held-out set of queries, against the base model as the baseline.
Expect the win to be domain-shaped: large on jargon-heavy corpora, small on general prose where the base model was already good. If you see a 2-point gain, a reranker was probably the better spend.
The operational cost
Everything from What are embeddings? about changing models applies, because you have changed the model:
- Re-embed and re-index the entire corpus. Vectors from the old and new embedder are not comparable.
- Version the index and cut over, rather than mixing.
- You now own a model. It needs storage, serving, and retraining when the domain drifts — which is a real commitment against a hosted API.
That last point is the honest counterargument, and worth raising yourself.
The alternatives, ranked by effort
- Better chunking. Usually the biggest retrieval win, and free.
- Hybrid search plus a reranker. A cross-encoder captures much of what fine-tuning would, without a new model to own.
- A different off-the-shelf embedder, evaluated on your data.
- Fine-tune, once the above are exhausted and the domain is genuinely unusual.
Naming that order is the senior answer: fine-tuning the embedder is a real tool and it is fourth, not first.
Related
Interview angle 5
- “Retrieval is poor on our corpus. What would you try?” - chunking first, then hybrid search with a reranker, then a different off-the-shelf embedder, and only then fine-tune the embedder. Naming that order matters: fine-tuning is a real tool and it is fourth.
- “What does fine-tuning an embedder need?” - (query, relevant passage) pairs, not labels. Search logs are the best source because a click is a weak relevance signal available in volume; support tickets work too; generating a question per chunk is the fallback.
- “What are hard negatives and why do they matter?” - documents the current retriever ranks highly but which are wrong. A random negative teaches nothing because the model already separates unrelated text. The catch is false negatives — genuinely relevant documents that were never labelled — so skip the very top ranks when mining.
- “How do you know it worked?” - recall@k and MRR on held-out queries against the base model, not the training loss. Expect a large win on jargon-heavy corpora and a small one on general prose, where a reranker would have been the better spend.
- “What does it cost operationally?” - a full re-embed and re-index, since old and new vectors are not comparable, plus you now own a model that needs serving and retraining as the domain drifts. That is the real argument against it.