Skip to content

Feat/proxy model gfx1250 1023 - #6969

Draft
kkHuang-amd wants to merge 21 commits into
ROCm:mainfrom
kkHuang-amd:feat/proxy-model-gfx1250-1023
Draft

kkHuang-amd wants to merge 21 commits into
ROCm:mainfrom
kkHuang-amd:feat/proxy-model-gfx1250-1023

Conversation

@kkHuang-amd

Copy link
Copy Markdown
Contributor

Motivation

Technical Details

Test Plan

Test Result

Submission Checklist

sogalin and others added 4 commits October 23, 2026 06:52
…columns

The fused dynamic MX quant + MoE sort HIP kernel
(FUSED_MX_QUANT_MOE_SORT_KERNEL_DISPATCH in csrc/kernels/quant_kernels.cu)
supports at most 32 * 256 = 8192 columns. With hidden_size = 16384 the first
MoE GEMM's MXFP4 activation quant aborts during decode CUDA-graph capture:

  quant_kernels.cu:2020 input last dim has exceeded the maximum value 8192

The split path (per_1x32_mx_quant_hip + mxfp4_moe_sort_hip) supports up to
16384 columns and produces the same layout; it is already used above the
token-count switch. Route group_size=32 inputs wider than 8192 columns to it.
The split path costs about 1% of decode GPU time where it replaces the fused
kernel.

The fix comes from the Meta proxy MoE SGLang package
(patches/aiter_fused_mx_quant_moe_sort_large_hidden.patch), observed on
MI350X with amd-aiter 0.1.22.dev120, and reproduces on gfx1250 with
amd-aiter 0.1.25.dev178. This version is restructured for black and is
logically equivalent to that patch.

(cherry picked from commit ee56e61)
….scaled_downcast

The gfx1250 Gluon kernels behind dynamic_mxfp4_quant and dynamic_mxfp8_quant
call gl.amd.cdna5.scaled_downcast. That API landed on Triton main in
triton-lang/triton#11377 and is not in the release/3.8 branch, so with
Triton 3.8 (e.g. 3.8.0+git669b31ac in rocm/sgl-dev:v0.5.22-rocm101-mi45x)
every MXFP4 linear fails to compile:

  AttributeError: module 'triton.experimental.gluon.language.amd.cdna5'
  has no attribute 'scaled_downcast'

The gfx950 path already guards the same API with _has_scaled_downcast()
and falls back to the Triton kernel. Add the equivalent cdna5 check to both
gfx1250 dispatches so they fall back too; backend="gluon" still raises with
the missing requirement.

Tested on gfx1250 with Triton 3.8.0+git669b31ac: the Triton fallback
matches a PyTorch MXFP4 dequant reference for hidden size 16384,
M = 1, 16 and 8192, and SGLang serves a Quark MXFP4 W4A4 MoE model.

(cherry picked from commit 5dd80d3)
gfx1250 ships no normalization configs, so the first RMSNorm with
M > 8192 and small N (e.g. a per-head q-norm over 6000 tokens x 16 heads)
raises:

  FileNotFoundError: Required config file doesn't exist:
  .../configs/gfx1250/triton/normalization/rmsnorm_large_m_small_n/DEFAULT.json

Add the file. gfx942 and gfx950 both use num_warps=8, but gfx1250 runs
wave32, so the same value gives half the threads per block; measured on
gfx1250 (forward, bf16, N=128), fewer warps is faster:

  num_warps   M=96000    M=131072
  2           14.6 us    18.3 us
  4           15.1 us    18.4 us
  8           15.7 us    19.1 us
  16          20.1 us    25.0 us
  32          31.9 us    41.2 us

All settings match torch.nn.functional.rms_norm; num_stages 1-3 makes no
measurable difference. Use num_warps=4, num_stages=2: within 3% of the
best at both sizes.

(cherry picked from commit d02a891)
@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 (added automatically when gfx942 configs change); main branch always runs both MI35X and MI300X
ci:triton-355 Run the full Triton test suite on MI35X, not only the tests the change affects
ci:windows Windows JIT op smoke test on the gfx1201 ROCm runner
ci:multi-gpu Aiter multi-GPU tests on the 8-GPU runner
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: DeepSeek-V4-Pro, MiniMax-M3-MXFP4
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 6969 --add-label <label>

One backend per PR:
A PR changes one kernel backend: [Triton/Gluon] (Triton and Gluon count as one), [HIP], [ASM], [CK], [OPUS] or [FlyDSL]. If the title ends up with two backend tags, split the PR -- as stacked pull requests when one part cannot merge without the other.

PR title tags & labels:
Component tags ([Triton/Gluon], [HIP], [CK], [ASM], ...) are added to the PR title and as PR labels automatically from the changed files and re-synced on every push — change-type tags like [fix]/[Perf], op tags like [MLA], and human labels (ci:*) are left untouched. Add the no-auto-title label to stop the title rewrites; labels stay in sync either way.

kkHuang-amd and others added 8 commits October 27, 2026 18:07
…na bf16x3 router)

The default bandwidth_bound BLOCK_M128 config (M_LEQ_8192) hit GPU page faults
in the proxy-MoE server when the router GEMM ran at prefill sizes. Use
compute_bound 128x128x64 (2 buffers, 4 warps) for M >= 128; M <= 64 keeps
DEFAULT. ~20% slower than the faulting config at M=8192, 5x faster than
hipBLASLt for this fp32-output shape.
Buckets that spill registers to scratch hit GPU page faults on the
scratch stores when dispatches run concurrently (caught with rocgdb in
the proxy-MoE TP2 server: SIGSEGV right after scratch_store_b32 in
bandwidth_bound 128x128x64 NB4 waves_per_eu=4, 22 VGPR spills, 92 B).

- M_LEQ_256:  32x64x256 nb3 wpe4 (132-260 B scratch) -> 64x64x128 nb4 wpe0
- M_LEQ_2048: 128x128x128 nb4 (40 B)                 -> nb3
- M_LEQ_8192, any: 128x128x64 nb4 wpe4 (92 B)        -> nb6 wpe1 (= M_LEQ_4096)

All buckets now compile with 0 scratch. On qkv (N=9216 K=16384),
o_proj (N=16384 K=8192) and the bf16x3 router (N=1536 K=16384):
M=256 -34% / -37% / -8%, M=2048 -3%, M=8192 -4.6% to +1.1%.

The N=1536 K=16384 compute_bound override is no longer needed: the
router now uses the fixed DEFAULT.
…I size

Since 2bafed4 the grouped a8w4 TDM launch passes rank-1 views
(out.view(-1), the scales, and quant_scale / ep_row_map which default to
out) through the cached FlyDSL launcher, whose C ABI packs a tensor's size
as int32. A large prefill makes out = contiguous_m x N exceed 2**31
elements (e.g. 139264 x 16384 = 2,281,701,376) and the launch fails with
"struct.error: 'i' format requires -2147483648 <= number <= 2147483647".

The launcher reads these tensors only through their base pointer
(fx.get_iter), so pass a rank-1 view clamped to 2**31 - 1 elements: same
base address, same kernel, no recompile (rank and specialization key are
unchanged).
…ace fix, EP-compact gather-reduce

- grouped_moe_gfx1250: large-M tile rule for model_dim 16384 when no CSV row matches
  (inter 4096 / EP8 and inter 512 / EP1, token_num >= 2048; AITER_TDM_PREFILL_TILE_RULE=0 disables).
- layout_align_m: expert segments align to max(tile_m, tile_m2); the GEMM with the smaller tile
  skips tiles that lie wholly in an expert's alignment gap.
- mxfp4_preshuffle_gfx1250_tdm: retire LDS reads (s_wait_dscnt) before the stage-free barrier
  (rare wrong outputs when the compiler moved WMMAs past it); default-off experiment switches
  AITER_TDM_PW_OVERLAP / AITER_TDM_TILEMAP_LDS / AITER_TDM_SPLIT_FENCE.
- moe_gather_reduce: EP compact mode for route maps with dropped routes (skip dropped slots,
  whole-row block for large T); EP8 0.578 -> 0.227 ms per layer. AITER_GR_EP_COMPACT=0 disables.

Verified: op test --scenario verify, a8w4 --no-bias, EP1 (E512 top16 inter512) and EP8 (E64 top2
inter4096) at T=2/64/8192, all pass.
…M<=4, M<=16)

Laguna TP8 decode shapes (qkv N=2304 K=16384, o N=16384 K=2048, lm_head N=25256 K=16384) used
DEFAULT.json. Swept with the tuning harness (cold weights, CUDA graph); other M fall back to DEFAULT.

