Skip to content

[Triton/Gluon] Enable gfx1201 BF16 G1U1 MoE large-M path and CK2Stage fallback - #5059

Open
keneoneth wants to merge 29 commits into
ROCm:mainfrom
keneoneth:feat/gfx1201-bf16-g1u1-selected-large-m-moe
Open

keneoneth wants to merge 29 commits into
ROCm:mainfrom
keneoneth:feat/gfx1201-bf16-g1u1-selected-large-m-moe

Conversation

@keneoneth

@keneoneth keneoneth commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

Enable Qwen3.6 35B-A3B BF16 AITER MoE serving on gfx1201/RDNA4 systems such as Navi48/R9700S.

The existing AITER MoE stack could route Qwen3.6 BF16 G1U1 shapes into paths that were unsupported or not comparable on gfx1201. Small decode shapes are handled by the related small-M PR; this PR adds the selected large-M Triton path and makes the existing CK2Stage fallback baseline runnable for comparison.

Related work:

This PR

  • Adds a guarded gfx1201 BF16 G1U1 Triton MoE path for 1024 <= M <= 4096.
  • Adds current-architecture Triton route alignment for already-selected topk_ids / topk_weight.
  • Adds BF16/A16W16 grouped MoE GEMM wrappers and Triton kernels for the two stage shapes used by this path.
  • Adds JSON MOE config entries under the existing get_moe_dispatch() style, with exact keys for the launched GEMM shapes.
  • Corrects the gfx1201 BF16 CK2Stage fallback config rows to AITER's runtime CU convention, cu_num=64.
  • Adds the missing token=4096, inter_dim=128 CK2Stage row needed by the fallback path when larger M values pad to 4096.

The original gfx1201 BF16 CK2Stage rows used cu_num=32, which appears to come from PyTorch reporting the Navi48/RDNA4 WGP count. AITER runtime config lookup uses get_cu_num(), which reports 64 on the tested R9700S/Navi48 system, matching the rocminfo Compute Unit count. With rows keyed as 32, runtime misses the tuned rows unless CU_NUM=32 is forced manually. A follow-up may be needed in the tuning/config-generation path so future RDNA configs write AITER's runtime CU convention instead of PyTorch's WGP count.

The new Triton path is selected only when all of these are true:

  • runtime arch is gfx1201
  • AITER runtime CU count is 64
  • activations and weights are BF16
  • quant_type == QuantType.No
  • activation == ActivationType.Silu
  • G1U1 separated gate/up layout
  • model_dim == 2048
  • inter_dim == 128
  • num_experts == 256
  • topk == 8
  • 1024 <= M <= 4096
  • no expert mask
  • no EP/stage2 scatter
  • no bias
  • no hidden/intermediate padding
  • no quant scales
  • no local-token override
  • contiguous, non-shuffled w1 and w2

Unsupported inputs fall back to the existing AITER path.

Test Plan

Focused correctness and dispatch coverage:

GPU_ARCHS=gfx1201 python3 -m pytest -q   op_tests/triton_tests/moe/test_moe_gemm_a16w16.py   op_tests/triton_tests/moe/moe_routing/test_topk_align.py   op_tests/test_moe_ck2stage_bf16.py   --tb=short

Benchmark smoke:

GPU_ARCHS=gfx1201 PYTHONPATH=/workspace python3   op_tests/op_benchmarks/triton/bench_moe_gemm_a16w16.py   --tokens 1024   --warmup 1   --rep 1

Full correctness validation compared public AITER fused_moe against torch_moe for every integer M=1024..4096.

Full performance validation compared public AITER fused_moe against vLLM fused_experts for every integer M=1024..4096 with warmup=10, iters=30.

A forced-policy diagnostic sweep compared the Triton large-M implementation against the corrected CK2Stage fallback for every integer M=17..4096. That diagnostic was used to verify the large-M implementation's range and to show why lower M values are not included in the production policy.

Test Results

Latest focused gfx1201 validation at reviewed branch head 84b421d7e72c:

op_tests/triton_tests/moe/test_moe_gemm_a16w16.py
op_tests/triton_tests/moe/moe_routing/test_topk_align.py
op_tests/test_moe_ck2stage_bf16.py

42 passed in 18.18s

Benchmark smoke:

tokens,time_ms
1024,0.971000

Production interval vs vLLM, M=1024..4096:

Metric Value
Rows measured 3073
Mean-latency wins/equal vs vLLM 3073
Mean-latency losers vs vLLM 0
Average mean improvement vs vLLM +6.08%
Median mean improvement vs vLLM +5.55%
Worst mean improvement vs vLLM +0.11%
Best mean improvement vs vLLM +25.41%

Production interval vs corrected CK2Stage fallback, M=1024..4096:

Metric Value
Rows compared 3073
Large-M mean-latency wins/equal 3073
CK2Stage mean-latency wins 0
Average mean improvement vs CK2Stage +63.58%
Median mean improvement vs CK2Stage +65.54%
Worst mean improvement vs CK2Stage +40.73%
Best mean improvement vs CK2Stage +71.93%

