Correct the asm prefill speedup, which the old benchmark overstated - #370
Conversation
…erstated bench_asm_vs_cktile.py called torch.cuda.empty_cache() between timing the two arms, so the second one re-acquired memory through hipMalloc inside its own measurement. #365 moved that out of the per-arm loop; these are the numbers the corrected harness gives at e802be3. gfx950 at and above qo_len 2048: published geomean 1.210, worst 1.00, 0 of 24 cells below 1.00 corrected geomean 1.166, worst 0.975, 1 of 24 (b1 hq16 s2048) The A/A floor also tightened, from 0.903-1.099 to 0.987-1.013, which is what makes the 0.975 cell a real loss rather than noise -- so "all 24 cells are wins or level" has to go, not just the headline figure. gfx942 moves 1.03 -> 1.02 with 9 rather than 8 cells regressing; the conclusion there is unchanged and it still ships nothing. Also drops the "correctness is covered either way" hedge on non-square shapes: #365 ended up adding kv_len == 4 * qo_len coverage on the asm arm, so that is now a statement of fact about a test rather than an assurance. Co-Authored-By: Claude <noreply@anthropic.com>
Self-review caught that the first pass corrected every figure taken from the old harness but left two same-vintage *claims* standing. Both turn out wrong against the corrected data: - "the per-seqlen geomean rises monotonically above 1024" on gfx950. It does not: 1.01, 1.09, 1.15, 1.14, 1.19, 1.19 -- a step down at 3072. The dip is 0.4%, inside that seqlen's A/A floor of 0.995-1.003, so the shipping conclusion holds, but "monotonically" does not. - "1.34x at seqlen 1024 falls to 0.90x at 1536" on gfx942. Corrected, those points are 0.955 and 0.971. The non-monotonic conclusion survives on entirely different numbers: 1.08 at 2048, 0.97 at 3072, 1.06 at 4096, 0.99 at 6144, every step outside the floor. Also corrects the benchmark docstring, which told readers the gfx950 s=256 column does not survive the A/A floor. Under the tightened floor every per-seqlen geomean in the sweep sits outside it, s=256 included. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The policy rationale in arch_caps.py still contains the superseded measurements and conclusions.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Corrects overstated AITER asm prefill performance documentation without changing routing behavior.
Changes:
- Revises gfx942/gfx950 benchmark results and noise-floor analysis.
- Updates the headline speedup from 1.21× to 1.17×.
- Clarifies non-square correctness coverage.
File summaries
| File | Description |
|---|---|
README.md |
Updates headline performance. |
docs/rocm/backends.md |
Documents corrected benchmark conclusions. |
benchmarks/rocm/bench_asm_vs_cktile.py |
Updates benchmark guidance. |
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
That comment is the single source of truth for the threshold, and it carried every superseded claim: monotone above 1024, 1.21x geomean, worst cell 1.00, "nothing regresses", and the gfx942 1.34x/0.90x points. Found by review, not by my own sweep: the repo-wide grep I ran to check for surviving copies was piped through `head -20` and cut off before reaching flashinfer/. Re-run untruncated, this was the last one. Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The advertised single-prefill result mixes unreachable batch-4 measurements with batch-1 data, and the policy source comment remains stale.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
docs/rocm/backends.md:440
- The corrected evidence here leaves the policy's stated source of truth stale:
flashinfer/rocm/arch_caps.py:316-322still records a monotonic gfx950 curve, 1.21×/1.00 with no regressions, and the old gfx942 1.34×→0.90× points.single_prefill.cuh:52-54explicitly directs maintainers to that comment for the measurement, so it should be updated alongside these documentation figures.
* **gfx942** — non-monotonic, and the oscillation does not damp with length:
1.08 at `seqlen` 2048, 0.97 at 3072, 1.06 at 4096, 0.99 at 6144. Every one of
those steps is outside the A/A floor, so no threshold holds and the arm stays
unreachable. The same 2048 cut scores geomean 1.02 with 9 of 24 cells
regressing.
- Files reviewed: 4/4 changed files
- Comments generated: 2
- Review effort level: Balanced
There was a problem hiding this comment.
🟡 Changes recommended
The benchmark docstring incorrectly claims every per-sequence geomean falls outside the stated noise floor.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Balanced
…n emit
single_prefill_with_kv_cache serves one request and the dispatcher pins
args.batch = 1 (single_prefill.cuh:181), but the sweep varies batch over {1, 4}.
Half of every gate-region aggregate quoted so far describes a shape this path
cannot produce.
Gate region (qo_len >= 2048), reachable batch-1 subset against the mixed one:
gfx950 mixed 24 cells 1.166 batch-1 12 cells 1.138, 1 below 1.00
gfx942 mixed 24 cells 1.024 batch-1 12 cells 0.976, 8 below 1.00
The threshold survives -- 11 of 12 reachable cells win on gfx950 -- and gfx942
reads worse on the reachable subset than on the mixed one, so both decisions
stand. Only the published figures were measuring the wrong population.
The batch-4 rows stay in the sweep: they are unreachable from here but are what
the batched prefill paths see, so they are labelled rather than deleted.
Also drops a blanket claim from the benchmark docstring that every per-seqlen
geomean sits outside the noise floor. That was true against per-shape A/A
ranges but invites reading a 0.1% margin as signal; the instruction is now to
compare each cell against its own control.
Co-Authored-By: Claude <noreply@anthropic.com>
|
Suppressed comment (docs/rocm/backends.md:440) — fixed in |
There was a problem hiding this comment.
🟡 Changes recommended
The batch-4 results are incorrectly described as representative of batched-prefill execution paths.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 4/4 changed files
- Comments generated: 2
- Review effort level: Balanced
They do not. This sweep times batch-mode mha_fwd against fmha_v3_fwd, while batched prefill goes to group-mode mha_varlen_fwd with args.use_asm_v3 = false (batch_prefill.cuh:60,63) or to mha_batch_prefill (batch_prefill.cuh:162-174). Introduced one commit ago while scoping the headline to batch-1: the rows needed a reason to stay and I gave them one that sounded plausible and is not true. They characterise no shipping path and are kept only so the previously published mixed aggregate stays reproducible. Co-Authored-By: Claude <noreply@anthropic.com>
Summary
The asm prefill speedup published by #365 was measured with a benchmark that released the allocator cache between timing the two arms, so the second arm paid
hipMallocinside its own measurement. #365 fixed the harness but shipped the numbers taken before the fix. These are the numbers the corrected harness gives, re-measured ate802be34e.What changed
docs/rocm/backends.md— gfx950 gate-region figures, the per-seqlencurves for both architectures, the A/A floor they are judged against, and the non-square correctness claim.README.md— the headline 1.21× becomes 1.17×.flashinfer/rocm/arch_caps.py— the policy comment behind the threshold, which carried every superseded claim and is the single source of truth for it.benchmarks/rocm/bench_asm_vs_cktile.py— docstring only; it told readers to disregard the gfx950s=256column as noise, which the tightened floor no longer supports.No code changes; the routing gate and its threshold are unaffected.
Architecture / design notes
The threshold does not move. 23 of 24 gate-region cells still win or hold and the geomean is comfortably above 1, so
qo_len >= 2048on gfx950 remains the right cut. What changes is that the docs can no longer claim a clean sweep: one admitted cell is a small loss, and saying so is the difference between a measurement and a sales figure.Two claims in the same paragraph turned out to need re-deriving, not just renumbering — they were written against the old harness and nothing in the first pass re-checked them:
seqlengeomean rises monotonically above 1024"seqlen1024 falls to 0.90× at 1536"The non-square hedge also goes. #365 opened by saying correctness on chunked-prefill shapes was "covered either way" and closed by adding
kv_len == 4 * qo_lencoverage on the asm arm — the doc still carried the opening position.Benchmark results
AITER asm vs its CK Tile arm, bf16
head_dim128, 60 cells (batch 1 and 4 × 16/32/64 q-heads × seqlen 256-6144), causal. Gate region isqo_len >= 2048, 24 cells.single_prefill_with_kv_cacheserves one request and the dispatcher pinsargs.batch = 1, so only the batch-1 half of the sweep is reachable through it. The batch-1 column is the one the routing decision rests on; the mixed column is what #365 published.gfx950 / MI350X
The regressing cell is
b1 hq16 s2048at 0.9752, outside the batch-1 A/A floor of 0.995-1.008.gfx942 / MI300X — conclusion unchanged, and the reachable subset makes it stronger: the gate region is a net loss there.
A/A control, same box and session. This is the part that makes the 0.975 cell meaningful rather than noise:
Removing
empty_cache()from the per-arm loop cut the A/A spread from roughly ±10% to ±1.3%, which is the direct evidence that the old placement was injecting the noise rather than merely being untidy.Reproduce:
python benchmarks/rocm/bench_asm_vs_cktile.py --aathen without--aa.Test plan
rc=0e802be34e; suite running, will update if it is not cleanpre-commit runon both changed files