Skip to content

[gfx950] Retune the small-M tiles and wave counts in the A16W16 fallback config - #4781

Open
akii96 wants to merge 2 commits into
ROCm:mainfrom
akii96:fix/gfx950-a16w16-m-leq-16-tile
Open

[gfx950] Retune the small-M tiles and wave counts in the A16W16 fallback config#4781
akii96 wants to merge 2 commits into
ROCm:mainfrom
akii96:fix/gfx950-a16w16-m-leq-16-tile

Conversation

@akii96

@akii96 akii96 commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

What this changes

bucket change
M_LEQ_8 BLOCK_SIZE_N 32 → 16, num_warps 4 → 8
M_LEQ_16 BLOCK_SIZE_M and BLOCK_SIZE_N 32 → 16
M_LEQ_32 num_warps 4 → 8
M_LEQ_64 num_warps 4 → 8

Why

Two problems in the same table.

The M_LEQ_16 tile overshoots its own bucket. It only handles M of 16 or smaller, but the tile was sized for 32 rows, so half of every tile did no useful work and the kernel took a slower path for leftover rows that were not really there.

Wave count stops tracking tile size in the middle of the table. M_LEQ_32 computes a 32x32 tile with four waves and M_LEQ_64 a 64x32 tile with four, while every bucket from M_LEQ_128 up already uses eight. These are skinny bandwidth-bound GEMMs, and four waves cannot cover the memory latency.

M_LEQ_8 has both problems, and matters more than its name suggests: the table has no M_LEQ_1 or M_LEQ_4 key, so every M from 1 to 8 resolves to it.

Numbers

Weighted over every measurable gfx950 bf16 shape in each bucket. Nothing regresses.

bucket shapes weighted median worst
M_LEQ_8 49 1.83x 1.66x 1.10x
M_LEQ_16 34 1.80x 1.77x 1.00x
M_LEQ_32 34 1.98x 1.90x 1.14x
M_LEQ_64 10 1.18x 1.15x 1.09x

Accuracy

Bit for bit identical. BLOCK_SIZE_N and num_warps change how a tile is partitioned across waves, not the order any output element accumulates over K, and NUM_KSPLIT stays at 1. Verified with torch.equal against the shipped config on all 93 shapes: zero differ.

BLOCK_SIZE_M=32 overshoots the bucket's own M=16 bound, wasting half of
every MFMA tile and forcing the masked address-wrapping path. Setting
BM/BN to 16 is 1.796x weighted over the 34 affected rows with no
regression, and is bit-identical since only M/N tiling changes.

Signed-off-by: Aakif Nawaz <aakif.nawaz@amd.com>
@akii96
akii96 requested a review from a team August 16, 2026 00:17
@github-actions

Copy link
Copy Markdown
Contributor

🏷️ CI Guide