Forced diagnostic sweep vs CK2Stage, M=17..4096:

Metric Value
Rows compared 4080
Large-M mean-latency wins/equal 3846
CK2Stage mean-latency wins 234
Average mean improvement vs CK2Stage +52.57%
Median mean improvement vs CK2Stage +61.51%
Worst mean improvement vs CK2Stage -33.16%
Best mean improvement vs CK2Stage +71.93%

The CK2Stage-winning rows are below the production gate or in low/transition regions: 17..202, 204..219, 221..235, 237..239, 241..244, 247, 250, 252..255, 270..271, 282, 484. This is why the production policy starts at M=1024.

Reference correctness for the new Triton path over the production interval:

Metric Value
Rows checked 3073
Runtime errors 0
AITER MoE criterion failures 0
Strict tiny mismatch rows 27
Max rounded err ratio 0.000000
Max logits diff 0.000005

Representative per-M rows from the full validation table. The sampled rows include every 32nd production M plus the tested policy boundary/transition rows, including the 4x / 8x / 16x rows 1024, 2048, and 4096.

M public AITER mean us vLLM mean us delta vs vLLM CK2Stage diagnostic mean us delta vs CK2Stage correctness
1024 1015.298 1058.611 +4.09% 1869.997 +40.73% ok / err 0.000000 / diff 0.000005
1025 1013.507 1058.875 +4.28% 1883.377 +46.13% ok / err 0.000000 / diff 0.000005
1056 1021.487 1060.796 +3.71% 1876.990 +46.43% ok / err 0.000000 / diff 0.000005
1088 1021.972 1070.809 +4.56% 1926.200 +46.39% ok / err 0.000000 / diff 0.000005
1120 1027.266 1072.601 +4.23% 2030.913 +48.62% ok / err 0.000000 / diff 0.000005
1136 1027.501 1076.603 +4.56% 2014.753 +48.06% ok / err 0.000000 / diff 0.000005
1152 1032.002 1077.185 +4.19% 1996.584 +47.65% ok / err 0.000000 / diff 0.000005
1184 1034.206 1080.841 +4.31% 2067.758 +48.83% ok / err 0.000000 / diff 0.000005
1216 1039.559 1092.006 +4.80% 2090.351 +49.91% ok / err 0.000000 / diff 0.000005
1248 1043.826 1094.795 +4.66% 2167.631 +51.43% ok / err 0.000000 / diff 0.000005
1280 1046.759 1100.765 +4.91% 2156.958 +51.05% ok / err 0.000000 / diff 0.000005
1312 1050.653 1106.731 +5.07% 2227.402 +52.04% ok / err 0.000000 / diff 0.000005
1344 1059.384 1112.940 +4.81% 2216.902 +51.98% ok / err 0.000000 / diff 0.000005
1376 1059.697 1119.427 +5.34% 2304.975 +53.86% ok / err 0.000000 / diff 0.000005
1408 1066.383 1128.784 +5.53% 2313.392 +52.98% ok / err 0.000000 / diff 0.000005
1440 1073.592 1133.794 +5.31% 2386.119 +54.93% ok / err 0.000000 / diff 0.000005
1472 1080.093 1134.405 +4.79% 2385.316 +55.05% ok / err 0.000000 / diff 0.000005
1504 1080.298 1143.037 +5.49% 2465.043 +55.66% ok / err 0.000000 / diff 0.000005
1536 1086.289 1148.516 +5.42% 2516.606 +56.31% ok / err 0.000000 / diff 0.000005
1543 1088.866 1144.956 +4.90% 2485.696 +55.23% ok / err 0.000000 / diff 0.000005
1544 1089.706 1148.753 +5.14% 2462.439 +55.60% ok / err 0.000000 / diff 0.000005
1568 1089.252 1153.785 +5.59% 2582.196 +56.56% ok / err 0.000000 / diff 0.000005
1600 1096.250 1157.526 +5.29% 2619.434 +57.60% ok / err 0.000000 / diff 0.000005
1632 1101.353 1164.881 +5.45% 2590.904 +56.13% ok / err 0.000000 / diff 0.000005
1664 1103.195 1172.762 +5.93% 2633.071 +56.73% ok / err 0.000000 / diff 0.000005
1678 1111.602 1179.881 +5.79% 2665.677 +57.15% ok / err 0.000000 / diff 0.000005
1696 1110.552 1175.167 +5.50% 2725.787 +58.43% ok / err 0.000000 / diff 0.000005
1724 1118.344 1184.654 +5.60% 2706.391 +58.67% ok / err 0.000000 / diff 0.000005
1728 1117.998 1186.741 +5.79% 2740.878 +58.69% ok / err 0.000000 / diff 0.000005
1760 1125.247 1192.753 +5.66% 2717.908 +59.19% ok / err 0.000000 / diff 0.000005
1792 1126.791 1205.747 +6.55% 2774.941 +58.97% ok / err 0.000000 / diff 0.000005
1799 1135.811 1202.541 +5.55% 2851.065 +59.95% ok / err 0.000000 / diff 0.000005
1803 1136.079 1199.765 +5.31% 2831.085 +59.66% ok / err 0.000000 / diff 0.000005
1815 1140.268 1202.984 +5.21% 2854.812 +59.76% ok / err 0.000000 / diff 0.000005
1824 1142.404 1202.724 +5.02% 2857.498 +59.60% ok / err 0.000000 / diff 0.000005
1837 1142.933 1210.962 +5.62% 2882.605 +60.92% ok / err 0.000000 / diff 0.000005
1856 1148.032 1209.998 +5.12% 2895.562 +59.51% ok / err 0.000000 / diff 0.000005
1888 1149.091 1219.017 +5.74% 2876.425 +60.21% ok / err 0.000000 / diff 0.000005
1920 1155.793 1226.458 +5.76% 2993.063 +61.40% ok / err 0.000000 / diff 0.000005
1944 1164.622 1232.586 +5.51% 2997.033 +60.58% ok / err 0.000000 / diff 0.000005
1945 1165.425 1226.537 +4.98% 2999.726 +61.44% ok / err 0.000000 / diff 0.000005
1952 1163.594 1231.283 +5.50% 3027.496 +61.35% ok / err 0.000000 / diff 0.000005
1984 1167.091 1235.063 +5.50% 3006.056 +60.35% ok / err 0.000000 / diff 0.000005
2016 1173.953 1245.066 +5.71% 3047.853 +60.90% ok / err 0.000000 / diff 0.000005
2039 1181.944 1248.797 +5.35% 3134.427 +62.21% ok / err 0.000000 / diff 0.000005
2048 1176.384 1244.399 +5.47% 3137.730 +58.73% ok / err 0.000000 / diff 0.000005
2080 1181.984 1257.450 +6.00% 3107.907 +61.33% ok / err 0.000000 / diff 0.000005
2112 1189.622 1259.583 +5.55% 3190.564 +61.76% ok / err 0.000000 / diff 0.000005
2144 1197.152 1270.868 +5.80% 3296.425 +60.42% ok / err 0.000000 / diff 0.000005
2176 1203.446 1274.160 +5.55% 3273.408 +59.45% ok / err 0.000000 / diff 0.000005
2208 1212.049 1279.604 +5.28% 3283.805 +60.41% ok / err 0.000000 / diff 0.000005
2240 1220.544 1286.421 +5.12% 3336.169 +63.10% ok / err 0.000000 / diff 0.000005
2272 1221.951 1297.640 +5.83% 3346.555 +63.17% ok / err 0.000000 / diff 0.000005
2304 1228.821 1292.621 +4.94% 3409.409 +62.78% ok / err 0.000000 / diff 0.000005
2336 1238.380 1311.668 +5.59% 3485.223 +60.68% ok / err 0.000000 / diff 0.000005
2368 1248.391 1324.078 +5.72% 3509.310 +63.46% ok / err 0.000000 / diff 0.000005
2400 1250.855 1328.856 +5.87% 3552.750 +64.11% ok / err 0.000000 / diff 0.000005
2432 1258.203 1332.052 +5.54% 3635.787 +64.85% ok / err 0.000000 / diff 0.000005
2446 1263.901 1344.088 +5.97% 3581.324 +63.93% ok / err 0.000000 / diff 0.000005
2464 1263.033 1345.781 +6.15% 3658.614 +65.17% ok / err 0.000000 / diff 0.000005
2496 1267.384 1349.760 +6.10% 3692.825 +65.34% ok / err 0.000000 / diff 0.000005
2528 1275.716 1355.555 +5.89% 3738.958 +65.48% ok / err 0.000000 / diff 0.000005
2560 1273.068 1362.786 +6.58% 3799.692 +65.92% ok / err 0.000000 / diff 0.000005
2592 1164.385 1272.444 +8.49% 3738.025 +66.11% ok / err 0.000000 / diff 0.000005
2624 1167.322 1278.404 +8.69% 3804.372 +65.25% ok / err 0.000000 / diff 0.000005
2656 1186.282 1293.409 +8.28% 3861.249 +66.14% ok / err 0.000000 / diff 0.000005
2688 1178.943 1288.380 +8.49% 3897.889 +66.40% ok / err 0.000000 / diff 0.000005
2720 1181.653 1301.721 +9.22% 3933.916 +66.02% ok / err 0.000000 / diff 0.000005
2752 1200.953 1302.441 +7.79% 3981.154 +66.32% ok / err 0.000000 / diff 0.000005
2784 1192.728 1304.117 +8.54% 4025.087 +66.55% ok / err 0.000000 / diff 0.000005
2816 1212.542 1321.381 +8.24% 4058.178 +66.94% ok / err 0.000000 / diff 0.000005
2848 1217.805 1314.806 +7.38% 4187.008 +65.93% ok / err 0.000000 / diff 0.000005
2880 1223.481 1336.217 +8.44% 4126.935 +66.61% ok / err 0.000000 / diff 0.000005
2912 1236.929 1355.739 +8.76% 4259.305 +65.94% ok / err 0.000000 / diff 0.000005
2944 1222.026 1345.734 +9.19% 4232.966 +66.38% ok / err 0.000000 / diff 0.000005
2976 1247.873 1358.969 +8.17% 4225.372 +68.25% ok / err 0.000000 / diff 0.000005
3008 1244.574 1363.845 +8.75% 4369.916 +67.27% ok / err 0.000000 / diff 0.000005
3040 1241.549 1363.986 +8.98% 4396.544 +67.86% ok / err 0.000000 / diff 0.000005
3072 1281.641 1404.465 +8.75% 4382.513 +68.26% ok / err 0.000000 / diff 0.000005
3104 1265.536 1382.625 +8.47% 4433.324 +68.33% ok / err 0.000000 / diff 0.000005
3136 1252.905 1376.562 +8.98% 4473.798 +68.59% ok / err 0.000000 / diff 0.000005
3168 1270.653 1392.699 +8.76% 4551.675 +68.90% ok / err 0.000000 / diff 0.000005
3200 1282.325 1402.575 +8.57% 4588.645 +69.52% ok / err 0.000000 / diff 0.000005
3232 1274.262 1400.858 +9.04% 4629.372 +69.37% ok / err 0.000000 / diff 0.000005
3264 1279.710 1400.789 +8.64% 4658.969 +69.39% ok / err 0.000000 / diff 0.000005
3296 1298.245 1419.126 +8.52% 4707.153 +69.74% ok / err 0.000000 / diff 0.000005
3328 1309.512 1426.528 +8.20% 4752.503 +69.87% ok / err 0.000000 / diff 0.000005
3360 1317.531 1441.982 +8.63% 4710.583 +69.82% ok / err 0.000000 / diff 0.000005
3392 1308.570 1415.979 +7.59% 4761.834 +69.50% ok / err 0.000000 / diff 0.000005
3424 1313.584 1419.673 +7.47% 4767.090 +70.04% ok / err 0.000000 / diff 0.000005
3456 1314.452 1430.685 +8.12% 4875.677 +70.44% ok / err 0.000000 / diff 0.000005
3488 1329.029 1435.093 +7.39% 4933.354 +70.21% ok / err 0.000000 / diff 0.000005
3520 1331.061 1450.396 +8.23% 4950.895 +70.50% ok / err 0.000000 / diff 0.000005
3552 1343.220 1430.817 +6.12% 4947.285 +69.87% ok / err 0.000000 / diff 0.000005
3584 1369.986 1473.869 +7.05% 4946.279 +70.65% ok / err 0.000000 / diff 0.000005
3616 1379.177 1460.056 +5.54% 5077.030 +70.68% ok / err 0.000000 / diff 0.000005
3648 1386.348 1474.595 +5.98% 5100.220 +70.89% ok / err 0.000000 / diff 0.000005
3680 1374.970 1454.061 +5.44% 5076.324 +70.05% ok / err 0.000000 / diff 0.000005
3712 1381.339 1459.470 +5.35% 5140.967 +68.90% ok / err 0.000000 / diff 0.000005
3744 1411.400 1473.634 +4.22% 5213.771 +69.58% ok / err 0.000000 / diff 0.000005
3776 1410.982 1485.942 +5.04% 5255.608 +70.49% ok / err 0.000000 / diff 0.000005
3808 1412.358 1487.757 +5.07% 5236.492 +69.46% ok / err 0.000000 / diff 0.000005
3840 1440.159 1480.407 +2.72% 5336.299 +69.99% ok / err 0.000000 / diff 0.000005
3872 1415.342 1485.506 +4.72% 5323.572 +70.45% ok / err 0.000000 / diff 0.000005
3904 1414.087 1491.999 +5.22% 5412.943 +69.78% ok / err 0.000000 / diff 0.000005
3936 1459.656 1508.820 +3.26% 5394.960 +70.72% ok / err 0.000000 / diff 0.000005
3968 1467.143 1505.865 +2.57% 5488.023 +71.27% ok / err 0.000000 / diff 0.000005
4000 1444.876 1500.854 +3.73% 5526.894 +71.35% ok / err 0.000000 / diff 0.000005
4032 1449.727 1507.382 +3.82% 5476.544 +71.55% ok / err 0.000000 / diff 0.000005
4064 1478.128 1520.344 +2.78% 5655.032 +71.63% ok / err 0.000000 / diff 0.000005
4096 1507.791 1534.730 +1.76% 5581.934 +70.69% ok / err 0.000000 / diff 0.000005

The full per-M table for M=17..4096 remains in local pr.md; it was not pasted here because the complete table is several thousand rows and too large for a practical GitHub PR body.

PR Self-Review

Reviewed revision

84b421d7e72ccf6abf2fad0e58a7019a6e40c4ae

Behavior and intent

This PR changes gfx1201 64-CU BF16 G1U1 MoE dispatch for Qwen3.6 35B-A3B large-M rows in the validated 1024 <= M <= 4096 range. The old #5059 implementation was no longer compatible with current AITER because it used removed legacy Triton MoE wrappers. This revision provides a current-architecture Triton MoE route for the validated production range while keeping unsupported inputs on the existing AITER path.

Validation and unverified scope

Validated on a Radeon AI Pro R9700S / gfx1201 system using a ROCm vLLM container with flydsl==0.2.4 installed inside the disposable container. Public-path correctness, dispatch-gate coverage, route-alignment wrapper coverage, CK2Stage fallback config coverage, and benchmark smoke passed.

The full correctness sweep over every integer M=1024..4096 found zero runtime errors and zero AITER MoE criterion failures. The full performance sweep over every integer M=1024..4096 found zero mean-latency losers against the vLLM rerun baseline. The forced diagnostic sweep over M=17..4096 showed CK2Stage can win in low/transition rows below the production gate, while the new Triton path wins or ties every row in 1024..4096.

