Measure the counterpart honestly: warm models, vectorised search, scope split

Three defects made the external baseline numbers and labels wrong.

- bench/systems.py: _sentence_model rebuilt the model on every query, so dense arm
  latency was mostly model loading and the latency-vs-size curve was flat because a
  constant dominated it. Models are now cached per process.
- bench/counterpart.py: visible() and search() walked 404k Python objects per query.
  Per-item holder/source/time columns are built once and the query path is numpy;
  12.5 queries/s instead of 0.7.
- bench/metrics.py: the aggregate key was named wrong_holder_confidence_unanswerable
  while it counts top hits that ARE from the asking holder. BASELINE.md read that
  backwards. Renamed to top_hit_from_holder_unanswerable and the sentence rewritten.
- bench/run_external.py: the by-modality table read row['answerable'] but aggregate
  emits n_answerable, so the writer died after scoring finished. Each store is now
  scored twice, [holder scope] and [whole corpus], so the gain from the holder
  boundary is separable from the gain from ranking; progress lines every 500 queries.

Verification: pytest bench/tests -q -> 61 passed; ruff check bench/ -> all checks
passed; mypy (fresh cache) -> no issues in 25 source files.
This commit is contained in:
ZacharyZhang-NY committed 2026-10-07 13:33:13 -04:00
1 parent c4e2982506
commit dbe1d2cc82
8 files changed
+381 -34

No files matched your search

+28 -8
View File
@@ -31,16 +31,29 @@ from bench.systems import machine_facts
REPORTS = ROOT / "reports"
TOP_K = 10
HOLDER_SCOPE = "holder scope"
WHOLE_CORPUS_SCOPE = "whole corpus"
SCOPES = (HOLDER_SCOPE, WHOLE_CORPUS_SCOPE)
PROGRESS_EVERY = 500
def score_store(store: Counterpart, queries: list[Query], holder_of: dict[str, str]) -> dict[str, Any]:
"""Rank every query against one counterpart store and aggregate the same metrics the arms use."""
def score_store(store: Counterpart, queries: list[Query], holder_of: dict[str, str], scope: str) -> dict[str, Any]:
"""Rank every query against one counterpart store and aggregate the same metrics the arms use.
scope "holder" answers only from the asking holder's memory; scope "whole corpus" hides the
holder boundary instead, which is the only difference from what the product's arms are shown.
Without both rows the gain from scoping cannot be told apart from the gain from ranking.
"""
store.timings.clear()
scores = []
for query in queries:
ranked = store.search(query.text, query.holder, query.as_of, TOP_K)
scores.append(score_query(query.query_id, ranked, set(query.gold), holder=query.holder, holder_of=holder_of))
for index, query in enumerate(queries, start=1):
holder = query.holder if scope == HOLDER_SCOPE else None
ranked = store.search(query.text, holder, query.as_of, TOP_K)
scores.append(score_query(query.query_id, ranked, set(query.gold), holder=holder, holder_of=holder_of))
if index % PROGRESS_EVERY == 0:
print(f" {store.name} [{scope}] {index}/{len(queries)}", flush=True)
summary: dict[str, Any] = {
"store": store.name,
"store": f"{store.name} [{scope}]",
"items": len(store.items),
"closed_items": sum(1 for item in store.items if item.valid_to is not None),
"overall": aggregate(scores),
@@ -92,6 +105,13 @@ def write_markdown(report: dict[str, Any], path: Path) -> None:
f"| {latency['p50']:.3f} | {latency['p95']:.3f} |"
)
lines += ["", f"n per modality: {json.dumps(meta['queries_by_modality'], sort_keys=True)}", ""]
lines += [
"",
"`[holder scope]` answers only from the asking holder's memory; `[whole corpus]` searches every",
"holder's memory, which is what the product's arms are shown. The gap between the two rows is the",
"value of the holder boundary, not of a better ranker.",
"",
]
for store in report["stores"]:
lines.append(f"## {store['store']} by modality")
lines.append("")
@@ -99,7 +119,7 @@ def write_markdown(report: dict[str, Any], path: Path) -> None:
lines.append("| --- | --- | --- | --- | --- | --- |")
for modality, row in sorted(store["by_modality"].items()):
lines.append(
f"| {modality} | {row['answerable']} | {row['recall_at_10']:.3f} | {row['hit_at_10']:.3f} "
f"| {modality} | {row['n_answerable']} | {row['recall_at_10']:.3f} | {row['hit_at_10']:.3f} "
f"| {row['hit_at_10_wilson_lower']:.3f} | {row['mrr']:.3f} |"
)
lines.append("")
@@ -146,7 +166,7 @@ def main() -> int:
"extraction": "lexical (see bench/counterpart.py); LLM extraction deferred with the generator",
"machine": machine_facts(),
},
"stores": [score_store(store, queries, holder_of) for store in stores],
"stores": [score_store(store, queries, holder_of, scope) for store in stores for scope in SCOPES],
}
REPORTS.mkdir(parents=True, exist_ok=True)
args.out.write_text(json.dumps(report, indent=1, sort_keys=True), encoding="utf-8")