Perf Report
Goal
Produce a trustworthy performance report for one LLK perf test and hand it to
analysis with enough provenance to reproduce it.
A report is a build artifact, not a repository file: perf_data/ is
gitignored. What identifies a report is the test, the architecture, the
commit, and the exact command that produced it. Record all four.
Related skills
quasar-perf-test — create or repair the perf test and its PerfRunType
paths, and choose tile/dimension coverage. Use it first when the test does
not exist, hangs, reports implausible metrics, or needs sweep-axis changes.
perf-parameter-impact — analyze a finished .post.csv.
run-test — the repository test-runner workflow.
1. Establish the sweep
Read the perf test — tests/python_tests/perf_[op].py or
tests/python_tests/quasar/perf_[op]_quasar.py — and determine:
- which
run_types the test actually reports;
loop_factor, tile_cnt, and the other axes recorded as columns;
- how many variants
@parametrize produces;
- the architecture: the
quasar/ directory or a *_[arch].py suffix implies
it, otherwise ask.
For Quasar, compare_test_and_perf.py --dir quasar is the sweep-audit against
the functional counterpart (composite list = matrix, tuple = tile shape).
Flag stale-report risk when current test axes are absent from the CSV.
Decide scope before running. A narrowed sweep (-k, --op) is right for
debugging; a report meant for analysis must cover the full intended sweep.
Never narrow the sweep in the test file to make a run finish.
2. Run the sweep
Use the two-phase producer/consumer flow, never a single serial invocation:
cd tests
CHIP_ARCH=<arch> pytest --compile-producer -n 10 -m perf ./python_tests/perf_[op].py
CHIP_ARCH=<arch> pytest --compile-consumer -n 15 -m perf ./python_tests/perf_[op].py
Prefer the run-test workflow where it applies; it serializes simulator
access and diagnoses hangs. tests/run_llk_perf_wormhole.sh and
tests/run_llk_perf_blackhole.sh show the exact CI invocation.
Rules:
--speed-of-light turns runtime parameters into compile-time constants and
changes measured cycles. CI passes it. Match CI when the report will be
compared with CI numbers, and never mix speed-of-light and normal rows in
one report.
--enable-perf-counters produces a different, mutually exclusive kind of
report. It compiles with -DPERF_COUNTERS_COMPILED (the WC build), which
reduces ZONE_SCOPED to metadata only: the run emits no wall-clock
mean(<run type>) columns, only <RUN_TYPE>_..._pct efficiency columns.
It also writes to the same <module>.csv path, overwriting the timing
report. Move the timing report out of the way first (see Refresh and
compare), run counters as a separate sweep, and validate the result as a
counter report. --dump-perf-counters
additionally writes raw counter values to <module>.counters.csv.
- Perf counters are unavailable on Quasar. The build gate keeps the define off
there and
counters.h #errors if it ever slips through, so the flag
yields no counter columns.
- SFPU sweep modules need
--mode perf; the selector defaults to accuracy
and deselects the perf sweep.
- Do not pass
--coverage. Instrumentation invalidates perf numbers.
- Avoid
-x on a report run. It aborts mid-sweep and the combined CSV is
silently partial. Use it only while debugging.
- Never hand-edit a CSV. Fix the test or rerun.
3. Know where the files come from
- Each worker writes
<module>.<worker>.csv and <module>.<worker>.post.csv
into /tmp/tt-llk-build/temp_perf_data/ when the module-scoped
perf_report fixture tears down. Under GitHub Actions the root is
$RUNNER_TEMP/tt-llk-build instead. The worker is gw0, gw1, … under
-n, otherwise master. Look there for partial artifacts after an
aborted run.
pytest_sessionfinish calls combine_perf_reports(), which merges the
per-worker files into perf_data/runs/<tag>/<module>/<module>.csv,
<module>.post.csv, and <module>.counters.csv, sorts them, and deletes
the per-worker files. Each run writes its own runs/<tag>/ directory and
perf_data/latest is repointed at it, so a rerun neither overwrites an
earlier report nor leaves part of one behind. PERF_KEEP_RUNS (default 10)
bounds how many runs are retained. PERF_RUN_TAG sets the tag; off CI it
defaults to local-<utc timestamp>.
- The producer phase writes no report and skips combining.
- The raw CSV holds per-marker means. The
.post.csv divides the mean(...)
and std(...) columns of TILE_LOOP rows by loop_factor * tile_cnt,
giving cycles per tile; INIT and KERNEL rows are left unnormalized.
Analysis uses .post.csv.
No report at all usually means the consumer phase never reached session
finish, or every selected test was skipped.
4. Validate the artifact
- Schema. A
PerfSchemaError means one test emits different columns
across its sweep — usually a parameter that is None for some values — or
two ops share one module. Fix the test; do not work around it.
- Row count. Reconcile rather than assert equality. Start from
selected variants × markers, then subtract skipped or deselected
variants; duplicate keys are rejected, never merged. Markers are the
zones the kernel declares — INIT and TILE_LOOP in the perf sources,
plus the KERNEL zone that trisc.cpp wraps around every profiler build.
A counter report has no profiler-derived rows, so expect only the counter
zones INIT and TILE_LOOP there. An unexplained shortfall means an
aborted or partly skipped sweep; a shortfall you can attribute to skips or
collapse is fine.
- Duplicate keys.
combine_perf_reports() warns when it collapses rows
sharing a (sweep, marker) key. Differing metrics on a collapsed key are
either run-to-run noise or a parameter that changes the kernel without
being recorded as a column. Resolve which before shipping the report.
- Plausibility. Inspect
marker == TILE_LOOP. Each L1_CONGESTION stage
should sit near its isolate. Values near 2048, 4096, or 8192, an isolate
orders of magnitude above the real stage, or a healthy first variant
followed by slow ones all indicate handshake or wait-mask bugs — switch to
quasar-perf-test.
- Freshness. Compare CSV columns with the current test axes. Missing axes
mean the report predates the test; regenerate instead of analyzing.
- Completeness, by report kind. A timing report carries a
mean(<run type>) column for every requested run type, and a
TEXT_SIZE(<run type>) column only for L1_TO_L1, UNPACK_ISOLATE,
MATH_ISOLATE, and PACK_ISOLATE. L1_CONGESTION is deliberately absent
from the code-size map, so a missing TEXT_SIZE(L1_CONGESTION) is correct
rather than a defect. A counter report has no wall-clock means at all:
check its <RUN_TYPE>_..._pct columns, expect only the INIT and
TILE_LOOP markers, and note that its .post.csv is identical to the raw
file because normalization only rescales columns named mean(...) and
std(...).
Never present metrics from a run whose pytest phase failed.
5. Record provenance
Report back, and keep alongside the CSV when it is archived:
- test file and module name;
- architecture and
CHIP_ARCH;
- repository commit;
- the exact producer and consumer commands, including worker counts,
--speed-of-light, and counter flags;
- run types and markers present;
- row count and output paths.
Refresh and compare
- Nothing needs moving aside. Each run lands in its own
perf_data/runs/<tag>/
and the previous run is untouched, so a rerun that skips everything or dies
before the consumer phase cannot leave an earlier CSV looking like the new
result. Read the run you mean, not latest, when comparing two runs.
- A counter run is still a separate report kind from a timing run, and the two
share no metric columns — but they now land in different run directories, so
one no longer replaces the other.
- Compare like with like: same architecture, same speed-of-light setting, same
loop_factor and marker, and the same report kind. Timing and counter
reports measure different things and share no metric columns.
- Repeat a run before attributing a small delta to a code change.
Checklist
Example triggers
- “Generate a perf report for
perf_matmul_quasar.py.”
- “Refresh the SFPU unary report and tell me what changed.”
- “Why is there no
.post.csv for this test?”
- “Is this report stale?”
1---2name: perf-report-23description: Generate, refresh, and validate an LLK performance report by running a perf sweep end to end and checking the resulting perf_data CSV. Use when asked to produce or refresh a perf report for an op, run a perf sweep, or when a report is missing, partial, stale, or implausible.4---56# Perf Report78## Goal910Produce a trustworthy performance report for one LLK perf test and hand it to11analysis with enough provenance to reproduce it.1213A report is a build artifact, not a repository file: `perf_data/` is14gitignored. What identifies a report is the test, the architecture, the15commit, and the exact command that produced it. Record all four.1617## Related skills1819- `quasar-perf-test` — create or repair the perf test and its `PerfRunType`20 paths, and choose tile/dimension coverage. Use it first when the test does21 not exist, hangs, reports implausible metrics, or needs sweep-axis changes.22- `perf-parameter-impact` — analyze a finished `.post.csv`.23- `run-test` — the repository test-runner workflow.2425## 1. Establish the sweep2627Read the perf test — `tests/python_tests/perf_[op].py` or28`tests/python_tests/quasar/perf_[op]_quasar.py` — and determine:2930- which `run_types` the test actually reports;31- `loop_factor`, `tile_cnt`, and the other axes recorded as columns;32- how many variants `@parametrize` produces;33- the architecture: the `quasar/` directory or a `*_[arch].py` suffix implies34 it, otherwise ask.3536For Quasar, `compare_test_and_perf.py --dir quasar` is the sweep-audit against37the functional counterpart (composite `list` = matrix, `tuple` = tile shape).38Flag stale-report risk when current test axes are absent from the CSV.3940Decide scope before running. A narrowed sweep (`-k`, `--op`) is right for41debugging; a report meant for analysis must cover the full intended sweep.42Never narrow the sweep in the test file to make a run finish.4344## 2. Run the sweep4546Use the two-phase producer/consumer flow, never a single serial invocation:4748```bash49cd tests50CHIP_ARCH=<arch> pytest --compile-producer -n 10 -m perf ./python_tests/perf_[op].py51CHIP_ARCH=<arch> pytest --compile-consumer -n 15 -m perf ./python_tests/perf_[op].py52```5354Prefer the `run-test` workflow where it applies; it serializes simulator55access and diagnoses hangs. `tests/run_llk_perf_wormhole.sh` and56`tests/run_llk_perf_blackhole.sh` show the exact CI invocation.5758Rules:5960- `--speed-of-light` turns runtime parameters into compile-time constants and61 changes measured cycles. CI passes it. Match CI when the report will be62 compared with CI numbers, and never mix speed-of-light and normal rows in63 one report.64- `--enable-perf-counters` produces a different, mutually exclusive kind of65 report. It compiles with `-DPERF_COUNTERS_COMPILED` (the WC build), which66 reduces `ZONE_SCOPED` to metadata only: the run emits no wall-clock67 `mean(<run type>)` columns, only `<RUN_TYPE>_..._pct` efficiency columns.68 It also writes to the same `<module>.csv` path, overwriting the timing69 report. Move the timing report out of the way first (see Refresh and70 compare), run counters as a separate sweep, and validate the result as a71 counter report. `--dump-perf-counters`72 additionally writes raw counter values to `<module>.counters.csv`.73- Perf counters are unavailable on Quasar. The build gate keeps the define off74 there and `counters.h` `#error`s if it ever slips through, so the flag75 yields no counter columns.76- SFPU sweep modules need `--mode perf`; the selector defaults to `accuracy`77 and deselects the perf sweep.78- Do not pass `--coverage`. Instrumentation invalidates perf numbers.79- Avoid `-x` on a report run. It aborts mid-sweep and the combined CSV is80 silently partial. Use it only while debugging.81- Never hand-edit a CSV. Fix the test or rerun.8283## 3. Know where the files come from8485- Each worker writes `<module>.<worker>.csv` and `<module>.<worker>.post.csv`86 into `/tmp/tt-llk-build/temp_perf_data/` when the module-scoped87 `perf_report` fixture tears down. Under GitHub Actions the root is88 `$RUNNER_TEMP/tt-llk-build` instead. The worker is `gw0`, `gw1`, … under89 `-n`, otherwise `master`. Look there for partial artifacts after an90 aborted run.91- `pytest_sessionfinish` calls `combine_perf_reports()`, which merges the92 per-worker files into `perf_data/runs/<tag>/<module>/<module>.csv`,93 `<module>.post.csv`, and `<module>.counters.csv`, sorts them, and deletes94 the per-worker files. Each run writes its own `runs/<tag>/` directory and95 `perf_data/latest` is repointed at it, so a rerun neither overwrites an96 earlier report nor leaves part of one behind. `PERF_KEEP_RUNS` (default 10)97 bounds how many runs are retained. `PERF_RUN_TAG` sets the tag; off CI it98 defaults to `local-<utc timestamp>`.99- The producer phase writes no report and skips combining.100- The raw CSV holds per-marker means. The `.post.csv` divides the `mean(...)`101 and `std(...)` columns of `TILE_LOOP` rows by `loop_factor * tile_cnt`,102 giving cycles per tile; `INIT` and `KERNEL` rows are left unnormalized.103 Analysis uses `.post.csv`.104105No report at all usually means the consumer phase never reached session106finish, or every selected test was skipped.107108## 4. Validate the artifact1091101. **Schema.** A `PerfSchemaError` means one test emits different columns111 across its sweep — usually a parameter that is `None` for some values — or112 two ops share one module. Fix the test; do not work around it.1132. **Row count.** Reconcile rather than assert equality. Start from114 `selected variants × markers`, then subtract skipped or deselected115 variants; duplicate keys are rejected, never merged. Markers are the116 zones the kernel declares — `INIT` and `TILE_LOOP` in the perf sources,117 plus the `KERNEL` zone that `trisc.cpp` wraps around every profiler build.118 A counter report has no profiler-derived rows, so expect only the counter119 zones `INIT` and `TILE_LOOP` there. An unexplained shortfall means an120 aborted or partly skipped sweep; a shortfall you can attribute to skips or121 collapse is fine.1223. **Duplicate keys.** `combine_perf_reports()` warns when it collapses rows123 sharing a (sweep, marker) key. Differing metrics on a collapsed key are124 either run-to-run noise or a parameter that changes the kernel without125 being recorded as a column. Resolve which before shipping the report.1264. **Plausibility.** Inspect `marker == TILE_LOOP`. Each `L1_CONGESTION` stage127 should sit near its isolate. Values near 2048, 4096, or 8192, an isolate128 orders of magnitude above the real stage, or a healthy first variant129 followed by slow ones all indicate handshake or wait-mask bugs — switch to130 `quasar-perf-test`.1315. **Freshness.** Compare CSV columns with the current test axes. Missing axes132 mean the report predates the test; regenerate instead of analyzing.1336. **Completeness, by report kind.** A timing report carries a134 `mean(<run type>)` column for every requested run type, and a135 `TEXT_SIZE(<run type>)` column only for `L1_TO_L1`, `UNPACK_ISOLATE`,136 `MATH_ISOLATE`, and `PACK_ISOLATE`. `L1_CONGESTION` is deliberately absent137 from the code-size map, so a missing `TEXT_SIZE(L1_CONGESTION)` is correct138 rather than a defect. A counter report has no wall-clock means at all:139 check its `<RUN_TYPE>_..._pct` columns, expect only the `INIT` and140 `TILE_LOOP` markers, and note that its `.post.csv` is identical to the raw141 file because normalization only rescales columns named `mean(...)` and142 `std(...)`.143144Never present metrics from a run whose pytest phase failed.145146## 5. Record provenance147148Report back, and keep alongside the CSV when it is archived:149150- test file and module name;151- architecture and `CHIP_ARCH`;152- repository commit;153- the exact producer and consumer commands, including worker counts,154 `--speed-of-light`, and counter flags;155- run types and markers present;156- row count and output paths.157158## Refresh and compare159160- Nothing needs moving aside. Each run lands in its own `perf_data/runs/<tag>/`161 and the previous run is untouched, so a rerun that skips everything or dies162 before the consumer phase cannot leave an earlier CSV looking like the new163 result. Read the run you mean, not `latest`, when comparing two runs.164- A counter run is still a separate report kind from a timing run, and the two165 share no metric columns — but they now land in different run directories, so166 one no longer replaces the other.167- Compare like with like: same architecture, same speed-of-light setting, same168 `loop_factor` and marker, and the same report kind. Timing and counter169 reports measure different things and share no metric columns.170- Repeat a run before attributing a small delta to a code change.171172## Checklist173174- [ ] Test, architecture, and intended scope confirmed.175- [ ] Producer and consumer phases both completed without aborting.176- [ ] Coverage off; speed-of-light setting deliberate and uniform.177- [ ] The report read is the run you just did — `perf_data/latest`, or the178 `runs/<tag>/` you intended.179- [ ] `perf_data/runs/<tag>/<module>/` holds the raw and `.post.csv` files, plus180 counters when requested.181- [ ] Single schema, row count reconciled, duplicate warnings reviewed.182- [ ] Column expectations applied for the report kind actually produced.183- [ ] `TILE_LOOP` metrics inspected for plausibility.184- [ ] Columns match the current test sweep.185- [ ] Provenance recorded.186- [ ] Report handed to `perf-parameter-impact` for analysis.187188## Example triggers189190- “Generate a perf report for `perf_matmul_quasar.py`.”191- “Refresh the SFPU unary report and tell me what changed.”192- “Why is there no `.post.csv` for this test?”193- “Is this report stale?”