[triton-mha] add gfx1101 tuning config - #4493
Open
Ragua1 wants to merge 1 commit into
Open
Conversation
`_get_config` in `_triton_kernels/attention/mha.py` opens
`configs/{arch}-MHA-DEFAULT.json` with no fallback, so on an architecture
that has no such file every call to
`aiter.ops.triton.attention.mha.flash_attn_func` raises FileNotFoundError
before any kernel runs. Of the eight architectures listed in RDNA_ARCHS,
only gfx1151 ships a config today.
This adds gfx1101 (RDNA3, e.g. RX 7800 XT). Nine of the ten entries are
taken verbatim from gfx1151-MHA-DEFAULT.json, the nearest tuned
architecture (RDNA3.5, added in ROCm#3423 and tuned in ROCm#3560). The forward
`default` entry is tuned on this card instead of inherited: BLOCK_M 128
and num_stages 1, against gfx1151's BLOCK_M 64 and num_stages 2. The
remaining forward entries and both backward sections are unchanged from
gfx1151, so the diff against that file is two numbers.
Tuning followed the two-stage structure of ROCm#3560, on a narrower shape set.
Stage 1 swept 48 combinations of BLOCK_M, BLOCK_N, num_warps and
num_stages on SDXL self-attention (2, 4096, 4096, 10, 64) fp16: all 48
were valid, none exceeded the 64 KiB LDS budget, and the spread between
best and worst was 5.8x. Stage 2 took the best three plus the inherited
values across four shapes, also varying PRELOAD_V and waves_per_eu.
Relative to the inherited entry, the chosen values give 0.77x on SDXL
self-attention at 4096, 0.89x at 1024, 1.00x on a Flux joint-attention
shape at head_dim 128, and 1.08x on SDXL cross-attention, where the
absolute cost is 0.028 ms.
Stage 2 was necessary: BLOCK_M 128 with num_stages 2 was the fastest
single result on SDXL (0.76x) but 1.35x slower than inherited on the
head_dim 128 shape. With num_stages 1 that shape returns to parity.
All thirteen configurations measured in stage 2 produced an identical
maximum absolute difference per shape, so the choice of tile parameters
has no numerical effect on this kernel. Output matches an fp32 SDPA-MATH
reference to 6.47e-05, which is the figure torch's own AOTriton flash
kernel returns on the same input.
Measured on gfx1101 (RX 7800 XT, Windows, torch
2.11.0+rocm7.15.0a20260728, triton 3.7.1), 5 warmup and 20 timed
iterations per point, median reported.
Scope of the claim: the sweep covers four diffusion attention shapes, not
an LLM shape set of the kind ROCm#3560 used (n=112), so these values are tuned
for diffusion-shaped attention on this architecture rather than in
general. The backward sections were not exercised, as this host is used
for inference only.
Signed-off-by: Martin Domanský <ragua@email.cz>
Contributor
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
|
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
aiter.ops.triton.attention.mha.flash_attn_funccannot run at all on gfx1101 (RDNA3)._get_configin_triton_kernels/attention/mha.pyopensconfigs/{arch}-MHA-DEFAULT.jsonunconditionally, and with no per-arch file and no fallback the call raisesFileNotFoundErrorbefore any kernel is compiled.RDNA_ARCHSin_triton_kernels/flash_attn_triton_amd/utils.pylists eight architectures and only gfx1151 ships an MHA config, so the default MHA path is unreachable on the other seven. This PR adds the one architecture available to me for testing on hardware, following #3423 and #3560, which between them added and then tunedgfx1151-MHA-DEFAULT.json.Technical Details
One new file,
aiter/ops/triton/configs/gfx1101-MHA-DEFAULT.json: 92 lines added, nothing removed, no code touched.Nine of the ten entries are taken verbatim from
gfx1151-MHA-DEFAULT.json, the nearest tuned architecture — RDNA3.5 against RDNA3, same Wave32 execution and the same WMMA generation.fwd/defaultis tuned on this card instead of inherited, so the difference against the gfx1151 file is two numbers:BLOCK_M64 → 128,num_stages2 → 1.That entry and not the others because
fwd/defaultis what_get_configselects whenever dropout is disabled and the dtype is not fp32 — the branch testsdtype == torch.float32, so fp16, bf16 and fp8 all resolve to it.fwd/dropout_or_fp32, bothpeentries and both backward sections are inherited unchanged; only the forward path was exercised, as this host is used for inference, so no claim is made about backward here.Outside the scope of this change: a second call in the same process reports
KeyError: 'default'rather than the original error, because the memo on_get_configis populated before the file is opened. That is addressed in #4447.Test Plan
The failure is total — the op raises before any kernel runs — so verification has two parts: the call must complete, and the result must be numerically correct. Compare the output against an fp32 SDPA-MATH reference, with torch's own fp16 SDPA measured against that same reference in the same process so the tolerance comes from a control rather than a quoted figure; confirm the failure reappears when the file is removed; and exercise all four forward entries, not only the one a fp16 non-dropout shape selects, since the inherited
peentries carry the largest tile in the file.fwd/defaultwas tuned in the two stages #3560 used, with 5 warmup and 20 timed iterations per point,torch.cuda.synchronize()per iteration, fp16, seed 424242, median reported. Stage 1: 48 combinations ofBLOCK_M∈ {32, 64, 128, 256} ×BLOCK_N∈ {32, 64} ×num_warps∈ {2, 4, 8} ×num_stages∈ {1, 2} on SDXL self-attention(2, 4096, 4096, 10, 64)— all 48 valid, none exceeding the 64 KiB LDS budget. Stage 2: the best three plus the inherited values across four shapes — SDXL self-attention at 4096 and 1024, SDXL cross-attention, and a Flux joint-attention shape athead_dim128 — additionally varyingPRELOAD_Vandwaves_per_eu. The leading candidates were then repeated seven times each, so a ratio is only claimed where the ranges are disjoint, and re-verified in bf16.n=112LLM shape set behind #3560, and I have no way to check these values against the shapes that PR targeted. If that basis is too narrow to accept, the alternative is to inheritfwd/defaultfrom gfx1151 unchanged along with the other nine entries, which is a two-number revert.op_tests/triton_tests/attention/test_mha.pycould not be run on this host:AITER_TRITON_ONLYis unconditional on win32, andaiter.test_mha_commonimportsaiter.dtypes, which the Triton-only branch ofaiter/__init__.pydoes not bind. This is independent of the present change, and I preferred not to introduce a workaround into the verification being reported.Reproducer, run with the AITER checkout on
PYTHONPATH(no install):Tested on: RX 7800 XT (gfx1101, RDNA3), Windows, torch
2.11.0+rocm7.15.0a20260728(HIP 7.15.26290), triton 3.7.1,AITER_TRITON_ONLYimplicit. Not tested on CDNA or on any other RDNA part; no other hardware is available to me.Test Result
With the file in place:
A control that accounts for the residual: with
TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1, torch's own AOTriton flash kernel becomes selectable on this card, and against the same fp32 reference it returns exactly the figures this config produces —max abs 6.470e-05, normalized2.294e-04. The gap to the fp16 SDPA-MATH figure is therefore attributable to flash-style tiling and online softmax rather than to these tile sizes.Performance, median of 20 iterations, seven independent repeats (range across repeats):
head_dim128The claim is therefore confined to diffusion self-attention. On cross-attention and at
head_dim128 no reliable difference was established — the ranges overlap, the inherited entry has a coefficient of variation of 11.5 % on cross-attention, and across three independent runs the Flux ratio measured 0.988×, 1.064× and 1.15×. bf16 gives the same ordering, with the chosen entry at 0.792× of the inherited one on the dominant shape.Two supporting results behind the two changed numbers:
num_stages: 1.BLOCK_M 128with two stages was the single fastest cell on SDXL but 1.35× slower than inherited athead_dim128; one stage costs 0.028 ms on SDXL and carries no such regression. On this architecture an extra stage can only buffer in registers —is_pingpong_schedule_enabledin the AMD backend admits gfx942 and gfx950 only, and enabling async copy makesnum_stages ≥ 2fail to compile outright (ttg.async_copy_global_to_local ... explicitly marked illegal) — andn_spillson the compiled kernel rises monotonically with the value.BLOCK_M: 128. SweepingBLOCK_M×BLOCK_Nwithn_regs/n_spillsread off each kernel,BLOCK_M 128/BLOCK_N 32is the only combination reachingn_regs 256with zero spills athead_dim64.BLOCK_M 64leaves 68 registers unused while reading K and V twice as many times;BLOCK_M 256halves that volume again but spills 168 registers and is slower, 3.003 ms against 2.219 ms.Every other
fwd/defaultinaiter/ops/triton/configspairsBLOCK_M: 128withnum_stages: 1— gfx942, gfx950 and gfx1250 — and gfx1151 is the only entry at 64 and 2. The sweep here converged on the same pair independently. That is offered as corroboration for the gfx1101 values, not as a claim about gfx1151, which is an APU with a different cache hierarchy.📌 All thirteen configurations in stage 2 produced an identical maximum absolute difference per shape — one distinct value per shape, including for the inherited entry. Tile parameters therefore have no numerical effect on this kernel; the choice is purely a performance decision. All four forward entries were also exercised in separate processes, since
_get_configmemoises, and each ran with the expected entry selected.Why
fwd/dropout_or_fp32was left inherited, as a measurement rather than as caution. That entry was swept as well and a configuration exists that is faster in fp32 —BLOCK_M 128,BLOCK_N 32, 4 warps, 2 stages at 0.81×. It was not applied because_get_configroutes two different workloads to that one entry, fp32 without dropout and fp16 with dropout, and that configuration is 25.3× slower on the dropout path and 4.24× slower in fp32 athead_dim128, with the runner-up failing to launch (OutOfResources) — reproduced in two independent runs. The inheritedBLOCK_M 32with 2 warps is the only one acceptable across all four combinations. That hazard is also whyfwd/defaultwas verified across four shapes and two dtypes rather than on the shape it was selected on.Related: a fallback for the same defect, prepared but not included here
_get_confighas no fallback at all, so 11 of the 14 architectures listed inCDNA_ARCHSandRDNA_ARCHSraiseFileNotFoundErroron this path — only gfx942, gfx950 and gfx1151 ship a config. This PR is the narrow remedy for one of the eleven. A same-family fallback is implemented and tested: +56/−1 in_get_configplus an 89-line test, CDNA falling back to gfx942 and RDNA to gfx1151 with a single warning naming the donor architecture, covering 10 of the 11. gfx1030 is excluded deliberately — it is listed inRDNA_ARCHSbut has no WMMA units. Because path resolution needs no GPU the test covers all fourteen architectures on any host (28 passed, 1 skipped), and on this card the fallback runs end to end with no gfx1101 config present, producing the same6.470e-05.The two changes are complementary rather than alternatives: a fallback would hand gfx1101 the gfx1151 values, which are the ones in the inherited column above, so it makes the op reachable on ten architectures while this file makes it fast on the one that could be measured. I would prefer a maintainer to choose between (a) the fallback added here as a second commit, (b) the fallback as a separate PR with this one remaining data-only, or (c) no fallback, retaining one config file per architecture as in #3423 and #3560 — that is a policy question about how the repository handles missing configs rather than a technical one. In any order, the fallback modifies the line immediately above the one #4447 (also mine) moves, and #4414 edits the same function further down; both resolve as one-line rebases.
Diff shape: