Add CI benchmark suite: latency + recall regression tracking#446
Add CI benchmark suite: latency + recall regression tracking#446Sravan1011 wants to merge 14 commits into
Conversation
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
…emoss#435) Adds an automated benchmark harness under benchmarks/ci/ that runs on every push/PR to main, records p50/p95/p99 latency and recall@5/@10 against a fixed 1K-doc corpus, and fails the build when results regress past configurable thresholds versus a checked-in baseline. - test_bench_ci_moss.py: pytest suite (latency, recall, regression guard, JSON results writer) - conftest.py: CLI flags for output path, baseline file, and thresholds - generate_ground_truth.py + ground_truth.json: pre-computed expected results per query (top_k=50, moss-minilm) - baseline.json: placeholder baseline; latency guard skips until a real baseline is captured from CI hardware - .github/workflows/benchmark.yml: CI job with artifact upload and a manual update_baseline dispatch input Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
1 issue found across 12 files
You’re at about 97% of the monthly reviewed-line limit. You may want to disable incremental reviews to conserve quota. Reviews will continue until that limit is exceeded. If you need help avoiding interruptions, please contact contact@cubic.dev.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="benchmarks/ci/test_bench_ci_moss.py">
<violation number="1" location="benchmarks/ci/test_bench_ci_moss.py:196">
P2: Tests share a mutable `benchmark_results` dict and rely on implicit alphabetical class ordering for correct behavior (measurement → regression guard → writer). Consider using `@pytest.mark.order` or `pytest-dependency` to make the sequence explicit and resilient to random-ordering plugins or future renames.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| @@ -0,0 +1,374 @@ | |||
| """CI Benchmark Suite — Latency and Recall for Moss. | |||
There was a problem hiding this comment.
P2: Tests share a mutable benchmark_results dict and rely on implicit alphabetical class ordering for correct behavior (measurement → regression guard → writer). Consider using @pytest.mark.order or pytest-dependency to make the sequence explicit and resilient to random-ordering plugins or future renames.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At benchmarks/ci/test_bench_ci_moss.py, line 196:
<comment>Tests share a mutable `benchmark_results` dict and rely on implicit alphabetical class ordering for correct behavior (measurement → regression guard → writer). Consider using `@pytest.mark.order` or `pytest-dependency` to make the sequence explicit and resilient to random-ordering plugins or future renames.</comment>
<file context>
@@ -0,0 +1,374 @@
+# ---------------------------------------------------------------------------
+
+
+class TestBenchmarkLatency:
+ """Measure end-to-end query latency over multiple rounds."""
+
</file context>
- Share QUERIES/index config between tests and generator via bench_queries.py so the sets can never drift - Check index existence via list_indexes() instead of a broad except around get_index, so auth/network errors surface rather than silently triggering index creation - Add --recreate flag to generate_ground_truth.py to rebuild the index after corpus/model changes before capturing ground truth - Run baseline-update workflow dispatches without the regression comparison so an intentional perf change can't fail its own baseline refresh; clarify that persisting requires committing the artifact - Activate the recall guard: baseline now carries measured recall (hardware-independent); latency stays placeholder until a CI- runner baseline is committed - Pin benchmark dependencies exactly to keep results attributable to repo changes - Docs: fix non-runnable shell example, qualify CI coverage (fork PRs skip), document ground truth as a ranking-stability reference Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds a CI-oriented benchmark harness under benchmarks/ci/ to measure Moss query latency (p50/p95/p99) and retrieval stability (recall@5/@10) per commit, persist results to JSON, and optionally gate PRs/commits on regressions vs a checked-in baseline. This fits the repo’s broader goal of keeping the on-device runtime fast and stable across SDK/native changes.
Changes:
- Introduces a pytest-based benchmark suite that builds/loads a fixed CI index, measures latency + recall, compares to
baseline.json, and writesbenchmark_results.json. - Adds a dedicated GitHub Actions workflow to run the suite on push/PR and upload per-commit benchmark artifacts (with a manual baseline-refresh mode).
- Documents the benchmark suite and links it from existing benchmark/agent guidance docs; updates ignores for generated outputs.
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
benchmarks/README.md |
Links the main benchmark docs to the new CI benchmark suite and describes behavior at a high level. |
benchmarks/ci/test_bench_ci_moss.py |
Core pytest suite: index setup, latency/recall measurement, regression guards, and JSON writer. |
benchmarks/ci/requirements.txt |
Minimal pinned deps for running the benchmark suite from repo root. |
benchmarks/ci/README.md |
Usage and operational documentation for running benchmarks, guards, and baseline updates. |
benchmarks/ci/ground_truth.json |
Committed ground-truth doc IDs per query for recall computation. |
benchmarks/ci/generate_ground_truth.py |
Script to (re)generate ground_truth.json when corpus/model changes. |
benchmarks/ci/conftest.py |
Adds pytest CLI flags for output paths and regression thresholds. |
benchmarks/ci/bench_queries.py |
Shared benchmark constants (queries, model id, index name, doc count). |
benchmarks/ci/baseline.json |
Initial placeholder baseline for regression comparison (recall active; latency intentionally zero). |
benchmarks/.gitignore |
Ignores CI benchmark __pycache__ and generated benchmark_results.json within benchmarks/. |
AGENTS.md |
Documents the new benchmark workflow in agent guidance. |
.gitignore |
Broadens .venv ignore pattern and ignores benchmark_results.json at repo root. |
.github/workflows/benchmark.yml |
New workflow that runs the benchmark suite and uploads results artifacts; supports manual baseline refresh behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| for q in QUERIES: | ||
| expected_ids = ground_truth.get(q, []) | ||
| if not expected_ids: | ||
| continue |
| output_path = request.config.getoption("--benchmark-output") | ||
| with open(output_path, "w") as f: | ||
| json.dump(benchmark_results, f, indent=2) |
| help="Max allowed absolute decrease in recall@k vs baseline " | ||
| "(default: 0.05 = 5 percentage points)", |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
- Restore indentation of the lazy moss import inside the moss_client fixture (auto-applied suggestion dedented it, breaking collection) - Fail fast when a benchmark query has no ground-truth entry instead of silently shrinking the evaluated set and inflating recall - Create parent directories for --benchmark-output before writing - Correct --recall-threshold help text (guard compares recall@5) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes #435
What this adds
An automated benchmark harness (
benchmarks/ci/) that runs on every push/PR tomain, records p50/p95/p99 latency and recall@5/@10 per commit, and fails the build when results regress past configurable thresholds versus a checked-in baseline.How it works
TestBenchmarkLatencymoss-minilm,top_k=5TestBenchmarkRecallground_truth.json, generated withtop_k=50); reports recall@5 and recall@10TestRegressionGuardbaseline.json(both thresholds configurable via CLI flags)TestWriteResultsThe GitHub Actions workflow (
.github/workflows/benchmark.yml) uploadsbenchmark_results.jsonas an artifact (benchmark-results-<sha>, 90-day retention) on every run — including failed ones — so numbers accumulate per commit and can be published/graphed later.Files
benchmarks/ci/test_bench_ci_moss.py— the suite (latency, recall, guard, writer)benchmarks/ci/conftest.py— pytest CLI flags (--benchmark-output,--baseline-file,--latency-threshold,--recall-threshold)benchmarks/ci/generate_ground_truth.py— regeneratesground_truth.jsonwhen the corpus/model changesbenchmarks/ci/ground_truth.json— committed ground truth for the 15 benchmark queriesbenchmarks/ci/baseline.json— performance baseline (placeholder for now, see below).github/workflows/benchmark.yml— CI job withworkflow_dispatch+update_baselineinputbenchmarks/ci/README.md— full usage docs; top-levelbenchmarks/README.mdandAGENTS.mdlink to itVerification
Tested end-to-end in a
python:3.12-slim-trixiecontainer (same Python as the workflow), invoked exactly as CI invokes it from the repo root:pip install -r benchmarks/ci/requirements.txtfrom repo root installs the local SDK + deps4 passed, 1 skipped in 43s(skip = latency guard, expected with placeholder baseline)P95 latency regressed by +181%,Recall@5 dropped by 0.0600 (threshold 0.0500))if: always()) has dataNotes for reviewers
baseline.jsonis intentionally a placeholder. Latency numbers are hardware-dependent, so the first real baseline should come from CI: trigger the workflow once after merge, download thebenchmark-results-<sha>artifact, and commit it asbenchmarks/ci/baseline.json. Until then the latency guard skips (recall guard is active but trivially passes against a zero baseline).update_baselinedispatch input does not auto-commit — the workflow hascontents: read, so it copies the results overbaseline.jsonon the runner and prints them; persisting is a documented manual commit. Happy to add an auto-commit step (needscontents: write) if maintainers prefer.MOSS_PROJECT_ID/MOSS_PROJECT_KEYsecrets the suitepytest.skips rather than erroring, so external contributors' PRs won't break — at the cost of not benchmarking them.moss-minilmmodel-download timeout was observed across many local runs; if it shows up in CI,pytest-rerunfailureswould be a cheap hardening follow-up.