Eval report · 2026-09-20

Keyword search vs embeddings + reranking, measured on 648 real questions.

The question: when someone asks a financial Q&A corpus a question in plain English, how often does the right answer show up in the top 10 — and which parts of the “modern retrieval” recipe actually move that number? The answer, on a public benchmark anyone can re-run:

recall@10 · right answer in top 10
0.2950.487
keyword search → embeddings + rerank
nDCG@10 · and ranked near the top
0.2330.429
keyword search → embeddings + rerank
questions fixed
158/648
no answer in top 10 before, answer in top 10 after · 23 went the other way

BEIR FiQA-2018 · 57,638 passages · 648 test questions with human relevance labels · run on one laptop, no API keys · reproduce it

What this means if you are not an IR person

A RAG system or an AI agent can only answer from what its search step hands it. If the right document is not in the top 10, no prompt and no model upgrade will produce the right answer. So retrieval quality is the ceiling on the whole system, and it has to be measured, not assumed.

Keyword search (BM25 — what most databases and “add search” plugins give you) finds the right answer in the top 10 for about 30% of these questions. Swapping in a small embedding model lifts that to 46% — the single biggest win, and it costs one millisecond. Adding a cross-encoder reranker on top takes it to 49% and, more visibly, pushes the right answer nearer the top: nDCG@10 goes 0.233 → 0.429, and the first correct passage moves up the list (MRR@10 0.287 → 0.516). 158 of the 648 questions go from “no correct answer shown” to “correct answer shown”; 23 go the other way.

That is the difference between an assistant that is right most of the time and one that confidently answers from the wrong page.

Results

Systemrecall@10nDCG@10MRR@10recall@50latency / query
Keyword (BM25)0.2950.2330.2870.4460.5 ms
Embeddings (bge-small)0.464+0.1680.404+0.1710.488+0.2000.621+0.1751.2 ms
Hybrid (RRF)0.420+0.1240.345+0.1130.417+0.1290.627+0.1811.8 ms
Embeddings + rerank0.487+0.1920.429+0.1960.516+0.2290.621+0.1752,332 ms

Latency is end-to-end per query on an M2 Max laptop (Metal, no GPU server, no caching), so treat it as relative, not absolute. The reranker is the expensive stage: 2331 ms here for a 568M-parameter model scoring 50 candidates. On a server GPU the same pass is tens of milliseconds, and cutting the candidate list from 50 to 20 roughly halves it again — it is the lever you tune per product, not a fixed cost.

What did not work

Hybrid retrieval hurt. Fusing the keyword and embedding lists (reciprocal rank fusion, the standard recipe) scored below embeddings alone — 0.420 vs 0.464 recall@10. On this corpus BM25 is weak enough that blending it in drags the good ranker down. A small reranker did not rescue it: our first run reranked the hybrid list with the older BAAI/bge-reranker-base and reached only 0.431 recall@10 / 0.350 nDCG@10 — still below plain embeddings. The numbers in the table come from the second run: rerank the best retriever’s candidates with a current-generation model. Both runs are in the repository. This is the point of measuring: the default recipe is not always right, and you cannot tell without the number.

How to read the columns. recall@10: of the passages labelled relevant for a question, what share appeared in the top 10. nDCG@10: the same, but rewarding results that appear at rank 1–3 over rank 8–10. MRR@10: on average, how high the first relevant passage sits. recall@50: what the reranker had to work with — it can only promote what the retrievers found.

Method

Nothing here is tuned. Small open models, default settings, first run. On a client corpus we tune chunking, the fusion weights, the reranker cut-off and, where it pays, fine-tune the reranker on the client’s own query logs — so these numbers are a floor for what the approach does, not a ceiling.

Caveats, stated plainly. A public benchmark is not your data; the relative gap between systems transfers better than the absolute numbers. Relevance labels in FiQA are incomplete (some genuinely relevant passages are unlabelled), which depresses every system’s score equally. Latency was measured on a laptop with other processes running.

Reproduce it

The harness is ~120 lines of Python and lives in the same repository as this website. Two commands, no API keys; about 30 minutes on an M2 Max, most of it the reranker (the model downloads are ~2.4 GB the first time):

git clone https://github.com/RerankLab/reranklab.com && cd reranklab.com/evals/fiqa
uv run run.py        # downloads FiQA (~20 MB) + two open models, writes results.json
uv run render.py     # turns results.json into this page

If your numbers differ from the ones above by more than rounding, tell us — we will either fix the harness or update the page.

Want this run on your own corpus and your own questions? That is the 3–10 day prototype: your data, a golden set we build together, a number you can hold us to. From US$5k, fixed.

Start a project →