Skip to content

[WIP DO NOT MERGE] [AMD] fix(mxfp4): route AITER MXFP4+swiglu through FlyDSL gate_mode=INTERLEAVE - #25580

Closed
bingxche wants to merge 3 commits into
mainfrom
bingxche/mxfp4-aiter-swiglu-gate-mode-5279
Closed

[WIP DO NOT MERGE] [AMD] fix(mxfp4): route AITER MXFP4+swiglu through FlyDSL gate_mode=INTERLEAVE#25580
bingxche wants to merge 3 commits into
mainfrom
bingxche/mxfp4-aiter-swiglu-gate-mode-5279

Conversation

@bingxche

@bingxche bingxche commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Motivation

Resolve issue ROCm/aiter#3227 surfaced after AITER commit ff9bf15cc5c5afcd527d5695cf5462ba0bc1cc2f (ROCm/aiter#3123)

Credit to @XiaobingSuper

Fix the nightly-accuracy-8-gpu-mi35x / nightly-accuracy-8-gpu-mi35x-rocm720 failure on test_gpt_oss_eval_mi35x.py (runs 76071145339 / 76071145701).

The crash chain (logs):

[aiter] [fused_moe] using 2stage default for
  (256, 128, 3072, 512, 32, 4, <ActivationType.Swiglu: 2>,
   'torch.bfloat16', 'torch.bfloat16', 'torch.float4_e2m1fn_x2',
   'QuantType.per_1x32', True, False)

[aiter] start build [module_moe_ck2stages_b16_fp4x2_preshuffle_off_b16_swiglu_per_1x32_mulWeightStage2]

gen_instances.py: error: argument -act/--activation:
    invalid choice: 'swiglu' (choose from 'silu', 'gelu')

/sgl-workspace/aiter/csrc/ck_gemm_moe_2stages_codegen/gemm_moe_ck2stages.cu:6:10:
    fatal error: 'gemm_moe_ck2stages_lookup.h' file not found

ModuleNotFoundError: No module named
  'aiter.jit.module_moe_ck2stages_b16_fp4x2_preshuffle_off_b16_swiglu_per_1x32_mulWeightStage2'

Root cause:

  1. Mxfp4MoEMethod hard-codes activation="swiglu" for the AITER backend (python/sglang/srt/layers/quantization/mxfp4.py:1019).
  2. aiter.fused_moe(...) exposes gate_mode as a caller-controlled parameter (defaults to GateMode.SEPARATED). SGLang was not setting it, so AITER took the SEPARATED path.
  3. For the gpt-oss MI35x workload no tuned config row matches, so AITER falls back to the default CK 2-stage kernel and synthesizes a JIT module name carrying swiglu (module_moe_ck2stages_..._swiglu_per_1x32_...).
  4. The CK 2-stage codegen (ck_gemm_moe_2stages_codegen/gen_instances.py) only accepts silu or gelu for -act. Codegen aborts, gemm_moe_ck2stages_lookup.h is never produced, the subsequent .cu compile fails, and the scheduler dies with ModuleNotFoundError.

The correct path for MXFP4 + swiglu on AITER is the FlyDSL interleaved-gate stage1 kernels (flydsl_moe1_afp4_wfp4_bf16_*), which natively implement swiglu via fused silu_and_mul_fq. Those kernels are selected when the caller passes gate_mode=GateMode.INTERLEAVE.

