yilunzhao/bright-pro-mteb-validation

Cross-validation of BRIGHT-Pro × MTEB retrieval integration (embeddings-benchmark/mteb#4651)

0

stars

3

commits

Python

primary language

Jul 15, 2026

updated

README

BRIGHT-Pro × MTEB integration: cross-validation

Companion repository for embeddings-benchmark/mteb#4651 — the PR adding BRIGHT-Pro as a retrieval benchmark in MTEB.

This repo cross-validates that running 4 retrievers through the new MTEB task classes (BrightPro{Domain}Retrieval) reproduces the numbers from BRIGHT-Pro's own evaluation harness on all 7 StackExchange domains.

Setup

Each (model × domain) cell is evaluated end-to-end via:

import mteb
mteb.evaluate(
    mteb.get_model_meta(model_name),
    mteb.get_task(task_name),
    encode_kwargs={"batch_size": ...},
)

with the per-model max_seq_length aligned to BRIGHT-Pro's original settings:

modelmax_seq_lengthbatch
mteb/baseline-bm25s
google/embeddinggemma-300m204832
Qwen/Qwen3-Embedding-8B40968–32
ReasonIR/ReasonIR-8B40968–32

The MTEB task prompt is set to "Given a {domain} post, retrieve relevant passages that help answer the post" — the same instruction body BRIGHT-Pro's paper uses, so the Qwen3 / ReasonIR wrappers format it into the identical Instruct:…\nQuery: / <|user|>…\n<|embed|>\n prefix as the BRIGHT-Pro harness.

Results

nDCG@10 per (domain, model). Δ = MTEB − BRIGHT-Pro.

domainmodelBRIGHT-ProMTEBΔ
biologyBM250.346630.19397-0.15266
biologyembeddinggemma-300m0.402470.40375+0.00128
biologyQwen3-Embedding-8B0.435370.43637+0.00100
biologyReasonIR-8B0.333220.35323+0.02001
earth_scienceBM250.405690.25198-0.15371
earth_scienceembeddinggemma-300m0.478930.48646+0.00753
earth_scienceQwen3-Embedding-8B0.515920.52582+0.00990
earth_scienceReasonIR-8B0.441870.44398+0.00211
economicsBM250.320550.22313-0.09742
economicsembeddinggemma-300m0.253650.25389+0.00024
economicsQwen3-Embedding-8B0.367720.36599-0.00173
economicsReasonIR-8B0.261260.27006+0.00880
psychologyBM250.254570.17058-0.08399
psychologyembeddinggemma-300m0.258990.25812-0.00087
psychologyQwen3-Embedding-8B0.386070.38508-0.00099
psychologyReasonIR-8B0.276930.28199+0.00506
roboticsBM250.317570.22331-0.09426
roboticsembeddinggemma-300m0.287290.28251-0.00478
roboticsQwen3-Embedding-8B0.412630.40843-0.00420
roboticsReasonIR-8B0.326990.35841+0.03142
stackoverflowBM250.330490.34426+0.01377
stackoverflowembeddinggemma-300m0.291850.29070-0.00115
stackoverflowQwen3-Embedding-8B0.460690.46043-0.00026
stackoverflowReasonIR-8B0.380970.39817+0.01720
sustainable_livingBM250.320290.23875-0.08154
sustainable_livingembeddinggemma-300m0.274900.27227-0.00263
sustainable_livingQwen3-Embedding-8B0.339860.34064+0.00078
sustainable_livingReasonIR-8B0.272190.27728+0.00509

Per-model summary (7 domains each)

modelmean abs Δmax abs Δcomment
embeddinggemma-300m0.00230.0075sub-1% on every domain
Qwen3-Embedding-8B0.00280.0099sub-1% on every domain
ReasonIR-8B0.01420.0314MTEB systematically 0.2–3% higher; same ranking direction
BM250.08260.1537MTEB uses bm25s + Porter stemmer + English stopwords; BRIGHT-Pro uses a different BM25 implementation. The two BM25 variants are not expected to match — this row is informative about the choice of BM25 baseline, not about the task integration.

The three dense-embedding models reproduce BRIGHT-Pro's numbers within 3% on every cell, with two of them sub-1%. This confirms the MTEB task classes (BrightPro{Domain}Retrieval) pass identical corpora, queries, and qrels to the evaluation pipeline as BRIGHT-Pro's own harness — any remaining differences are attributable to model-side details (precision, EOS handling, tokenizer behaviour), not to the task integration.

Re-validation on the mteb 2.18.1 rebase (2026-07-14)

After rebasing the PR branch onto mteb main (2.18.1, which now includes the #4684 fix), all 4 retrievers were re-run end-to-end on the 7 BrightPro tasks. Dense models reproduce BRIGHT-Pro harness scores with mean |dnDCG@10| = 0.0029 (max 0.0152); ReasonIR-8B is within +/-0.005 on every domain. Full table: comparison_2p18.md, per-cell outputs in results_2p18/.

Root cause analysis of the ReasonIR-8B gap

The ~1–3 nDCG-point gap for ReasonIR-8B above is not noise. We traced it to a separate MTEB bug, independent of this BrightPro task integration, in mteb/models/abs_encoder.py's get_task_instruction:

if self.instruction_template and len(instruction) > 0:
    return self.format_instruction(instruction, prompt_type)
return instruction

When a task defines prompt={"query": "..."} without a "document" key (BRIGHT-Pro, BRIGHT, BRIGHT v1.1, and many other retrieval tasks), the document-side instruction is "". The len(instruction) > 0 gate then causes get_task_instruction to return "" directly — the model's instruction_template is never invoked for documents.

For models whose instruction_template is a callable that emits a non-empty prefix on empty input — most prominently ReasonIR-8B and GritLM-7B (<|embed|>\n), also Octen, Sarashina v2 (text: ), BMRetriever — the document-side prefix the model was trained with is silently dropped. Documents get encoded without the prefix.

Diagnostics

The scripts/verify_reasonir_*.py + results/diagnostics/verify_reasonir_*.json files trace the gap step by step. The decisive observations on BrightProBiologyRetrieval:

diagnosticfinding
verify_reasonir_full_eval.pyRe-running ReasonIR via BRIGHT-Pro's own AutoModel + custom .encode on the full 60K corpus in our env: nDCG@10 = 0.33755 (paper saved: 0.33322).
verify_reasonir_full_paths.pySide-by-side AutoModel + custom .encode vs MTEB's InstructSentenceTransformerModel: per-doc cosine median 0.98, min 0.71 on 60K docs — the embeddings genuinely diverge.
verify_reasonir_strip.pyMTEB's corpus .strip() preprocessing contributes only +0.002 nDCG. Not the main source.
verify_reasonir_chunking.pyMTEB's 50K corpus chunking contributes 0 nDCG (Mode 1 = Mode 2 = 0.33792). Ruled out.
verify_reasonir_wrapper_vs_direct.pyWrapper vs direct st.encode: per-doc cosine median 0.94, query cosine 0.998. Bug specifically affects documents, not queries.
verify_reasonir_doc_prefix.pyMinimal isolation. Same docs, same model, only difference is prompt="" vs prompt="<|embed|>\n": 9985 / 10000 docs have cosine < 0.99 between the two encodings. nDCG@10 differs by 0.028. Definitive smoking gun.

Empirical impact of the fix

ReasonIR-8B × BrightProBiologyRetrieval, full 60K corpus:

pipelinenDCG@10
BRIGHT-Pro paper saved0.33322
BRIGHT-Pro AutoModel + custom .encode (our env)0.33755
MTEB pipeline (current upstream, buggy)0.35323
MTEB pipeline (with get_task_instruction fix)0.34269

The fix narrows the MTEB-vs-paper gap from +0.020 nDCG to +0.009 nDCG. The residual ~0.009 is implementation-level noise (sentence-transformers' tokenizer/padding vs HuggingFace's custom .encode, bf16 numerical precision).

A separate MTEB issue + PR for this bug will reference this analysis.

Layout

scripts/
  run_mteb_eval.py     # single (model, task) evaluation
  submit_eval.sh       # SLURM script (GPU)
  submit_eval_cpu.sh   # SLURM script (BM25, CPU)
  dispatch_all.sh      # fire one job per (model, task)
  build_comparison.py  # aggregate JSON results -> markdown table
results/
  BrightPro{Domain}Retrieval__{model_slug}.json  # one per cell
  diagnostics/
    verify_reasonir_*.json  # gap-investigation artifacts

Reproducing

# one (model, task) cell
MODEL_NAME=Qwen/Qwen3-Embedding-8B \
TASK_NAME=BrightProBiologyRetrieval \
MAX_SEQ_LENGTH=4096 BATCH_SIZE=16 \
python scripts/run_mteb_eval.py

Contributors

yilunzhao

3 commits

yilunzhao/bright-pro-mteb-validation

Cross-validation of BRIGHT-Pro × MTEB retrieval integration (embeddings-benchmark/mteb#4651)

0

stars

3

commits

Python

primary language

Jul 15, 2026

updated

README

BRIGHT-Pro × MTEB integration: cross-validation

Companion repository for embeddings-benchmark/mteb#4651 — the PR adding BRIGHT-Pro as a retrieval benchmark in MTEB.

This repo cross-validates that running 4 retrievers through the new MTEB task classes (BrightPro{Domain}Retrieval) reproduces the numbers from BRIGHT-Pro's own evaluation harness on all 7 StackExchange domains.

Setup

Each (model × domain) cell is evaluated end-to-end via:

import mteb
mteb.evaluate(
    mteb.get_model_meta(model_name),
    mteb.get_task(task_name),
    encode_kwargs={"batch_size": ...},
)

with the per-model max_seq_length aligned to BRIGHT-Pro's original settings:

modelmax_seq_lengthbatch
mteb/baseline-bm25s
google/embeddinggemma-300m204832
Qwen/Qwen3-Embedding-8B40968–32
ReasonIR/ReasonIR-8B40968–32

The MTEB task prompt is set to "Given a {domain} post, retrieve relevant passages that help answer the post" — the same instruction body BRIGHT-Pro's paper uses, so the Qwen3 / ReasonIR wrappers format it into the identical Instruct:…\nQuery: / <|user|>…\n<|embed|>\n prefix as the BRIGHT-Pro harness.

Results

nDCG@10 per (domain, model). Δ = MTEB − BRIGHT-Pro.

domainmodelBRIGHT-ProMTEBΔ
biologyBM250.346630.19397-0.15266
biologyembeddinggemma-300m0.402470.40375+0.00128
biologyQwen3-Embedding-8B0.435370.43637+0.00100
biologyReasonIR-8B0.333220.35323+0.02001
earth_scienceBM250.405690.25198-0.15371
earth_scienceembeddinggemma-300m0.478930.48646+0.00753
earth_scienceQwen3-Embedding-8B0.515920.52582+0.00990
earth_scienceReasonIR-8B0.441870.44398+0.00211
economicsBM250.320550.22313-0.09742
economicsembeddinggemma-300m0.253650.25389+0.00024
economicsQwen3-Embedding-8B0.367720.36599-0.00173
economicsReasonIR-8B0.261260.27006+0.00880
psychologyBM250.254570.17058-0.08399
psychologyembeddinggemma-300m0.258990.25812-0.00087
psychologyQwen3-Embedding-8B0.386070.38508-0.00099
psychologyReasonIR-8B0.276930.28199+0.00506
roboticsBM250.317570.22331-0.09426
roboticsembeddinggemma-300m0.287290.28251-0.00478
roboticsQwen3-Embedding-8B0.412630.40843-0.00420
roboticsReasonIR-8B0.326990.35841+0.03142
stackoverflowBM250.330490.34426+0.01377
stackoverflowembeddinggemma-300m0.291850.29070-0.00115
stackoverflowQwen3-Embedding-8B0.460690.46043-0.00026
stackoverflowReasonIR-8B0.380970.39817+0.01720
sustainable_livingBM250.320290.23875-0.08154
sustainable_livingembeddinggemma-300m0.274900.27227-0.00263
sustainable_livingQwen3-Embedding-8B0.339860.34064+0.00078
sustainable_livingReasonIR-8B0.272190.27728+0.00509

Per-model summary (7 domains each)

modelmean abs Δmax abs Δcomment
embeddinggemma-300m0.00230.0075sub-1% on every domain
Qwen3-Embedding-8B0.00280.0099sub-1% on every domain
ReasonIR-8B0.01420.0314MTEB systematically 0.2–3% higher; same ranking direction
BM250.08260.1537MTEB uses bm25s + Porter stemmer + English stopwords; BRIGHT-Pro uses a different BM25 implementation. The two BM25 variants are not expected to match — this row is informative about the choice of BM25 baseline, not about the task integration.

The three dense-embedding models reproduce BRIGHT-Pro's numbers within 3% on every cell, with two of them sub-1%. This confirms the MTEB task classes (BrightPro{Domain}Retrieval) pass identical corpora, queries, and qrels to the evaluation pipeline as BRIGHT-Pro's own harness — any remaining differences are attributable to model-side details (precision, EOS handling, tokenizer behaviour), not to the task integration.

Re-validation on the mteb 2.18.1 rebase (2026-07-14)

After rebasing the PR branch onto mteb main (2.18.1, which now includes the #4684 fix), all 4 retrievers were re-run end-to-end on the 7 BrightPro tasks. Dense models reproduce BRIGHT-Pro harness scores with mean |dnDCG@10| = 0.0029 (max 0.0152); ReasonIR-8B is within +/-0.005 on every domain. Full table: comparison_2p18.md, per-cell outputs in results_2p18/.

Root cause analysis of the ReasonIR-8B gap

The ~1–3 nDCG-point gap for ReasonIR-8B above is not noise. We traced it to a separate MTEB bug, independent of this BrightPro task integration, in mteb/models/abs_encoder.py's get_task_instruction:

if self.instruction_template and len(instruction) > 0:
    return self.format_instruction(instruction, prompt_type)
return instruction

When a task defines prompt={"query": "..."} without a "document" key (BRIGHT-Pro, BRIGHT, BRIGHT v1.1, and many other retrieval tasks), the document-side instruction is "". The len(instruction) > 0 gate then causes get_task_instruction to return "" directly — the model's instruction_template is never invoked for documents.

For models whose instruction_template is a callable that emits a non-empty prefix on empty input — most prominently ReasonIR-8B and GritLM-7B (<|embed|>\n), also Octen, Sarashina v2 (text: ), BMRetriever — the document-side prefix the model was trained with is silently dropped. Documents get encoded without the prefix.

Diagnostics

The scripts/verify_reasonir_*.py + results/diagnostics/verify_reasonir_*.json files trace the gap step by step. The decisive observations on BrightProBiologyRetrieval:

diagnosticfinding
verify_reasonir_full_eval.pyRe-running ReasonIR via BRIGHT-Pro's own AutoModel + custom .encode on the full 60K corpus in our env: nDCG@10 = 0.33755 (paper saved: 0.33322).
verify_reasonir_full_paths.pySide-by-side AutoModel + custom .encode vs MTEB's InstructSentenceTransformerModel: per-doc cosine median 0.98, min 0.71 on 60K docs — the embeddings genuinely diverge.
verify_reasonir_strip.pyMTEB's corpus .strip() preprocessing contributes only +0.002 nDCG. Not the main source.
verify_reasonir_chunking.pyMTEB's 50K corpus chunking contributes 0 nDCG (Mode 1 = Mode 2 = 0.33792). Ruled out.
verify_reasonir_wrapper_vs_direct.pyWrapper vs direct st.encode: per-doc cosine median 0.94, query cosine 0.998. Bug specifically affects documents, not queries.
verify_reasonir_doc_prefix.pyMinimal isolation. Same docs, same model, only difference is prompt="" vs prompt="<|embed|>\n": 9985 / 10000 docs have cosine < 0.99 between the two encodings. nDCG@10 differs by 0.028. Definitive smoking gun.

Empirical impact of the fix

ReasonIR-8B × BrightProBiologyRetrieval, full 60K corpus:

pipelinenDCG@10
BRIGHT-Pro paper saved0.33322
BRIGHT-Pro AutoModel + custom .encode (our env)0.33755
MTEB pipeline (current upstream, buggy)0.35323
MTEB pipeline (with get_task_instruction fix)0.34269

The fix narrows the MTEB-vs-paper gap from +0.020 nDCG to +0.009 nDCG. The residual ~0.009 is implementation-level noise (sentence-transformers' tokenizer/padding vs HuggingFace's custom .encode, bf16 numerical precision).

A separate MTEB issue + PR for this bug will reference this analysis.

Layout

scripts/
  run_mteb_eval.py     # single (model, task) evaluation
  submit_eval.sh       # SLURM script (GPU)
  submit_eval_cpu.sh   # SLURM script (BM25, CPU)
  dispatch_all.sh      # fire one job per (model, task)
  build_comparison.py  # aggregate JSON results -> markdown table
results/
  BrightPro{Domain}Retrieval__{model_slug}.json  # one per cell
  diagnostics/
    verify_reasonir_*.json  # gap-investigation artifacts

Reproducing

# one (model, task) cell
MODEL_NAME=Qwen/Qwen3-Embedding-8B \
TASK_NAME=BrightProBiologyRetrieval \
MAX_SEQ_LENGTH=4096 BATCH_SIZE=16 \
python scripts/run_mteb_eval.py

Contributors

yilunzhao

3 commits

Languages

Python

91.1%

Shell

8.9%