Unverified scope: non-gfx1201 devices, non-64-CU gfx1201 devices, quantized MoE, expert-parallel scatter/combine, shuffled weights, non-contiguous weights, alternate gate modes, padded hidden/intermediate dimensions, inter_dim=256, M < 1024, and M > 4096. These are intentionally outside the gate.

PR-specific question

The changed large-M kernel path is selected only when runtime architecture is gfx1201, get_cu_num() == 64, inputs/weights are BF16, quantization is disabled, activation is Silu, layout is G1U1 with separated gate mode, shape is model_dim=2048, inter_dim=128, num_experts=256, topk=8, weights are contiguous and non-shuffled, EP/scatter metadata is absent, padding/bias/scales/local-token overrides are absent, and 1024 <= M <= 4096. Unsupported inputs are prevented from reaching this backend by _can_use_bf16_g1u1_gfx1201_triton_large_m(...); otherwise dispatch falls back to the existing AITER path.

Changed op tests

op_tests/triton_tests/moe/test_moe_gemm_a16w16.py: covers the public aiter.fused_moe(...) path for the gfx1201 BF16 G1U1 current-architecture large-M backend, compares against independent torch_moe, checks caller-owned output=, and validates the 1024..4096 policy gate plus neighboring rejected rows. This is not redundant with #4740's small-M direct HIP test because this PR uses a different Triton backend, different route-alignment path, and selected large-M dispatch gate.

op_tests/triton_tests/moe/moe_routing/test_topk_align.py: covers the route-alignment wrapper used by the new backend. It validates histograms, raw offsets, padded/tile offsets, block maps, inverse indices, sorted weights, empty experts, uneven expert loads, and per-stream scratch isolation. This is not redundant with public MoE correctness because public output checks can pass on a single stream while shared routing metadata remains unsafe for concurrent streams or graph replay.

op_tests/test_moe_ck2stage_bf16.py: covers the corrected gfx1201 BF16 CK2Stage fallback config convention and 4096 padded-M coverage, verifies that the Opus sorting source has the GFX10+ safe fallback shape needed by current main, and runs a representative gfx1201 CK2Stage 4096-bucket path. This is not redundant because the large-M PR uses the fallback as a comparison baseline and the config convention bug would otherwise make that baseline unavailable.

op_tests/op_benchmarks/triton/bench_moe_gemm_a16w16.py: provides a standalone public-wrapper benchmark for the new Triton MoE backend. This is not a correctness test; it is included so the launchable Triton kernel family has a local benchmark entry point.

Submission Checklist

@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
multigpu 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: 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 5059 --add-label <label>

PR title tags:
Component tags ([Triton/Gluon], [HIP], [CK], [ASM], ...) are added to the PR title automatically from the changed files and re-synced on every push — change-type tags like [fix]/[Perf] and op tags like [MLA] are left untouched. Add the no-auto-title label to opt this PR out of title tagging.

@keneoneth
keneoneth force-pushed the feat/gfx1201-bf16-g1u1-selected-large-m-moe branch 5 times, most recently from 6cb9ebc to d3ae81d Compare August 31, 2026 18:30
@keneoneth
keneoneth marked this pull request as ready for review August 31, 2026 18:37
@keneoneth
keneoneth requested a review from a team August 31, 2026 18:37
@github-actions github-actions Bot changed the title Add gfx1201 BF16 G1U1 selected large-M Triton MoE path [JIT] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path Aug 31, 2026
@github-actions github-actions Bot added the JIT label Aug 31, 2026
@zufayu
zufayu self-requested a review September 1, 2026 01:16
@zufayu

zufayu commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

aiter PR #5059 — [JIT] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path

Routes ten measured Qwen3.6 35B-A3B token counts on gfx1201 to the existing Triton MoE kernels, reaching parity with the vLLM baseline that the current AITER path loses to by 2–2.7x.

