Spatial Attention: XCD-aware spatial workgroup mapping for MHA and GQA (SWIZZLE=1) - #3936
Conversation
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
|
brunomazzottiamd
left a comment
There was a problem hiding this comment.
Hi @mc186! Thanks for you PR!
Code looks good but I have two blockers:
- change in pid preprocessing function that might affect grouped GEMM kernel
- global state mutation in unit test
Please check my comments.
I've also left some suggestions regarding the validation of MHA swizzle modes and an attempt to improve code understanding with strings instead of 0 and 1.
Introduces remap_workgroup_spatial() in pid_preprocessing.py and wires it into the flash-attention forward kernel behind AITER_SWIZZLE=1. MHA path (spatial_mha): groups ceil(NUM_Q_HEADS/NUM_XCDS) consecutive Q heads onto each XCD so each head's full KV tensor is processed by one XCD, keeping it hot in that XCD's 4 MB L2 partition. GQA path (spatial_gqa): assigns each KV head exclusively to one XCD so all Q heads sharing a KQA head run on the same XCD. Ordering within each XCD is block-first, which avoids the causal load-imbalance that head-first ordering creates. Three regimes are handled: HK==NXCD (aligned), HK>NXCD, and HK<NXCD. AITER_SWIZZLE=0 (default) preserves the existing remap_xcd behaviour; AITER_SWIZZLE=1 selects the new spatial mapping. The mode can also be set programmatically via mha_set_swizzle(). Correctness verified bit-identical (rel=0.00e+00) across 17 configs covering GQA ratios 2-16, MHA head counts 16-128, causal/non-causal, and batch sizes 1-4 on MI355X (thor-4).
- revert tall_xcds ternary to tl.cast() to preserve int64 correctness in grouped GEMM
- change SWIZZLE from int {0,1} to string {"default","spatial"} throughout
- add tl.static_assert for SWIZZLE value in kernel with descriptive message
- rename AITER_SWIZZLE to AITER_TRITON_MHA_SWIZZLE (more specific)
- harden env var parsing: validate against allowed set, raise ValueError on bad input
- add validation in mha_set_swizzle() before mutating global state
- fix test: add autouse fixture to save/restore _MHA_SWIZZLE around each test
- update test docstring to reflect new env var name and string values
|
All feedback addressed in cb2a345:
All tests pass (22/22), rebased on current main. |
|
Hello @mc186. Why did you close this PR? Do you still want to merge it? |
|
I did not mean to close it! I might have hit close instead of comment... |
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
|
brunomazzottiamd
left a comment
There was a problem hiding this comment.
LGTM! Let's get CI green and then merge it.
azaidy
left a comment
There was a problem hiding this comment.
LGTM based on @cagrikymk's earlier approval
|
added formatting fixes for failing Black test |
|
@mc186 |
|
resolved conflicts |
Spatial Attention: XCD-aware spatial workgroup mapping for MHA and GQA (SWIZZLE=1)
Motivation
AMD CDNA3/3.5 GPUs (MI300X, MI350X, MI355X) are built from 8 XCDs (chiplets), each with a dedicated 4 MB L2 cache. The hardware assigns workgroup
widto an XCD bywid % NUM_XCDS— round-robin, determIn standard attention scheduling (SWIZZLE=0), consecutive workgroups cycle through all Q heads before advancing the sequence block. For MHA with many heads, this causes each head's KV data to be spread acros
Spatial scheduling (SWIZZLE=1) re-orders workgroups so that all computation touching a given KV head is pinned to one XCD. Each head's KV tensor stays hot in a single 4 MB slice rather than being replicated
Why GQA is unaffected: models with 8 KV heads (e.g. Llama3, GQA ratio 16) already achieve this locality "accidentally" — round-robin workgroup dispatch across 8 XCDs naturally maps one KV head per XCD. Spati
Changes
aiter/ops/triton/utils/_triton/pid_preprocessing.pyAdds
remap_workgroup_spatial(wid, NUM_Q_HEADS, NUM_BLOCKS, BATCH, NUM_QUERIES_PER_KV, NUM_XCDS), a unified Triton JIT function with two specialised paths selected at compile time byNUM_QUERIES_PER_KV:MHA path (
NUM_QUERIES_PER_KV == 1): groupsceil(NUM_Q_HEADS / NUM_XCDS)consecutive Q heads onto each XCD. Ordering within each XCD is head-first (all blocks for head i before head i+1).GQA path (
NUM_QUERIES_PER_KV > 1): assigns each KV head exclusively to one XCD so that all Q heads sharing a KV head run on the same XCD. Ordering within each XCD is block-first (all Q heads in thegroup process block b together before advancing to b+1), which avoids the causal load-imbalance that head-first ordering creates. Three sub-regimes are handled:
HK == NXCD(aligned),HK > NXCD, andHK < NXCD.aiter/ops/triton/_triton_kernels/attention/mha.pySWIZZLE: tl.constexprkernel parameter (string type, values:"default"or"spatial").tl.static_assertto validate SWIZZLE value at compile time with descriptive error message.SWIZZLE="default": preserves the existingremap_xcdbehaviour (backward-compatible default).SWIZZLE="spatial": callsremap_workgroup_spatial;NUM_QUERIES_PER_KVis derived fromNUM_Q_HEADS // NUM_K_HEADSat compile time, so GQA and MHA specialise independently with zero runtime overhead.aiter/ops/triton/attention/mha.pyAITER_TRITON_MHA_SWIZZLEenvironment variable at module import (default"default").mha_set_swizzle(value: Literal["default", "spatial"])for programmatic control.ValueErrorat import or call time.Usage
Kernel-level benchmark results (AMD MI355X, BF16, batch=1)
MHA — SWIZZLE=0 vs SWIZZLE=1, non-causal
End-to-end model results (AMD MI355X, BF16, batch=1, random-init weights)
Whole-model forward-pass latency and socket energy (idle-baseline subtracted) on representative architectures:
Correctness
Verified bit-identical output (
rel_max = 0.00e+00) vs SWIZZLE=0 across 17 configurations on MI355X (thor-4):Test script:
op_tests/test_mha_spatial_swizzle.py(included in this branch).