us, DEFAULT -> tuned:  qkv M2 22.4 -> 17.8;  o M2 26.0 -> 14.4, M16 31.9 -> 16.4;
lm_head M2 190 -> 136, M16 278 -> 159 (qkv M16: DEFAULT already best).
Conflicts in the gfx1250 grouped a8w4 TDM kernel (upstream: IGLP_PARAMS, a_preshuffled,
padded/unpadded C store rework):
- mxfp4_preshuffle_gfx1250_tdm.py: take main; re-apply layout_align_m (gap-tile skip for
  tile_m < layout rows) on top. The LDS race fix is already in main. Drop the default-off
  experiment switches AITER_TDM_PW_OVERLAP / AITER_TDM_TILEMAP_LDS / AITER_TDM_SPLIT_FENCE
  (no gain measured).
- grouped_gemm_mxfp4.py: pass both a_preshuffled and layout_align_m.

Verified on gfx1250: op test --scenario verify a8w4 --no-bias, EP1 (E512 top16 inter512,
EP1 CSV v4) T=2/64/8192 and EP8 (E64 top2 inter4096) T=2/64/8192, all pass.
EP prefill was re-quantizing a token for every route. Behind AITER_FLYDSL_EP_TOKEN_MULTIDEST, quantize it once and give remote routes a zero-length descriptor so topk 16 stays cheap. The flag stays off by default.

Co-authored-by: Cursor <cursoragent@cursor.com>
@aiter-bot

aiter-bot commented Oct 29, 2026 •

Copy link
Copy Markdown

NOT READY FOR HUMAN REVIEW

PR Self-Review

Revision c440a7f92998 · mode shadow

Waiting for the PR author. The readiness report will appear after the self-review answers are complete for this revision.

Questions to answer in the PR description

  1. What behavior does this PR change, and why is the existing behavior insufficient?
  2. What did you validate, and what remains unverified? Include commands, hardware/configuration and results.
  3. Which exact workload keys consume these configuration changes, and what evidence shows that unrelated shapes keep their previous selection?
  4. For every changed op_tests/ file, what behavior does it cover and why is it not redundant? Write None if no op test changed.

Edit the PR Self-Review section in the PR description. Include the current revision SHA; this comment refreshes automatically.

Missing or invalid answers

  • FAIL — missing ### Reviewed revision in the PR description
  • FAIL — missing ### Behavior and intent in the PR description
  • FAIL — missing ### Validation and unverified scope in the PR description
  • FAIL — missing ### PR-specific question in the PR description
  • FAIL — missing ### Changed op tests in the PR description
  • FAIL — Reviewed revision must contain at least the first 7 characters of the current head SHA
  • FAIL — Behavior and intent has no substantive answer
  • FAIL — Validation and unverified scope has no substantive answer
  • FAIL — PR-specific question has no substantive answer

Shadow mode reports what would block but keeps the status successful. Set AITER_PR_SELF_REVIEW_MODE=enforce only after reviewing real false positives.

chien-an-chen and others added 9 commits November 3, 2026 09:31
…250-1023

# Conflicts:
#	aiter/ops/flydsl/grouped_gemm_mxfp4.py
#	aiter/ops/flydsl/kernels/mxfp4_preshuffle_gfx1250_tdm.py
… GEMM

Laguna's shared expert gate/up is GEMM N=1024 K=16384. With no config
it uses the 256x256x256 DEFAULT tile. Add a tuned table for the Gluon
preshuffle kernel (gfx1250 only):

- M<=31: 16x32x1024, M_LEQ_16 uses NUM_BUFFERS 2
- M_LEQ_32 and M_LEQ_64: 32x32x1024, 2 warps, 4 buffers
- M_LEQ_128: 32x32x1024, 2 warps, 2 buffers
- M_LEQ_256: 64x64x1024; M_LEQ_512: 128x64x512; M_LEQ_1024: 64x128x256
- M_LEQ_2048: 256x128x512; M_LEQ_4096, M_LEQ_8192, any: unchanged 256x256x256

Microbench (10/22, isolated kernel, DEFAULT vs tuned): M=32 48.6 -> 7.9 us,
M=64 47.7 -> 8.4 us, M=128 65.8 -> 9.7 us, M=256 66.7 -> 11.1 us,
M=512 53.1 -> 22.2 us, M=1024 72.2 -> 41.9 us. M=8192 is 164 -> 142 us
and is not used.

h13-16, proxy_12l TP8 EP1, bench_serving with the same server args on
and off: decode gate/up 54.1 -> 7.9 us per launch, median ITL 1K1K
c=64 14.71 -> 14.22 ms, c=32 11.18 -> 10.77 ms. E2E about 3% better.
Prefill M=8192 is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
…x1250-1023

Keep both the gfx1250 m_align layout and upstream split-K k_waves on the grouped GEMM launches, and accept gfx1250-strict while still requiring cdna5.scaled_downcast before the Gluon quant kernel.

Co-authored-by: Cursor <cursoragent@cursor.com>
…s for 96 CU

Laguna TP16 runs three BF16 dense GEMMs per rank with no tuned entry on
the 96-CU gfx1250 tree: qkv N=1280 K=16384, o_proj N=16384 K=1024 and
lm_head N=12628 K=16384. Every M used the 96cu gemm_a16w16 DEFAULT tile.

Add, for gfx1250 96 CU only:

- 96cu Gluon gemm_a16w16 tables for the three shapes, with
  DEFAULT_FALLBACK, so untuned buckets keep DEFAULT.json. o_proj has all
  13 buckets, lm_head M<=128 has 7, qkv has the 4 buckets where the best
  Gluon tile beats DEFAULT by more than 2%.
- model_configs/laguna_gfx1250_tp16_bf16_tuned_gemm.csv, 17 rows at
  power-of-two M, where hipBLASLt or FlyDSL beats the best Gluon tile by
  more than 3%. The gl=1 padding maps every M of a bucket onto its row.
  qkv M<=16, 64 and 128 use FlyDSL; qkv M>=256 uses hipBLASLt; o_proj
  M=512, 1024 and 4096 use hipBLASLt; lm_head M=64 uses FlyDSL.

Tuning (h13-16, GPU 0): one CUDA graph over weight copies above 1 GiB,
median of rounds. 56 sampled Gluon tiles per bucket at the top M, then
the best 6, DEFAULT, hipBLASLt and FlyDSL at three M in the bucket.

tgemm.mm on the same bench, A B A B, old -> new us:
qkv M=1 10.1 -> 8.2, M=64 18.9 -> 14.1, M=1024 118.7 -> 70.7,
M=8192 577 -> 396. o_proj M=1 7.0 -> 5.6, M=32 13.0 -> 7.5,
M=256 73.8 -> 26.3, M=1024 153.8 -> 64.6, M=8192 942 -> 466.
lm_head M=8 67.8 -> 51.6, M=64 125.9 -> 89.9, M=128 168.3 -> 148.9.
All 60 measured M are faster. Error against an fp32 reference is
1.7e-3 in both arms.

E2E on h13-16: a 12-layer proxy at TP8 EP8 with 64 heads, 4 KV heads
and shared intermediate 2048, so qkv and o_proj per rank match TP16.
lm_head stays the TP8 shape there, so it is checked by the kernel bench
only. bench_serving random-ids, A B A B, total tok/s:
ISL 8192 OSL 1024 c=32 18110 -> 18799 (+3.8%), median TTFT 2275 ->
1942 ms, median ITL 11.70 -> 11.65 ms. ISL 1024 OSL 1024 c=32
5289 -> 5331 (+0.8%), median TTFT 339 -> 304 ms. The two A runs
differ by 0.1%. Measured with the CSV rows passed through
AITER_CONFIG_GEMM_BF16 on 56fab27.

Co-authored-by: Cursor <cursoragent@cursor.com>
…er-of-two M

The 96 CU lm_head shape N=12628 K=16384 had a single FlyDSL row at M=64. A FlyDSL plus hipBLASLt sweep on the same tuner writes the measured winner at each power-of-two M: Gluon through M=8, hipBLASLt from M=16 to 8192.

Co-authored-by: Cursor <cursoragent@cursor.com>
…unings per M

Two TP16 BF16 GEMM tunings existed for the 96 CU gfx1250 tree: this file
(88834dd) and a separate aiter tuner run on h13-14 (qkv and o_proj only,
hipBLASLt / FlyDSL / torch). Each wins different M, so measure all three
dispatch paths on the same GPU and keep the fastest per (N, K, M).

Op bench (h13-14, GPU 0, this tree + the Gluon tables of df929c0): one
process per AITER_CONFIG_GEMM_BF16 arm (this file / the h13-14 CSV / header
only = Gluon tables only), tgemm.mm at every power-of-two M 1..8192, 20 calls
in one CUDA graph with weights rotated over more than 1 GiB, median of 7
replays, two rounds in opposite order. A row is kept from the faster arm;
no row where the Gluon path is fastest.

Changes vs 88834dd (us, this file -> merged):
- o_proj N=16384 K=1024: hipBLASLt for M 256..8192, FlyDSL at M 64.
  M=256 26.9 -> 14.9, M=1024 63.8 -> 46.5, M=8192 455.5 -> 314.5.
  M <= 32 stays on the Gluon tables (fastest).
- qkv N=1280 K=16384: hipBLASLt for M 256, 512, 2048, 4096 (was torch),
  FlyDSL rows at M 4, 32, 64, 128. M=64 13.7 -> 11.1, M=128 20.3 -> 17.3,
  M=512 48.3 -> 41.6. torch kept at M 1024 and 8192.
- lm_head N=12628 K=16384: unchanged from M 4 up; the M 1 and 2 triton rows
  are dropped (Gluon path equal within 0.4%).

E2E, TP16 across two DPX nodes (24-layer proxy, EP4, carv2), random-ids,
TTT/GPU this file -> merged: 70000/300 c1 2800.6 -> 2829.6, TTFT 2044 ->
1998 ms; 8192/1024 c16 1178.9 -> 1186.2, TTFT 1881 -> 1828 ms; 1024/1024
c16 330.9 -> 332.8. TTFT -0.9..-3.2% at all 5 points measured, ITL equal.
Single run per point (~3% noise).

hipBLASLt solidx values come from image
rocm/sgl-dev:v0.5.23-rocm101-mi45x-20261107_8066cd93b__test; retune if the
hipBLASLt version differs.
Laguna at TP16 with --ep-size 4 runs MoE as EP4 x MoE-TP4: 128 local experts,
inter_dim 1024, fp8 activations x MXFP4 weights. The EP lookup key is
(token, 16384, 1024, experts=128, topk=-1) and had no row, so every bucket
used the kernel default tile (64 x 256 x 128, 2 buffers, both stages).

Add 12 rows for gfx1250 96 CU (DPX partition). Tokens 1024 and 2048 keep
the default (it was fastest there).

Tuning (h13-14, one DPX partition, other partitions idle):
op_tests/flydsl_tests/test_flydsl_grouped_gemm.py a8w4 silu --no-bias,
E=128 topk=4 so the per-expert M (T*4/128 = T/32) matches the EP4 rank,
whole fused MoE in a CUDA graph. Stage-1 and stage-2 grids, then the
finalists in 3 interleaved repeats (median, spread <= 3%). Partitions of
one physical GPU running at the same time share power and add up to 20%
noise, so the final numbers come from a single busy partition.

us per call, default -> row: T=1 56.9 -> 29.5, T=2 70.0 -> 39.4,
T=4 86.1 -> 61.7, T=8 117.0 -> 102.0, T=16 220.1 -> 180.1,
T=32 304.5 -> 277.0, T=64 397.1 -> 339.9, T=128 441.8 -> 395.3,
T=256 463.9 -> 423.7, T=512 487.2 -> 457.5, T=4096 1307.8 -> 1230.0,
T=8192 2395.5 -> 2133.4. All rows pass the op test's accuracy gate.

E2E, TP16 across two DPX nodes (24-layer proxy, EP4, carv2, merged BF16
GEMM rows of 335b817), random-ids, TTT per GPU without -> with the rows:
70000/300 c1 2829.6 -> 3069.7 (ITL 5.31 -> 4.64 ms), 8192/1024 c2
384.1 -> 428.2, c16 1186.2 -> 1285.7, 1024/1024 c2 93.6 -> 106.1,
c16 332.8 -> 366.4. ITL -9..-13% at all 5 points, TTFT -3..-4% on
8192 and 70000. Single run per point (~3% noise).

The rows are merged into the default config only when
AITER_CONFIG_GROUPED_FMOE is unset (model_configs merge); checked with
update_config_files + _find_grouped_config on this tree.
…de M on 96 CU

Adds 16 flydsl rows (M 1..128) for the TP16 per-rank shared-expert shapes of the
Laguna proxy model on gfx1250 (MXFP4 dequantized to bf16): gate_up N=512 K=16384
and down N=16384 K=256. Tuned with gemm_a16w16_tune.py --with-hipblaslt; only the
flydsl winners up to M=128 are kept (the hipBLASLt winners at M >= 256 faulted at
other M mapped to the same padded row, e.g. a row tuned at M=2048 at M=1536).

Used by sglang SGLANG_OPT_QUARK_BF16_TGEMM=1 (small M only): 15-17 -> 5.3 us
(gate_up) and 11 -> 3.1 us (down) per call at M=2 vs F.linear; TP16 two-node
1k/1k c2 TTT/GPU +15.1%, 70000/300 c1 +7.0%.
…l M=8192

Exact-M hipBLASLt solidx 244 for gate_up 8192x512x16384 (176.8 us) and down
8192x16384x256 (147.2 us). h13-15 ran them at 173/148 us solo and 206-209/165-168 us
with both partitions of one card, with the same max abs as F.linear.

This branch has not been deployed

No deployments
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.

4 participants