Runs automatically on every PR:

  • ✅ Pre-checks (submodule verification, code formatting)
  • ✅ Aiter op tests (gfx942 + gfx950)
  • ✅ Triton tests on MI35X (only when aiter/ops/triton/** or related paths are changed)

Extended tests (opt-in via labels):

Label Tests
ci:gfx1250-ffm-triton Run the five-shard gfx1250 FFM Triton test suite
ci:triton-300x Run an additional Triton test job on MI300X in PRs; main branch always runs both MI35X and MI300X
ci:sglang SGLang integration tests: DeepSeek-R1-MXFP4 accuracy, Qwen 3.5 accuracy
ci:atom ATOM benchmark: DeepSeek-R1-0528, GPT-OSS-120B
ci:atom_full ATOM accuracy suite for PR and main models from ATOM models_accuracy.json
ci:vllm vLLM benchmark: GPT-OSS-120B, DeepSeek-R1-0528, Kimi-K2.5
ci:all All standard extended tests (excludes ci:atom_full)

Only add ci:atom_full for FlyDSL or Triton upgrades.
Add labels via the sidebar or gh pr edit 4781 --add-label <label>

Wave count should track tile size, and it stops doing so in the middle of this
table. M_LEQ_32 computes a 32x32 tile with four waves and M_LEQ_64 a 64x32 tile
with four, while every bucket from M_LEQ_128 up already uses eight. These are
skinny bandwidth-bound GEMMs, so four waves do not cover the memory latency.

  M_LEQ_8   BLOCK_SIZE_N 32 -> 16, num_warps 4 -> 8   1.83x weighted, min 1.10x
  M_LEQ_32  num_warps 4 -> 8                          1.98x weighted, min 1.14x
  M_LEQ_64  num_warps 4 -> 8                          1.18x weighted, min 1.09x

Weighted over the measurable gfx950 bf16 shapes in each bucket (49, 34 and 10),
with no shape in any bucket regressing. Measured on one gfx950 with CUDA-graph
replay, arms interleaved within each pass, median of seven passes.

M_LEQ_8 also takes a narrower N tile because it is the bucket that serves every
M from 1 to 8 -- the table has no M_LEQ_1 or M_LEQ_4 key -- so it carries the
low-concurrency decode shapes. Doubling its waves alone is worth more in
aggregate (2.05x) but costs 4-5% near N=4096/K=512, and halving N recovers
those while keeping most of the gain.

M_LEQ_16 and M_LEQ_128 were measured the same way and are left alone: both lose
to a doubled wave count (0.79x and 0.85x), the 16x16 and 64x64 tiles already
being matched to their waves. Halving N does not transfer above M_LEQ_8 either,
costing 0.41x at M_LEQ_32.

Signed-off-by: Aakif Nawaz <aakif.nawaz@amd.com>
@akii96 akii96 changed the title [gfx950] Fix M_LEQ_16 tile in the A16W16 fallback config [gfx950] Retune the small-M tiles and wave counts in the A16W16 fallback config Aug 16, 2026
@zufayu
zufayu requested a review from azaidy August 17, 2026 01:36
@akii96

akii96 commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

@azaidy validated this PR alone on on 4x MI355X (gfx950) with amd/MiniMax-M3-MXFP4

Image: vllm/vllm-openai-rocm:nightly-311b3513af33bc29b4acb2fde2e9313e5e9966a0
Additional Fix applied on image to enable serving with ATTN backend: #51585

Before the said Additional fix mentioned above, server startup fails during graph capture.

Performance

ISL/OSL Conc TTFT before PR (ms) TTFT after (ms) Δ TPOT before PR (ms) TPOT after PR (ms) Δ
60k/600 8 11,884 8,733 -26.5% 18.50 17.60 -4.9%
60k/600 16 10,064 10,115 32.72 30.14 -7.9%
8k/1k 8 980 991 10.43 9.73 -6.7%
8k/1k 16 1,720 1,613 -6.2% 12.44 11.56 -7.1%
8k/1k 64 3,366 3,372 24.39 24.36
Serving command
export VLLM_USE_BREAKABLE_CUDAGRAPH=1
export VLLM_ROCM_USE_AITER=1
export VLLM_ROCM_USE_AITER_MOE=1
export VLLM_ROCM_USE_AITER_UNIFIED_ATTENTION=1
export VLLM_ROCM_USE_AITER_FUSION_SHARED_EXPERTS=1
export VLLM_ROCM_QUICK_REDUCE_QUANTIZATION=INT4
export VLLM_WORKER_MULTIPROC_METHOD=spawn
export SAFETENSORS_FAST_GPU=1

vllm serve amd/MiniMax-M3-MXFP4 \
  --tensor-parallel-size 4 \
  --distributed-executor-backend mp \
  --trust-remote-code \
  --block-size 128 \
  --no-enable-prefix-caching \
  --language-model-only \
  --max-model-len 133120 \
  --gpu-memory-utilization 0.95 \
  --max-num-batched-tokens 32768 \
  --max-num-seqs 128 \
  --enable-chunked-prefill \
  --async-scheduling \
  --attention-backend ROCM_AITER_UNIFIED_ATTN \
  --moe-backend aiter \
  --tool-call-parser minimax_m3 \
  --enable-auto-tool-choice \
  --reasoning-parser minimax_m3 \
  --no-enable-log-requests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant