Tune MHA config & small-head pipeline pathology - #4414
Merged
Merged
Conversation
Contributor
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
|
1 similar comment
Contributor
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
|
nidal567
force-pushed
the
golden-tot-mha_tune
branch
from
July 28, 2026 15:53
4eacb1b to
2be8284
Compare
Contributor
|
@nidal567, can you please rebase on top of |
nidal567
force-pushed
the
golden-tot-mha_tune
branch
from
August 5, 2026 14:45
2be8284 to
3bf82f9
Compare
Ragua1
added a commit
to Ragua1/aiter
that referenced
this pull request
Sep 10, 2026
gfx1101 (RDNA3, e.g. RX 7800 XT) ships no MHA config, so `_get_config` in `_triton_kernels/attention/mha.py` finds no `configs/gfx1101/triton/attention/mha/DEFAULT.json` and every call to `aiter.ops.triton.attention.mha.flash_attn_func` fails before a kernel runs. Of the architectures in RDNA_ARCHS, only gfx1151 ships one today. Nine of the eleven entries are taken verbatim from the gfx1151 donor (RDNA3.5, added in ROCm#3423, tuned in ROCm#3560), which is the nearest tuned architecture. Two forward entries are tuned on gfx1101 instead of inherited, and they differ from each other only in `num_warps` and `num_stages`: fwd/default BLOCK_M 128, num_warps 8, num_stages 3 (donor: 64 / 4 / 2) fwd/small_head BLOCK_M 128, num_warps 4, num_stages 1 The split uses the `small_head` bucket added in ROCm#4414, which is opt-in per architecture by the mere presence of the key, so this stays a data-only change. It is needed because one `fwd/default` cannot serve both halves on this card: measured against the donor, `M128 w4 s1` is 0.935x on head_dim 64 but 1.269x on head_dim 128, while `M128 w8 s3` is 0.914x on head_dim 128 but 1.062x on head_dim 64. Measured on Windows native ROCm, triton 3.8.0, fp16, 10 independent repeats of 20 iterations after 5 warmups, `torch.cuda.synchronize()` per iteration; a result counts only when the [min, max] intervals across repeats are disjoint. Ratios are against the gfx1151 donor entry, i.e. against what a donor-inherited config would do. head_dim 128, `default` Flux joint 0.914x / 0.891x / 0.892x on three independent torch+ROCm stacks (2.11/7.15, 2.11/10.1, 2.15/10.1), all pinned to the same triton; llama3-8B 0.882x, mixtral-7B 0.880x, kimik25-tp4 0.849x at seqlen 16384 head_dim <= 64, `small_head` SDXL self-attn 0.935x; deepseek-V3 0.658x and glm47fp8-tp4 0.857x at seqlen 16384 The LLM shapes come from `op_tests/op_benchmarks/triton/utils/model_configs.json` (prefill, causal, GQA, batch 1); the sequence lengths are not in that file and are chosen here. Not covered: batch > 1, varlen/thd, sliding window, decode. Note that the `small_head` comment in `_get_config` does not describe gfx1101. It states that 16 < d <= 64 suffers a num_stages=1 pipelining pathology which num_stages=3 cures. On this card the ordering is the opposite -- on the tuned M128/N32/w4 tile, num_stages 1/2/3 measured 2.300 / 2.399 / 2.492 ms. The bucket is still the right mechanism here, for a different reason: the two head_dim ranges want a different `num_warps`, not a different `num_stages`. Signed-off-by: Martin Domanský <ragua@email.cz>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
On
gfx950, the TOT MHA forward path (Triton 3.8 + async_copy) regressed against the golden baseline (Triton 3.4) on a set of shapes: all 18 MLA-prefill PE shapes (DeepSeek-R1 / Kimi-K2, dqk=192/dv=128) up to +8.7%, and a head_dim=64 causal shape by +204%. The latter pathological, with individual_attn_fwddispatches intermittently stalling up to 1.6 seconds.This PR retunes
gfx950-MHA-DEFAULT.json(plus a minimal head-dim-keyed config selection) to recover them without disturbing the shapes that already improved.TLDR
Tuning
gfx950-MHA-DEFAULT.json(+ a small head-dim-keyed config-selection correction) on the TOT env:ok/improvement; 0 new regressions introduced.ok: 19 → 10.num_stages=1intermittently stalled individual dispatches up to 1.6 s, eliminated by the fix.test_mha.py905 passed / 132 skipped / 0 failed (backward included, unaffected). The two config changes are numerically transparent (pipelining only) on the shapes they touch; anum_stages=3Triton-3.8 miscompile for head_dim≤16 was found and explicitly excluded.Technical Details
Two independent, config-first changes:
pe.num_stages1 -> 3 - restores golden's value that the tot config had dropped. This recovers all 18 existing PE regressions; full 32-shape PE bucket is now 4-13% faster than golden.small_headconfig bucket + head-dim gate in_get_config.head_dimisn't a config selection input, sod64/d32shared thedefaultbucket withd128; settingnum_stages=3fixes the small-head pipelining pathology but regresses small-Md128. Fix:_get_configtakeshead_dim_v(wrapper passesv_head_dim) and routes16 < d_head_v <= 64to a newsmall_headbucket (num_stagesk=3), leavingd128onnum_stages=1. Branch order keeps PR/dropout/fp32 unaffected. Lower bound>16is a correctness guard:num_stages=3miscompiles head_dim <= 16 on Triton 3.8, so those stay ondefault.Test Plan
Correctness test for validation:
pytest op_tests/triton_tests/attention/test_mha.py -vExpected: 905 passed, 132 skipped, 0 failedRunning smoke tests with Triton's
do_benchNumbers are collected using
rocprofv3 --kernel-trace, undergoing a trimmed mean methodology to grab reliable single average numbers for each test case for all existing shapes shared in the defaultgfx950-MHA-DEFAULT.jsonconfig, themodel_shapes.json, and the default in Triton'sdo_bench(240 shapes in total).Source of shapes:
240 deduplicated shapes. M = N_CTX_Q (sq), N = N_CTX_K (sk), K = d_head_qk. Source tags (unioned on dedup):
bench(bench_mha.py make_workloads × the 10utils/model_configs.jsonmodels) on 160,model_shapes(model_shapes.jsonmhaentries incl. DeepSeek-R1/Kimi-K2 PE and GPT-OSS sink+sliding-window) on 136,test(distinct test_mha.py perf shapes: head_dim=64, GQA 48:8, 2048², varlen head_dim 8/32) on 16. TP ∈ {1, 8} where per-rank HQ/HK stay GQA-valid (no KV-head replication; Qwen3 hk=4 → TP1 only)Test Result
Correctness: full
test_mha.py905 passed, 132 skipped, 6 warnings in 127.86s (0:02:07)Performance (with rocprofv3 --kernel-trace, chronological middle-80% trimmed mean, 3–5 reps, golden vs tot over 240 dedup shapes):
The remaining 8 regressions:
Validation checks with
black --checkandruff checkcleanProfile 1 — the +204 % outlier
dense_b8_hq48_hk8_sq2048_sk2048_dqk64_dv64_c1(200 dispatches, rep0):At
num_stages=1on Triton 3.8, individual_attn_fwddispatches intermittently stall up to 1.64 seconds (median stays ~390 µs).num_stages=3eliminates the stalls → clean 333 µs, matching golden.Profile 2 — PE improver
dense_b1_hq128_hk128_sq8192_sk8192_dqk192_dv128_c1(DeepSeek-R1 prefill):Mechanism (Triton LDS metadata):
num_stages=3kernels allocate more shared/LDS memory for pipelining buffers (e.g. 16384 vs 8192 bytes) — deeper software pipelining hides memory latency, which is why it helps the PE and mid-small-head shapes but pressures occupancy on small-M d128 (hence the head-dim gate).Final status:
For the following tables, for simplicity, keep in mind the following:
M = N_CTX_Q (sq),N = N_CTX_K (sk), andK = d_head_qkThe 20 fixed (representative):
The 8 remaining regressions:
6 of 8 are synthetic test-coverage shapes (small few-head 2048² workloads, 60–86 µs); 2 are marginal real-model d128 (+1.3–1.5 %, ≈4 µs).
num_stages=3(small_head) improves the synthetic ones vs baseline (e.g. hq6/hk1 varlen +19.8 % → +14.9 %) but does not cross theokthreshold.Submission Checklist