Skip to content

Commit bf9d12b

Browse files
arekay-nvclaude
andauthored
feat(metrics): consolidate gpt-oss/deepseek accuracy, dedup reporting, surface TPS (#400)
feat(metrics): consolidate gpt-oss/deepseek accuracy, dedup reporting, surface TPS Consolidate per-dataset accuracy reporting, reorganize the run-artifact layout to the MLCommons submission shape, and fix accuracy-denominator correctness across the scorers. BREAKING (report artifacts): - Remove results.json. A run now writes two separate artifacts: * performance/result_summary.json - perf rollups (latency / TTFT / TPOT / OSL percentiles, qps, and the newly-surfaced tps). Accuracy is no longer in it. * accuracy/accuracy_results.json - accuracy_scores, always a LIST of one entry per accuracy dataset (previously a dict); an empty list when no accuracy datasets ran. - Both artifacts are written atomically (tmp + fsync(file) + rename + fsync(parent dir)) via the new utils/atomic_write.py. - All readers migrated off results.json: metrics/report.py, compliance/checker.py, metrics/results_plots.py, scripts/publish_submission.py, evaluation/bfcl_v4_multi_turn_cli.py, and the examples. Downstream tooling that parsed results.json (e.g. the upstream submission checker) must switch to the two new paths. Per-dataset accuracy consolidation: - _score_accuracy returns one entry per accuracy dataset, each carrying dataset_name, score, unit_samples, num_repeats, total_samples, complete, dataset_type, plus an optional per-subset breakdown, response_counts, and output_sequence_lengths. - Remove the bespoke gpt-oss-120b composite dataset + scorer; gpt-oss now reports as its constituent per-subset datasets like every other model. - average_accuracy adds a cross-dataset mean over the accuracy components, excluding the inline perf-scored entry. - Drop the duplicated DeepSeek overall fields from the breakdown block; the headline overall lives on the entry's scalar score. dataset_type discriminator: - Add a DatasetType (PERFORMANCE / ACCURACY) discriminator on the accuracy config, serialized onto each entry. Averaging, plotting, and the compliance gate branch on dataset_type instead of dataset_name == "performance", so a dataset legitimately named "performance" is no longer misclassified. OSL / tokenization: - Per-dataset output-sequence-length stats for accuracy datasets, computed post-run (off the hot path) from a shared reference tokenizer (token_metrics.load_reference_backend / encode_lengths) rather than a bespoke tokenizer or unreliable server completion_tokens; wall-clock recorded as osl_tokenize_s. - Single-source the token-series (OSL/ISL) builder in metrics_aggregator/registry.build_token_series_dict; report.py and the aggregator now share it (removes duplicated HDR constants + private imports). - Split the scorer output contract into get_raw_outputs (what the model generated) and get_scoring_outputs (text to score; only BFCL overrides). Accuracy-denominator correctness (missing-as-failure): - Base Scorer.score() now divides by the ISSUED count (len(sample_index_map), written at issue time), not the surviving COMPLETE subset. A partial run (drain-timeout / crash) no longer reports accuracy inflated over only the samples that came back, and an all-missing run yields 0.0 instead of mean([]) == NaN (invalid JSON). - BFCLv4Scorer.score() (the compliance-gated scorer) gets the same fix by padding each issued-but-missing sample as 0.0 under its subset, plus drop_duplicates(sample_uuid) so a duplicate COMPLETE cannot double-weight a sample. total_samples keeps its completed-count meaning (read by min_sample_count); a new issued_samples records the accuracy denominator. - DeepSeek-R1 accuracy runs the vendored MLCommons evaluator out-of-process; its denominator is unchanged. Compliance gate: - check_accuracy reads the accuracy entry + breakdown and falls back to the entry's scalar score for overall_accuracy when the breakdown omits it (DeepSeek-style), matching the plot path. - Move the BFCL golden-metric -> breakdown-key map out of the generic accuracy_results.py into a dependency-light evaluation/bfcl_v4_metrics.py, so compliance and plots import it without pulling in the heavy BFCL scorer / the optional bfcl extra. Report layout: - Conform to the mlcommons/inference#2628 layout (result_summary.json under performance/, accuracy_results.json under accuracy/). Surface tps (output tokens/sec over the run window) and per-accuracy-phase durations. Robustness / serialization: - Coerce numpy scalar scores to native Python float for JSON serialization. Dependencies (pip-audit CVE floors): - pillow 12.2.0 -> 12.3.0, click >= 8.3.3, setuptools >= 83.0.0. Tests: - New / expanded coverage: test_score_accuracy, test_accuracy_results, test_scoring (denominator), test_bfcl_v4_scorer (missing / duplicate), test_bfcl_v4_metrics, test_report_builder, test_results_plots, test_atomic_write, test_deepseek_r1_scorer, test_publish_submission, plus integration updates. --------- Signed-off-by: Rashid Kaleem <230885705+arekay-nv@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent ca91f61 commit bf9d12b

42 files changed

Lines changed: 2687 additions & 435 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ Dataset Manager --> Load Generator --> Endpoint Client --> External Endpoint
9999
| **TensorRT-LLM** | `src/inference_endpoint/trtllm/` | Adapter for TensorRT-LLM endpoints. `TRTLLMAdapter` sends requests; `TRTLLMSSEAccumulator` handles SSE streaming responses. |
100100
| **DeepSeek-R1 (MLPerf)** | `src/inference_endpoint/evaluation/scoring.py` (`LegacyMLPerfDeepSeekR1Scorer`), `examples/07_DeepSeekR1_Example/` | MLPerf DeepSeek-R1 accuracy. TensorRT-LLM is OpenAI-compatible, so it is served via `api_type: openai` / `openai_completions` (no dedicated trtllm adapter). The combined multi-subset eval (`math500`/`aime`/`gpqa`/`mmlu_pro`/`livecodebench`) is the official MLCommons `eval_accuracy.py`, run out-of-process via `uv run --project` against the isolated subproject at `src/inference_endpoint/evaluation/legacy_mlperf_deepseek_r1/` (a uv subproject excluded from the parent wheel; mirrors the VBench pattern). The example feeds the exact MLPerf prompt via pre-tokenized `input_tokens` to `/v1/completions`. |
101101
| **VideoGen** | `src/inference_endpoint/videogen/` | Adapter for video-generation endpoints (e.g. trtllm-serve `POST /v1/videos/generations`, used by MLPerf WAN2.2-T2V-A14B). Defaults to `response_format=video_path` (server saves video to shared storage and returns path) to avoid large byte payloads. Accuracy mode also runs on `video_path`: the adapter mirrors the path into `response_output` so the event log carries it to `VBenchScorer` (see `evaluation/scoring.py`), which scores videos via VBench from a sibling `uv` subproject at `examples/09_Wan22_VideoGen_Example/accuracy/` (vbench's `transformers==4.33.2` + `numpy<2` pins are incompatible with the parent env, so it runs out-of-process via `uv run --project`). Dataset is ingested via the generic JSONL loader. |
102-
| **Compliance (submission checker)** | `src/inference_endpoint/compliance/checker.py`, `scripts/check_compliance.py` | Validates a completed run's report directory against a registered ruleset. `check_submission(report_dir, ruleset, model)` reads the resolved `config.yaml` plus scorer output (`results.json` for accuracy, `scores.json` for the agentic perf run) and runs config-lock (deterministic + single-stream), the accuracy gate (`score >= factor x reference`, factor 0.97 for Edge-Agentic), and run validity (0 dropped turns). Server-side launch flags (`--reasoning off`, `--ctx-size`) aren't in client artifacts, so they're surfaced as manual attestations. CLI: `scripts/check_compliance.py REPORT_DIR` (exit 0 = pass). |
102+
| **Compliance (submission checker)** | `src/inference_endpoint/compliance/checker.py`, `scripts/check_compliance.py` | Validates a completed run's report directory against a registered ruleset. `check_submission(report_dir, ruleset, model)` reads the resolved `config.yaml` plus scorer output (`accuracy/accuracy_results.json` for accuracy, `scores.json` for the agentic perf run) and runs config-lock (deterministic + single-stream), the accuracy gate (`score >= factor x reference`, factor 0.97 for Edge-Agentic), and run validity (0 dropped turns). Server-side launch flags (`--reasoning off`, `--ctx-size`) aren't in client artifacts, so they're surfaced as manual attestations. CLI: `scripts/check_compliance.py REPORT_DIR` (exit 0 = pass). |
103103
| **Compliance (audit tests)** | `src/inference_endpoint/compliance/`, `commands/audit.py` | MLPerf compliance audits. `AuditTest` protocol + `AuditRunSpec`/`AuditRunArtifacts` + registry (`compliance/__init__.py`); `OutputCachingAudit` (`compliance/audit_test/output_caching_test.py`, which also owns the QPS-specific `AuditRunStats`) implements MLPerf **TEST04** output-caching detection — reference phase (distinct samples) vs. fixed-sample audit phase, comparing QPS against `threshold`. `commands/audit.py:run_audit` runs phases via `AuditTest.plan_runs`/`validate`, writing `audit_result.json`/`verify_<TEST>.txt` atomically via `compliance/result.py`. Enabled by the `audit:` YAML block; `cli._run` runs it after the main benchmark (upstream MLPerf order: perf run, then TEST04), or standalone with `audit.only: true`. Performance-only. |
104104

105105
### Hot-Path Architecture

‎docs/compliance_audit_plan.md‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ _intent_ over this repo's own artifacts.
8484
This repo names MLPerf **TEST04** the **output-caching test**: id `output_caching_test`
8585
(`AuditTestId.OUTPUT_CACHING_TEST`), config class `OutputCachingTestConfig`, audit
8686
`OutputCachingAudit`, and artifacts (under `<report_dir>/audit/`)
87-
`audit_result.json` + `verify_OUTPUT_CACHING_TEST.txt`. Where this doc writes
87+
`audit_output_caching_test.json` + `verify_OUTPUT_CACHING_TEST.txt`. Where this doc writes
8888
"TEST04" it means the upstream MLPerf test the output-caching audit re-implements.
8989

9090
---
@@ -134,7 +134,7 @@ benchmark from-config
134134
│
135135
│ 3. verify(runs, cfg) ; 4. write_result (atomic)
136136
▼
137-
<report_dir>/audit/ : audit_result.json + verify_OUTPUT_CACHING_TEST.txt
137+
<report_dir>/audit/ : audit_output_caching_test.json + verify_OUTPUT_CACHING_TEST.txt
138138
```
139139

140140
### Program flow (output-caching audit / MLPerf TEST04, two phases)
@@ -227,7 +227,7 @@ report is already written).
227227
▼
228228
┌──────────────────────────────────────────┐
229229
│ write_result → <report_dir>/audit/ [atomic]│
230-
│ audit_result.json (durable first) │
230+
│ audit_output_caching_test.json (durable first) │
231231
│ verify_OUTPUT_CACHING_TEST.txt (marker) │
232232
└──────────────┬───────────────────────────┘
233233
▼
@@ -404,7 +404,7 @@ The generic loop never names a specific test:
404404
`sys.exit` — `0` (PASS) / `1` (FAIL). Errors are not flattened to a single code:
405405
they propagate to `main.py`'s handler, which uses the repo-wide scheme
406406
(`InputValidationError` → `2`, `SetupError` → `3`, `ExecutionError` → `4`). The on-disk
407-
`audit_result.json` is the durable record; the exit code is the automation signal.
407+
`audit_output_caching_test.json` is the durable record; the exit code is the automation signal.
408408

409409
### Verifier — one core + in-process adapter
410410

@@ -440,7 +440,7 @@ re-check-from-disk adapter — the audit runs only via `benchmark from-config`.
440440
```
441441
src/inference_endpoint/compliance/
442442
├── __init__.py # AuditTest protocol, AuditRunSpec/Stats/Artifacts, AUDIT_TESTS map, get_audit_test()
443-
├── result.py # AuditResult + atomic write → audit_result.json + verify_<TEST>.txt
443+
├── result.py # AuditResult + atomic write → audit_<test_id>.json + verify_<TEST>.txt
444444
└── audit_test/
445445
├── __init__.py # package marker for the AuditTest implementations
446446
├── output_caching_test.py # OutputCachingAudit: plan_runs (reference + audit specs) + validate + verify_output_caching core
@@ -478,7 +478,7 @@ report_dir/
478478
├── reference/ # audit reference phase (samples=64)
479479
├── output_caching/ # audit fixed-sample phase (samples=64)
480480
├── verify_OUTPUT_CACHING_TEST.txt
481-
└── audit_result.json
481+
└── audit_output_caching_test.json
482482
```
483483

484484
### WAN2.2-T2V — the first target
@@ -598,7 +598,7 @@ run; `verify` reads `events.jsonl` and checks mean OSL within `[ref × 0.9, ref
598598
## 8. Success criteria (goal-driven; verify before done)
599599

600600
1. **Integration** — `benchmark from-config` with an `audit:` block runs both phases
601-
back-to-back and writes `audit_result.json` + `verify_OUTPUT_CACHING_TEST.txt`; PASS against a
601+
back-to-back and writes `audit_output_caching_test.json` + `verify_OUTPUT_CACHING_TEST.txt`; PASS against a
602602
no-caching `mock_http_echo_server`, FAIL against a caching mock.
603603
2. **Completion guard** — a phase that completes far fewer than its _requested_ count fails
604604
the result (`completed < requested × (1 − threshold)` → FAIL), independent of the other

‎docs/evaluation/DESIGN.md‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,9 +88,9 @@ documented prominently in the dataset README.
8888

8989
## Integration Points
9090

91-
| Component | Role |
92-
| ---------------------------------- | ------------------------------------------------------ |
93-
| `commands/benchmark/execute.py` | Builds scorer/extractor configs and runs scoring |
94-
| `dataset_manager/predefined/` | Provides ground truth labels alongside prompts |
95-
| `evaluation/livecodebench/` | Provides external execution path for LiveCodeBench |
96-
| `results.json` / benchmark reports | Receives computed accuracy summary during finalization |
91+
| Component | Role |
92+
| -------------------------------- | ------------------------------------------------------ |
93+
| `commands/benchmark/execute.py` | Builds scorer/extractor configs and runs scoring |
94+
| `dataset_manager/predefined/` | Provides ground truth labels alongside prompts |
95+
| `evaluation/livecodebench/` | Provides external execution path for LiveCodeBench |
96+
| `accuracy/accuracy_results.json` | Receives computed accuracy summary during finalization |

‎examples/03_BenchmarkComparison/compare_with_vllm.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ def run_inference_endpoint(
349349
results = parse_inference_endpoint_output(full_output)
350350

351351
# Load report JSON to enrich or backfill metrics from stdout (e.g., detailed TTFT/TPOT stats, output lengths)
352-
report_json_path = report_dir / "result_summary.json"
352+
report_json_path = report_dir / "performance" / "result_summary.json"
353353
if report_json_path.exists():
354354
try:
355355
with open(report_json_path) as f:

‎examples/11_Edge_Agentic_Example/README.md‎

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -241,30 +241,26 @@ Results are written to `results/bfcl_v4_multi_turn/`.
241241
# Single-turn overall accuracy
242242
python3 -c "
243243
import json, pathlib
244-
r = json.loads(pathlib.Path('results/edge_agentic_full_run/results.json').read_text())
245-
print('Overall ST accuracy:',
246-
r['accuracy_scores']['bfcl_v4::function_calling']['breakdown']['overall_accuracy'], '%')
244+
r = json.loads(pathlib.Path('results/edge_agentic_full_run/accuracy/accuracy_results.json').read_text())
245+
e = next(x for x in r['accuracy_scores'] if x['dataset_name'] == 'bfcl_v4::function_calling')
246+
print('Overall ST accuracy:', e['breakdown']['overall_accuracy'], '%')
247247
"
248248

249249
# Multi-turn overall accuracy (only if you ran the optional Step 3)
250250
python3 -c "
251251
import json, pathlib
252-
r = json.loads(pathlib.Path('results/bfcl_v4_multi_turn/results.json').read_text())
253-
print('Overall MT accuracy:',
254-
r['accuracy_scores']['bfcl_v4::multi_turn']['score']['overall_accuracy'], '%')
252+
r = json.loads(pathlib.Path('results/bfcl_v4_multi_turn/accuracy/accuracy_results.json').read_text())
253+
e = next(x for x in r['accuracy_scores'] if x['dataset_name'] == 'bfcl_v4::multi_turn')
254+
print('Overall MT accuracy:', e['breakdown']['overall_accuracy'], '%')
255255
"
256256
```
257257

258-
> Note: the single-turn pipeline writes `results.json` (accuracy nested under
259-
> `accuracy_scores['bfcl_v4::function_calling']`). There is no separate
260-
> `accuracy_scores.json`. A human-readable summary is also written to
258+
> Note: accuracy is written to `accuracy/accuracy_results.json`, whose
259+
> `accuracy_scores` is a **list** of per-dataset entries — index it by
260+
> `dataset_name` (as above). Each entry has a scalar `score` and, for
261+
> multi-subset scorers, a `breakdown` block with per-subset detail. A
262+
> human-readable summary is also written to
261263
> `results/edge_agentic_full_run/report.txt`.
262-
>
263-
> Result shape differs between the two pipelines: the **single-turn** entry
264-
> stores `score` as a scalar fraction (0–1) with the per-subset dict under a
265-
> separate `breakdown` key (hence `['breakdown']['overall_accuracy']` above),
266-
> whereas the **multi-turn** CLI writes `score` as a dict (hence
267-
> `['score']['overall_accuracy']` in the multi-turn snippet).
268264
269265
---
270266

@@ -325,26 +321,27 @@ print('Inline accuracy score:', s['score'], '| valid run:', missing == 0, '| mis
325321
# BFCL v4 single-turn accuracy (the gated metric)
326322
python3 -c "
327323
import json, pathlib
328-
r = json.loads(pathlib.Path('results/edge_agentic_full_run/results.json').read_text())
329-
print('Overall ST accuracy:',
330-
r['accuracy_scores']['bfcl_v4::function_calling']['breakdown']['overall_accuracy'], '%')
324+
r = json.loads(pathlib.Path('results/edge_agentic_full_run/accuracy/accuracy_results.json').read_text())
325+
e = next(x for x in r['accuracy_scores'] if x['dataset_name'] == 'bfcl_v4::function_calling')
326+
print('Overall ST accuracy:', e['breakdown']['overall_accuracy'], '%')
331327
"
332328
```
333329

334330
Both scores land in `results/edge_agentic_full_run/`: the BFCL gate under
335-
`results.json` (`accuracy_scores['bfcl_v4::function_calling']`) and the inline
336-
performance checker in `scores.json`, alongside the performance metrics
337-
(throughput, TTFT, TPOT, per-turn latency, ISL/OSL).
331+
`accuracy/accuracy_results.json` (the `bfcl_v4::function_calling` entry in the
332+
`accuracy_scores` list) and the inline performance checker in `scores.json`,
333+
alongside the performance metrics (throughput, TTFT, TPOT, per-turn latency,
334+
ISL/OSL) in `performance/result_summary.json`.
338335

339336
### Publish for the MLPerf submission checker
340337

341338
The MLPerf Inference submission checker (`tools/submission/submission_checker`
342339
in `mlcommons/inference`, v5.0+) reads endpoints results directly from the
343-
artifacts a run already writes — `result_summary.json`, `results.json`, and
344-
`config.yaml` — so no separate "log" format is needed. `scripts/publish_submission.py`
345-
copies that trio into the directory layout the checker walks and self-verifies
346-
the fields it reads (primary-metric QPS, p99 latency, TTFT/TPOT p99, and the
347-
accuracy score):
340+
artifacts a run already writes — `performance/result_summary.json`,
341+
`accuracy/accuracy_results.json`, and `config.yaml` — so no separate "log" format
342+
is needed. `scripts/publish_submission.py` copies those into the directory layout
343+
the checker walks and self-verifies the fields it reads (primary-metric QPS, p99
344+
latency, TTFT/TPOT p99, and the accuracy score):
348345

349346
```bash
350347
python scripts/publish_submission.py \

‎pyproject.toml‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ conflicts = [
2121
]
2222
# CVE floors for transitive deps we don't declare directly. click is pulled in
2323
# via transformers -> typer; 8.3.2 carries PYSEC-2026-2132 (fixed in 8.3.3).
24-
constraint-dependencies = ["click>=8.3.3"]
24+
# setuptools is pulled in via pbr/torch; 82.0.1 carries PYSEC-2026-3447 (fixed
25+
# in 83.0.0).
26+
constraint-dependencies = ["click>=8.3.3", "setuptools>=83.0.0"]
2527

2628
[tool.uv.build-backend]
2729
module-root = "src"

‎scripts/plot_results.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ def main() -> int:
3939
"report_dir",
4040
nargs="+",
4141
type=Path,
42-
help="Run report directory (contains results.json / scores.json / "
43-
"result_summary.json).",
42+
help="Run report directory (contains accuracy/accuracy_results.json / "
43+
"scores.json / performance/result_summary.json).",
4444
)
4545
parser.add_argument(
4646
"--out-dir",

0 commit comments

Comments
 (0)