Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path →
ColBERT,
explained.
Squash a whole passage into one vector and you gain speed but lose detail — two documents can look identical to the search index even when only one truly contains your terms. ColBERT keeps a vector for every token and scores with a simple, clever operation called MaxSim: let each query word find its single best match anywhere in the document, then add those up. It buys back the precision of exact term matching without giving up the speed of vector search. Here's the idea, and the case where it clearly wins.
What changes against one vector per passage: keep one per token
Rung 6's passage encoder must compress a whole passage into a single vector, and that is a genuine information bottleneck rather than an inefficiency: a passage about several things has one point to represent all of them, and a query matching one specific term in it has no way to say so. Keyword scoring, for all its crudeness, never loses that — it knows exactly which words matched.
The change is to stop compressing. Keep one vector per token for the passage and for the query, and score a pair by, for each query token, finding its best-matching passage token and summing those maxima. Fine-grained term matching is recovered, in a learned space where "matching" means semantic similarity rather than string equality — so a query term can match a paraphrase, which keyword scoring cannot, while still being attributable to a position, which rung 6 cannot.
What keeps it affordable is when the interaction happens, and this is the rung's real contribution. The expensive encoder still runs once per passage, offline, exactly as at rung 6; only the cheap maximum-and-sum runs at query time. That is late interaction — query and document meet after both have been encoded independently, not before. It sits precisely between rung 6, where they never interact, and pairwise scoring, where they interact from the first layer and nothing can be precomputed.
What one vector loses
Dense retrieval, in its standard form, encodes a whole query into one vector and a whole passage into one vector, then ranks by their similarity. It is fast and it captures meaning — but pooling an entire passage into a single point is lossy. Fine details get averaged away, and two very different passages can end up near the same location in vector space. The one that actually contains your search terms and the one that merely sounds related can become indistinguishable to the index.
Meanwhile the opposite extreme — a cross-encoder that jointly reads the query and passage together — is exquisitely precise but ruinously slow: it must run the full model on every query-document pair, so it cannot scan millions of documents. ColBERT threads between them: keep the fine-grained, token-level matching of a cross-encoder, but arrange the computation so documents can be encoded once, offline, and searched fast.
Don't crush a passage into one vector — keep one per token, and let every query word grab its best matching word in the document.
A vector per token
ColBERT runs a BERT encoder over the query and over each document, but instead of pooling to a single vector it keeps the contextual embedding of every token. A query becomes a small set of vectors (one per query token); a document becomes a larger set (one per document token). These are contextual — the vector for "bank" differs in "river bank" and "bank loan" — so token-level matching here is semantic, not just string equality.
Documents are encoded ahead of time and their token vectors stored in an index. That offline step is the key to making a per-token model practical: at search time you never re-run the encoder on documents, you only compare the query's handful of vectors against precomputed ones.
Late interaction: MaxSim
The scoring is the heart of the paper, and it is beautifully simple. For each query token, find its maximum similarity to any document token — the single document word that best matches it — and sum those maxima over all query tokens. Compare that to what single-vector retrieval does:
single-vector: score(q, d) = sim( pool(q) , pool(d) )
Every query token votes independently, matching to the best evidence anywhere in the document, and the votes add up. Because the pooling in single-vector retrieval averages first, two documents can tie even when only one holds the query's terms — while MaxSim, matching term by term, separates them. The runnable version below builds exactly that case: a single pooled vector scores two documents identically, and ColBERT ranks the one with the real terms higher.
Why 'late' means fast
The name is precise. In a cross-encoder the query and document interact early — fused at the input and processed together by the full model, which is why it is accurate but unscalable. In ColBERT the two are encoded completely independently, and they only meet at the very end, in the cheap MaxSim step. The interaction is late, and being late is what makes it cheap.
In practice ColBERT is often used as a strong first-stage or re-ranking retriever: fast enough to search large collections, precise enough to surface the right passages, and cheap enough to store. The trade it accepts is index size — many vectors per document instead of one — which later work (ColBERTv2, PLAID) shrinks with compression and clustering.
How ColBERT encodes, scores and retrieves, step by step
ColBERT (Khattab and Zaharia, SIGIR 2020) fine-tunes a single BERT-base model to turn any text into a bag of small vectors, one per token, and defines relevance as a sum of maxima over them. The details that make that work are in how the two encoders differ and in how the index is searched.
- Mark the input. One BERT is shared by queries and documents. A query has a special [Q] token inserted after [CLS]; a document gets [D] instead. The marker is the only signal telling the model which side it is encoding.
- Augment the query with [MASK]. Every query is padded with BERT's [MASK] token up to a fixed length Nq = 32, and longer queries are truncated. The masked positions still produce output vectors, which the authors describe as a learned, differentiable form of query expansion: they can re-weight the terms that are present or stand in for terms the query did not use. Documents are not padded this way.
- Project and normalise. Each output vector passes through a linear layer with no activation, from 768 down to m = 128 dimensions, and is L2-normalised, so every dot product is a cosine. Document vectors belonging to punctuation tokens are dropped to save space.
- Train on triples. From MS MARCO triples — a query, a relevant passage and a non-relevant one — compute the late-interaction score of both passages and apply a softmax cross-entropy over the pair. Adam at a learning rate of 3 × 10^−6, batch size 32; BERT, the marker embeddings and the projection are all fine-tuned together.
- Index the collection offline. Encode every passage — MS MARCO has 8.8 million — and store its token vectors, 128 numbers each. Encoding the query is the only BERT pass left for search time.
- Re-rank mode. Given a candidate list, typically BM25's top 1,000, gather the candidates' stored vectors and compute the score on a GPU as one batched matrix product per query, followed by a max over document tokens and a sum over query tokens.
- End-to-end mode. To search the whole collection, put every document token vector into a FAISS IVFPQ index — 2,000 cells, the nearest 10 probed, 16-byte codes. Each of the 32 query vectors retrieves its top k′ nearest document vectors (k′ = k/2 in the paper's description, k′ = k = 1,000 in its reported runs); the documents those vectors belong to form a candidate set of at most 32 · k′, which is then re-ranked exactly with the full vectors.
End-to-end mode is what separates ColBERT from a re-ranker. Because the score decomposes into one maximum per query vector, each query vector's nearest document tokens are a natural way to find candidates, and a standard vector index can do that search. The price is storage: a 128-dimensional vector for every token of every passage, which the paper reduces further by testing fewer dimensions and lower-precision storage, at a modest cost in quality.
ColBERT's encoders, score and training loss
Ed = Filter( Normalize( W · BERT( [CLS] [D] d1 … dn ) ) )
Sq,d = Σi maxj Eq,i · Ed,j
L = −log [ e^S(q, d+) / ( e^S(q, d+) + e^S(q, d−) ) ]
W— a linear projection from BERT's 768 dimensions to m = 128, with no activation.Normalize— L2 normalisation, so each dot product is a cosine between −1 and 1.Filter— removes the vectors of punctuation tokens from documents.Nq = 32— the fixed query length; the [MASK] pads are part of Eq and contribute their own maxima.d+, d−— the relevant and non-relevant passages of a training triple.
Because each term of the sum is at most 1, no score can exceed Nq = 32, and every query position — real word or mask — gets one equal vote. At the extremes: with a single query vector the score collapses to one nearest-token match, and replacing the max with a mean over document tokens turns the score into a comparison of averages, discarding exactly the term-level evidence the design exists to keep; the paper's ablation found that average-similarity version clearly worse. The cost per pair is Nq × |d| dot products of length 128 — about 740,000 multiply-adds for a 180-token passage — which is why scoring 1,000 candidates fits in milliseconds on a GPU.
ColBERT on MS MARCO: quality, latency and FLOPs
The main benchmark is MS MARCO passage ranking — 8.8 million passages, measured by MRR@10 on the development set — with TREC CAR as a second collection. Re-ranking experiments score the official BM25 top 1,000 for each query; end-to-end experiments search the whole collection. Latency is per query on a GPU, and FLOPs count the work per query.
| Method | MRR@10 (dev) | Latency per query | FLOPs per query |
|---|---|---|---|
| BM25 (official ranking) | 16.7 | — | — |
| KNRM | 19.8 | 3 ms | 592 million |
| fastText + ConvKNRM | 29.0 | 28 ms | 78 billion |
| BERT-base cross-encoder (published) | 34.7 | 10,700 ms | 97 trillion |
| BERT-base cross-encoder (authors' training) | 36.0 | 10,700 ms | 97 trillion |
| BERT-large cross-encoder | 36.5 | 32,900 ms | 340 trillion |
| ColBERT, re-ranking the BM25 top 1,000 | 34.9 | 61 ms | 7 billion |
| ColBERT, end-to-end retrieval | 36.0 | 458 ms | — |
ColBERT re-ranking matched the published BERT-base cross-encoder at about 170 times lower latency and roughly 13,900 times fewer FLOPs, while staying about a point below a cross-encoder the authors trained themselves — the honest size of the trade. End-to-end retrieval beat re-ranking because BM25's top 1,000 misses relevant passages that ColBERT's own search finds; its recall at 1,000 was about 97%.
The ablations, run with a smaller five-layer BERT for speed, isolate the design: a single [CLS] vector scored by dot product was clearly below late interaction; replacing the max with an average similarity lost much of the gain; and removing the [MASK] query augmentation cost a noticeable amount. The cost the paper is open about is space — a vector per token makes the index far larger than one vector per passage — which is what ColBERTv2's residual compression and the PLAID engine later attacked.
What the authors admit, and what happened next
The cost is storage, and it is large: one vector per token rather than per passage is an index with roughly the average passage length times as many vectors — in bytes, about that length divided by six at equal precision, because each vector is 128-dimensional rather than 768. The paper acknowledges it and later work attacked it hard — aggressively reducing the per-token dimension, quantising with the techniques from rung 4, and pooling similar token vectors — which brought the index to a practical size and is the main reason the approach is usable at all.
Retrieval is also more involved than a single inner-product search: finding candidates when the score is a sum of maxima over token vectors does not reduce to one nearest-neighbour query, so the serving path is a multi-stage affair rather than a drop-in index lookup.
How it held up: as the strongest available argument that the single-vector bottleneck is real, and as a family that keeps returning because it occupies a genuinely useful point on the accuracy-cost curve. Most production stacks still choose rung 6 plus a pairwise re-ranker, because it is simpler and the re-ranker recovers the fine-grained matching at the top of the list. The lasting idea is late interaction as a design axis — decide how early the query and the document are allowed to meet, and you have chosen your position on that curve.
Where MaxSim beats a single vector
MaxSim is one line: each query token takes its best cosine match among the document's token vectors, and the maxes are summed. The demo is the whole argument for ColBERT — a query for two distinct terms, one document that contains both and one that sits in the blurry middle mentioning neither sharply. Pool everything to one vector and the two documents tie exactly; score with MaxSim and the document with the real terms wins. The runnable also checks MaxSim is order-independent and that adding a matching token never lowers the score.
Write the body of single_vector_score. Everything else in the file is
already written; this is the function the paper is about. Press ▶ Run to check it against
the 5 assertions below — they start red.
- MaxSim picks the best-matching doc token
- ColBERT sums each query token's best match
- Single-vector ties but ColBERT distinguishes
- MaxSim is order-independent (bag of vectors)
- Adding a matching doc token never lowers the score
Where it sits
ColBERT staked out the productive middle of neural retrieval, and the field has kept building there. Its lasting contribution is late interaction as a design pattern: encode independently for scalability, interact cheaply at the end for precision. That template shows up well beyond text search now — anywhere you want fine-grained matching between two sets of things without paying to jointly process every pair.
It also reframed a false choice. Retrieval had felt like a binary between fast-but-coarse single vectors and accurate-but-unscalable cross-encoders. ColBERT showed the axis is continuous, and that a well-placed operation — take the max, then sum — can capture most of the precision at a fraction of the cost. Simple math, carefully positioned, moving the whole frontier.
MaxSim's "each query token finds its best match" is exactly a soft version of what a keyword search does when it checks whether each query term appears — which is why ColBERT tends to keep the term-precision of BM25 while adding semantic matching on top.
Why it still matters
ColBERT sits between the two dominant retrieval styles and takes the best of both. A bi-encoder squashes a whole document into one vector — fast but lossy. A cross-encoder reads query and document together — accurate but far too slow to run over a corpus. ColBERT's late interaction keeps a vector per token and compares them only at query time, capturing fine-grained matching without the cross-encoder's cost.
The mechanism is "MaxSim": for each query token, find its most similar document token and sum those maxima. This lets the model reward a document that strongly matches each part of the query — catching term-level and phrase-level relevance a single pooled vector blurs away — while still precomputing all document token embeddings offline, so search stays fast.
The trade-off is storage: a vector per token is far larger than one vector per document, which is why later work (ColBERTv2, PLAID) focused on compressing and indexing those token embeddings efficiently. In exchange you get retrieval quality that often beats single-vector dense retrieval, especially on queries where precise term matching matters.
ColBERT matters because it named and popularized late interaction as a distinct, practical point on the retrieval spectrum — more expressive than bi-encoders, cheaper than cross-encoders. In RAG systems chasing better recall and precision, token-level late-interaction retrievers remain a strong option, and the idea keeps resurfacing as teams push retrieval quality past what a single embedding per document can deliver.
Questions people ask about ColBERT
What is query augmentation in ColBERT and why does it use MASK tokens?
ColBERT pads every query with BERT's [MASK] token up to a fixed length of 32 tokens before encoding it, and truncates longer queries. The masked positions still pass through BERT and produce output vectors, and those vectors take part in MaxSim scoring exactly like the vectors of real query words. Khattab and Zaharia describe this as a soft, differentiable form of query expansion: a mask vector can learn to stand for terms the user did not type, or to add weight to the important terms that are there. Because the encoder is trained with the padding in place, the model learns what the masks should represent. The paper's ablation shows that removing query augmentation lowers MS MARCO ranking quality, and documents are never padded this way.
How does ColBERT do end-to-end retrieval with FAISS?
Every document token vector in the collection goes into a FAISS index; the paper used IVFPQ with 2,000 partitions, searched the 10 nearest, and compressed each 128-dimensional vector into a 16-byte code. At query time each of the query's 32 vectors runs its own approximate nearest-neighbour search and returns its top k′ document token vectors (k/2 in the paper's description, 1,000 in its reported runs). Those hits are mapped back to the documents they belong to, giving at most 32 × k′ candidate documents, and the candidates are then scored exactly by loading their full token vectors and computing MaxSim. On MS MARCO this two-stage search reached 36.0 MRR@10, better than re-ranking BM25's top 1,000, at about 458 milliseconds per query. ColBERTv2 and the PLAID engine later replaced the token-level search with centroid-based candidate generation to cut that latency sharply.
How much faster is ColBERT than a BERT cross-encoder?
In the ColBERT paper, re-ranking the BM25 top 1,000 for one MS MARCO query took about 61 milliseconds with ColBERT against about 10,700 milliseconds for a BERT-base cross-encoder, roughly 170 times faster, and the work per query fell from about 97 trillion to about 7 billion floating-point operations. Quality stayed close: 34.9 MRR@10 for ColBERT, against 34.7 for the published BERT-base cross-encoder, 36.0 for one the authors trained themselves and 36.5 for BERT-large. The speed comes from where the expensive encoding happens. A cross-encoder runs BERT once for every query-document pair at query time; ColBERT runs BERT once per document offline and once per query online, leaving only a small matrix product, a max and a sum per pair.
Why does ColBERT project to 128 dimensions and normalise its vectors?
Storage and speed. ColBERT keeps one vector for every token of every document, so the index holds dozens of vectors per passage instead of one; projecting BERT's 768-dimensional outputs down to 128 cuts that storage six-fold and makes each dot product six times cheaper. The projection is a single linear layer with no activation, learned during fine-tuning. Normalising every vector to unit length turns each dot product into a cosine similarity between minus one and one, so every query position contributes a bounded amount to the sum of maxima. The paper also tested fewer dimensions and lower-precision storage and found quality fell only modestly, a direction ColBERTv2 later pushed much further with residual compression.
What changed in ColBERTv2 and PLAID?
ColBERTv2, by Santhanam, Khattab and colleagues in 2022, kept late interaction and attacked its two weaknesses. For storage it introduced residual compression: each token vector is stored as the id of its nearest centroid plus a residual quantised to one or two bits per dimension, which the authors report shrinks the index six to ten times. For quality it used denoised supervision, distilling scores from a cross-encoder and mining hard negatives, which helped most out of domain. PLAID, a retrieval engine for ColBERTv2 from the same group, uses those centroids to generate and prune candidates before any full scoring, cutting search latency by a large factor on both CPU and GPU. Together they made late interaction cheap enough to serve as a first-stage retriever.
4 questions to test what you just read
Work each one out before you open it — a number, a prediction or a reason, not a definition. Producing the answer is what makes it stick. Your progress is kept on this device.
A 6-word query is encoded by ColBERT. How many query vectors take part in MaxSim, and what is the highest score any document can reach?
Show answer
32, whatever the query length: it is padded with [MASK] to the fixed length, and every position, pads included, produces a vector. Each term of the sum is a cosine of at most 1, so the maximum possible score is 32.
Re-ranking took 61 ms with ColBERT and 10,700 ms with a BERT-base cross-encoder, at 7 billion against 97 trillion FLOPs per query. Compute both ratios. Why is the FLOP ratio so much larger?
Show answer
Latency: 10,700 / 61 ≈ 175×. FLOPs: 97 × 10^12 / 7 × 10^9 ≈ 13,900×. Most of ColBERT’s remaining arithmetic is one short BERT pass over the query; its latency also includes gathering stored document vectors and fixed overheads, which do not shrink with the arithmetic.
MS MARCO has 8.8 million passages. If passages average 70 tokens after punctuation is dropped, how large is a ColBERT index of 128-dimensional vectors stored as 2-byte floats, against one 768-dimensional float32 vector per passage?
Show answer
ColBERT: 8.8M × 70 × 128 × 2 bytes ≈ 158 GB. Single vector: 8.8M × 768 × 4 bytes ≈ 27 GB. About 6× larger even after the 128-d projection and half precision — the problem ColBERTv2’s residual compression later cut by 6–10×.
With the paper’s example setting k′ = k/2 and k = 1,000, each of 32 query vectors fetches k′ nearest document vectors in end-to-end retrieval. What is the most candidate documents that can reach exact re-ranking, and why is the real number lower?
Show answer
32 × 500 = 16,000 token hits, so at most 16,000 documents. The real number is lower because hits collide: several query vectors, and several tokens of the same passage, retrieve vectors belonging to the same document, which is counted once.
From a word vector to RAG
The longest ladder here, and the most linear: nine papers that carry one idea — text as geometry — from a 2013 word embedding to a graph-structured retrieval system.
Explore the topic
See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.
More Paper Breakdowns
- Attention Is All You NeedThe paper that introduced the Transformer — the architecture underneath GPT, BERT, Claude, and Gemini. Self-attention, Query/Key/Value, multi-head attention, positional encoding, and the real BLEU results, explained in depth with analogies and animated visuals, not just equations.Read →
- Retrieval-Augmented GenerationThe paper behind every "chat with your docs" product. Pair a generator with a neural retriever so the model looks knowledge up instead of memorizing it — dense retrieval, top-k documents, RAG-Sequence vs RAG-Token, and why retrieval cuts hallucination.Read →
- Word2VecHow words became math. Dense word vectors learned from the company a word keeps, so king − man + woman lands near queen. Skip-gram, CBOW, the distributional hypothesis, and the origin of every embedding model powering search and RAG.Read →
- ResNetThe skip connection that made deep learning deep. Why stacking layers made networks worse, and how residual learning let them reach 150+ layers and win ImageNet — the identity shortcut every Transformer still uses inside every block.Read →
- Vision Transformer (ViT)When an image became 16×16 words. Cut a picture into patches, treat each patch as a token, and feed them to a plain Transformer — no convolutions. Patch embeddings, the class token, and why data scale lets attention beat the CNNs that ruled computer vision for a decade.Read →
- Latent DiffusionThe paper behind Stable Diffusion. Compress an image into a small latent space with a VAE, run the diffusion denoising there instead of on pixels for a tens-of-times efficiency win, and steer it with text via cross-attention. The architecture that put text-to-image generation on consumer GPUs.Read →