15 KiB
BEIR Public-Benchmark Results — ruflo
This page tracks ruflo's measured retrieval performance on BEIR datasets.
Every cell is reproducible from the commands in the rightmost column;
every cell has a run JSON in docs/benchmarks/runs/. Published baseline
numbers come from Thakur et al. 2021 (BEIR paper) and the BAAI BGE paper.
Result Matrix
Numbers from
docs/benchmarks/runs/beir-{dataset}-bge-latest.json. ADR-085 (harness + BGE swap) + ADR-086 (significance testing).
| Dataset | Corpus | Test Q | Pipeline | Model | nDCG@10 | 95% CI | vs BM25 (CI verdict) | vs Best Listed Baseline | Rank | Latency | Run JSON |
|---|---|---|---|---|---|---|---|---|---|---|---|
| NFCorpus | 3,633 | 323 | direct dense (no rerank) | BGE-base-en-v1.5 (110M) | 0.352 | [0.317, 0.387] | +0.027 ↑ (n.s.) | -0.028 ↓ BGE-large 0.380 (n.s.) | 2/11 | 388 ms | beir-nfcorpus-bge-latest.json |
| NFCorpus | 3,633 | 323 | pure BM25 (silent hash-fallback path) | no real dense | 0.289 | n/a | -0.036 ↓ | -0.091 ↓ | 11/11 | 950 ms | beir-nfcorpus-2026-05-30T19-16-23-024Z.json |
| SciFact | 5,183 | 300 | direct dense (no rerank) | BGE-base-en-v1.5 (110M) | 0.626 | [0.577, 0.672] | -0.053 ↓ (p<0.05) | -0.096 ↓ BGE-large 0.722 (p<0.05) | 10/11 | 410 ms | beir-scifact-bge-latest.json |
Bootstrap CI summary (per ADR-086, 10k resamples, seed=42)
The 95% confidence intervals tell the rigorous story. On NFCorpus, we beat BM25 by 0.027 point estimate but the CI overlaps the baseline (n.s. at p<0.05) — the "rank-2" headline is a single-realisation outcome, not a statistically distinguishable win. On SciFact, we lose to BM25 by 0.053 and the CI excludes the baseline (significant at p<0.05) — that loss is real, not noise.
Two-dataset mean (rough generalisation gauge)
| System | NFCorpus | SciFact | Mean |
|---|---|---|---|
| BGE-large-v1.5 (published) | 0.380 | 0.722 | 0.551 |
| SPLADE++ | 0.347 | 0.704 | 0.526 |
| BM25 (Lucene published) | 0.325 | 0.679 | 0.502 |
| ruflo + BGE-base (direct dense, no rerank) | 0.352 | 0.626 | 0.489 |
| ruflo + BM25+BGE-base RRF k=60 (3.10.27, did NOT improve) | 0.328 | 0.569 | 0.449 |
We're below BM25 on the 2-dataset mean (0.489 vs 0.502). RRF made it worse (0.449). The BEIR-average story requires more datasets and domain-specific tuning. The NFCorpus rank-2 is real but not representative.
ADR-087 RRF ablation (3.10.27 honest negative result)
Standard BM25+dense RRF k=60 — the textbook "lowest-regret" first move — degrades nDCG@10 on both datasets because our multi-field BM25 is weaker than Lucene's (our pure-BM25 NFCorpus = 0.279 vs Lucene 0.325). RRF averages BM25 noise into top-K when one input is materially weaker than the other.
| Config | NFCorpus nDCG@10 | SciFact nDCG@10 | NFCorpus R@100 | SciFact R@100 |
|---|---|---|---|---|
| dense alone (BGE-base) | 0.352 | 0.626 | 0.305 | 0.828 |
| BM25 alone (ours) | 0.279 | 0.576 | 0.223 | 0.824 |
| RRF k=60 equal (default) | 0.328 ↓ | 0.569 ↓ | 0.321 ↑ | 0.951 ↑ |
| RRF k=30 equal | 0.335 ↓ | 0.582 ↓ | 0.321 | 0.954 |
| RRF k=60 dense=1.2, bm25=0.8 | 0.334 ↓ | 0.577 ↓ | 0.324 | 0.961 |
Recall@100 does improve (RRF surfaces more candidates) — which makes RRF a useful first stage before reranking. Tracked for ADR-088 (cross-encoder rerank).
The default BEIR runner stays at dense-only. RRF is opt-in.
ADR-088 — Lucene BM25 + cross-encoder rerank (3.10.28) — the pipeline that works
Fixing the BM25 (Porter stemmer + Lucene stopwords + length norm, single-field over title+text) closes the asymmetric-strength problem and makes RRF + cross-encoder rerank produce real wins.
| Configuration | NFCorpus | SciFact | Mean | Notes |
|---|---|---|---|---|
| dense alone (BGE-base) | 0.352 | 0.626 | 0.489 | baseline |
| Lucene BM25 alone | 0.328 | 0.681 | 0.505 | matches published baseline (0.325 / 0.679) |
| Lucene RRF k=30 (no CE) | 0.363 | 0.639 | 0.501 | RRF works once BM25 is strong |
| Multi-field RRF + CE rerank | 0.355 | 0.685 | 0.520 | rerank rescues weak BM25 |
| Lucene RRF + CE rerank (best) | 0.358 | 0.683 | 0.521 | rank 2 NFCorpus, rank 3 SciFact |
Acceptance test PASSES with Lucene RRF + CE rerank: beats published BM25 on both datasets (+0.033 NFCorpus, +0.004 SciFact). Mean 0.521 beats every listed BEIR baseline except SPLADE++ (0.526) and BGE-large (0.551).
Final two-dataset means leaderboard
| System | Params | Mean nDCG@10 | NFCorpus | SciFact |
|---|---|---|---|---|
| BGE-large-v1.5 (published) | 335M | 0.551 | 0.380 | 0.722 |
| SPLADE++ (published) | 110M | 0.526 | 0.347 | 0.704 |
| ruflo Lucene RRF + CE rerank (3.10.28) | 110M | 0.521 | 0.358 | 0.683 |
| ruflo multi-field RRF + CE rerank | 110M | 0.520 | 0.355 | 0.685 |
| ruflo Lucene BM25 alone | — | 0.505 | 0.328 | 0.681 |
| BM25 (published Lucene) | — | 0.502 | 0.325 | 0.679 |
| Contriever (published) | 110M | 0.502 | 0.328 | 0.677 |
| DocT5query (published) | 60M | 0.501 | 0.328 | 0.675 |
| ColBERT (published) | 110M | 0.488 | 0.305 | 0.671 |
| GTR-XL (published) | 1.2B | 0.502 | 0.343 | 0.662 |
| ruflo dense alone (BGE-base) | 110M | 0.489 | 0.352 | 0.626 |
| TAS-B (published) | 66M | 0.481 | 0.319 | 0.643 |
| SBERT msmarco (published) | 110M | 0.414 | 0.272 | 0.555 |
We rank 3rd of 13 entries on the 2-dataset mean. Using a 110M-param base model (vs BGE-large's 335M and GTR-XL's 1.2B).
ADR-089 — 3-dataset BEIR (3.10.29)
ArguAna joins NFCorpus + SciFact. Same harness, same Lucene-style BM25, same BGE-base-en-v1.5.
| Dataset | Best ruflo | Pipeline | Rank | Best Listed |
|---|---|---|---|---|
| NFCorpus | 0.358 | Lucene + RRF + CE rerank | 2/11 | BGE-large 0.380 |
| SciFact | 0.683 | Lucene + RRF + CE rerank | 3/11 | BGE-large 0.722 |
| ArguAna | 0.432 | Lucene + RRF (CE rerank hurt) | 5/11 | BGE-large 0.636 |
| 3-dataset mean | 0.491 | mixed | — | BGE-large 0.579 |
3-dataset means vs every listed baseline
| System | Params | NFCorpus | SciFact | ArguAna | Mean |
|---|---|---|---|---|---|
| BGE-large-v1.5 (published) | 335M | 0.380 | 0.722 | 0.636 | 0.579 |
| SPLADE++ (published) | 110M | 0.347 | 0.704 | 0.521 | 0.524 |
| GenQ (published) | 110M | 0.319 | 0.644 | 0.493 | 0.485 |
| ruflo best (per-dataset) | 110M | 0.358 | 0.683 | 0.432 | 0.491 |
| GTR-XL (published) | 1.2B | 0.343 | 0.662 | 0.439 | 0.481 |
| BM25 (published Lucene) | — | 0.325 | 0.679 | 0.397 | 0.467 |
| Contriever (published) | 110M | 0.328 | 0.677 | 0.379 | 0.461 |
| TAS-B (published) | 66M | 0.319 | 0.643 | 0.429 | 0.464 |
| ColBERT (published) | 110M | 0.305 | 0.671 | 0.233 | 0.403 |
| SBERT msmarco (published) | 110M | 0.272 | 0.555 | 0.371 | 0.399 |
Rank 4 of 11 entries on the 3-dataset mean. Beats published BM25 (+0.024), beats GTR-XL (with 1/10× our params), beats Contriever, TAS-B, ColBERT, SBERT. Loses to SPLADE++ (-0.033), GenQ (-0.006, basically tied), and BGE-large (-0.088).
Counter-findings honestly reported
ArguAna kills the cross-encoder rerank. Pulled at the 50-query checkpoint (running nDCG 0.283 vs dense alone 0.431). Estimated 6+ hours wall time and was actively hurting. ArguAna is counter-argument retrieval — rerank's pointwise relevance scoring doesn't help when the task requires understanding opposition.
BGE-large NFCorpus = no lift. Xenova/bge-large-en-v1.5 (335M, int8 quantized) measured 0.350 vs our BGE-base 0.352 — no improvement. Below the published BAAI BGE-large baseline (0.380). Likely Xenova int8 quantization + no query prefix. ADR-089.
BGE query prefix is mixed. Per BAAI's docs (Represent this sentence for searching relevant passages: ): NFCorpus +0.009 ✓, SciFact -0.007 ✗, ArguAna +0.003 ~noise. Opt-in only via BGE_QUERY_PREFIX=1. ADR-090.
ADR-091 — 4-dataset BEIR (3.10.30): SciDocs joins, dense alone wins it
Same harness extended to SciDocs (25,657 docs, 1000 queries). Different best config:
| Dataset | Best ruflo | Pipeline | Rank |
|---|---|---|---|
| NFCorpus | 0.358 | Lucene + RRF + CE rerank | 2/11 |
| SciFact | 0.683 | Lucene + RRF + CE rerank | 3/11 |
| ArguAna | 0.432 | Lucene + RRF (CE rerank hurt) | 5/11 |
| SciDocs | 0.211 | dense alone (RRF hurt by 0.008) | 2/11 |
| 4-dataset mean | 0.421 | mixed | — |
4-dataset means — final leaderboard
| System | Params | NFCorpus | SciFact | ArguAna | SciDocs | Mean |
|---|---|---|---|---|---|---|
| BGE-large-v1.5 (published) | 335M | 0.380 | 0.722 | 0.636 | 0.225 | 0.491 |
| SPLADE++ (published) | 110M | 0.347 | 0.704 | 0.521 | 0.159 | 0.433 |
| ruflo best (per-dataset) | 110M | 0.358 | 0.683 | 0.432 | 0.211 | 0.421 |
| GTR-XL (published) | 1.2B | 0.343 | 0.662 | 0.439 | 0.174 | 0.405 |
| GenQ (published) | 110M | 0.319 | 0.644 | 0.493 | 0.143 | 0.400 |
| BM25 (published Lucene) | — | 0.325 | 0.679 | 0.397 | 0.158 | 0.390 |
| Contriever (published) | 110M | 0.328 | 0.677 | 0.379 | 0.165 | 0.387 |
| TAS-B (published) | 66M | 0.319 | 0.643 | 0.429 | 0.149 | 0.385 |
| DocT5query (published) | 60M | 0.328 | 0.675 | 0.349 | 0.162 | 0.378 |
| ColBERT (published) | 110M | 0.305 | 0.671 | 0.233 | 0.145 | 0.339 |
| SBERT msmarco (published) | 110M | 0.272 | 0.555 | 0.371 | 0.122 | 0.330 |
Rank 3 of 11 on 4-dataset mean. Beats every published baseline except SPLADE++ (-0.012) and BGE-large (-0.070, mostly the ArguAna gap). Using a 110M-param base — beats GTR-XL's 1.2B (+0.016, 1/10× the params).
Third config-specific finding (SciDocs adds to the pattern)
| Dataset | Best config | What hurts |
|---|---|---|
| NFCorpus | Lucene+RRF+CE | nothing — full pipeline wins |
| SciFact | Lucene+RRF+CE | CE rerank wins, but Lucene BM25 alone is competitive (0.681) |
| ArguAna | Lucene+RRF (no CE) | CE rerank actively hurts (0.283 at 50q vs 0.432 RRF) |
| SciDocs | dense alone | RRF hurt by 0.008 (0.211 → 0.203) |
Three of four datasets pick a different best config. No single pipeline wins everywhere. Auto-selecting per-dataset would require a calibration step we don't have. Until then, callers should A/B their corpus.
What pipeline is reported here: the NFCorpus 0.352 row is the direct BGE dense path — no fine-tuning, no hybrid BM25+dense fusion, no cross-encoder reranker. The hybrid pipeline (cosine + multi-field BM25 + MMR + opt-in rerank, ADRs 078-083) is what ruflo uses internally for small-corpus retrieval; the BEIR runner deliberately isolates the dense path for clean comparison to dense baselines. Hybrid + rerank variants on BEIR are tracked for a future ADR.
Published Baselines (for reference)
NFCorpus nDCG@10 (medical IR, n=323 test queries)
| Method | Params | nDCG@10 | Source |
|---|---|---|---|
| BGE-large-v1.5 | 335M | 0.380 | BAAI BGE paper |
| ruflo + BGE-base-en-v1.5 | 110M | 0.352 | this repo |
| SPLADE++ | 110M | 0.347 | Formal et al. 2022 |
| GTR-XL | 1.2B | 0.343 | Ni et al. 2022 |
| DocT5query | 60M | 0.328 | Nogueira & Lin 2019 |
| Contriever | 110M | 0.328 | Izacard et al. 2022 |
| BM25 (Lucene) | — | 0.325 | Thakur et al. 2021 |
| TAS-B | 66M | 0.319 | Hofstätter et al. 2021 |
| GenQ | 110M | 0.319 | Thakur et al. 2021 |
| ColBERT | 110M | 0.305 | Khattab & Zaharia 2020 |
| SBERT (msmarco) | 110M | 0.272 | Reimers & Gurevych 2019 |
SciFact nDCG@10 (scientific IR, n=300 test queries)
| Method | nDCG@10 | Source |
|---|---|---|
| BGE-large-v1.5 | 0.722 | BAAI BGE paper |
| SPLADE++ | 0.704 | Formal et al. 2022 |
| BM25 (Lucene) | 0.679 | Thakur et al. 2021 |
| Contriever | 0.677 | Izacard et al. 2022 |
| DocT5query | 0.675 | Nogueira & Lin 2019 |
| ColBERT | 0.671 | Khattab & Zaharia 2020 |
| GTR-XL | 0.662 | Ni et al. 2022 |
| GenQ | 0.644 | Thakur et al. 2021 |
| TAS-B | 0.643 | Hofstätter et al. 2021 |
| SBERT (msmarco) | 0.555 | Reimers & Gurevych 2019 |
How to reproduce
git clone https://github.com/ruvnet/ruflo && cd ruflo
npm install && ( cd v3/@claude-flow/cli && npx tsc )
# NFCorpus
mkdir -p /tmp/beir-nfcorpus && cd /tmp/beir-nfcorpus
curl -sL -o nfcorpus.zip 'https://public.ukp.informatik.tu-darmstadt.de/thakur/BEIR/datasets/nfcorpus.zip' && unzip -q nfcorpus.zip
node /path/to/ruflo/v3/@claude-flow/cli/scripts/run-beir-bge.mjs
# → nDCG@10 0.352, rank 2 of 11
# SciFact
mkdir -p /tmp/beir-scifact && cd /tmp/beir-scifact
curl -sL -o scifact.zip 'https://public.ukp.informatik.tu-darmstadt.de/thakur/BEIR/datasets/scifact.zip' && unzip -q scifact.zip
BEIR_DATA_DIR=/tmp/beir-scifact/scifact node /path/to/ruflo/v3/@claude-flow/cli/scripts/run-beir-bge.mjs
# Paired bootstrap significance test (ADR-086)
node /path/to/ruflo/v3/@claude-flow/cli/scripts/beir-bootstrap-significance.mjs \
/path/to/ruflo/docs/benchmarks/runs/beir-nfcorpus-bge-latest.json
Model size / speed / quality trade-offs
| Model | Params | Embed dim | Cache size (NFCorpus) | Ingest (3,633 docs) | Query latency |
|---|---|---|---|---|---|
Xenova/bge-small-en-v1.5 |
33M | 384 | ~5.5 MB | ~15 min | ~250 ms |
Xenova/bge-base-en-v1.5 |
110M | 768 | ~11 MB | ~25 min | ~330 ms |
Xenova/bge-large-en-v1.5 |
335M | 1024 | ~15 MB | ~60 min (est.) | ~700 ms (est.) |
Per-row latency is on Apple Silicon CPU through @xenova/transformers
int8-quantised ONNX. GPU would be ~10-50× faster.
Methodology notes
- No fine-tuning. All numbers are zero-shot — we use BAAI's released BGE models as-is. NFCorpus has a 110K-pair train split that fine-tuning could exploit for an additional ~0.02-0.05 nDCG lift; not done here.
@xenova/transformersdirect API (notpipeline()) used to bypass thesharp/libvipstransitive dependency that breaks on darwin-arm64 (ADR-085 §"sharp-on-darwin-arm64 bug").- CLS-token pooling + L2 normalisation per BAAI's BGE spec; cosine becomes dot product on normalised vectors.
- Graded relevance for nDCG — qrels use 0/1/2 grades; we use
(2^rel - 1) / log2(i+1)per BEIR convention. - Reproducibility:
BOOTSTRAP_SEED=42for the significance test (mulberry32 PRNG). Run JSONs include full per-query metrics so external bootstrap-CI checks reproduce exactly.
Limits & next steps
- Two-dataset coverage isn't BEIR-average. BEIR ships 18 datasets; the published "BEIR average" is the standard generalisation gauge. Tracking: TREC-COVID, FiQA-2018, ArguAna, HotpotQA, NQ next.
- Single-annotator labelled retrieval for internal ruflo bench (ADR-081); not relevant to BEIR's externally-curated qrels.
- The 0.005 gap to SPLADE++ (0.352 vs 0.347) is on the edge of noise at N=323. The paired bootstrap test (ADR-086) gives a confidence interval; report both point estimate AND CI.