Vibe Engines
YouTube

Vibe Engines is free, interactive AI engineering & system design — runs in your browser. Pick a learning path

~11 min readSIGIR 2020

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.

The delta

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.

01

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.

The one-sentence version

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.

02

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.

03

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:

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.

04

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.

Three points on the accuracy/speed line
Single-vectorOne vector per passage. Fastest, coarsest — term detail pooled away.
ColBERTOne vector per token + MaxSim. Token-level precision, precomputed docs — the middle ground.
Cross-encoderJoint query+doc pass. Most accurate, slowest — can't pre-index.

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 it works

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.

query tokens document tokens, encoded offline the moon's gravity pulls ocean water row max what causes ocean tides [MASK] 0.31 0.58 0.97 0.74 0.52 score(q, d) = Σ of row maxima Σ = 3.12
Late interaction on one query–document pair: each query vector, including a [MASK] pad, takes its best-matching document token (highlighted), and the score is the sum of those row maxima. Document vectors are computed offline; only the query is encoded at search time.
  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. 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.
  7. 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.

The math

ColBERT's encoders, score and training loss

Eq = Normalize( W · BERT( [CLS] [Q] q1 … ql [MASK] … [MASK] ) ), Nq = 32 vectors
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.

The evidence

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.

MethodMRR@10 (dev)Latency per queryFLOPs per query
BM25 (official ranking)16.7——
KNRM19.83 ms592 million
fastText + ConvKNRM29.028 ms78 billion
BERT-base cross-encoder (published)34.710,700 ms97 trillion
BERT-base cross-encoder (authors' training)36.010,700 ms97 trillion
BERT-large cross-encoder36.532,900 ms340 trillion
ColBERT, re-ranking the BM25 top 1,00034.961 ms7 billion
ColBERT, end-to-end retrieval36.0458 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.

Since then

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.

SOLVE IT YOURSELF

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.

YOUR TASK

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.

WHAT IT HAS TO SATISFY — 5 ASSERTIONS
  1. MaxSim picks the best-matching doc token
  2. ColBERT sums each query token's best match
  3. Single-vector ties but ColBERT distinguishes
  4. MaxSim is order-independent (bag of vectors)
  5. Adding a matching doc token never lowers the score
CPython · WebAssembly
06

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.

Worth knowing

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.

07

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

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.

Check yourself

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.

Where this sits

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.

  1. 1Word2Vec2013
  2. 2GloVe2014
  3. 3Sentence-BERT2019
  4. 4Product Quantization2011
  5. 5HNSW2016
  6. 6Dense Passage Retrieval2020
  7. 7ColBERT2020
  8. 8Retrieval-Augmented Generation2020
  9. 9GraphRAG2024

All 110 papers, in 19 ladders →

ColBERT: Efficient and Effective Passage Search via Contextualized Late Interaction over BERT · Khattab & Zaharia · SIGIR 2020 · read the original paper on arXiv → · Vibe Engines · 2026
Finished this one? 0 / 110 Paper Breakdowns done
NextRetrieval-Augmented Generation Next on the “From a word vector to RAG” ladder · rung 8 of 9

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More Paper Breakdowns