Retrieval evaluation · research

What does reranking cost on a CPU, and what does it actually buy?

An offline retrieval pipeline and evaluation harness: hybrid retrieval, cross-encoder reranking, a latency sweep and an OCR-aware document quality study. The retrieval scores are shown as originally reported, with the scoring limitation that changes how to read them.

What the scoring rule doesn’t touch · reported measurements · CPU

  1. 6 vs 901 ms

    Warm mean per query: plain BM25 against hybrid retrieval with a cross-encoder reranking 20 candidates.

  2. 1,605 ms

    The 95th percentile for that same c=20 setting. “Under a second” holds on average, not for every query.

  3. 89.56% vs 2/11

    Pages classified clean, against whole documents that pass the stricter document-level rule.

§ 1

The problem

Document-grounded AI can fail before a single word is generated. The retriever misses the passage that holds the answer, ranks a near miss first, or works from text that came out of a scanned page garbled.

For document collections that can’t leave the building, the usual remedies (a hosted embedding API, a managed vector database, a bigger model) may not be available at all. LocalRAG++ asks a narrower, practical question: how much retrieval quality can you get locally, on a CPU, and when does extra computation stop paying for itself?

§ 2

Constraints

  • Offline. Models are downloaded once; evaluation calls no external service.
  • CPU only. No GPU acceleration in any measured run.
  • Small models on purpose. A 33M-parameter embedder and a 22M-parameter reranker.
  • A fixed, inspectable benchmark. 114 questions, scored by the same rule for every configuration. That rule is document-level; see the scoring note above.

§ 3

Approach

LocalRAG++ retrieval signal chainPDF, then extraction with OCR fallback, then structure-aware chunks, then BM25 and FAISS indexes in parallel, fused by reciprocal rank fusion, optionally reranked by a cross-encoder, producing a ranked top-10 list of evidence. A dashed branch from extraction leads to a separate document quality diagnostic.PDFinputExtraction/ OCRDocling → Marker→ PyMuPDF; OCR ifconfidence < 0.55Structure-aware chunks≤ 1,500 chars150 overlapBM25k1 1.5 · b 0.75FAISSbge-small · 33MRRFk = 60Cross-encoderMiniLM-L-6 · 22Mc = 20 candidatesRankedevidencetop 10INGESTINDEX + RETRIEVEOPTIONAL RERANKDocument quality diagnostic18 deterministic page features · 5 page classesdocument-level rule · reported separatelyDIAGNOSTICLocalRAG++ retrieval signal chain, vertical layout. Same content as the description above.PDFinputExtraction / OCRDocling → Marker → PyMuPDFOCR if confidence < 0.55Structure-aware chunks≤ 1,500 chars150 overlapBM25k1 1.5 · b 0.75FAISSbge-small · 33MRRFk = 60Cross-encoderMiniLM-L-6 · 22Mc = 20 candidatesRanked evidencetop 10Quality diagnostic18 page features · 5 page classesdocument-level rulereported separately
Fig. 1 · Signal chainA diagram of the evaluated pipeline, not a screenshot. Components are those named in the manuscript and repository configuration; the optional OCR and converter fallbacks exist in the pipeline, but the study does not benchmark each path separately.

Every configuration reads from the same chunk corpus. PDFs pass through a cascade of converters: Docling first for layout-aware conversion, Marker as a fallback for math-heavy content, PyMuPDF for fast baseline extraction. A confidence score over six text signals decides whether a page is re-extracted with Tesseract or PaddleOCR.

Text is split on section boundaries (up to 1,500 characters, 150 overlap) and each chunk gets a deterministic SHA-1-based identifier, so runs stay comparable. Two indexes sit over those chunks: BM25 with code-aware tokenisation, and an exact FAISS index of bge-small embeddings. Hybrid retrieval fuses the two ranked lists with reciprocal rank fusion.

For the reranking runs, hybrid retrieval supplies c candidates to a MiniLM cross-encoder, sweeping c across 10, 20, 40, 60 and 80, and the final top 10 is scored. That sweep is the experiment: it isolates what candidate depth buys and what it costs.

§ 4

What I did

LocalRAG++ is co-authored with Dr Salaja S, and I don’t want this page to blur that. What I can point to publicly is the repository under my GitHub account: the evaluation harness, its configuration, the question set and the raw outputs behind every number below.

A precise split of who did what isn’t written up here yet. Until it is, I’d rather under-claim than over-claim.

Read the code and outputs on GitHub

§ 5

How it was measured

Each configuration returns a top-10 list for each of the 114 questions. Each metric has a name that promises one thing and, on this question set, an implementation that measures something narrower. Both columns matter:

What each metric asks · as named, and as computed here
MetricThe question its name asksWhat the code computes on this question setUnanswerable (20)
P@1Is the first result the evidence?Is the first result from the question’s source PDF?always 0
EH@10Is the evidence anywhere in the top 10?Is any top-10 chunk from the source PDF?always 1
MRRHow high is the first piece of evidence?1 / rank of the first chunk from the source PDFalways 0
R@10How much of the evidence is in the top 10?Share of the source PDF’s chunks that are in the top 10always 1
Warm meanMean per-query retrieval (+ reranking) time after models and indexes are loaded. Unaffected by the scoring rule. Cold start is separate.

Read from src/eval/metrics.py andquestions.jsonl at commit ce5909e: all 94 evidence records have page: null. This describes the code; no metric was recomputed for this page.

What the 114 questions are

Counted from the repository’s questions.jsonl, the benchmark covers 2 PDFs:

  • 70 section questions on a public corporate annual report
  • 12 section questions on an arXiv-style preprint
  • 12 factual questions on the same preprint
  • 20 deliberately unanswerable questions spanning both

The questions were generated deterministically from the extracted chunks by a script in the repository: no LLM, fixed seed. That makes them reproducible. It also makes them templated (“What is discussed in the section …?” is a typical form), and in my reading that favours lexical matching, because the question shares its words with the heading it came from. Keep that in mind when you get to the BM25 result.

Does it come out the same twice?

The repository includes three repeat runs of the unreranked hybrid configuration. Quality metrics were identical each time, as you’d expect from deterministic retrieval. Latency was not:

Repeat runs · hybrid, no reranking
RunP@1EH@10Warm mean msWarm P95 msCold start sof which embedder load s
metrics_run1.json0.7540.93030.034.218.218.0
metrics_run2.json0.7540.93038.056.818.918.7
metrics_run3.json0.7540.93029.834.521.621.4

Repo files metrics_run1–3.json, hybrid without reranking, n = 114. Latency is retrieval-only, so it differs slightly from the 34 ms in Table 1, which comes from a separate run.

Cold start is almost entirely the embedding model loading (about 18–21 s); the indexes load in about 0.2 s. In practice that argues for keeping the process warm for a session rather than starting it per query.

§ 6

Results

Two kinds of number appear below. Latency is a reported measurement the scoring rule doesn’t touch. Retrieval scores are the originally reported results under document-level matching (see the scoring note), so they say whether a configuration reached the right PDF, not the right passage.

Originally reported retrieval scores, under document-level matching, against warm mean latency, n = 114Originally reported scores, not re-run, under the evaluation code’s document-level relevance matching. Two panels share a logarithmic latency axis from 3 milliseconds to 8 seconds. Top panel, Precision at 1: BM25, dense, hybrid and reranking with 10 candidates all score 0.754; reranking with 20, 40, 60 and 80 candidates all score 0.789. Bottom panel, EvidenceHit at 10: BM25 scores 0.974 at 6 milliseconds, the same as reranking with 20 candidates at 901 milliseconds. Dense, hybrid and c=10 score 0.930. c=40 and c=60 reach 0.982 and c=80 reaches 0.991 at 4,040 milliseconds. The full data is in the table below.1 s: the paper’s cut-offPrecision@1 · as reported0.740.760.780.80+0.035 P@1= 4 of 114 questionsSparse (BM25): Precision@1 · as reported 0.754, warm mean 6 msBM25Dense (FAISS): Precision@1 · as reported 0.754, warm mean 29 msdenseHybrid (RRF): Precision@1 · as reported 0.754, warm mean 34 mshybridHybrid + cross-encoder, c=10: Precision@1 · as reported 0.754, warm mean 395 msc=10Hybrid + cross-encoder, c=20: Precision@1 · as reported 0.789, warm mean 901 msc=20Hybrid + cross-encoder, c=40: Precision@1 · as reported 0.789, warm mean 1,572 msc=40Hybrid + cross-encoder, c=60: Precision@1 · as reported 0.789, warm mean 3,338 msc=60Hybrid + cross-encoder, c=80: Precision@1 · as reported 0.789, warm mean 4,040 msc=80EvidenceHit@10 · as reported0.920.940.960.981.00same reported EH@10 · 6 ms vs 901 msSparse (BM25): EvidenceHit@10 · as reported 0.974, warm mean 6 msBM25Dense (FAISS): EvidenceHit@10 · as reported 0.930, warm mean 29 msdenseHybrid (RRF): EvidenceHit@10 · as reported 0.930, warm mean 34 mshybridHybrid + cross-encoder, c=10: EvidenceHit@10 · as reported 0.930, warm mean 395 msc=10Hybrid + cross-encoder, c=20: EvidenceHit@10 · as reported 0.974, warm mean 901 msc=20Hybrid + cross-encoder, c=40: EvidenceHit@10 · as reported 0.982, warm mean 1,572 msc=40Hybrid + cross-encoder, c=60: EvidenceHit@10 · as reported 0.982, warm mean 3,338 msc=60Hybrid + cross-encoder, c=80: EvidenceHit@10 · as reported 0.991, warm mean 4,040 msc=803 ms10 ms30 ms100 ms300 ms1 s3 s8 swarm mean latency per query, log scale · CPU · n = 114no rerankinghybrid + cross-encoder, c candidates

