Skip to content

feat(experiments): union of embedding and BM25 candidates, a one-question probe and a hard set - #29

Merged
yodablocks merged 16 commits into
exp/jev-rerankerfrom
exp/recall-union
Oct 4, 2026
Merged

yodablocks merged 16 commits into
exp/jev-rerankerfrom
exp/recall-union

Conversation

@yodablocks

Copy link
Copy Markdown
Owner

Stacked on #28 (base is exp/jev-reranker). Merge #28 first, then retarget this PR to master; delete the base branch after merging #28 or this PR will land on it instead.

What

A follow-up experiment on first-stage recall. In #28 both re-rankers missed the same 2 gold questions because the gold page was not in the embedding top 40. This adds BM25 candidates next to the embedding candidates and tests two ways of merging them. Everything is under experiments/recall/; nothing in quarq/ changes.

  • hybrid.py: BM25 (no new dependency), reciprocal rank fusion (RRF), and a round-robin union.
  • probe.py: checks one question at a time (thread count capped, query embedding and BM25 index cached on disk, Jev is a cloud call, bge never loaded).
  • regress.py, e2e_union.py, run.py: pool-recall regression over the 38 questions, and end to end with Jev.
  • hard_v1.jsonl: 15 questions that are hard for retrieval (2 known misses, 9 new, 4 French rewrites).

Predictions and pass conditions were committed before each run.

Results

Gold page inside the first N chunks, 38 original questions:

Pool Embedding BM25 RRF Union
10 36 36 35 38
20 36 37 37 38
  • RRF lost b2-013 at pool 10 and buried pages that one method had in its top 6 on 4 of 11 hard questions. The union loses nothing.
  • End to end, union pool of 10 + Jev: 38/38 Hit@1, MRR 1.000 (baseline 36/38).
  • Hard set, union + Jev: gold ranked first for 9 of 11; h-009 and h-011 are in no pool.
  • French rewrites of the four French-page questions: embedding rank improves on prose pages (14 to 1, 4 to 2, miss to 9); the B7 table page is still missed by the embedding, so for table rows the embedding, not the language, is the issue. BM25 rescues it.

Caveats

  • A perfect score on a set that has been iterated on partly reflects saturation. The two gains on the original set are the questions that motivated the merge. The better evidence is h-008 and h-010, which were not used to design it, and that is two questions.
  • The hard questions h-003..h-015 were drafted by an LLM from page text, labelled synthetic-draft, and have not been human-reviewed. quarq eval refuses them until they are accepted.
  • The French rewrites share the page's own vocabulary, so "language" and "word overlap" are not separated.
  • One correction recorded in the README: I first wrote that neither method found b2-011; BM25 does, and RRF buried it.
  • Jev is a cloud API (question and passage text leave the machine). Jev answers are cached, one per pair; run-to-run variation is not measured.
  • Not done: any real scale test (the corpus is 2,571 chunks), tuning of BM25 or fusion parameters.

Reproduce

python3 experiments/recall/probe.py --id h-002 --pool 10 --merge union
python3 experiments/recall/regress.py        # no Jev calls
python3 experiments/recall/e2e_union.py 10   # needs TYPESAFE_API_KEY

pytest tests/: 247 passed, unchanged.

@yodablocks
yodablocks merged commit 5af8056 into exp/jev-reranker Oct 4, 2026
yodablocks added a commit that referenced this pull request Oct 4, 2026
feat(experiments): bring the recall experiment (#29) onto master
@yodablocks
yodablocks deleted the exp/recall-union branch October 4, 2026 04:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant