How retrieval quality is measured on the BEIR sets, with the comparator side first. Speed is in benchmarks.md.
Datasets
BEIR test splits of SciFact, ArguAna, NQ and SCIDOCS, fetched and hash-checked by tools/fetch.sh (BEIR zips; the MD5s
are BEIR's own, the SHA-256s are ours; both are in tools/pins.tsv). Datasets are never committed. Queries are the test queries that
have judgements; judgements are the qrels/test.tsv grades. Other BEIR sets are not used.
Runner
oneindex-harness beir (tools/run_relevance.sh) drives any ITextEngine: it indexes the corpus (title and text), retrieves the top
1,000 documents for every test query under each BM25 setting, writes a TREC run file per setting, and scores it.
- Settings. Three field layouts times two parameter sets, so six runs per dataset:
- layouts:
Flat(title and text concatenated into one field, Kamalloo et al.'s "flat");MultiFieldSum(title and text as two fields, scores summed with equal weights, Kamalloo et al.'s "multifield" and the Anserini configuration the BEIR paper describes);MultiFieldMax(the better of the two fields' scores, Elasticsearch'sbest_fields, the BEIR leaderboard's original Elasticsearch run); - parameters: k1=0.9, b=0.4 (Anserini's defaults, the ones the BEIR paper states) and k1=1.2, b=0.75 (Lucene's defaults).
- layouts:
- Analysis. The engine's English chain: lower-casing, possessive removal, English stop words and Porter-family stemming. Queries are analysed with the same chain and the terms OR-ed within each field.
- nDCG@10. Our implementation (
TrecEval.NdcgAt) follows trec_eval: gain is the judged grade, discount 1/log2(rank+1), ranking by score descending with ties broken by document id descending, the mean taken over queries present in both run and judgements, ideal DCG from all positively graded judgements. Two numbers are reported per run: the raw one, and the BEIR-convention one, which drops a hit whose document id equals the query id before scoring (BEIR's default; it matters for ArguAna, whose queries are documents of the corpus). - Cross-check.
tools/crosscheck_ndcg.pyevaluates the same run files withpytrec_eval(ndcg_cut_10), the evaluator BEIR uses, and fails if any run differs from ours by more than 5e-5 (agreement to four decimals).
Published baselines
Quoted with their source; "ours" are compared with both.
- BEIR paper: Thakur, Reimers, Rücklé, Srivastava, Gurevych, BEIR: A Heterogeneous Benchmark for Zero-shot Evaluation of Information Retrieval Models, NeurIPS 2021 Datasets and Benchmarks, arXiv:2104.08663 (v4), Table 2, BM25 column. The text describes the lexical baseline as Anserini with k1=0.9 and b=0.4, title and passage indexed as separate fields.
- Kamalloo et al.: Resources for Brewing BEIR: Reproducible Reference Models and an Official Leaderboard, arXiv:2306.07471 (SIGIR 2024), Table 3, BM25 columns "multifield" (the BEIR paper's configuration, equal weights over title and contents), "flat" (title and text in one field), both using Lucene's default English analyzer.
| Dataset | BEIR paper Table 2 | Kamalloo Table 3 multifield | Kamalloo Table 3 flat |
|---|---|---|---|
| SciFact | 0.665 | 0.665 | 0.679 |
| ArguAna | 0.315 | 0.414 | 0.397 |
| NQ | 0.329 | 0.329 | 0.306 |
| SCIDOCS | 0.158 | 0.158 | 0.149 |
The ArguAna figures differ between the two papers as printed (0.315 and 0.414); we report against both and do not reconcile them.
Gate
OneIndex BM25 nDCG@10 on each set is at most one point below the published baseline (above it is fine) and not below Lucene.NET at matched settings: OneIndex's default k1 and b against Lucene.NET at the same layout, k1 and b.
Results: Lucene.NET 4.8.0-beta00018 (the comparator side)
Engine: Lucene.NET 4.8.0-beta00018 (assembly build commit:[df99b8abd9]; packages Lucene.Net, Lucene.Net.Analysis.Common, Lucene.Net.Facet) on .NET 10.0.13 (SDK 10.0.402), Linux x64.
Analysis: EnglishAnalyzer (lower-case, possessive removal, English stop words, Porter stemming) for documents and queries. Top 1,000 per
query, test split. Worker: 32 logical cores (AMD Ryzen 9 7950X3D), NVMe ext4, 8 indexing and search threads. One run covered all four sets and the pytrec_eval cross-check; an earlier attempt produced identical nDCG values. Code at commit 1956a0d.
Datasets (SHA-256 of the zip, MD5 matches BEIR): SciFact 536e1444…0165 (5,183 documents, 300 queries), ArguAna cfdf79ad…557b (8,674; 1,406),
SCIDOCS 96640201…17ef1 (25,657; 1,000), NQ 2553bf7b…e752 (2,681,468; 3,452). Full hashes: tools/pins.tsv.
nDCG@10 (BEIR convention in parentheses where it differs; it drops a hit whose id equals the query id):
| Run (layout, k1/b) | SciFact | ArguAna | SCIDOCS | NQ |
|---|---|---|---|---|
| Flat, 0.9/0.4 | 0.6789 | 0.3111 (0.4118) | 0.1509 | 0.3058 |
| Flat, 1.2/0.75 | 0.6848 | 0.3642 (0.4882) | 0.1566 | 0.2775 |
| MultiFieldSum, 0.9/0.4 | 0.6647 | 0.3217 (0.4240) | 0.1587 | 0.3252 |
| MultiFieldSum, 1.2/0.75 | 0.6628 | 0.3632 (0.4880) | 0.1619 | 0.3051 |
| MultiFieldMax, 0.9/0.4 | 0.6685 | 0.2877 (0.3784) | 0.1461 | 0.2437 |
| MultiFieldMax, 1.2/0.75 | 0.6684 | 0.3414 (0.4584) | 0.1480 | 0.2309 |
| Published: BEIR paper Table 2 | 0.665 | 0.315 | 0.158 | 0.329 |
| Published: Kamalloo Table 3 multifield | 0.665 | 0.414 | 0.158 | 0.329 |
| Published: Kamalloo Table 3 flat | 0.679 | 0.397 | 0.149 | 0.306 |
Reading: with k1=0.9, b=0.4, Lucene.NET reproduces the Kamalloo flat column on SciFact (0.6789 vs 0.679) and NQ (0.3058 vs 0.306) to three
decimals and its multifield column on SciFact (0.6647 vs 0.665), SCIDOCS (0.1587 vs 0.158) and, with the BEIR convention, ArguAna
(0.4240 vs 0.414, 1.0 point high). NQ MultiFieldSum is 0.3252 against the published 0.329 (0.4 point low). The BEIR paper's ArguAna
value (0.315) is close to the raw, convention-free figure (0.3217), the Kamalloo values to the BEIR-convention figures; the two papers'
ArguAna numbers do not agree with each other, so the gate on ArguAna should be read against Kamalloo with the convention applied.
MultiFieldMax (Elasticsearch best_fields over the two fields) is clearly worst on NQ, where the title field alone matches many
irrelevant documents; it is reported for completeness, not as a target. Lucene defaults (1.2/0.75) beat 0.9/0.4 on ArguAna and SCIDOCS
and SciFact-flat, 0.9/0.4 on NQ.
pytrec_eval 0.5.11 (ndcg_cut_10) on the same run files agrees with our nDCG to better than 2e-15 on all 48 run files (raw and
BEIR-convention variants of 24 runs): four decimals and far beyond.
Indexing time (8 threads, one commit): SciFact 1.8 s, ArguAna 1.0 s, SCIDOCS 1.6 s, NQ 26.8 s (index 1.1 GiB). All six settings for NQ answered 3,452 queries in 15 to 22 s each.
Results: OneIndex against Lucene.NET and the baselines
Run on the worker (32 logical cores, AMD Ryzen 9 7950X3D, NVMe ext4), .NET 10 (SDK 10.0.4xx), 8 threads, same datasets and hashes as above.
OneIndex defaults: Bm25F layout (one score per term over title and text, title weight 3), k1 = 0.9, b = 0.4, English analysis
(lower-case, possessive removal, Snowball English stop words, Snowball English stemmer). NQ was indexed and searched in separate
runs (index phase, then a search phase on the reopened index). Every run file was cross-checked with
pytrec_eval (ndcg_cut_10); the largest difference to our nDCG@10 is 1.8e-15.
nDCG@10, BEIR convention (a hit whose id equals the query id is dropped):
| Set | OneIndex Bm25F 0.9/0.4 (default) | OneIndex Bm25F 1.2/0.75 | Lucene.NET MultiFieldSum 0.9/0.4 | Lucene.NET MultiFieldSum 1.2/0.75 | Lucene.NET Flat 0.9/0.4 | Published baseline (multifield) |
|---|---|---|---|---|---|---|
| SciFact | 0.6912 | 0.6998 | 0.6647 | 0.6628 | 0.6789 | 0.665 |
| ArguAna | 0.4333 | 0.5072 | 0.4240 | 0.4880 | 0.4118 | 0.414 |
| SCIDOCS | 0.1531 | 0.1601 | 0.1587 | 0.1619 | 0.1509 | 0.158 |
| NQ | 0.3420 | 0.3377 | 0.3252 | 0.3051 | 0.3058 | 0.329 |
SciFact (2.6 points) and ArguAna (1.9) are more than one point above the baseline, NQ is 1.3 above; SCIDOCS is 0.2 below it. The Lucene.NET figures are those of the section above (re-run once more in the equal-weight job; identical to four decimals).
OneIndex with the same layout as Lucene.NET's MultiFieldSum (two separate field scores added, title weight 1, so no BM25F):
| Set | OneIndex MultiFieldSum, title weight 1, 0.9/0.4 | Lucene.NET MultiFieldSum 0.9/0.4 | OneIndex, 1.2/0.75 | Lucene.NET, 1.2/0.75 |
|---|---|---|---|---|
| SciFact | 0.6598 | 0.6647 | 0.6653 | 0.6628 |
| ArguAna | 0.4270 | 0.4240 | 0.4861 | 0.4880 |
| SCIDOCS | 0.1587 | 0.1587 | 0.1641 | 0.1619 |
| NQ | 0.3323 | 0.3252 | 0.3114 | 0.3051 |
At matched layout and parameters OneIndex's BM25 scores equal Lucene.NET's within noise (identical on SCIDOCS at 0.9/0.4, above on ArguAna and NQ, 0.5 point below on SciFact).
SCIDOCS against Lucene.NET MultiFieldSum
Bm25F (0.1531) is 0.56 points below Lucene.NET's MultiFieldSum (0.1587) on SCIDOCS, and above its Flat layout (0.1509). Measured causes:
- It is the layout, not the scoring or the analysis. The equal-weight sum in OneIndex reproduces Lucene.NET to four decimals on SCIDOCS
(0.1587 / 0.1587). Lucene.NET's
Explainoutput (public API) shows the same idf and tf formula as OneIndex's: idfln(1 + (N - n + 0.5)/(n + 0.5)),tf (k1+1) / (tf + k1 (1 - b + b len/avgLen)). - Analysis is not it. Experimental builds (not kept) with Lucene's 33-word stop list and the original Porter stemmer moved the sum layout by at most 0.1 point either way (0.1552 default, 0.1548 Lucene stop list, 0.1551 Porter, 0.1543 both), while the title weight moved it 0.35 (the harness had given the sum layout OneIndex's title weight 3 until this run; at weight 1 it is 0.1587). With the same stop list the two analyzers' token streams differ in 20 of 6,000 sampled texts (stems of short words); with each side's own list they differ in 5,286, almost all by stop words.
- Collection size and average length are not it. Counting empty-field documents in N and average length (two variants, run on all three
small sets) changed SCIDOCS
Bm25Fby at most 0.04 points. - What decides it: SCIDOCS (queries are paper titles, relevant documents are cited papers) rewards a document whose title and abstract both match,
which adding two saturating field scores does and BM25F (one saturation over the weighted sum) does not. Title weight sweep,
Bm25F0.9/0.4:
| Title weight | SciFact | ArguAna | SCIDOCS |
|---|---|---|---|
| 1 | 0.6838 | 0.4188 | 0.1502 |
| 1.5 | 0.6878 | 0.4248 | 0.1517 |
| 2 | 0.6872 | n/a | 0.1520 |
| 3 (default) | 0.6912 | 0.4333 | 0.1531 |
No weight reaches 0.1587 on SCIDOCS while all four sets keep their other results; the sum layout wins on SCIDOCS and loses 3 points on
SciFact and 1 on NQ against Bm25F. BM25F is the engine's documented base score, so it stays the default; the sum layout is a query composition
(BooleanQuery of per-field TermQuery) any caller can write.
Operational notes
- Loading NQ takes 173 s at 8 threads (index 746 MiB; Lucene.NET: 26.8 s, 1.1 GiB), commit and merges included;
IndexWriter.AddUniqueskips the per-document identifier lookup that made the first attempt time out at 30 minutes. Reopening the finished index and reading 2.68 M identifiers takes 36 s; the 3,452 queries take 12 to 25 s per setting. - The runner takes
ONEINDEX_PHASE=index|search(build and commit in one job, search in another),ONEINDEX_LAYOUTS,ONEINDEX_WORKand the OneIndex adapterONEINDEX_TITLE_WEIGHT.
Typo tolerance on (information)
ONEINDEX_TYPO=on makes the OneIndex adapter match the Bm25F layout with TypoTermQuery (default typo budgets on the analyzed word, the
typo and exactness factors, no prefix matching). It is information only; the gated runs leave it off, where queries are exactly the plain
BM25F term queries.
Term proximity bonus (default weight 0.1)
The bonus of docs/scoring.md ("Term proximity") on the gate sets, by weight. The table and its reading are in that section; the default keeps every set at or above the no-bonus numbers of the table above (within noise on ArguAna at 1.2/0.75) and adds 0.1 to 0.3 points at the default BM25 setting.
Milestone re-run
The gate sets re-run on the code of the first public version. nDCG@10 under the BEIR convention (a hit whose id equals the query id is dropped); our runner, cross-checked with pytrec_eval (largest difference 2.2e-15). Lucene.NET is 4.8.0-beta00018 with matched analysis. The BM25 numbers equal the recorded ones except the proximity default (weight 0.1 on user text, described above and in docs/scoring.md).
BM25 at k1 = 0.9, b = 0.4
| Set | OneIndex default (BM25F + proximity 0.1) | OneIndex without the bonus | Lucene.NET Flat | Lucene.NET MultiFieldSum | Baseline (multifield) | Default minus baseline |
|---|---|---|---|---|---|---|
| SciFact | 0.6918 | 0.6912 | 0.6789 | 0.6647 | 0.665 | +2.7 |
| ArguAna | 0.4347 | 0.4333 | 0.4118 | 0.4240 | 0.414 | +2.1 |
| SCIDOCS | 0.1544 | 0.1531 | 0.1509 | 0.1587 | 0.158 | -0.4 |
| NQ | 0.3452 | 0.3420 | 0.3058 | 0.3252 | 0.329 | +1.6 |
ArguAna without the BEIR own-hit convention: 0.3260 (OneIndex) against 0.3111 (Lucene.NET Flat). Every set is within one point of the baseline or above it, and at or above Lucene.NET Flat. SCIDOCS is read against Flat, as ruled (BM25F is a single-stream model); it sits 0.4 below the per-field-sum layout, which loses on SciFact and NQ when OneIndex runs it (below).
At k1 = 1.2, b = 0.75 (SciFact / ArguAna / SCIDOCS / NQ):
| Run | SciFact | ArguAna | SCIDOCS | NQ |
|---|---|---|---|---|
| OneIndex default | 0.7033 | 0.5067 | 0.1615 | 0.3412 |
| OneIndex without the bonus | 0.6998 | 0.5072 | 0.1601 | 0.3377 |
| Lucene.NET Flat | 0.6848 | 0.4882 | 0.1566 | 0.2775 |
| Lucene.NET MultiFieldSum | 0.6628 | 0.4880 | 0.1619 | 0.3051 |
Other layouts, 0.9/0.4 then 1.2/0.75 (SciFact / ArguAna / SCIDOCS / NQ): OneIndex Flat 0.6822 / 0.4181 / 0.1513 / 0.3148 then 0.6891 / 0.4930 / 0.1568 / 0.2891; OneIndex MultiFieldSum 0.6452 / 0.4272 / 0.1552 / 0.3228 then 0.6456 / 0.4818 / 0.1592 / 0.3042; Lucene.NET MultiFieldMax 0.6685 / 0.3784 / 0.1461 / 0.2437 then 0.6684 / 0.4584 / 0.1480 / 0.2309.
Dense and hybrid
Typed BM25 arm, bge-small dense arm, default hybrid (weighted RRF, rank constant 10, semantic ratio 0.75).
| Set | BM25 | Dense | Hybrid |
|---|---|---|---|
| SciFact | 0.6912 | 0.7126 | 0.7257 |
| ArguAna | 0.4333 | 0.6048 | 0.6100 |
| SCIDOCS | 0.1531 | 0.2041 | 0.2064 |
| NQ (recorded, not re-run) | 0.3420 | 0.5018 | 0.5249 |
| Mean | 0.4049 | 0.5058 | 0.5168 |
Hybrid is above both arms on every set and on the mean. NQ needs GPU-embedded vectors, which this run did not use, so its row is the recorded one (held-out confirmation). Other fusion settings reproduce the recorded numbers to four decimals on the three CPU sets (for example SciFact RRF k 60 ratio 0.5 gives 0.7282 and linear 0.5 gives 0.7377).
.Models rows
| Model | SciFact | ArguAna | SCIDOCS | NQ | State |
|---|---|---|---|---|---|
| bge-small dense (int8) | 0.7126 | 0.6048 | 0.2041 | 0.5018 | three sets re-embedded on CPU this run; NQ recorded |
| multilingual-e5-small (int8) | 0.6782 | 0.3859 | 0.1375 | re-embedded on CPU this run; card 0.67704 / 0.39063 / 0.13895 | |
| cross-encoder rerank of BM25 | 0.6890 | 0.4164 (0.3112 with own hits kept) | 0.1646 | 0.5404 | scores reused from the cache (the run scored 0 pairs); NQ recorded |
| OpenSearch sparse | 0.5432 | recorded, not re-run |
What stands as recorded: all NQ model rows, the sparse encoder, and the cross-encoder scores (the cache was valid; the pair scores were not recomputed).