Scroll the chart sideways →

Fig. 2 · Where the extra milliseconds go · scores as reportedRedrawn from manuscript Table II, not re-run. The vertical axes are the originally reported scores, which match relevance at the document level (see the scoring note). Latency is warm mean retrieval plus reranking per query after models are loaded. It is not time to a generated answer. The +0.035 in Precision@1 is 3.5 percentage points (≈ 4.6% relative), and on 114 questions that is four questions.

The cost side is clear

Warm mean latency climbs from 6 ms for BM25 and 34 ms for hybrid to 395 ms with 10 reranked candidates and 901 ms with 20, then 1,572, 3,338 and 4,040 ms at 40, 60 and 80. The difference over hybrid is the reranking stage, a cross-encoder scoring every candidate on a CPU, so it grows with candidate count.

The averages hide the tail. The repository also records the 95th percentile, and for c=20 it’s 1,605 ms. If an interface has a hard one-second budget, c=20 doesn’t meet it for every query.

The benefit side, as reported, is document-level

In the reported table, reranking 10 candidates changes nothing: every score matches plain hybrid. At 20, reported P@1 goes from 0.754 to 0.789, MRR from 0.754 to 0.794 and EH@10 from 0.930 to 0.974. P@1 then stays at 0.789 through c=80, while EH@10 reaches 0.982 at c=40 and 0.991 at c=80.

Under the code’s matching rule, those four questions are cases where reranking moved a chunk from the correct PDF into first place. That is a real change in the ranking. It isn’t evidence that the reranker found the annotated passage more often, and with two PDFs and four questions it is a thin signal either way. The manuscript reads c=20 as the knee of the curve. On these scores that’s where the reported numbers stop moving; whether passage-level quality behaves the same way is what a chunk-level re-run would show.

The cheap baseline belongs in the same table

As reported, BM25 alone reached the same EvidenceHit@10 as the reranked pipeline, 0.974, in 6 ms instead of 901. Read with the scoring note, that is a narrower statement than it looks. It means that for the same share of questions, at least one of the top ten chunks came from the right PDF, and 20 of the 114 count as hits automatically. It does not show BM25 finding the evidence passage as often as the reranker. What does survive is the method point: the fastest configuration was measured at the same n, under the same rule, and it was competitive on the coverage score. “Hybrid with reranking wins everything” isn’t what the table says, even before the caveat.

Table 1 · Originally reported results · document-level matching
ConfigurationP@1R@10MRREH@10Warm mean
ms
Warm P95
ms
Sparse (BM25)0.7540.2920.7720.974612.8
Dense (FAISS)0.7540.2950.7540.9302932.1
Hybrid (RRF)0.7540.2980.7540.9303438
Hybrid + cross-encoder, c=100.7540.2980.7540.930395651.2
Hybrid + cross-encoder, c=20manuscript’s pick0.7890.3080.7940.9749011,605.2
Hybrid + cross-encoder, c=400.7890.3070.7950.9821,5722,424.7
Hybrid + cross-encoder, c=600.7890.3060.7950.9823,3384,819.1
Hybrid + cross-encoder, c=800.7890.3080.7990.9914,0406,473.6

Scroll the table sideways →

All eight configurations as originally reported in manuscript Table II, n = 114, CPU, warm mean after initialisation. Not re-run. Quality columns use the code’s document-level relevance rule, and the 20 unanswerable questions count as EH@10 and R@10 hits and as P@1 and MRR misses (scoring note). Underlined = best in column. P95 comes from the repository’s paper_results_summary.csv, not from the manuscript.

The quality problem starts upstream

Separately from the retrieval benchmark, the quality layer scored 11 documents (2,432 pages) on 18 deterministic page features: character, token and line statistics, OCR-confusion patterns and a bigram-entropy proxy. Each page got one of five classes. No trained model, no GPU, no network.

Counted by page

89.56%

of 2,432 pages classified digital_clean

Page-level quality classes, 11 documents, 2,432 pages
Page classPagesShare
digital_clean2,17889.56%
likely_scanned1174.81%
mixed_or_uncertain592.43%
low_text401.64%
digital_noisy381.56%

Counted by document

2 of 11

documents pass the stricter document-level digital_clean rule

  • 01
  • 02
  • 03
  • 04
  • 05
  • 06
  • 07
  • 08
  • 09
  • 10
  • 11

digital_clean · 2mixed_quality · 9

A document is digital_clean only if it clears three guards at once: clean-page fraction ≥ 0.85, mean page quality ≥ 0.78 and degraded fraction ≤ 0.10. Scan-derived documents with readable OCR pass page checks but sit at lower mean quality.

Fig. 3 · Two denominatorsLeft counts pages, right counts documents. The two are not in conflict: most pages look clean, but most documents contain enough lower-quality pages to fail a document-level rule. Corpus: 11 documents, 2,432 pages, separate from the 114-question retrieval benchmark. Document numbering is arbitrary.

89.56% of pages came out digital_clean. Only 2 of the 11 documents did. The denominators differ, and both numbers are true: scan-derived documents with readable OCR pass page-level checks while sitting at a systematically lower mean quality score.

The document-level rule was calibrated on a four-document, human-labelled pilot. A single-threshold rule agreed with the human labels on 2 of 4 documents; the three-guard rule agreed on all 4. That’s a useful sanity check, and the margin it rests on is narrow: mean quality 0.780 for the best scan-derived document, 0.787 for the weakest born-digital one.

Calibration pilot · four documents
Document typeHuman labelSingle ruleThree-guard ruleq̄
Born-digital reportdigital_cleandigital_cleandigital_clean0.809
Scan-origin annualmixed_qualitydigital_cleanmixed_quality0.780
Archival historicalmixed_qualitydigital_cleanmixed_quality0.767
Table-heavy digitaldigital_cleandigital_cleandigital_clean0.787
Agreement with human labels2 / 44 / 4

Manuscript Table I. Calibration pilot, four human-labelled documents. q̄ = mean page quality score.

§ 7

What it does not show

  • Passage-level retrieval quality. The reported scores match relevance at the document level and give unanswerable questions automatic EvidenceHit@10 credit (see the scoring note). They don’t show how often the annotated passage was retrieved.
  • Independent verification. Every number here is reported by the authors from the repository outputs and the manuscript. None was re-run for this page.
  • Answer quality. Nothing here measures generated-answer accuracy, hallucination or faithfulness. No answers were generated.
  • Generality. 114 templated questions over two PDFs is enough to compare these configurations against each other. It is not enough to rank retrieval methods in general, and legal, medical or multilingual corpora may behave differently.
  • Statistical certainty. The P@1 gain is four questions. The repository includes a bootstrap confidence-interval script, but no intervals are reported for these results.
  • Cause and effect. Retrieval metrics were not broken down by document quality class, so this work does not show that low-quality pages caused specific misses.
  • Hardware. “Commodity CPU”, without a model number. A GPU would move the latency curve, and possibly the knee.

§ 8

What I learned, and what I’d do next

Read the scorer, not just the scores. A metric’s name says what it was meant to measure; the code says what it does. On this project the two came apart, and the gap only shows up in the source.

Keep the cheap baseline in the same table, at the same n. The most useful comparison here is one that could easily have been left out.

Report latency as a distribution. The mean said “under a second”; the P95 said “not always”. Both belong on the page.

Pages and documents answer different questions. A corpus can be mostly clean page by page and still mostly mixed document by document.

Next

  • Re-score at the chunk level, using the chunk_id already stored in every evidence record, and report unanswerable questions separately instead of folding them into EvidenceHit@10. This is tracked as its own piece of work; the numbers on this page will change only when it’s done and reviewed.
  • Break retrieval metrics down by document quality class, which is the manuscript’s own stated next step.
  • Report bootstrap intervals alongside every row of the main table.
  • Try query-adaptive candidate counts: rerank deeply only when the first stage is unsure.
  • Add human-written questions with exact evidence spans next to the templated set.
  • Only then evaluate generated answers end to end.

Sources: the co-authored manuscript (Tables I–III, §III–VIII) and the public repository at commit ce5909e. Figures are redrawn from those numbers; none are screenshots. Retrieval scores are reproduced as originally reported and were not re-run.