Modifications

  • python/sglang/srt/layers/moe/moe_runner/aiter.py
    • Add gate_mode: str = "separated" to AiterMoeQuantInfo (default preserves existing AITER MoE semantics — AITER's own default is also SEPARATED).
    • In AiterRunnerCore.run, resolve a single gate_mode and forward it into the aiter.fused_moe(...) call. This is the post-[MoE] Unify DeepEPMoE+MoriEPMoE through AITER MoeRunner pre/post-permute #23760 equivalent of the original patch's fused_experts_none_to_aiter edit, and now covers the unified standard / DeepEP normal / DeepEP LL dispatch paths through the AITER runner.
  • python/sglang/srt/layers/quantization/mxfp4.py
    • In Mxfp4MoEMethod.apply AITER branch, set gate_mode=GateMode.INTERLEAVE.value when self.runner.config.activation == "swiglu", otherwise keep GateMode.SEPARATED.value. Import GateMode from aiter.ops.flydsl.moe_common locally inside the branch (kept lazy because aiter is ROCm-only).

Reconcile with main #26208 (added during the main merge)

After branching, main landed #26208 which, inside AiterRunnerCore.run, sets extra["gate_mode"] = GateMode.INTERLEAVE.value and extra["swiglu_limit"] whenever quant_info.swiglu_limit > 0 (the DSv4 FP8 path). A naive merge would then pass gate_mode twice to aiter.fused_moe(...) (once via this PR's explicit kwarg, once via **extra), raising TypeError: fused_moe() got multiple values for keyword argument 'gate_mode' on any swiglu_limit > 0 call.

The merge therefore reconciles both into a single source of truth for gate_mode in run:

gate_mode = quant_info.gate_mode          # MXFP4/GPT-OSS sets this from activation
if quant_info.swiglu_limit > 0:           # FP8/DSv4 path
    gate_mode = GateMode.INTERLEAVE.value  # swiglu_limit > 0 implies interleaved gate/up
    extra["swiglu_limit"] = quant_info.swiglu_limit
output = fused_moe(..., gate_mode=gate_mode, **extra)   # never via extra → no double-pass

This is byte-for-byte equivalent to #26208 on the DSv4 FP8 path (swiglu_limit > 0gate_mode="interleave" + swiglu_limit forwarded), so DSv4 behavior is unchanged, while the MXFP4/GPT-OSS path gets INTERLEAVE from quant_info.gate_mode as intended.

Required AITER version. This patch depends on two AITER changes:

  1. aiter.fused_moe(...) exposing gate_mode as a caller-controlled kwarg.
  2. The aiter.ops.flydsl.moe_common module that defines GateMode.

Both are present from ROCm/aiter PR #3123 (commit ff9bf15c). Older AITER pins (e.g. a6bb499... shipped in some rocm/sgl-dev images) will fail at runtime with either TypeError: fused_moe() got an unexpected keyword argument 'gate_mode' or ModuleNotFoundError: No module named 'aiter.ops.flydsl.moe_common'. CI images / Dockerfile pins must be bumped to at least ff9bf15c, or workflows must be dispatched with aiter_ref set to a sufficiently new commit, before this PR can land cleanly across all AITER MoE tests (LLaDA2, Qwen3 MoE, DeepEP, etc., not only MXFP4).

For AITER versions that meet the above bound, this change is a no-op on every non-MXFP4 / non-swiglu AITER path: quant_info.gate_mode stays at the "separated" default and matches AITER's own default behavior.

Tests (commit 134d74a)

Re-dispatched nightly-accuracy-8-gpu-mi35x on this PR branch after merging latest main (which required reconciling the gate_mode change with the new swiglu_limit handling from #26208). Both passed ✅:

Speed Tests and Profiling

The INTERLEAVE FlyDSL stage1 kernels are the ones the MXFP4 tuned CSVs target on gfx950, so this also takes the MXFP4 path off the un-tuned CK default and onto the intended high-performance path. No standalone microbench in this PR.

Checklist


CI States

Latest PR Test (Base): ❌ Run #26733545189
Latest PR Test (Extra): ❌ Run #26733545103

@cursor

cursor Bot commented May 18, 2026

Copy link
Copy Markdown

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@bingxche bingxche changed the title fix(mxfp4): route AITER MXFP4+swiglu through FlyDSL gate_mode=INTERLEAVE [AMD] fix(mxfp4): route AITER MXFP4+swiglu through FlyDSL gate_mode=INTERLEAVE May 18, 2026
@bingxche bingxche changed the title [AMD] fix(mxfp4): route AITER MXFP4+swiglu through FlyDSL gate_mode=INTERLEAVE [AMD] fix(mxfp4): route AITER MXFP4+swiglu through FlyDSL gate_mode=INTERLEAVE May 18, 2026
@bingxche
bingxche marked this pull request as ready for review May 18, 2026 08:38
@bingxche

Copy link
Copy Markdown
Collaborator Author

@amd-bot ci-status

@amd-bot

amd-bot commented May 20, 2026

Copy link
Copy Markdown

@bingxche

CI Status for PR #25580

PR: [AMD] fix(mxfp4): route AITER MXFP4+swiglu through FlyDSL gate_mode=INTERLEAVE
Changed files: python/sglang/srt/layers/moe/moe_runner/aiter.py (+3/-0), python/sglang/srt/layers/quantization/mxfp4.py (+7/-0)

AMD: 3 test-job failures (0 likely related) | Others: 16 base-b job failures (0 related — all fast-failed on lint)

AMD CI Failures

Job Test File Test Function Error Related? Explanation Log
stage-b-test-1-gpu-large-amd (linux-mi300-1gpu-sglang, 0) test/registered/perf/test_bench_serving_1gpu_part1.py test_online_lora_latency_with_concurrent_adapter_updates AssertionError: 15850.98 not less than 4000 (median E2E latency) 🟢 Unlikely LoRA-adapter online-serving latency perf test on a Llama-3.1-8B BF16 path. PR only touches the MXFP4-AITER swiglu branch (Mxfp4MoEMethod.apply + AiterMoeQuantInfo.gate_mode default = "separated"). No LoRA / BF16 path passes through the modified gate_mode branch — extra["gate_mode"] is only injected when quant_info.gate_mode != "separated". Looks like a perf flake / runner-noise issue. Log
stage-b-test-1-gpu-large-amd (linux-mi300-1gpu-sglang, 1) test/registered/perf/test_bench_serving_1gpu_part2.py test_score_api_batch_scaling AssertionError: 80.09 not less than 70 (retry: 81.45 not less than 80) 🟢 Unlikely Score-API latency perf test, marginal failure ~1.5% over threshold. Unrelated to MXFP4 — uses the score API path, not the MoE quant runner the PR touches. Log
stage-b-test-2-gpu-large-amd (linux-mi300-2gpu-sglang, 0) test/registered/quant/test_int4fp8_moe.py N/A (job-level timeout) Scheduler watchdog timeout (300s) then The action 'Run test' has timed out after 45 minutes. Hung in aiter JIT build for module_moe_ck2stages_f8_i4_preshuffle_off_b16_silu_per_token_mulWeightStage2 (ActivationType.Silu, QuantType.per_Token). 🟢 Unlikely Int4-FP8 MoE path, not MXFP4. Uses Silu, not Swiglu, so the new gate_mode=INTERLEAVE branch is never hit; AiterMoeQuantInfo.gate_mode default stays "separated" and extra["gate_mode"] is not injected. Symptom is JIT build of a silu_per_token 2-stage kernel hanging — looks like an aiter-JIT/CK build / runner-disk issue, not anything this PR could affect. Log

Other CI Failures

Job Test File Test Function Error Related? Explanation Log
lint N/A N/A ruff F821 Undefined name 'BaseTpWorker' at python/sglang/srt/managers/scheduler.py:1004 (def get_draft_kv_pool(*, draft_worker: "BaseTpWorker", …)) 🟢 Unlikely The PR does not modify scheduler.py. The undefined BaseTpWorker annotation is in code that lives in the PR's base merge ref, not in this PR's diff. This is a pre-existing/transient base-branch lint issue — needs the missing BaseTpWorker import (TYPE_CHECKING) to be added on main. Log
15× base-b-test-{1,2,4}-gpu-{small,large,b200} + wait-for-base-b + pr-test-finish N/A N/A All show ##[error]Fast-fail: lint check failed — never executed any test. 🟢 Unlikely Cascade of the lint failure above; the base-b stage is gated on lint. Once the unrelated BaseTpWorker ruff error is fixed on main and the PR is rebased, these jobs will run. Run

Details

PR semantics (why none of the failures are PR-caused):

  • python/sglang/srt/layers/moe/moe_runner/aiter.py: adds gate_mode: str = "separated" field to AiterMoeQuantInfo and only injects extra["gate_mode"] into the aiter.fused_moe(...) call when quant_info.gate_mode != "separated". For every existing caller (non-MXFP4, non-swiglu paths — including the failing int4fp8_moe, bf16 LoRA, score-API/perf benches), the default is "separated"no behavior change vs. main.
  • python/sglang/srt/layers/quantization/mxfp4.py: change is gated on the _use_aiter branch of Mxfp4MoEMethod.apply AND on self.runner.config.activation == "swiglu". None of the three failing AMD tests, nor any non-AMD test, exercise that combination.

Blocker / next step: the cascade of NV/base-b failures and the lint failure are caused by a pre-existing F821 BaseTpWorker ruff error in scheduler.py on the base ref — not by this PR. Rebasing onto a main that has the BaseTpWorker TYPE_CHECKING import fixed will unblock all 16 non-AMD failures. The 3 AMD failures appear to be perf flakes / JIT-build hang on the runner (none touch the MXFP4-swiglu path) — recommend /rerun-failed-ci after the rebase to confirm.

Generated by amd-bot using Claude Code CLI

AITER's CK 2-stage MoE codegen (gen_instances.py) only accepts silu/gelu
as activation; passing swiglu makes the JIT build fail with
'invalid choice: swiglu' -> 'gemm_moe_ck2stages_lookup.h not found' ->
ModuleNotFoundError when no tuned config row matches the workload (e.g.
gpt-oss-20b/120b MXFP4 on mi35x gfx950 8-GPU).

aiter.fused_moe exposes gate_mode as a caller-controlled parameter
(default GateMode.SEPARATED). The MXFP4 path was not setting it, so
swiglu fell back to the CK default and tried to JIT a non-existent
ck2stages swiglu kernel. Thread gate_mode through AiterMoeQuantInfo and
AiterRunnerCore.run, and select GateMode.INTERLEAVE in
Mxfp4MoEMethod.apply when activation is swiglu. The FlyDSL
interleaved-gate stage1 kernels natively support swiglu and are what
the MXFP4 tuned CSV rows target.

Note: AITER >= the commit that adds the gate_mode kwarg to
aiter.fused_moe and ships aiter.ops.flydsl.moe_common is required.

Co-authored-by: Bingxu Chen <Bingxu.Chen@amd.com>
@cursor
cursor Bot force-pushed the bingxche/mxfp4-aiter-swiglu-gate-mode-5279 branch from 6dd60d9 to 24b408c Compare May 20, 2026 11:24
@bingxche bingxche mentioned this pull request May 20, 2026
5 tasks

@1am9trash 1am9trash left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@bingxche bingxche changed the title [AMD] fix(mxfp4): route AITER MXFP4+swiglu through FlyDSL gate_mode=INTERLEAVE [WIP DO NOT MERGE] [AMD] fix(mxfp4): route AITER MXFP4+swiglu through FlyDSL gate_mode=INTERLEAVE May 21, 2026
Resolve conflict in AiterMoeQuantInfo (keep both swiglu_limit and
gate_mode fields) and reconcile AiterRunnerCore.run() into a single
gate_mode source.

main #26208 set extra["gate_mode"]=INTERLEAVE when swiglu_limit>0, while
this branch passes gate_mode=quant_info.gate_mode directly to fused_moe.
A naive merge would pass gate_mode twice (TypeError) on the FP8/DSv4 path
where swiglu_limit>0. Now gate_mode is resolved once: it comes from
quant_info.gate_mode (MXFP4/GPT-OSS sets it from activation), and
swiglu_limit>0 forces INTERLEAVE to preserve the original DSv4 behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@bingxche bingxche closed this Jun 4, 2026
Rohan138 added a commit to Rohan138/vllm that referenced this pull request Jun 8, 2026
The MXFP4 W4A16 weight-load path in oracle/mxfp4.py uses
shuffle_weight_a16w4 (is_guinterleave=True), which interleaves gate/up
columns within each weight tile. The CK/FlyDSL MoE kernels in aiter
must be told this via gate_mode=GateMode.INTERLEAVE so they decode the
gate/up packing correctly.

Without the explicit gate_mode, aiter defaults to SEPARATED and (since
ROCm/aiter#3123) dispatches the (SEPARATED + Swiglu + per_1x32 + fp4x2)
case to a path that returns garbage for shuffled weights or crashes
during CK2stages JIT for the unshuffled Quark variant
(amd/gpt-oss-20b-w-mxfp4-a-bf16). This was the root cause of ROCM-25517
(gpt-oss-120b W4A16 gsm8k acc = 0) and ROCM-25478 (gpt-oss-20b Quark
JIT crash).

Other paths are unaffected:
  - FP8 W8A8 (DeepSeek-V4-Pro, DeepSeek-V3.2): shuffled with
    quark_ocp_mx.py:shuffle_weight(layout=(16,16)) — non-interleaved.
    use_mxfp4_w4a16 is False, default SEPARATED preserved.
  - MXFP4 W4A4 (amd/DeepSeek-R1-0528-MXFP4): shuffled via
    rocm_aiter_ops.shuffle_weights — non-interleaved. use_mxfp4_w4a16
    is False, default SEPARATED preserved.

The gate_mode kwarg was added to aiter.fused_moe in
ROCm/aiter#3123 (aiter>=0.1.14). To stay compatible with older aiter
shipping with vllm (e.g. aiter 0.1.13.post1 in the vllm-rocm:nightly
image), we probe the aiter signature and drop the kwarg when unsupported
— pre-vllm-project#3123 aiter tolerated the implicit SEPARATED default for
interleave-shuffled weights, so dropping the kwarg is safe there.
GateMode itself only exists on aiter>=0.1.14 and is imported under
try/except for the same reason.

Validation on MI355X (gfx950):
  vllm@main + aiter@main (6aeba41) openai/gpt-oss-120b W4A16 gsm8k:
    TP=1: 0.000 -> 0.905    TP=8: 0.000 -> 0.905
  vllm@main + aiter@main amd/gpt-oss-20b-w-mxfp4-a-bf16 TP=2 enforce-eager:
    CK2stages JIT crash -> serves cleanly
  vllm-rocm:nightly + aiter 0.1.13.post1 openai/gpt-oss-120b W4A16 gsm8k:
    TP=1: 0.910 (backward-compat — gate_mode kwarg silently dropped)
  vllm-rocm:v0.22.0 + aiter@main openai/gpt-oss-120b W4A16 gsm8k:
    TP=1: 0.895

amd/gpt-oss120b-w-mxfp4-a-fp8 W4A8 (this PR composes with vllm-project#44804):
  TP=8 mc=1=326, mc=8=2087, mc=32=6523, mc=64=11610 tok/s

Reference: sgl-project/sglang#25580 (sglang's
equivalent fix). Recommended by aiter maintainer (XiaobingZhang) on
ROCm/aiter#3586.

Signed-off-by: Rohan Potdar <rohan.potdar@amd.com>
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.

4 participants