Review (advisory): ⚠️ NEEDS WORK
Validation (deterministic): NOT RUN — no exact-head validation_report.json. The index-width scan over this diff returns 0 candidates, correctly: the only products here are M * topk and E * block_m, both bounded well under 2^31.

Worth stating plainly, because it changes how the rest should be read: this PR adds no kernel. Every kernel it calls — fused_moe_silu, triton_moe, moe_align_block_size_triton, moe_sum — already exists. The 260 lines in fused_moe.py are a gate, a config loader and an orchestration helper. And the measured gap it closes is large: the existing AITER path runs 1979 µs against vLLM's 1015 µs at M=1152, and 3136 µs against 1161 µs at M=2048. That is a real regression to fix, not a marginal tuning win.

⚠️ The 128-line test never executes in any CI job. op_tests/tuning_tests/test_triton_fmoe_dispatch.py uses unittest.TestCase, which is the right framework for where it lives — but tuning-tests.yaml invokes a hard-coded module list across three jobs (test_csv_validation, test_tuner_infra, test_mp_tuner_logic, test_a6w6_tuning at line 96; test_tune_pipeline at 179; test_run_config at 262), and the new module is in none of them. It also cannot arrive via the standard shards, which collect op_tests at depth 1 only (aiter_test.sh:30).

More importantly, that workflow's triggers are schedule and workflow_dispatch — there is no pull_request. So even after the module is added to the list, this test gives no PR-time protection; it would report the next morning at the earliest. That matters here because the gate below is the whole PR. Author must add the module to tuning-tests.yaml, and it is worth asking separately whether a dispatch-gate test belongs somewhere that runs on PRs at all. [verified]

⚠️ The config lookup matches the token count exactly, so M = 1543 hits and M = 1544 falls through. _get_gfx1201_triton_fmoe_config builds a 14-field key including str(M) and resolves it with .get(key), returning None on any miss. The shipped rows are 1152, 1543, 1728, 1799, 1803, 1815, 1837, 1944, 1945, 2048 — values that read like observed batch sizes rather than a designed sweep. The very next statement in _fused_moe_impl is get_2stage_cfgs(get_padded_M(M), ...), i.e. the path this one sits in front of buckets M rather than matching it.

Under real serving M moves continuously with batch composition, so the fast path will fire far less often than a ten-row table suggests, and the 2–2.7x gap remains for everything in between. cu_num is in the key as well, so a Navi48 reporting a different CU count misses every row. Reviewer should ask whether bucketing or nearest-M is intended, and — given the measured gap grows monotonically with M from 97% to 168% — whether the underlying HIP path should be fixed rather than bypassed at ten points. [verified]

📝 Two silences stack. @unittest.skipUnless(fused_moe_mod is not None, ...) turns an import failure into a green skip, so even once the module is in CI a broken import reports success. And _load_triton_fmoe_configs is wrapped in functools.lru_cache(maxsize=1), so AITER_CONFIG_TRITON_FMOE is read once per process and a later change to it has no effect. Author must make the import failure an error rather than a skip. [verified]

📝 .contiguous() is called unconditionally on the hot path. _triton_bf16_g1u1_moe(hidden_states.contiguous(), w1.contiguous(), w2.contiguous(), ...) runs on every forward. It is free when the tensors are already contiguous, but if the expert weights ever arrive non-contiguous this copies all 256 experts per call, which would dwarf the gain the PR is measuring. Reviewer should ask whether the gate should assert contiguity instead of silently repairing it. [verified]

📝 stage1 is allocated with torch.empty and its padding rows are never written. sorted_token_ids is filled with the sentinel topk_ids.numel(), so fused_moe_silu masks those rows out, leaving them uninitialized in stage1. Whether the second GEMM or moe_sum ever reads them depends on how triton_moe walks sorted_token_ids, which I did not trace — so this is a question, not a finding. Reviewer should ask the author to confirm the padded rows are unreachable downstream. [inferred]

@github-actions github-actions Bot changed the title [JIT] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path [CI] [JIT] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path Sep 1, 2026
@github-actions github-actions Bot added the CI label Sep 1, 2026
@keneoneth

keneoneth commented Sep 1, 2026 •

Copy link
Copy Markdown
Contributor Author

aiter PR #5059 — [JIT] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path

Routes ten measured Qwen3.6 35B-A3B token counts on gfx1201 to the existing Triton MoE kernels, reaching parity with the vLLM baseline that the current AITER path loses to by 2–2.7x.

Review (advisory): ⚠️ NEEDS WORK Validation (deterministic): NOT RUN — no exact-head validation_report.json. The index-width scan over this diff returns 0 candidates, correctly: the only products here are M * topk and E * block_m, both bounded well under 2^31.

Worth stating plainly, because it changes how the rest should be read: this PR adds no kernel. Every kernel it calls — fused_moe_silu, triton_moe, moe_align_block_size_triton, moe_sum — already exists. The 260 lines in fused_moe.py are a gate, a config loader and an orchestration helper. And the measured gap it closes is large: the existing AITER path runs 1979 µs against vLLM's 1015 µs at M=1152, and 3136 µs against 1161 µs at M=2048. That is a real regression to fix, not a marginal tuning win.

