Deep learning
Bi-encoders and cross-encoders
A bi-encoder embeds the query and the document separately, so search is fast. A cross-encoder reads them together, so it is more accurate but too slow for the whole corpus.
Retrieval needs two things at once: speed over millions of documents, and accuracy on the final few. The two main encoder designs trade these off. A bi-encoder embeds the query and the document separately, then compares the vectors. A cross-encoder packs the query and the document into one input and scores the pair directly.
Concept
A bi-encoder encodes each side on its own, so document vectors can be computed in advance. A cross-encoder reads both texts together, so it can compare them token by token, but it must run once for every query-document pair.
Sentence embeddings
Averaging the token states from BERT is a weak sentence vector. Sentence-BERT fine-tunes an encoder with a contrastive or triplet loss, so pooled sentence vectors compare well with cosine similarity.
Bi-encoders are how many vector database systems and RAG pipelines retrieve. Embed documents offline, embed the query online, then run an approximate nearest-neighbor search.
Comparing the two
| Pattern | Speed at query time | Quality | Typical use |
|---|---|---|---|
| Bi-encoder | Fast, through an ANN index | Good | First-stage retrieval |
| Cross-encoder | Slow, one pass per pair | Better | Re-ranking the top 50 to 200 |
The usual pipeline is a bi-encoder to collect candidates, then a cross-encoder to reorder them.
Training a bi-encoder
The training idea is an in-batch softmax. Each query's positive document sits on the diagonal of a similarity grid, and every other document in the batch is a negative. This runnable sketch uses random vectors to show the mechanics:
import torch
import torch.nn.functional as F
torch.manual_seed(0)
B, D, temperature = 4, 8, 0.05
q = F.normalize(torch.randn(B, D), dim=1) # 4 query embeddings
p = F.normalize(q + 0.1 * torch.randn(B, D), dim=1) # matching docs, close to their queries
logits = (q @ p.T) / temperature # (4, 4) similarity grid
labels = torch.arange(B) # positives sit on the diagonal
loss = F.cross_entropy(logits, labels)
print(round(loss.item(), 4))Output:
0.079The loss is small because each query is closest to its own document. In a real model, q and p come from two encoder networks, and the vectors are learned.
Hard negatives replace random batch items with documents a weaker model retrieved, which are related but not labeled relevant. Filter these carefully. A near-duplicate that is labeled irrelevant poisons training.
Making vectors cheaper
Matryoshka Representation Learning trains an embedding so its first d′ dimensions already form a good embedding on their own. You can store and search the short vectors, and use the full length only when you need it.
Trade-off
Truncating dimensions saves memory and search time roughly in proportion to the cut. Quality drops gradually when the model was trained for it. Truncating a vector that was not trained this way usually costs much more quality. For large corpora, try 256 or 512 dimensions before you pay for 1,536 everywhere.
Common mistakes
- Using a cross-encoder for the whole corpus. It must run once per pair, so it cannot scan millions of documents.
- Mixing normalized and unnormalized vectors. Cosine search assumes unit-length vectors. Normalize before indexing.
- Choosing hard negatives that are actually relevant. The model then learns to reject good matches.
Check yourself
- Why can a bi-encoder search millions of documents but a cross-encoder cannot? Answer: document vectors are computed ahead of time, while a cross-encoder runs on every query-document pair.
- What does Matryoshka training let you do? Answer: shorten the vector to fewer dimensions and keep most of the quality.
Key takeaways
- A bi-encoder embeds query and document separately, so it searches fast with an ANN index.
- A cross-encoder reads the pair together, so it is accurate but slow, and it reranks a short list.
- The common pipeline is bi-encoder for recall, cross-encoder for precision.
- Matryoshka embeddings can be truncated to save storage and search time.
Further learning
Next, see how the same contrastive idea links images and text in CLIP.
Related
Contrastive learning and InfoNCE
Contrastive learning pulls positive pairs together and pushes negatives apart. InfoNCE is the loss that does it with a softmax over a batch, and temperature controls how sharp it is.
CLIP and zero-shot classification
CLIP trains an image encoder and a text encoder so matching image and caption pairs land close together. That lets you classify images by writing the class names as text.
Vector search and vector databases
Approximate nearest neighbor algorithms (HNSW, IVF, PQ) and choosing between pgvector and dedicated stores.