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 --arch 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-report3description: 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 --arch quasar` is the37sweep-audit against the functional counterpart (composite `list` = matrix,38`tuple` = tile shape). Flag stale-report risk when current test axes are absent39from the CSV.4041Decide scope before running. A narrowed sweep (`-k`, `--op`) is right for42debugging; a report meant for analysis must cover the full intended sweep.43Never narrow the sweep in the test file to make a run finish.4445## 2. Run the sweep4647Use the two-phase producer/consumer flow, never a single serial invocation:4849```bash50cd tests51CHIP_ARCH=<arch> pytest --compile-producer -n 10 -m perf ./python_tests/perf_[op].py52CHIP_ARCH=<arch> pytest --compile-consumer -n 15 -m perf ./python_tests/perf_[op].py53```5455Prefer the `run-test` workflow where it applies; it serializes simulator56access and diagnoses hangs. `tests/run_llk_perf_wormhole.sh` and57`tests/run_llk_perf_blackhole.sh` show the exact CI invocation.5859Rules:6061- `--speed-of-light` turns runtime parameters into compile-time constants and62 changes measured cycles. CI passes it. Match CI when the report will be63 compared with CI numbers, and never mix speed-of-light and normal rows in64 one report.65- `--enable-perf-counters` produces a different, mutually exclusive kind of66 report. It compiles with `-DPERF_COUNTERS_COMPILED` (the WC build), which67 reduces `ZONE_SCOPED` to metadata only: the run emits no wall-clock68 `mean(<run type>)` columns, only `<RUN_TYPE>_..._pct` efficiency columns.69 It also writes to the same `<module>.csv` path, overwriting the timing70 report. Move the timing report out of the way first (see Refresh and71 compare), run counters as a separate sweep, and validate the result as a72 counter report. `--dump-perf-counters`73 additionally writes raw counter values to `<module>.counters.csv`.74- Perf counters are unavailable on Quasar. The build gate keeps the define off75 there and `counters.h` `#error`s if it ever slips through, so the flag76 yields no counter columns.77- SFPU sweep modules need `--mode perf`; the selector defaults to `accuracy`78 and deselects the perf sweep.79- Do not pass `--coverage`. Instrumentation invalidates perf numbers.80- Avoid `-x` on a report run. It aborts mid-sweep and the combined CSV is81 silently partial. Use it only while debugging.82- Never hand-edit a CSV. Fix the test or rerun.8384## 3. Know where the files come from8586- Each worker writes `<module>.<worker>.csv` and `<module>.<worker>.post.csv`87 into `/tmp/tt-llk-build/temp_perf_data/` when the module-scoped88 `perf_report` fixture tears down. Under GitHub Actions the root is89 `$RUNNER_TEMP/tt-llk-build` instead. The worker is `gw0`, `gw1`, … under90 `-n`, otherwise `master`. Look there for partial artifacts after an91 aborted run.92- `pytest_sessionfinish` calls `combine_perf_reports()`, which merges the93 per-worker files into `perf_data/runs/<tag>/<module>/<module>.csv`,94 `<module>.post.csv`, and `<module>.counters.csv`, sorts them, and deletes95 the per-worker files. Each run writes its own `runs/<tag>/` directory and96 `perf_data/latest` is repointed at it, so a rerun neither overwrites an97 earlier report nor leaves part of one behind. `PERF_KEEP_RUNS` (default 10)98 bounds how many runs are retained. `PERF_RUN_TAG` sets the tag; off CI it99 defaults to `local-<utc timestamp>`.100- The producer phase writes no report and skips combining.101- The raw CSV holds per-marker means. The `.post.csv` divides the `mean(...)`102 and `std(...)` columns of `TILE_LOOP` rows by `loop_factor * tile_cnt`,103 giving cycles per tile; `INIT` and `KERNEL` rows are left unnormalized.104 Analysis uses `.post.csv`.105106No report at all usually means the consumer phase never reached session107finish, or every selected test was skipped.108109## 4. Validate the artifact1101111. **Schema.** A `PerfSchemaError` means one test emits different columns112 across its sweep — usually a parameter that is `None` for some values — or113 two ops share one module. Fix the test; do not work around it.1142. **Row count.** Reconcile rather than assert equality. Start from115 `selected variants × markers`, then subtract skipped or deselected116 variants; duplicate keys are rejected, never merged. Markers are the117 zones the kernel declares — `INIT` and `TILE_LOOP` in the perf sources,118 plus the `KERNEL` zone that `trisc.cpp` wraps around every profiler build.119 A counter report has no profiler-derived rows, so expect only the counter120 zones `INIT` and `TILE_LOOP` there. An unexplained shortfall means an121 aborted or partly skipped sweep; a shortfall you can attribute to skips or122 collapse is fine.1233. **Duplicate keys.** `combine_perf_reports()` warns when it collapses rows124 sharing a (sweep, marker) key. Differing metrics on a collapsed key are125 either run-to-run noise or a parameter that changes the kernel without126 being recorded as a column. Resolve which before shipping the report.1274. **Plausibility.** Inspect `marker == TILE_LOOP`. Each `L1_CONGESTION` stage128 should sit near its isolate. Values near 2048, 4096, or 8192, an isolate129 orders of magnitude above the real stage, or a healthy first variant130 followed by slow ones all indicate handshake or wait-mask bugs — switch to131 `quasar-perf-test`.1325. **Freshness.** Compare CSV columns with the current test axes. Missing axes133 mean the report predates the test; regenerate instead of analyzing.1346. **Completeness, by report kind.** A timing report carries a135 `mean(<run type>)` column for every requested run type, and a136 `TEXT_SIZE(<run type>)` column only for `L1_TO_L1`, `UNPACK_ISOLATE`,137 `MATH_ISOLATE`, and `PACK_ISOLATE`. `L1_CONGESTION` is deliberately absent138 from the code-size map, so a missing `TEXT_SIZE(L1_CONGESTION)` is correct139 rather than a defect. A counter report has no wall-clock means at all:140 check its `<RUN_TYPE>_..._pct` columns, expect only the `INIT` and141 `TILE_LOOP` markers, and note that its `.post.csv` is identical to the raw142 file because normalization only rescales columns named `mean(...)` and143 `std(...)`.144145Never present metrics from a run whose pytest phase failed.146147## 5. Record provenance148149Report back, and keep alongside the CSV when it is archived:150151- test file and module name;152- architecture and `CHIP_ARCH`;153- repository commit;154- the exact producer and consumer commands, including worker counts,155 `--speed-of-light`, and counter flags;156- run types and markers present;157- row count and output paths.158159## Refresh and compare160161- Nothing needs moving aside. Each run lands in its own `perf_data/runs/<tag>/`162 and the previous run is untouched, so a rerun that skips everything or dies163 before the consumer phase cannot leave an earlier CSV looking like the new164 result. Read the run you mean, not `latest`, when comparing two runs.165- A counter run is still a separate report kind from a timing run, and the two166 share no metric columns — but they now land in different run directories, so167 one no longer replaces the other.168- Compare like with like: same architecture, same speed-of-light setting, same169 `loop_factor` and marker, and the same report kind. Timing and counter170 reports measure different things and share no metric columns.171- Repeat a run before attributing a small delta to a code change.172173## Checklist174175- [ ] Test, architecture, and intended scope confirmed.176- [ ] Producer and consumer phases both completed without aborting.177- [ ] Coverage off; speed-of-light setting deliberate and uniform.178- [ ] The report read is the run you just did — `perf_data/latest`, or the179 `runs/<tag>/` you intended.180- [ ] `perf_data/runs/<tag>/<module>/` holds the raw and `.post.csv` files, plus181 counters when requested.182- [ ] Single schema, row count reconciled, duplicate warnings reviewed.183- [ ] Column expectations applied for the report kind actually produced.184- [ ] `TILE_LOOP` metrics inspected for plausibility.185- [ ] Columns match the current test sweep.186- [ ] Provenance recorded.187- [ ] Report handed to `perf-parameter-impact` for analysis.188189## Example triggers190191- “Generate a perf report for `perf_matmul_quasar.py`.”192- “Refresh the SFPU unary report and tell me what changed.”193- “Why is there no `.post.csv` for this test?”194- “Is this report stale?”