⚠️ The 128-line test never executes in any CI job. op_tests/tuning_tests/test_triton_fmoe_dispatch.py uses unittest.TestCase, which is the right framework for where it lives — but tuning-tests.yaml invokes a hard-coded module list across three jobs (test_csv_validation, test_tuner_infra, test_mp_tuner_logic, test_a6w6_tuning at line 96; test_tune_pipeline at 179; test_run_config at 262), and the new module is in none of them. It also cannot arrive via the standard shards, which collect op_tests at depth 1 only (aiter_test.sh:30).

More importantly, that workflow's triggers are schedule and workflow_dispatch — there is no pull_request. So even after the module is added to the list, this test gives no PR-time protection; it would report the next morning at the earliest. That matters here because the gate below is the whole PR. Author must add the module to tuning-tests.yaml, and it is worth asking separately whether a dispatch-gate test belongs somewhere that runs on PRs at all. [verified]

⚠️ The config lookup matches the token count exactly, so M = 1543 hits and M = 1544 falls through. _get_gfx1201_triton_fmoe_config builds a 14-field key including str(M) and resolves it with .get(key), returning None on any miss. The shipped rows are 1152, 1543, 1728, 1799, 1803, 1815, 1837, 1944, 1945, 2048 — values that read like observed batch sizes rather than a designed sweep. The very next statement in _fused_moe_impl is get_2stage_cfgs(get_padded_M(M), ...), i.e. the path this one sits in front of buckets M rather than matching it.

Under real serving M moves continuously with batch composition, so the fast path will fire far less often than a ten-row table suggests, and the 2–2.7x gap remains for everything in between. cu_num is in the key as well, so a Navi48 reporting a different CU count misses every row. Reviewer should ask whether bucketing or nearest-M is intended, and — given the measured gap grows monotonically with M from 97% to 168% — whether the underlying HIP path should be fixed rather than bypassed at ten points. [verified]

📝 Two silences stack. @unittest.skipUnless(fused_moe_mod is not None, ...) turns an import failure into a green skip, so even once the module is in CI a broken import reports success. And _load_triton_fmoe_configs is wrapped in functools.lru_cache(maxsize=1), so AITER_CONFIG_TRITON_FMOE is read once per process and a later change to it has no effect. Author must make the import failure an error rather than a skip. [verified]

📝 .contiguous() is called unconditionally on the hot path. _triton_bf16_g1u1_moe(hidden_states.contiguous(), w1.contiguous(), w2.contiguous(), ...) runs on every forward. It is free when the tensors are already contiguous, but if the expert weights ever arrive non-contiguous this copies all 256 experts per call, which would dwarf the gain the PR is measuring. Reviewer should ask whether the gate should assert contiguity instead of silently repairing it. [verified]

📝 stage1 is allocated with torch.empty and its padding rows are never written. sorted_token_ids is filled with the sentinel topk_ids.numel(), so fused_moe_silu masks those rows out, leaving them uninitialized in stage1. Whether the second GEMM or moe_sum ever reads them depends on how triton_moe walks sorted_token_ids, which I did not trace — so this is a question, not a finding. Reviewer should ask the author to confirm the padded rows are unreachable downstream. [inferred]

Thanks for the review, this is helpful. Here's the updated fix point-by-point:

  1. Dispatch test coverage in CI

Agreed. This was a real gap.

  • We added op_tests.tuning_tests.test_triton_fmoe_dispatch to .github/workflows/tuning-tests.yaml.
  • We also added an explicit PR-time unittest step for this module in .github/workflows/aiter-test.yaml, because the standard op_tests sharding only collects depth-1 files and would not naturally pick up op_tests/tuning_tests/test_triton_fmoe_dispatch.py.

So this test is now both:

  • included in the tuning test workflow, and
  • exercised in PR-time validation.
  1. Import failure was previously a green skip

Agreed. This was also a real issue.

  • We removed the class-level @unittest.skipUnless(...).
  • Import/setup failure in test_triton_fmoe_dispatch.py now hard-fails instead of silently skipping.
  1. Exact M lookup

This behavior is intentional for this first step.

The current design here is an exact-match allow-list of validated gfx1201 rows, not a generic bucket/range policy. That is why the lookup key includes the exact token count and returns None on a miss.

We agree this is not a general solution for all nearby M values. For this PR, though, we want conservative behavior:

  • exact validated rows take the Triton route
  • everything else falls through to the existing AITER path unchanged

We do plan to revisit bucket/range policy in follow-up work, but only after we have broader measurements and test coverage to justify it.

  1. Unconditional .contiguous() on the hot path

Agreed for w1 / w2.

We tightened this:

  • w1 and w2 contiguity is now part of Triton-route eligibility
  • if either is non-contiguous, the Triton selector declines the route and we fall back to the existing path
  • we no longer silently copy w1 / w2 inside the Triton fast path

