Page-image retrieval with ColPali
- Source
- From the RAG & Agents Engineering course
- By
- Sourav Banerjee
- Last updated
- 9 Nov 2026
Late interaction
What you'll learn
- Explain late interaction: one vector per token and per patch, compared only at query time.
- Trace a Larkspur question through MaxSim, token by token, to a page score.
- Explain what page-level retrieval sends to the answer model, and what it skips.
- Weigh the storage, generation and citation costs of retrieving pages.
Lesson 1 of T6.5 ranked figures on a different scale from text; lesson 2 of T6.5 turned figures into text and trusted a vision model's reading forever. Both start by taking the page apart. ColPali keeps the page whole.
Retrieval so far has used two kinds of model. A single-vector retriever squeezes a chunk into one vector and a question into another, and compares them once. A cross-encoder (T6.3) reads question and chunk together, which is accurate and too slow to run over a whole index.
Late interaction sits between them. It encodes the document and the question separately, as with single vectors, so documents can be encoded ahead of time. It keeps one vector per token instead of one per text, and compares them only at the end. The comparison is called MaxSim:
| Step | What happens |
|---|---|
| 1 | Each query vector is compared with every document vector by dot product |
| 2 | Each query vector keeps only its best match: the maximum over the document's vectors |
| 3 | The page score is the sum of those maxima, one per query vector |
Table 1: MaxSim, step by step.
ColBERT introduced this for text passages (Khattab and Zaharia, SIGIR 2020). ColPali applies it to page images (Faysse et al., ColPali: Efficient Document Retrieval with Vision Language Models, 2024): a vision language model reads the rendered page, and every image patch gets its own vector:
- Indexing starts from the rendered page image, not from extracted text.
- The model turns the page into 1,139 vectors of 128 dimensions, one per patch or token.
- A customer asks “Which month missed the 99.95% availability target?”
- The question becomes 24 vectors: its 14 tokens and 10 tokens the processor appends.
- Each token keeps its best patch. “availability” scores 0.75 on page 1 and 0.25 on the other two.
- The 24 maxima add up to the page score, 15.95 for page 1.
- Page 1 leads page 2 by 6.21. Its image, chart included, goes to the model.
Figure 1: MaxSim on one page. The figures are from a real run.
Notebook 03a's model needs a GPU stack, so the Larkspur lab pages were scored with vidore/colSmol-256M, a small model of the same family, through the ColPali engine library. Each 850 by 1100 page image became 1,139 vectors of 128 dimensions, and each question 24 vectors: its own tokens plus 10 end-of-utterance tokens the processor appends, which count in the sum like any other. The library's multi-vector scoring computes Table 1's steps; a hand-written version matched it to within 2e-6.
Here are a few of the 24 per-token maxima for Which month missed the 99.95% availability target?:
| Query token | Page 1 (availability) | Page 2 (refunds) | Page 3 (server plans) |
|---|---|---|---|
month |
0.65 | 0.61 | 0.42 |
% |
0.73 | 0.45 | 0.22 |
availability |
0.75 | 0.25 | 0.25 |
target |
0.65 | 0.20 | 0.28 |
| 14 question tokens, summed | 9.04 | 6.80 | 3.89 |
| 10 appended tokens, summed | 6.91 | 2.94 | 2.93 |
| MaxSim score | 15.95 | 9.74 | 6.82 |
Table 2: Per-token maxima and page scores (vidore/colSmol-256M, the module's lab pages).
month scores 0.65 and 0.61 on pages 1 and 2, so it barely separates them. availability and target do: each finds a strong match on page 1 and nothing better than 0.28 elsewhere. One vector per page would have pooled those signals before the question was known. We call that loss early pooling; late interaction keeps the signals apart until the question arrives.
Page-level retrieval
In page-level retrieval the unit of retrieval is the page, and the answer model receives the page as an image. Nothing is extracted, split or captioned, so a chart, a table's grid and a scanned form arrive as they were printed.
03a does it in four calls with the byaldi library:
| Call | What it does |
|---|---|
| Load | Loads vidore/colpali-v1.2 |
| Index | Renders every page of the Attention paper and embeds its patches, keeping a base64 copy of each page in the index |
| Search | Returns the top page with its document id, page number, score, metadata and the stored page image |
| Answer | Decodes the page image and sends it with the question to a vision-capable Gemini model |
Table 3: The 03a pipeline in four calls.
Keeping the page images in the index is what lets search return the image with the hit, instead of a pointer to re-render.
The library's source (byaldi 0.0.7) explains why the course marks this lesson platform-specific:
- It defaults to a CUDA GPU. On a machine without CUDA, pass the Apple-silicon or CPU device instead. Weights load in bfloat16.
- PDFs go through a converter that needs the poppler tools installed on the machine.
- The Hugging Face token is read from a token argument or the
HF_TOKENenvironment variable.03asets a variable spelledHF_token, which the library never reads. We call this the misspelt token: it fails only when the model needs authentication.
In the interactive chart retrieval lab, choose Page images. ColSmol ranks the right page first for all three questions, by clear margins:
| Question | Right page | Runner-up |
|---|---|---|
| Which month missed the 99.95% availability target? | 15.95 | 9.74 |
| How many business days did a refund take in May? | 15.93 | 10.66 |
| How much memory does the Medium server have? | 12.97 | 6.41 |
Table 4: Page scores for the three golden questions (vidore/colSmol-256M).
At k = 1 the model receives the page with the chart on it, so every answer is in what it reads. Some of page 1's lead comes from its paragraph, which says "availability" and "target"; that is fine, because the page brings its chart along.
What pages cost
Pages cost more than chunks in three places:
| Cost | Page images | Text chunks |
|---|---|---|
| Storage | 1,139 × 128 = 145,792 numbers per page, about 583 KB in float32 | 384 numbers per all-MiniLM-L6-v2 chunk vector, about 1.5 KB |
| Generation | A whole page image to a vision model per hit, so k stays small |
A few hundred tokens per hit |
| Granularity | A page with five topics is retrieved whole; a citation can point only to the page | A citation can point to a chunk or a quote |
Table 5: Page retrieval against chunk retrieval.
Lower precision, pooled patch vectors, or a cheap first stage with MaxSim only on its shortlist keep a large collection affordable.
Interview tip: choose by where the meaning lives. Page images suit scans, slides, forms and dense tables, where layout carries meaning or there is no text layer. Text-heavy documents with a few charts are cheaper to serve with captions (lesson 2 of T6.5).
In practice: page retrieval for Larkspur
- Use pages for status reports and scanned invoices, where charts and grids carry the answer.
- Keep the handbook in text chunks, with captions for its few figures.
- Shortlist first: a cheap single-vector search picks 20 pages, MaxSim reranks them.
- Fix the token variable and pin the device before deploying
03a's code.
Run the notebooks
The code for this lesson is in these notebooks. Colab runs one in your browser: install the packages its first cell imports and set the API keys it reads. To run every notebook as written, clone the repository and use its uv environment.
| Notebook | GitHub | Colab |
|---|---|---|
| Multimodal RAG with colpali (optional) | Open on GitHub | Open in Colab |
Key Takeaways
- Late interaction keeps one vector per query token and per page patch, and scores a page as the sum of each token's best match (MaxSim). Pages are still encoded ahead of time, and no signal is pooled early.
- Page-level retrieval sends whole page images to a vision model, so charts, table grids and scans arrive as printed, with nothing extracted or captioned. On the Larkspur pages it ranks the right page first for all three questions.
- Pages cost more in storage (about 583 KB per page in float32), in generation and in citation granularity. Use them for scans, slides, forms and dense tables; caption text-heavy documents instead.
Check yourself
1. For the availability question, the token month scores 0.65 on page 1 and 0.61 on page 2. Which tokens actually separate the pages, and why does that matter?
availability and target: each matches page 1 strongly (0.75 and 0.65) and nothing better than 0.28 elsewhere. Late interaction keeps each token's best match separate until the question is known; a single page vector would have pooled those signals in advance.
2. ColPali stores 1,139 vectors of 128 dimensions per page. How does that compare with a text chunk, and how do you keep a large collection affordable?
145,792 numbers per page, about 583 KB in float32, against 384 numbers for an all-MiniLM-L6-v2 chunk vector. Use lower precision, pool patch vectors, or run a cheap first stage and apply MaxSim only to its shortlist.
3. Larkspur has a text-heavy handbook with a few charts, and a folder of scanned invoices. Where would you use page-image retrieval?
For the scanned invoices, which have no text layer and whose layout carries meaning. The handbook is cheaper and more precise as text chunks, with captions for its few figures, since page retrieval costs more storage, sends whole images to the model and can cite only a page.