Repository navigation
[Fix] compressed-tensors: Serve split-expert MXFP4 MoE via FP4 Marlin - #41289
Merged
Merged
Conversation
jlqibm
requested review from
Alisehen,
AniZpZ,
BBuf,
DarkSharpness,
Edwardf0t1,
FlamingoPg,
Fridge003,
HaiShaw,
HydraQYH,
OrangeRedeng,
Ying1123,
b8zhong,
celve,
ch-wan,
ispobock,
merrymercy,
mmangkad and
yuan-luo
as code owners
September 25, 2026 23:04
jlqibm
force-pushed
the
fix/compressed-tensors-mxfp4-moe
branch
2 times, most recently
from
October 8, 2026 20:34
103d8ff to
c354cc0
Compare
jlqibm
force-pushed
the
fix/compressed-tensors-mxfp4-moe
branch
from
October 10, 2026 04:34
c354cc0 to
e5705c4
Compare
5 tasks done
The dense FP4 Marlin kernel only emitted group_blocks == 1, so a group_size 32 call computed group_blocks == 2, matched no branch, and fell through to the no-op MarlinDefault. It also could not decode E8M0 scales: it called the unparameterized dequant_fp8_scales overload, which is the E4M3 decoder, and it dereferenced the global scale unconditionally although MXFP4 carries none. Thread s_type_id through the dense template, deriving S_TYPE inside _GET_IF from W_TYPE and GROUP_BLOCKS so no call site changes, and add an MXFP4 kernel family guarded on bfloat16. That guard is load-bearing rather than stylistic: dequant_fp8_scales<half2, kFE8M0fnu> is declared but never defined, so an fp16 instantiation fails to compile. Gate the global-scale requirement on group_size == 16 and reject fp16 MXFP4 on the host, which would otherwise reach MarlinDefault and return silent garbage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MXFP4 dense linears had no path at all: they fell through to the NVFP4 branches and were rejected for an unsupported group_size. Add a weight-only scheme over the FP4 Marlin kernel, which needs no FP4 tensor cores and so runs from SM80. Unlike NVFP4 this is not a pre-Blackwell fallback. No native MXFP4 dense-linear kernel exists on any architecture, so both mxfp4a16 and the w4a4 variant are served weight-only everywhere, and w4a4 warns that its activation quantization is ignored. The two variants' weight blocks are byte-identical, so one scheme covers both and the predicate keys on the weights alone. Scales are raw E8M0 exponent bytes, so they are reinterpreted rather than converted -- a conversion would read exponents as small integers -- and BF16 is the only supported activation dtype, matching the kernel's instantiation. Also add mxfp4_pack_quantized to the activation-quantization formats. The input_activations parse is gated on that list, so a w4a4 MXFP4 checkpoint had its input_activations silently discarded at parse time. Verified on H100 (SM90) against granite-4.2-30b mxfp4a16 and mxfp4, both generating coherent text; nvfp4, nvfp4a16, int4 pack-quantized and fp8 all unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Follow the base PR's consolidation: MXFP4 linear selection becomes rows in the shared selection table, its weight-creation checks join the per-format subTests, and its numeric cases join the shared linear dequant-reference test, replacing test_compressed_tensors_mxfp4_linear_numeric.py. The shared file already runs on SM100 and is registered on 4-gpu-b200, which is what the dropped standalone SM100 commit added for the MXFP4 file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A compressed-tensors mxfp4 MoE checkpoint whose experts are stored one tensor per expert per projection could not load. `_is_mxfp4_moe` diverted every MXFP4 MoE to `Mxfp4MoEMethod`, which registers the fused `w13_weight`/`w2_weight` that the GPT-OSS layout uses, so a split-expert checkpoint died with `KeyError: model.layers.0.block_sparse_moe.experts.w2_weight_packed`. Add `CompressedTensorsW4A16Mxfp4MoE`, mirroring the NVFP4 weight-only MoE scheme with group_size 32, uint8 E8M0 scales, and no weight global scale. The Marlin prep math already exists, so the scheme only registers the parameters and bridges the `_packed` names. Gate the `Mxfp4MoEMethod` bypass on the new `FusedMoE.serves_fused_mxfp4` flag rather than on the quant config: Kimi-K3 and GraniteMoe ship indistinguishable `mxfp4-pack-quantized` configs and differ only in whether the model's own loader renames `weight_packed` to `weight`. GraniteMoe, which does not, opts out. MXFP4 w4a4 is served weight-only on every capability since there is no FP4-activation MoE kernel in tree; the input global scales are registered so the split-expert loader finds a destination, then dropped. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Carries the sgl-project#40022 review fix to the MXFP4 MoE path. w13 was always allocated with two shards, so a non-gated checkpoint (up projection only) failed to load on a shape mismatch. Size w13 and its input scales from moe_runner_config.is_gated, and teach prepare_moe_mxfp4_layer_for_marlin the shard count. Layers without a runner config -- the fused GPT-OSS callers -- keep the gated default. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Follow the base PR's consolidation: MXFP4 MoE selection becomes rows in the shared selection table (which also covers the MXFP4/NVFP4 predicates not overlapping), its weight-creation checks -- uint8 E8M0 scales, no weight global scale, input-scale destination, non-gated shapes -- join the per-format subTests, and the fused-layout bypass test moves over unchanged. The MoE numeric test runs MXFP4 alongside NVFP4: checkpoint-format experts with raw E8M0 scale bytes, process_weights_after_loading, then the Marlin MoE forward against experts dequantized in PyTorch, for gated silu and non-gated relu2 and silu. Without the is_gated fix the two non-gated MXFP4 cases fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5 tasks done
jlqibm
force-pushed
the
fix/compressed-tensors-mxfp4-moe
branch
from
October 13, 2026 23:42
e5705c4 to
8ba1730
Compare
Collaborator
|
This branch includes the dense MXFP4 bias issue described in #41288 (comment). I reproduced the same Python-side mismatch at this PR's head, Please carry the fix and bias regression coverage from #41288 into this PR as well. The detailed reproduction and its CPU-only validation scope are in the linked comment. |
BBuf
approved these changes
Oct 17, 2026
BBuf
left a comment
Collaborator
There was a problem hiding this comment.
Approving the MoE changes. Please also bring in the dense MXFP4 bias fix and regression coverage from #41288, as noted in my earlier comment.
I'll trigger run-ci after the fix is pushed. Holding off on CI for now.
apply_fp4_marlin_linear adds the bias after the GEMM, since sglang's dense
gptq_marlin_gemm takes no bias, but prepare_{nv,mx}fp4_layer_for_marlin
padded the bias to the Marlin N and permuted it into Marlin's scale order
(a pattern from vLLM, whose kernel applies bias in-kernel). Each output
channel got another channel's bias, and with skip_bias_add the linear
returned the padded, permuted bias to its caller.
Leave layer.bias untouched at load and add it after cropping the output to
the logical N. The MoE Marlin path is unchanged: its kernel takes the bias
in-kernel, so permuting it there is correct.
test_marlin_linear_bias covers NVFP4 and MXFP4 with aligned (192) and padded
(160 -> 192) N, both bias added by the scheme and skip_bias_add, plus the
stored bias. Without the fix all four cases fail.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d-tensors-mxfp4-moe
Contributor
Author
Contributor
Author
|
@BBuf can you please trigger |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Stacks on #41288 (
fix/compressed-tensors-mxfp4-marlin), which fixed MXFP4 denselinear. This fixes MXFP4 MoE.
A compressed-tensors
mxfp4-pack-quantizedMoE checkpoint whose experts arestored split per expert (
experts.{e}.{gate,up,down}_proj.*, as emitted byllmcompressor) cannot load today. The A/B control, same node and same venv:
KeyError: 'model.layers.0.block_sparse_moe.experts.w2_weight_packed'at
granitemoe.py:93.The cause is a layout mismatch, not a missing kernel.
Mxfp4MoEMethodandMxfp4MarlinMoEMethodhandle the fused GPT-OSS layout and registerw13/w2_weight; a split-expert checkpoint's per-expert tensors have nodestination there.
Modifications
Add
CompressedTensorsW4A16Mxfp4MoE, a weight-only MXFP4 MoE scheme registeringthe
_packedparameter names and routing to the FP4 Marlin MoE runner. It servesboth
mxfp4a16andmxfp4w4a4; the latter's activation quantization isdropped, since the Marlin MoE kernel is weight-only. A w4a4 checkpoint still
ships input global scales and the split-expert loader raises
KeyErroron anytensor with no registered destination, so they are registered and only then
dropped in
process_weights_after_loading.Unlike NVFP4 there is no
weight_global_scale, so the compressed-tensorsreciprocal convention (CT stores
1/scalewhere Marlin wantsscale) does notarise here.
check_moe_marlin_supports_layeris deliberately not called: it testsgroup_sizeagainst the GPTQ/AWQ set and reports on int4 zero-point parametersthis layout does not have. The shape constraints it would have covered
(
hidden_size % 128 == 0,intermediate_size_per_partition % 64 == 0) arevalidated explicitly instead.
How the two layouts are told apart
They cannot be told apart from the config. GraniteMoe and Kimi-K3 ship
indistinguishable
mxfp4-pack-quantizedconfigs — samequant_method, sameformat,config_groupstargeting["Linear"]in both, both usingmake_expert_params_mapping. The real difference lives in the model loader:Kimi-K3 renames
weight_packed→weightand substitutesMxfp4Configbeforebuilding its experts.
So the selection is an explicit opt-out rather than config sniffing:
FusedMoEgains a
serves_fused_mxfp4: bool = Trueconstructor parameter, andgranitemoe.pypassesFalse. It must be a constructor parameter, not apost-construction attribute, because
get_quant_methodruns insideFusedMoE.__init__.Every existing fused MXFP4 MoE keeps the default and is unaffected. That is
asserted in both directions by a new test, since a silent kernel change on the
GPT-OSS path would be the worst failure mode here.
Accuracy Tests
Granite MoE (
GraniteMoeForCausalLM), H100 80GB, all coherent text:13
[OK], rc=0, 0 tensors dropped. 120b is independently valuable: 128 expertsvs 56, hidden 3072 vs 2048.
18 CPU scheme-selection tests + 8 subtests, covering the MXFP4/NVFP4 predicate
boundary (both are FP4 group quant; only
group_sizeandstrategyseparatethem, so a widened predicate would silently steal the other's checkpoints), the
registered-parameter contract against the checkpoint's names/shapes/dtypes, and
the fused-bypass gate in both directions.
Dense MXFP4 output is byte-identical to PR 2's, confirming the MoE scheme does
not perturb the dense path.
The fused GPT-OSS MXFP4 MoE path is covered by the CPU gate test rather than
end-to-end: GPT-OSS uses native
Mxfp4Config, so it never entersCompressedTensorsConfig.get_quant_method, and no compressed-tensors fused MXFP4MoE checkpoint was available to test against. The gate test asserts the dispatch
branch directly, which is a stronger check than inferring it from generated text.
Speed Tests and Profiling
No benchmark run. Weight-only via the existing FP4 Marlin MoE runner — no new
kernel, and activations stay bf16.
get_min_capability()is 80 and the routingis capability-independent, so Blackwell takes the same MoE path as Hopper. The one
asymmetry: an NVFP4 w4a4 MoE checkpoint on SM100 still keeps the native
CompressedTensorsW4A4Nvfp4MoEscheme (capability 100); the weight-only fallbackmust not lower that gate, which is why they are separate classes and is asserted
by
test_nvfp4_w4a4_keeps_native_scheme_on_blackwell.Checklist
🤖 Generated with Claude Code
Review and Merge Process
/tag-and-rerun-ci,/tag-run-ci-label,/rerun-failed-ciCI States
Latest PR Test (Base): ✅ Run #40493897581
Latest PR Test (Extra): ❌ Run #40493897438
Latest PR Test (AMD ROCm 10): ⏳ Run #40493897389