Multimodal and Vision-Native RAG
Embed page images, score with MaxSim, fuse modalities, cite evidence regions. Python and TypeScript from the lessons.
- Concepts and terms
- Tasks and commands
- Annotated code
- Diagrams
- Comparisons
- Defaults and numbers
- Troubleshooting
- Questions and tips
Run the lessons and the project#
| Command | What it does |
|---|---|
python main.py | Capstone MaxSim scaffold plus a pruning ablation |
npm start | Viewer self-check pass with --demo. Exits 0 |
npm run serve | HTTP viewer on 127.0.0.1. Free port when PORT is unset |
npm test | Fixture, HTML escape and route tests |
python3 scripts/ | Creates the incomplete workspace |
python3 scripts/ | Grades stage 1 |
python3 main.py --manifest ./ | Writes index.html and evidence.json |
python3 demo.py | Runs the search on the original fixtures |
From 19.04 code/main.py, 19.04 code/ts/README.md, 19.04 code/ts/package.json, visual-evidence-library/README.md
MaxSim late interaction#
def max_sim_score(query_tokens, doc_patches): total = 0.0 for q in query_tokens:one vector per query token best = -1e9 for p in doc_patches:every patch on the page s = dot(q, p) if s > best: best = s total += bestsum of the best match per token return totalidx = build_index(prune=True)DocPruner keeps 50% of patcheshits = idx.retrieve(q, k=3)list of (Page, score)From 19.04 code/main.py
Options side by side#
| Factor | ColPali | VisRAG | Text-RAG |
|---|---|---|---|
| Page vectors | N_p patch vectors | One pooled vector | One vector per chunk |
| Scoring | MaxSim | Bi-encoder | Cosine on chunks |
| OCR step | None | None | OCR / pdftotext |
| Storage | ~30x text-RAG | Smaller | ~150 kB per 50 chunks |
| Recall | Strong | Weaker | Loses charts and tables |
| Use for | Quality | Scale | Pure-text documents |
From Lesson 12.23
Manifest and proposal errors#
| Symptom | Cause | Fix |
|---|---|---|
| Asset IDs must be nonempty and unique | Empty or repeated id | Give each asset a unique id |
| Image dimensions must be positive integers at most 20000 | Bad width or height | Declare integer pixel sizes |
| Asset must be an image inside the manifest directory | Path escapes root or wrong suffix | Use .svg, .png, .jpg or .jpeg under root |
| Asset exceeds 5 MB | Large file | Shrink the image |
| Bounding box lies outside the declared image dimensions | x + w or y + h too large | Fix the bbox |
| Model-proposed text needs explicit review before indexing | reviewed is not true | Check the image, then set reviewed=true |
| Vision extraction requires --provider-url, --width and --height | Missing flags | Pass all three |
| Image bytes do not match the extension | PNG or JPEG magic bytes differ | Use the correct extension |
Vision-first document QA pipeline#
From Lesson 19.04
Tasks in code#
| Task | Code |
|---|---|
| Score one page | maxsim(query_tokens, |
| Rank toy pages | retrieve(query, |
| Prune patches | doc_prune(patches, |
| Index with pruning | build_index(prune= |
| Fuse three modalities | fuse([t, |
| Retry on low confidence | agentic_loop(query, |
| Check a manifest | validate_manifest(manifest, |
| Search text regions | search(assets, |
From 12.23 code/main.py, 19.04 code/main.py, 12.24 code/main.py, solution/main.py
Score fusion and agentic retry#
Use X when#
- Use ColPali or ColQwen2 for financial reports, papers, contracts and medical records.
- Use VisRAG when indexing speed and storage matter more than recall.
- Use text-RAG for wiki articles, chat logs and multi-million-page archives.
- Use text-RAG when regulation demands extractable OCR text.
- Use CLIP or SigLIP 2 for text and image. Use CLAP for text and audio.
- Use score fusion first. Upgrade to MoE fusion when A/B shows clear wins.
From Lesson 12.23, Lesson 12.24
Formulas and numbers#
- MaxSim
- \sum_i \max_j \cos(q_i, p_j) over query tokens and page patches.
- ColPali storage
- 50 729 128 * 4 bytes = ~18 MB raw, ~4 MB after PQ.
- Text-RAG storage
- 50 chunks 768-dim 4 bytes = ~150 kB.
- PQ
- shrinks storage ~8x and keeps vector similarity.
- ViDoRe
- ColPali-v1 ~80% nDCG@5. Text-RAG ~50-60%.
- ColQwen page
- ~2048 patch embeddings of dim 128.
- DocPruner
- 50% compression at < 0.5% accuracy loss.
- Capstone targets
- 10k pages, top-5 pages to the VLM, answers under 2s.
From Lesson 12.23, Lesson 19.04
Defaults#
| Setting | Default | Note |
|---|---|---|
EMB_DIM | 16 | Fake patch vectors in the capstone |
keep_fraction | 0.5 | doc_prune keeps at least 1 patch |
| Retrieve k | 5 | Top pages by MaxSim |
confidence_floor | 0.8 | main passes 0.7 |
search limit | 20 | Must be between 1 and 100 |
--query | seed return | Evidence library CLI |
--model | local-vision-model | Used with --vision-image |
--out | visual-output | Holds index.html and evidence.json |
From 19.04 code/main.py, 12.24 code/main.py, solution/main.py
Abbreviations#
- MaxSim
- max similarity per query token, summed across the query.
- ViDoRe
- Visual Document Retrieval Evaluation. Metric nDCG@5.
- PQ / OPQ
- product quantization. Compresses patch vectors.
- VisRAG
- pools each page into one vector with a VLM.
- M3DocRAG
- retrieves pages across documents for multi-page reasoning.
- MoE fusion
- a gating network routes to modality experts.
- MuRAG
- the 2022 paper that started multimodal RAG.
- Evidence region
- a bbox on the source page for the answer span.
From Lesson 12.23, Lesson 12.24, Lesson 19.04
Questions#
- Why MaxSim, not mean similarity?
- Irrelevant patches wash out the mean.
- How do you cite mixed sources?
- Text
[1], image[img 3], audio[audio 2 at 0:34]. - How do you grade cross-modal RAG?
- Recall@k per modality and fused top-k accuracy.
- Does the evidence library do OCR?
- No. It indexes supplied text rectangles.
- What is query coverage?
- Matched terms divided by query terms. It is not confidence.
From 12.23 code/main.py, Lesson 12.23, Lesson 12.24, visual-evidence-library/README.md