Two fan-out streams. Both bear directly on the ticket's "search
latency on real shards" headline finding.
## Lazy concept_relations loading
Phase 1 (migration memoization) cut SQLite executes 65% but warm-
cache wall barely moved. cProfile pinned the next hotspot:
synonym_expand 2.8 s × 2 calls + _load_token_idf 0.3 s. The eager
loader dumped all ~290 K concept_relations rows on first call —
the price of being able to answer ANY future question without
re-querying. Wrong tradeoff for single-query CLI use.
Refactored arborist/concepts/query.py:
- New _load_neighbors_for(shards_dir, tokens) — targeted
WHERE token IN (...) OR target IN (...) query. Returns just the
direct synonym neighborhood for the given tokens (~300 rows for
a typical 5-token question, vs 290 K for the full table).
- New _load_rivalry_rows(shards_dir) — process-wide cache of the
~2-row rivalry-relation set; near-zero cost.
- New _load_idf_for(shards_dir, tokens) — IDF only fetched when
expansion exceeds max_total (the cap). Most queries never reach
the truncation branch and skip IDF entirely.
- Per-token process-wide neighbor cache so multi-query bench scripts
don't re-query tokens already seen.
- synonym_expand and rivalry_excluded refactored to use the lazy
loaders. Eager _load_indices / _get_indices kept for any
back-compat caller; not used by hot paths.
- invalidate_cache() clears all three caches.
All 14 concept tests pass unchanged — the contract is preserved.
Re-profile of `who wrote virt-back?` against ~38 GB of real shards
(warm cache):
metric pre-fix post-Phase-1 post-lazy-concepts
wall_ms 14,500 13,400 9,300 (-36%)
search_ms 9,900 10,600 5,000 (-49%)
SQLite executes 10,623 3,687 3,708 ~same
synonym_expand 2,966 2,840 ~0 (lazy hit)
Search target was <5 s; we hit 5.0 s on the warm path. Cold cache
should drop further (the 290 K-row dump was disk-bound).
## Real-shard baseline artifact (Phase 2)
bench/scripts/real_shard_baseline.py — runs an 8-question fixture
through the full query() pipeline and emits:
- bench/results/real-shard-baseline.json (durable; commit_sha,
shards_fingerprint, per-query rows, summary)
- bench/results/real-shard-baseline.md (human-readable summary)
Question set in bench/fixtures/real-shard-baseline-v1.jsonl:
virt-back, France, Mac OS X, Linux, Microsoft, AMD/Intel
(rivalry path), and two canonical-projection cases (math + logic
preflight short-circuit).
First baseline run (commit e78814c plus this fan-out, BURN=1):
audit_mode n notes
STRICT 4 (virt-back, France, Mac OS X, Linux)
HYBRID 2 (Microsoft founder, AMD/Intel)
CANONICAL 2 (0.1+0.2, A IMPL B; <1 ms each)
wall median 4.1 s (range 0.6 ms – 7.2 s)
primary used 4 / 8
`who wrote virt-back?` lands at 6.3 s wall, audit STRICT, primary
source #1, cited evidence still includes a copyright footer
(reviewer's warrant-quality finding — deferred to follow-up ticket
since the latency fix was the gating concern).
Hard constraint preserved: baselines NEVER gate CI. The artifact
is for confirming a fix moved the needle, seeding ForkScore
comparisons, and noting findings worth tickets.
`make bench-real-shard` wires it. Honors ARBORIST_SHARDS_DIR.
## Test status
Full suite: 1306 passed, 36 skipped (no regressions from the
concept refactor; 14 concept tests cover the lazy/eager
equivalence).
335 lines
11 KiB
Python
335 lines
11 KiB
Python
"""Real-shard workload baseline (#000026 Phase 2).
|
||
|
||
Runs a fixed question set against the operator's real shard
|
||
directory and emits one JSON artifact + a short markdown summary.
|
||
|
||
Usage:
|
||
|
||
python -m bench.scripts.real_shard_baseline \
|
||
--shards-dir ~/.arborist/shards \
|
||
--fixtures bench/fixtures/real-shard-baseline-v1.jsonl \
|
||
--out bench/results/real-shard-baseline.json
|
||
|
||
Or via the Makefile:
|
||
|
||
make bench-real-shard
|
||
|
||
Outputs:
|
||
|
||
bench/results/real-shard-baseline.json one row per question
|
||
bench/results/real-shard-baseline.md markdown summary
|
||
|
||
Captured per question:
|
||
|
||
- question, audit_mode, verifier_method, n_verified, n_quotes
|
||
- wall_ms, search_ms, llm_ms, canonical_ms (preflight short-circuit)
|
||
- primary_source_used, sources_count
|
||
- status (canonical_projection | cache_hit | cache_miss_then_written
|
||
| no_sources | broad_quantifier_rejected | ...)
|
||
|
||
This is observation, not enforcement. Baselines NEVER gate CI per the
|
||
ticket's hard constraint. The artifact is for: (a) confirming a fix
|
||
moved the needle, (b) seeding ForkScore comparisons, (c) noting
|
||
warrant-quality findings worth tickets.
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import argparse
|
||
import datetime as _dt
|
||
import hashlib
|
||
import json
|
||
import os
|
||
import subprocess
|
||
import sys
|
||
import time
|
||
from pathlib import Path
|
||
|
||
|
||
def _shard_fingerprint(shards_dir: Path) -> str:
|
||
"""SHA-256 over (path, size, mtime_ns) for every *.db in shards_dir.
|
||
Stable across re-opens; changes when any shard mutates."""
|
||
if not shards_dir.is_dir():
|
||
return ""
|
||
parts = []
|
||
for p in sorted(shards_dir.glob("*.db")):
|
||
st = p.stat()
|
||
parts.append(f"{p.name}\t{st.st_size}\t{st.st_mtime_ns}")
|
||
return hashlib.sha256("\n".join(parts).encode("utf-8")).hexdigest()
|
||
|
||
|
||
def _commit_sha() -> str:
|
||
try:
|
||
return subprocess.check_output(
|
||
["git", "rev-parse", "HEAD"], cwd=Path(__file__).parent
|
||
).decode().strip()
|
||
except Exception:
|
||
return ""
|
||
|
||
|
||
def _git_dirty() -> bool:
|
||
try:
|
||
out = subprocess.check_output(
|
||
["git", "status", "--porcelain"], cwd=Path(__file__).parent
|
||
).decode()
|
||
return bool(out.strip())
|
||
except Exception:
|
||
return False
|
||
|
||
|
||
def _read_fixtures(path: Path) -> list[dict]:
|
||
rows = []
|
||
for line in path.read_text(encoding="utf-8").splitlines():
|
||
line = line.strip()
|
||
if not line or line.startswith("#"):
|
||
continue
|
||
obj = json.loads(line)
|
||
if "_meta" in obj:
|
||
continue
|
||
rows.append(obj)
|
||
return rows
|
||
|
||
|
||
def _run_one(
|
||
*,
|
||
question: str,
|
||
shards_dir: Path,
|
||
qa_db: Path,
|
||
chat_client,
|
||
model_id: str,
|
||
burn: bool,
|
||
) -> dict:
|
||
"""Execute one query through the real query() entry point and
|
||
extract a small, stable summary dict."""
|
||
from arborist.qa.query import query as _query
|
||
from arborist.qa.client import StubClient # noqa: F401 — type hint use
|
||
|
||
t0 = time.monotonic()
|
||
result = _query(
|
||
question=question,
|
||
qa_db=qa_db,
|
||
chat_client=chat_client,
|
||
model_id=model_id,
|
||
shards_dir=shards_dir,
|
||
burn_existing=burn,
|
||
)
|
||
elapsed_ms = round((time.monotonic() - t0) * 1000, 1)
|
||
|
||
timings = result.get("timings") or {}
|
||
sources = result.get("sources") or []
|
||
primary_used = any(
|
||
s.get("source_role") in (
|
||
"primary", "primary_answer", "primary_answer_source"
|
||
) and s.get("used") is not False
|
||
for s in sources
|
||
)
|
||
return {
|
||
"question": question,
|
||
"status": result.get("status"),
|
||
"audit_mode": result.get("audit_mode"),
|
||
"verifier_method": result.get("verifier_method"),
|
||
"n_verified": result.get("n_verified"),
|
||
"n_quotes": result.get("n_quotes"),
|
||
"pi_star_ref": result.get("pi_star_ref"),
|
||
"lookup_path": result.get("lookup_path"),
|
||
"wall_ms": elapsed_ms,
|
||
"total_ms": timings.get("total_ms"),
|
||
"search_ms": timings.get("search_ms"),
|
||
"llm_ms": timings.get("llm_ms"),
|
||
"verify_ms": timings.get("verify_ms"),
|
||
"canonical_preflight_ms": timings.get("canonical_preflight_ms"),
|
||
"sources_count": len(sources),
|
||
"primary_source_used": primary_used,
|
||
"answer_text": (result.get("answer_text") or "")[:240],
|
||
}
|
||
|
||
|
||
def _summarize(rows: list[dict]) -> dict:
|
||
"""Aggregate the per-question rows into headline metrics."""
|
||
n = len(rows)
|
||
walls = [r["wall_ms"] for r in rows if isinstance(r.get("wall_ms"), (int, float))]
|
||
walls_sorted = sorted(walls)
|
||
median = walls_sorted[len(walls_sorted) // 2] if walls_sorted else None
|
||
|
||
by_audit: dict[str, int] = {}
|
||
by_status: dict[str, int] = {}
|
||
for r in rows:
|
||
a = r.get("audit_mode") or "—"
|
||
s = r.get("status") or "—"
|
||
by_audit[a] = by_audit.get(a, 0) + 1
|
||
by_status[s] = by_status.get(s, 0) + 1
|
||
return {
|
||
"questions": n,
|
||
"wall_ms_median": median,
|
||
"wall_ms_max": max(walls) if walls else None,
|
||
"wall_ms_min": min(walls) if walls else None,
|
||
"by_audit_mode": by_audit,
|
||
"by_status": by_status,
|
||
"primary_source_used_count": sum(
|
||
1 for r in rows if r.get("primary_source_used")
|
||
),
|
||
"canonical_projection_count": sum(
|
||
1 for r in rows if r.get("status") == "canonical_projection"
|
||
),
|
||
}
|
||
|
||
|
||
def _markdown(rows: list[dict], summary: dict, meta: dict) -> str:
|
||
lines = [
|
||
"# Real-shard baseline — " + meta["timestamp_utc"],
|
||
"",
|
||
f"**Commit:** `{meta['commit_sha'][:12]}`"
|
||
+ (" (dirty)" if meta.get("git_dirty") else ""),
|
||
f"**Shards fingerprint:** `{meta['shards_fingerprint'][:16]}…`",
|
||
f"**Shards directory:** `{meta['shards_dir']}`",
|
||
"",
|
||
"## Summary",
|
||
"",
|
||
f"- Questions: **{summary['questions']}**",
|
||
f"- Wall median: **{summary['wall_ms_median']} ms**",
|
||
f"- Wall range: {summary['wall_ms_min']} – {summary['wall_ms_max']} ms",
|
||
f"- Primary source used: {summary['primary_source_used_count']}/"
|
||
f"{summary['questions']}",
|
||
f"- Canonical-projection short-circuits: "
|
||
f"{summary['canonical_projection_count']}",
|
||
"",
|
||
"**Audit-mode distribution:** "
|
||
+ ", ".join(f"`{k}` {v}" for k, v in sorted(summary["by_audit_mode"].items())),
|
||
"",
|
||
"**Status distribution:** "
|
||
+ ", ".join(f"`{k}` {v}" for k, v in sorted(summary["by_status"].items())),
|
||
"",
|
||
"## Per-question",
|
||
"",
|
||
"| # | Question | Audit | Status | Wall (ms) | Method |",
|
||
"|---|----------|-------|--------|-----------|--------|",
|
||
]
|
||
for i, r in enumerate(rows, 1):
|
||
q = r["question"]
|
||
if len(q) > 60:
|
||
q = q[:57] + "…"
|
||
lines.append(
|
||
f"| {i} | {q} | "
|
||
f"{r.get('audit_mode') or '—'} | "
|
||
f"{r.get('status') or '—'} | "
|
||
f"{r.get('wall_ms') or '—'} | "
|
||
f"{r.get('verifier_method') or r.get('pi_star_ref') or '—'} |"
|
||
)
|
||
return "\n".join(lines) + "\n"
|
||
|
||
|
||
def main(argv: list[str] | None = None) -> int:
|
||
p = argparse.ArgumentParser(description=__doc__.split("\n\n")[0])
|
||
p.add_argument(
|
||
"--shards-dir", type=Path, required=True,
|
||
help="path to the shard directory (e.g. ~/.arborist/shards)",
|
||
)
|
||
p.add_argument(
|
||
"--fixtures", type=Path,
|
||
default=Path("bench/fixtures/real-shard-baseline-v1.jsonl"),
|
||
help="JSONL question set (default: real-shard-baseline-v1)",
|
||
)
|
||
p.add_argument(
|
||
"--out", type=Path,
|
||
default=Path("bench/results/real-shard-baseline.json"),
|
||
help="JSON output path",
|
||
)
|
||
p.add_argument(
|
||
"--md-out", type=Path,
|
||
default=Path("bench/results/real-shard-baseline.md"),
|
||
help="markdown summary path",
|
||
)
|
||
p.add_argument(
|
||
"--endpoint", default=os.environ.get(
|
||
"ARBORIST_LLM_ENDPOINT", "https://hermes.ai.unturf.com/v1"
|
||
),
|
||
help="LLM endpoint URL",
|
||
)
|
||
p.add_argument(
|
||
"--model", default=os.environ.get(
|
||
"ARBORIST_LLM_MODEL",
|
||
"adamo1139/Hermes-3-Llama-3.1-8B-FP8-Dynamic",
|
||
),
|
||
help="model id (default $ARBORIST_LLM_MODEL or hermes-3)",
|
||
)
|
||
p.add_argument(
|
||
"--burn", action="store_true",
|
||
help="force fresh inference per question (skip cache)",
|
||
)
|
||
args = p.parse_args(argv)
|
||
|
||
if not args.shards_dir.is_dir():
|
||
print(f"error: shards-dir not found: {args.shards_dir}", file=sys.stderr)
|
||
return 2
|
||
if not args.fixtures.is_file():
|
||
print(f"error: fixtures not found: {args.fixtures}", file=sys.stderr)
|
||
return 2
|
||
|
||
# Defer LLM client construction until the args are known.
|
||
from arborist.qa.client import OpenAICompatibleClient
|
||
|
||
qa_db = args.shards_dir / "qa.db"
|
||
chat_client = OpenAICompatibleClient(base_url=args.endpoint)
|
||
|
||
fixtures = _read_fixtures(args.fixtures)
|
||
print(
|
||
f"running {len(fixtures)} questions against {args.shards_dir}",
|
||
file=sys.stderr,
|
||
)
|
||
|
||
rows = []
|
||
for fx in fixtures:
|
||
question = fx["question"]
|
||
print(f" → {question[:60]}", file=sys.stderr)
|
||
try:
|
||
row = _run_one(
|
||
question=question,
|
||
shards_dir=args.shards_dir,
|
||
qa_db=qa_db,
|
||
chat_client=chat_client,
|
||
model_id=args.model,
|
||
burn=args.burn,
|
||
)
|
||
except Exception as exc: # one bad question shouldn't kill the run
|
||
row = {
|
||
"question": question,
|
||
"status": "error",
|
||
"error": str(exc),
|
||
}
|
||
# Fold any operator notes from the fixture
|
||
if "expected_routing" in fx:
|
||
row["expected_routing"] = fx["expected_routing"]
|
||
if "note" in fx:
|
||
row["note"] = fx["note"]
|
||
rows.append(row)
|
||
|
||
summary = _summarize(rows)
|
||
meta = {
|
||
"schema_version": "real-shard-baseline-v1",
|
||
"timestamp_utc": _dt.datetime.now(_dt.timezone.utc).isoformat(
|
||
timespec="seconds"
|
||
),
|
||
"commit_sha": _commit_sha(),
|
||
"git_dirty": _git_dirty(),
|
||
"shards_dir": str(args.shards_dir),
|
||
"shards_fingerprint": _shard_fingerprint(args.shards_dir),
|
||
"model": args.model,
|
||
"endpoint": args.endpoint,
|
||
"burn": args.burn,
|
||
"fixtures_path": str(args.fixtures),
|
||
}
|
||
|
||
artifact = {**meta, "summary": summary, "queries": rows}
|
||
|
||
args.out.parent.mkdir(parents=True, exist_ok=True)
|
||
args.out.write_text(json.dumps(artifact, indent=2, ensure_ascii=False))
|
||
args.md_out.write_text(_markdown(rows, summary, meta))
|
||
|
||
print(f"wrote {args.out}", file=sys.stderr)
|
||
print(f"wrote {args.md_out}", file=sys.stderr)
|
||
print(json.dumps(summary, indent=2))
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(main())
|