Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
17dabb5
refactor(records): remove legacy results processors; route by record …
ajcasagrande Jul 11, 2026
c969921
Merge origin/main into ajc/accumulator-refactor-main
ajcasagrande Jul 15, 2026
cd8dbdd
fix(accuracy): phase-scope accuracy counts so warmup and profiling do…
ajcasagrande Jul 15, 2026
f219a0e
fix(records): surface metric-handler failures in the phase error summary
ajcasagrande Jul 15, 2026
07f6d05
docs/test: address CodeRabbit review nitpicks
ajcasagrande Jul 15, 2026
68e2e27
fix(metrics): port replay_sched_lag to a columnar analyzer; delete le…
ajcasagrande Jul 16, 2026
b8b436f
refactor(metrics): delete legacy pre-columnar aggregate machinery
ajcasagrande Jul 16, 2026
ee9fa97
docs: regenerate cli-options.md after merge (cyclopts list-default re…
ajcasagrande Jul 16, 2026
61b7d82
refactor(tests/docs): consolidate accumulator tests; scrub stale lega…
ajcasagrande Jul 16, 2026
8d752e7
feat(metrics): unified accumulator protocol, first-class analyzer rol…
ajcasagrande Jul 16, 2026
576d1c3
feat(metrics): fold power-integration energy source into energy analyzer
ajcasagrande Jul 16, 2026
8468140
feat(accuracy): add accuracy record/summary models, messages, and enums
ajcasagrande Jul 16, 2026
bc2360d
feat(accuracy): add AccuracyAccumulator + AccuracyJSONLWriter channel
ajcasagrande Jul 16, 2026
a3a86b8
feat(accuracy): light up dedicated accuracy channel end-to-end
ajcasagrande Jul 16, 2026
efaadac
feat(accuracy): deliver AccuracySummary to exporters via SystemContro…
ajcasagrande Jul 16, 2026
bc766fc
fix(records): guarantee exactly-once accuracy result publish; test sh…
ajcasagrande Jul 16, 2026
17868b4
refactor(accuracy): remove dead metric_records shoehorn
ajcasagrande Jul 16, 2026
0844597
refactor(accuracy): drop dead accuracy tag constants and stale refs
ajcasagrande Jul 16, 2026
fa08e63
fix(accuracy): add numeric bounds to accuracy summary/record fields
ajcasagrande Jul 16, 2026
e381e96
docs(accuracy): document dedicated accuracy record-type channel
ajcasagrande Jul 16, 2026
52b9d9e
fix(accuracy): reproduce byte-identical legacy accuracy exports from …
ajcasagrande Jul 16, 2026
877c12e
refactor(accuracy): drop dead export wiring + fix architecture doc
ajcasagrande Jul 16, 2026
106f01b
feat(accuracy): capture full model output and thinking in per-record …
ajcasagrande Jul 16, 2026
651ddd7
feat(accuracy): add conversation_id + x_request_id to per-record export
ajcasagrande Jul 16, 2026
90788c2
refactor(accuracy): rename per-record reasoning field to explanation
ajcasagrande Jul 16, 2026
9a2a016
docs(accuracy): document per-record JSONL format and fix stale accura…
ajcasagrande Jul 16, 2026
dbf5148
docs(accuracy): add the four lighteval_* graders to the grader table
ajcasagrande Jul 16, 2026
e7e61d4
refactor(records): 2-stage producers/observers + generic RecordsMessage
ajcasagrande Jul 16, 2026
2aa4e92
refactor(records): serialized record_type discriminator for wire reco…
ajcasagrande Jul 16, 2026
72fc55a
refactor(records): drop dead message enums + document generic pipeline
ajcasagrande Jul 16, 2026
347f0c7
fix(records/accuracy): address code-review findings on the accumulato…
ajcasagrande Jul 16, 2026
c9d2c5a
fix(records/accuracy): resolve second-pass code-review findings
ajcasagrande Jul 16, 2026
a921952
fix(records/accuracy): single-flight results processing + aligned acc…
ajcasagrande Jul 16, 2026
9c4062a
fix(records): address accumulator-refactor code-review findings
ajcasagrande Jul 16, 2026
90b1d41
fix(metrics): emit zero-valued per-watt metrics; normalize unbounded …
ajcasagrande Jul 16, 2026
3201929
fix(models): evaluate strict routing even when the lookup table is empty
ajcasagrande Jul 16, 2026
4785b67
docs/types: address PR review conventions (field descriptions, typed …
ajcasagrande Jul 16, 2026
2a89527
fix(controller): watchdog to bound the normal-completion side-channel…
ajcasagrande Jul 16, 2026
53bbdb6
fix(accuracy): make query_time_range sort-safe for interleaved arrivals
ajcasagrande Jul 16, 2026
e2245fd
refactor(controller): shared shutdown barrier via ResultJoinCoordinator
ajcasagrande Jul 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 105 additions & 18 deletions docs/accuracy/accuracy-benchmarking.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,12 @@ aiperf profile my-model --url http://localhost:8000 \
| `multiple_choice` | A/B/C/D match against gold letter (lighteval `ExactMatches`). Under `--accuracy-enable-cot` the model emits a reasoning trace ending in `The answer is (X)`. | MMLU |
| `mmlu_pro` | Extract the final `A`-`J` letter via the upstream 3-tier cascade: `answer is (X)` → `Answer: X` → last lone in-range letter. Fallback-tier or no-match responses are flagged `unparsed`. No optional dependencies. | MMLU-Pro |
| `math` | Extract last `\boxed{...}`, fall back to "answer is X" / last number. Apply trt-llm `strip_string` normalization, then compare via `math_equal` (lowercase string → numeric `isclose` → symbolic equivalence via sympy + latex2sympy2-extended). | AIME |
| `exact_match` | Stub. | (unused) |
| `code_execution` | Stub. | (unused) |
| `exact_match` | Strict `pred.strip() == gold.strip()` — case-sensitive, no normalization (mirrors DeepEval `Scorer.exact_match_score`). Empty/whitespace-only response scores 0 and is flagged `unparsed`. | HellaSwag, BigBench-Hard |
| `code_execution` | pass@1 by executing the model's generated code against the benchmark's bundled public + private test cases via lighteval's `codegen_metrics` (sandboxed `ProcessPoolExecutor`, 6s per-test timeout). Extracts the code block with lighteval's `extract_code`; `correct` when pass@1 == 1.0, `unparsed` when no code block was extractable. Requires the `[accuracy]` extra (lighteval). | LiveCodeBench (`lcb_codegeneration`) |
| `lighteval_expr` | Sympy-backed expression extraction and symbolic equivalence (lighteval `expr_gold_metric`): pulls the model's final expression and compares it to gold via lighteval's math parser. Requires the `[accuracy]` extra (lighteval). | AIME24, AIME25 |
| `lighteval_latex` | Same as `lighteval_expr` but the gold/prediction extractor uses lighteval's `LatexExtractionConfig` for `\boxed{...}` LaTeX answers (lighteval `latex_gold_metric`). Requires the `[accuracy]` extra. | MATH-500 |
| `lighteval_gpqa` | Multiple-choice `A`-`D` index extraction via lighteval's `gpqa_metric` (`NativeLetters`), using the simple-evals template the GPQA-Diamond loader mirrors for parity. Requires the `[accuracy]` extra. | GPQA-Diamond |
| `lighteval_gsm8k` | Extract the number after `####` from gold and the last number from the prediction (preferring a `####` marker when present); numeric comparison so `24` and `24.0` match (lighteval `quasi_exact_match_gsm8k`). Pure-regex — no lighteval install required. | GSM8K |

The `math` grader pipeline (aligned with `trt-llm-benchmark-recipe/src/accuracy/aime/`):

Expand All @@ -332,29 +336,112 @@ When extraction fell back past the `\boxed{}` step (i.e. the model didn't follow

## Output

Accuracy results are displayed in the console and exported to CSV:
Accuracy flows on a dedicated `accuracy` record-type channel (alongside the
`metric_records`, `gpu_telemetry`, and `server_metrics` channels — see
[Record-Type Channels](../architecture.md#record-type-channels)). Each graded
response is routed to two sinks: an accumulator that produces the per-task
summary, and a per-record JSONL writer.

Accuracy results are displayed in the console and exported to CSV. The console
table and the CSV both carry a per-task `Unparsed` count (responses where the
grader needed a regex fallback because the model output did not match the
expected format):

```text
Accuracy Benchmark Results
┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━┓
┃ Task ┃ Correct ┃ Total ┃ Accuracy ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━┩
│ abstract_algebra │ 35 │ 100 │ 35.00% │
│ ... │ ... │ ... │ ... │
│ OVERALL │ 8368 │ 14042 │ 59.59% │
└─────────────────────────┴─────────┴───────┴──────────┘
Accuracy Benchmark Results
┏━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━
┃ Task ┃ Correct ┃ Total ┃ Unparsed ┃ Accuracy ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━
│ abstract_algebra │ 35 │ 100 │ 2 │ 35.00% │
│ ... │ ... │ ... │ ... │ ... │
│ OVERALL │ 8368 │ 14042 │ 61 │ 59.59% │
└─────────────────────────┴─────────┴───────┴──────────┴──────────
```

CSV file: `<artifact_dir>/accuracy_results.csv`
**Summary CSV:** `<artifact_dir>/accuracy_results.csv` — one row per task plus a
trailing `OVERALL` row. Columns: `task, total, passed, unparsed, accuracy_rate,
unparsed_rate`.

### Per-record accuracy JSONL

**Path:** `<artifact_dir>/accuracy_export.jsonl` by default, or
`<prefix>_accuracy.jsonl` when an artifact prefix is configured (see
`AIPerfConfig.artifacts.accuracy_export_jsonl_file`). One JSON object per line,
one line per graded response — the full grading detail that the summary CSV and
console table roll up. Produced independently by the `AccuracyJSONLWriter`; it
is not affected by the summary/metric bridge that feeds the CSV and console.

Each line is a serialized `AccuracyRecordsData`
(`src/aiperf/accuracy/models.py`) with these fields, in order:

| Field | Type | Meaning |
|---|---|---|
| `session_num` | int | Conversation/session index this response came from |
| `conversation_id` | str \| null | Stable id of the benchmark problem/conversation; the key to look up the full prompt in `inputs.json` |
| `x_request_id` | str \| null | Unique per-request `X-Request-ID` for tracing this exact graded response back to the raw records |
| `worker_id` | str | Record processor that produced this record |
| `benchmark_phase` | str | Benchmark phase active when grading completed (`warmup` or `profiling`) |
| `timestamp_ns` | int | Nanosecond wall-clock timestamp when grading completed |
| `task` | str \| null | Accuracy task/subtask name (e.g. an MMLU subject); `null` when the dataset has no task label |
| `grader_name` | str | Which grader scored this response (e.g. `multiple_choice`) |
| `passed` | bool | Whether the response was graded correct |
| `unparsed` | bool | Whether the model output needed a regex fallback |
| `confidence` | float | Grading confidence (0.0–1.0) |
| `expected` | str | Ground-truth answer |
| `actual` | str | Answer extracted from the model response |
| `explanation` | str | The **grader's** explanation of why it scored the response correct/incorrect |
| `model_output` | str | The full answer content the model returned (the answer channel) |
| `model_thinking` | str \| null | The **model's** own reasoning (`reasoning_content`) when it emitted a separate reasoning channel; `null` otherwise |

Three of these fields carry distinct text and are easy to conflate:

- `explanation` — the **grader's** reasoning about the *score* (why it marked the
response right or wrong).
- `model_output` — the model's *answer* content (the answer channel).
- `model_thinking` — the model's own chain-of-thought / `reasoning_content`
channel, `null` when the model emitted no separate reasoning channel.

The full prompt is **not** embedded in each record: it lives in `inputs.json`
keyed by `session_id`, which equals this record's `conversation_id`. Join on
that id to recover the prompt — this avoids duplicating multi-KB prompts on
every graded response.

Example line (pretty-printed here; the file emits one compact object per line):

```json
{
"session_num": 0,
"conversation_id": "session_000000",
"x_request_id": "de56948f-8736-43e5-b636-303ebee20b20",
"worker_id": "worker_1c12efdd",
"benchmark_phase": "profiling",
"timestamp_ns": 1784176216352916652,
"task": "abstract_algebra",
"grader_name": "multiple_choice",
"passed": false,
"unparsed": false,
"confidence": 0.0,
"expected": "B",
"actual": "D",
"explanation": "first-line-of-response extracted to 'D'; ground_truth stripped to 'B'; match=False",
"model_output": "The answer is (D)",
"model_thinking": "I'll reason about each option in turn. Eliminating the implausible cases narrows it down. Therefore, The answer is (D)"
}
```

Use it for per-response post-hoc analysis — e.g. inspecting exactly what a
reasoning model thought before an `unparsed` answer.

## Architecture

```text
AccuracyDatasetLoader → Conversation/Turn objects (dataset pipeline)
AccuracyRecordProcessor → grades each response (record pipeline)
AccuracyResultsProcessor → aggregates per-task accuracy (results pipeline)
AccuracyConsoleExporter → Rich table output
AccuracyDataExporter → CSV export
```mermaid
flowchart LR
DL[AccuracyDatasetLoader] -->|Conversation/Turn objects| RP[AccuracyRecordProcessor<br/>grades each response]
RP -->|AccuracyRecordsData<br/>in RecordsMessage| RM[RecordsManager<br/>metadata-driven routing]
RM --> ACC[AccuracyAccumulator<br/>per-task AccuracySummary]
RM --> JW[AccuracyJSONLWriter<br/>accuracy_export.jsonl]
ACC --> CE[AccuracyConsoleExporter<br/>Rich table]
ACC --> DE[AccuracyDataExporter<br/>accuracy_results.csv]
Comment thread
ajcasagrande marked this conversation as resolved.
```

All components self-disable when `--accuracy-benchmark` is not set.
Loading
Loading