Skip to content

[Quantization] Clarify MXFP4 MoE W13 layout requirement from backends - #52240

Open
BowenBao wants to merge 3 commits into
vllm-project:mainfrom
BowenBao:bowenbao/clarify_mxfp4_moe_oracle
Open

BowenBao wants to merge 3 commits into
vllm-project:mainfrom
BowenBao:bowenbao/clarify_mxfp4_moe_oracle

Conversation

@BowenBao

@BowenBao BowenBao commented Aug 14, 2026 •

Copy link
Copy Markdown
Contributor

Background

GPT-OSS checkpoints store fused gate/up weights in interleaved row order [g0, u0, g1, u1, ...], while standard MoE checkpoints (DeepSeek, Kimi-K3, and most of the ckpts from different quantizers etc.) store them contiguously [gate; up].

In the beginning when mxfp4 oracle was initially created, this interleaved weight format is overlooked. Some backends accepts interleaved format while others accepts contiguous format. They are all mixed together in the same convert_weight_* and select_mxfp4_moe_backend call.

Later when DSV4 was added, two parallel functions (convert_gpt_oss_weight_* and convert_weight_*) were created. The original convert_weight_* was renamed convert_gpt_oss_weight_*, despite it handles more models than just gpt-oss. Now several backends shared identical code between them, meaning they silently accepted either layout without converting, which is incorrect for backends that assume a specific layout.

Audit DeepDive

This PR starts a complete, deepdive audit to the existing state of all the backends. NOTE: The audit is produced by Claude Code, and verified carefully by the author. There could be mistakes.

Terminology

  • Contiguous [w1;w3] = [gate_0..gate_N; up_0..up_N] (standard vLLM checkpoint layout)
  • Contiguous [w3;w1] = [up_0..up_N; gate_0..gate_N] (FlashInfer CUTLASS convention, halves swapped)
  • Interleaved [g,u] = [gate_0, up_0, gate_1, up_1, ...] (GPT-OSS checkpoint layout)
  • Interleaved [u,g] = [up_0, gate_0, up_1, gate_1, ...] (TRTLLM convention, w1/w3 swapped within pairs)

w13 Layout Expected by Backend x Activation

Backend Activation w13 Layout Proof
DEEPGEMM_MXFP4 SILU Contiguous [w1;w3] fp8_utils.py:143: hidden_size: tl.constexpr = N // 2; fp8_utils.py:165-178: loads gate from input_ptr + offset, up from input_ptr + hidden_size + offset
SWIGLUSTEP Contiguous [w1;w3] layers/activation.py:37-38: gate = tl.load(x_row_ptr + offsets), up = tl.load(x_row_ptr + offsets + d) with d = n // 2 at line 55
SITU Contiguous [w1;w3] activation_kernels.cu:483-484: gate_ptr = input + row * 2 * d, up_ptr = gate_ptr + d
TRTLLM SWIGLUOAI, SILU, SITU Interleaved [u,g] mxfp4.py:1310-1317: comment "TRTLLM kernel expects interleaved [w3_0, w1_0, ...]"; torch.stack([w3_weight, w1_weight], dim=2).reshape() produces [up_0, gate_0, up_1, gate_1, ...]
trtllm_mxfp4_moe.py:139-146: SITU support gated behind has_flashinfer_situ_activation()
FLASHINFER_CUTLASS SILU, GELU_TANH, SWIGLUOAI, SWIGLUOAI_UNINTERLEAVE, RELU2_NO_MUL Contiguous [w3;w1] (swapped halves) flashinfer_utils.py:53-56: swap_w13_to_w31 reshapes to (-1, 2, half, cols) and .flip(dims=[1]), swapping gate and up halves
mxfp4.py:1535-1539: comment "Standard checkpoints store [w1; w3], FlashInfer CUTLASS consumes [w3; w1]"
flashinfer_cutlass_moe.py:191-198: _supports_activation lists SILU, GELU_TANH, RELU2_NO_MUL, SWIGLUOAI, SWIGLUOAI_UNINTERLEAVE
TRITON (fused) SWIGLUOAI Interleaved [g,u] _swiglu.py:52: gelu, linear = tl.split(tl.reshape(input, (input.shape[0], input.shape[1] // 2, 2))) — reshapes to (M, N//2, 2) then splits, pairing consecutive elements
gpt_oss_triton_kernels_moe.py:967-968: _supports_activation returns activation == MoEActivation.SWIGLUOAI
mxfp4.py:1477-1485: shuffle_weight converts contiguous [w1;w3] to interleaved via torch.stack((first, second), dim=-1).reshape(shape)
TRITON_UNFUSED SWIGLUOAI Interleaved [g,u] activation_kernels.cu:410: comment // Interleaved gate/up: input has [gate0, up0, gate1, up1, ...]; vectorized path at line 448: ACT_FN(vp[2*j], vp[2*j+1], ...)
SWIGLUOAI_UNINTERLEAVE Contiguous [w1;w3] fused_moe/activation.py:262-268: dispatches to silu_and_mul_with_clamp → activation_kernels.cu:102-108: x_ptr = input + token_idx * 2 * d, y_ptr = x_ptr + d
SILU, GELU, SWIGLUSTEP Contiguous [w1;w3] fused_moe/activation.py:230-236: dispatches to silu_and_mul etc. → act_and_mul_kernel at activation_kernels.cu:104: input // [..., 2, d] midpoint split
gpt_oss_triton_kernels_moe.py:1057-1064: _supports_activation lists SILU, GELU, SWIGLUOAI, SWIGLUSTEP, SWIGLUOAI_UNINTERLEAVE
AITER W4A16 (CK) SILU, GELU, SWIGLUOAI, SWIGLUOAI_UNINTERLEAVE Interleaved [g,u] rocm_aiter_moe.py:384-385: gate_mode = GateMode.INTERLEAVE.value when use_mxfp4_w4a16=True
mxfp4.py:1429-1434: weight shuffle with is_guinterleave=True, gate_up=True
SITU Contiguous [w1;w3] (separated) rocm_aiter_moe.py:374-383: SITU defaults to GateMode.SEPARATED.value (unless env-var VLLM_ROCM_USE_AITER_MOE_SITUV2_A8W4=1 enables interleaved a8w4 path)
Note: weight prep at mxfp4.py:1429-1434 always uses is_guinterleave=True regardless of activation — is_guinterleave controls CK tile layout, not logical gate/up ordering. Runtime gate_mode controls which kernel variant is dispatched.
AITER W4A8 (triton) SWIGLUOAI Interleaved [g,u] mxfp4.py:1689-1691: _interleave_w13(w13_weight, w13_weight_scale, w13_bias) converts contiguous → interleaved before _swizzle_mxfp4
moe_op_gemm_a8w4.py:719-722: a_gelu = a[..., ::2], a_linear = a[..., 1::2] in swiglu_torch
aiter_mxfp4_w4a8_moe.py:305: only supports MoEActivation.SWIGLUOAI
AITER W4A4 (CK) SILU, GELU, SWIGLUOAI, SWIGLUOAI_UNINTERLEAVE Contiguous [w1;w3] rocm_aiter_moe.py:374: default gate_mode = "" (not set for W4A4 path); _aiter_ops.py:228: gate_mode: str = "" means gate_mode not passed to kernel
mxfp4.py:1624-1662: no _interleave_w13 call; only e8m0_shuffle + shuffle_weights
MARLIN/BATCHED_MARLIN SWIGLUOAI Interleaved [g,u] Pure GEMM → fused_moe/activation.py:260-261: torch.ops._C.swigluoai_and_mul(output, input) → activation_kernels.cu:410-466: interleaved gate = in_ptr[2*idx], up = in_ptr[2*idx+1]
SILU, GELU, SITU, SWIGLUSTEP, SWIGLUOAI_UNINTERLEAVE Contiguous [w1;w3] Pure GEMM → fused_moe/activation.py:230-268: dispatches to silu_and_mul etc. → activation_kernels.cu:102-108: x_ptr = input + token_idx * 2 * d, y_ptr = x_ptr + d
marlin_moe.py:170-177: calls apply_moe_activation after GEMM
XPU SILU, GELU, GELU_TANH, SWIGLUOAI, RELU2_NO_MUL Unknown xpu_moe.py:170: activation=activation.value passed to external XpuFusedMoe from vllm_xpu_kernels; cannot trace kernel source
CPU SILU, SWIGLUOAI Contiguous [w1;w3] moe.cpp:454-455: nb_upper = nb, nb_lower = nb + NB — NB is block count in first half, nb + NB indexes second half (midpoint split)
Both silu_and_mul and swiglu activation paths use identical nb_upper/nb_lower addressing. SWIGLUOAI differs only in activation math (clamped sigmoid), not in layout.
EMULATION All (via apply_moe_activation) Same activation dispatch as other external-activation backends ocp_mx_emulation_moe.py:38: inherits TritonExperts; ocp_mx_emulation_moe.py:188: dequantizes then calls super().apply() → apply_moe_activation
HUMMING All (via apply_moe_activation) Same activation dispatch as other external-activation backends fused_humming_moe.py:302-305: _supports_activation delegates to apply_moe_activation_supported(activation); fused_humming_moe.py:553-574: apply_activation() calls self.activation() → apply_moe_activation

The AITER backends are in a better shape after this change. Other backends are updated with best effort and would need careful community review.

Next steps

  • Remove convert_gpt_oss_weight_* and select_gpt_oss_mxfp4_moe_backend. Extend select_mxfp4_moe_backend to differentiate source gate-up layout.
  • Enable SITU aiter unittest.
  • Validate e2e model runs locally.

Local tests

model_name: "deepseek-ai/DeepSeek-V4-Flash-0731"
accuracy_threshold: 0.92
num_questions: 1319
num_fewshot: 8
startup_max_wait_seconds: 1800
server_args: >-
  --tensor-parallel-size 4
  --max-model-len 4096
  --trust-remote-code
  --kv-cache-dtype fp8
  --tokenizer-mode deepseek_v4
env:
  VLLM_ROCM_USE_AITER: "1"
  VLLM_ROCM_USE_AITER_MOE: "1"
GSM8K Results for deepseek-ai/DeepSeek-V4-Flash-0731:
  Measured metric: 0.9454
  Expected metric: 0.9200
  Tolerance: 0.0800
  Questions: 1319
  Invalid rate: 0.000
  Latency: 52.6s
  QPS: 25.1

)

elif mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_FP8:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is moved without changes

w13_bias=w13_bias,
w2_bias=w2_bias,
_cache_permute_indices=_cache_permute_indices,
elif mxfp4_backend == Mxfp4MoeBackend.AITER_MXFP4_MXFP4:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is moved without changes

@mergify

mergify Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @BowenBao.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Aug 14, 2026
@mergify mergify Bot added the nvidia label Aug 17, 2026
@mergify mergify Bot added the cpu Related to CPU backends label Aug 17, 2026
@BowenBao
BowenBao force-pushed the bowenbao/clarify_mxfp4_moe_oracle branch from e28e449 to 2d7822b Compare August 17, 2026 16:55
@mergify mergify Bot removed the needs-rebase label Aug 17, 2026
@BowenBao BowenBao changed the title [Quantization] Clarify MXFP4 oracle for gpt-oss vs others [Quantization] Clarify MXFP4 MoE W13 layout requirement from backends Aug 17, 2026

@fxmarty-amd fxmarty-amd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Not sure about the interleave/contiguous terminology given the extra swizzling done in convert_weight_to_mxfp4_moe_kernel_format ?

Is the semantic meaning of W13Layout => "layout expected by convert_weight_to_mxfp4_moe_kernel_format prior to any extra swizzling/interleaving" ?

Comment on lines +859 to +863
is_gfx1250 = False
if current_platform.is_rocm():
from vllm.platforms.rocm import on_gfx1250

is_gfx1250 = on_gfx1250()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Remove?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

used below

Comment on lines +84 to +90
class W13Layout(Enum):
"""Layout of the fused gate/up (w13) weight tensor."""

CONTIGUOUS_W1W3 = "contiguous_w1w3"
CONTIGUOUS_W3W1 = "contiguous_w3w1"
INTERLEAVED_W1W3 = "interleaved_w1w3"
INTERLEAVED_W3W1 = "interleaved_w3w1"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So eventually e.g. CONTIGUOUS_W1W3 could mean a different thing depending on the backend that is picked?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Some of these are overlooked duplicated pre-processes. The rest (the aiter side links) are swizzling which is another weight pre-process but orthogonal to gate-up interleave.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

On a higher level, a more developer friendly and new kernel integration friendly approach would be to have a canonical format of weights, then let the selected backend to apply process_weight_after_loading-like function. It seems there shouldn't be a standard for this interleaving definition or how the weights are expected. New backend and implementation are practically unknown and can have different layout.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

makes sense. Relaxing the PR to mainly focus on clarifying the incoming weight layout.

@mergify

mergify Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @BowenBao.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Aug 18, 2026
@BowenBao
BowenBao force-pushed the bowenbao/clarify_mxfp4_moe_oracle branch from 2d7822b to 3a24f68 Compare August 19, 2026 00:15
@mergify mergify Bot added rocm Related to AMD ROCm and removed needs-rebase labels Aug 19, 2026
@github-project-automation github-project-automation Bot moved this to Todo in AMD Aug 19, 2026
@mergify

mergify Bot commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @BowenBao.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot removed the needs-rebase label Oct 2, 2026
@mergify

mergify Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @BowenBao.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Oct 2, 2026
@BowenBao
BowenBao force-pushed the bowenbao/clarify_mxfp4_moe_oracle branch from b4267d7 to 979b814 Compare October 9, 2026 23:05
BowenBao and others added 3 commits October 9, 2026 23:15
…ormat

Add an is_w13_interleaved flag so GPT-OSS checkpoints (w13 gate/up rows
interleaved) can share the generic MXFP4 conversion. Each backend branch
converts w13 to the layout its kernel needs:

- TRTLLM, FlashInfer CUTLASS, AITER CK W4A16 and AITER W4A4 de-interleave
  first and reuse the contiguous path.
- TRITON, AITER Triton W4A16 and AITER W4A8 skip their interleave step
  when w13 is already interleaved.
- Other backends consume either layout as-is.

The AITER W4A4/W4A8 branches move here from the GPT-OSS converter (W4A8
now interleaves contiguous w13 itself), and OCP MX emulation reduces
per-expert static input scales like the GPT-OSS converter did. Nothing
calls the new flag yet.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Bowen Bao <bowenbao@amd.com>
Remove convert_gpt_oss_weight_to_mxfp4_moe_kernel_format. GptOssMxfp4MoEMethod
and Quark OCP MX (for gpt_oss models) call
convert_weight_to_mxfp4_moe_kernel_format(..., is_w13_interleaved=True);
other Quark models now go through the contiguous path instead of the
GPT-OSS one.

The ROCm oracle test feeds interleaved w13 for SWIGLUOAI and checks it
against a contiguous reference, and gains GELU, SWIGLUOAI_UNINTERLEAVE and
emulation cases.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Bowen Bao <bowenbao@amd.com>
Remove select_deepseek_v4_mxfp4_moe_backend. Mxfp4MoEMethod now calls
select_mxfp4_moe_backend(..., use_deepseek_v4_priority=True), which keeps
the DeepSeek-V4 behavior: its priority list, explicit aliases trying every
variant, and no user activation override. Other callers are unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Bowen Bao <bowenbao@amd.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The changes are easier to review by going over the 3 commits separately. Most changes are only moving code around, which is not as significant as the default github diff.

@mergify mergify Bot removed the needs-rebase label Oct 9, 2026

@fxmarty-amd fxmarty-amd left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Overall LGTM with the comments below, and assuming that:

  • Only gpt-oss uses GptOssMxfp4MoEMethod
  • All convert_gpt_oss_weight_to_mxfp4_moe_kernel_format processing code was duplicate and/or update to use convert_weight_to_mxfp4_moe_kernel_format + is_w13_interleaved

(did not use agent review)

if not use_deepseek_v4_priority:
# Select kernels in order of backend.
AVAILABLE_BACKENDS = _filter_by_activation(
_get_priority_backends_for_gpt_oss(), requested_activation_key

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This name is misleading now?

gpt-oss / other models should have the same default priority?

def select_mxfp4_moe_backend(
config: FusedMoEConfig,
activation_key: QuantKey | None = None,
*,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

remove

config: FusedMoEConfig,
activation_key: QuantKey | None = None,
*,
use_deepseek_v4_priority: bool = False,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Removing select_deepseek_v4_mxfp4_moe_backend seems unrelated to PR title (?), but having a single select_mxfp4_moe_backend that does not duplicate code seems like a good change to me, sounds good to have it here.

As per 11b62e2, this is only a refactor.

Comment on lines +1551 to +1560
"rtol": 0.3,
"percent": 0.95,
"requires_aiter": False,
"requires_gfx950": False,
"backend_override": "TRITON_UNFUSED",
},
"TRITON_UNFUSED__SWIGLUOAI_UNINTERLEAVE": {
"activation": "SWIGLUOAI_UNINTERLEAVE",
"rtol": 0.5,
"percent": 0.8,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@github-project-automation github-project-automation Bot moved this to Ready in NVIDIA Oct 12, 2026
@github-project-automation github-project-automation Bot moved this from To Triage to Ready in gpt-oss Issues & Enhancements Oct 12, 2026
@mergify

mergify Bot commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @BowenBao.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

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

cpu Related to CPU backends gpt-oss Related to GPT-OSS models mrv2 Model Runner V2 specific needs-rebase nvidia quantization rocm Related to AMD ROCm

Projects

Status: Todo
Status: Ready
Status: Ready

Development

Successfully merging this pull request may close these issues.

3 participants