We kept hidden_states.contiguous() as a bounded per-request activation repair, but removed the silent weight-copy behavior that would be problematic for a performance path.

  1. stage1 = torch.empty(...) / padded-row concern

We reviewed this path.

In the Triton helper, stage1 is allocated as:

stage1 = torch.empty((M * topk, inter_dim), ...)

Here M is the token count for this MoE call, and topk is the number of routed experts per token. So stage1 holds exactly one row per real token-expert route, i.e. exactly M * topk payload rows.

The padding only exists in the routing metadata:

sorted_token_ids
sorted_expert_ids

sorted_token_ids is padded up to the block-aligned routing length and the padded entries are filled with the sentinel topk_ids.numel() (which is also M * topk).

So the distinction is:

  • stage1: exact payload buffer for the real M * topk routed pairs
  • sorted_token_ids / sorted_expert_ids: padded metadata used for block alignment

In other words, there are no extra padded payload rows allocated in stage1 itself. The padded portion is only in the metadata arrays, not in the stage1 storage.

@zufayu
zufayu requested a review from yzhou103 September 2, 2026 01:30
Comment thread .github/workflows/aiter-test.yaml Resolved
@keneoneth
keneoneth force-pushed the feat/gfx1201-bf16-g1u1-selected-large-m-moe branch from 67f03eb to bc189b1 Compare September 2, 2026 04:27
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

No activity for 15 days, so this is now labelled stale. A push or a comment clears it; keep-open exempts it. Nothing is closed automatically.

@github-actions github-actions Bot added the stale The PR hasn't been updated in 2 weeks label Oct 9, 2026
@zufayu

zufayu commented Oct 10, 2026

Copy link
Copy Markdown
Contributor

[verified] This needs to be reworked using the current AITER Triton MoE architecture. The PR currently creates a parallel config loader and manually assembles routing, GEMM1, GEMM2, and reduction inside aiter/fused_moe.py, using legacy Triton MoE modules that were removed by #4833. The selected path therefore fails at runtime when those lazy imports execute.

Please implement the BF16 G1U1 backend under aiter/ops/triton/moe/, reuse AITER's current routing/GEMM/reduction and tuning infrastructure, and keep fused_moe.py limited to eligibility and backend dispatch. Do not restore or patch around the removed legacy implementation.

The tests must follow the existing AITER test structure as well. Do not place a Triton-specific test at the top level of op_tests merely so the depth-one collector finds it, and do not add a one-off hardcoded workflow step. Actual Triton MoE correctness coverage belongs under op_tests/triton_tests/moe/ and must execute the public AITER path on the target architecture against an independent reference. Pure config/lookup tests should use the existing tuning or unit-test infrastructure and be wired through its normal manifest/discovery mechanism.

@github-actions github-actions Bot removed the stale The PR hasn't been updated in 2 weeks label Oct 11, 2026
@github-actions

Copy link
Copy Markdown
Contributor

No activity for 15 days, so this is now labelled stale. A push or a comment clears it; keep-open exempts it. Nothing is closed automatically.

@github-actions github-actions Bot added the stale The PR hasn't been updated in 2 weeks label Oct 27, 2026
@github-actions github-actions Bot changed the title [CI] [JIT] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path [Triton/Gluon] [HIP] [JIT] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path Nov 3, 2026
@github-actions github-actions Bot added the HIP label Nov 3, 2026
@github-actions github-actions Bot changed the title [Triton/Gluon] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path [Triton/Gluon] [HIP] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path Nov 4, 2026
@github-actions github-actions Bot added the HIP label Nov 4, 2026
@github-actions github-actions Bot changed the title [Triton/Gluon] [HIP] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path [Triton/Gluon] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path Nov 4, 2026
@github-actions github-actions Bot removed the HIP label Nov 4, 2026
@github-actions github-actions Bot changed the title [Triton/Gluon] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path [Triton/Gluon] [HIP] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path Nov 5, 2026
@github-actions github-actions Bot added the HIP label Nov 5, 2026
@keneoneth keneoneth changed the title [Triton/Gluon] [HIP] Add gfx1201 BF16 G1U1 selected large-M Triton MoE path [Triton] [HIP] Enable gfx1201 BF16 G1U1 MoE large-M path and CK2Stage fallback Nov 5, 2026
@github-actions github-actions Bot changed the title [Triton] [HIP] Enable gfx1201 BF16 G1U1 MoE large-M path and CK2Stage fallback [Triton/Gluon] [HIP] Enable gfx1201 BF16 G1U1 MoE large-M path and CK2Stage fallback Nov 5, 2026
@github-actions github-actions Bot changed the title [Triton/Gluon] [HIP] Enable gfx1201 BF16 G1U1 MoE large-M path and CK2Stage fallback [Triton/Gluon] Enable gfx1201 BF16 G1U1 MoE large-M path and CK2Stage fallback Nov 9, 2026
@github-actions github-actions Bot removed the HIP label Nov 9, 2026

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants