Repository navigation
[Inference][MoE] Apply the GLU and probs-on-input options in inference-optimized MoE experts - #8566
Draft
lmcafee-nvidia wants to merge 3 commits into
Draft
lmcafee-nvidia wants to merge 3 commits into
lmcafee-nvidia wants to merge 3 commits into
Conversation
…xperts InferenceGroupedMLP mapped every gated-SiLU configuration to plain SwiGLU and passed only the activation type and the tanh clamp scale to the torch and vLLM grouped-GEMM backends. activation_func_clamp_value, glu_linear_offset, moe_mlp_glu_interleave_size and moe_apply_probs_on_input, which TEGroupedMLP applies in training, were dropped without an error, so a model trained with any of them produced wrong outputs under transformer_impl='inference_optimized' (normwise error 0.68 to 6.9 on H100). Carry the three GLU options in a GLUOptions value that InferenceGroupedMLP builds from the config and passes through mcore_fused_moe and vllm_fused_moe to every SwiGLU kernel (padded_swiglu, bounded_silu_mul and the batch-invariant swiglu_with_probs / weighted_silu_mul_bounded). Shared Triton helpers read the gate and linear columns of the interleaved FC1 layout and apply the clamp and the linear offset to the FP32 inputs. The options are constexpr-gated, so the default configuration computes exactly what it did before. For moe_apply_probs_on_input, scale each routed input row by its probability and round it to BF16, as training does, then leave the expert output unweighted. The torch backend does this in the permute kernels (including the fused MXFP8 permute + quantize), which then emit unit probabilities; the vLLM backend scales the A rows inside its FC1 GEMM and skips the probability downstream. The FlashInfer kernels apply routing probabilities only to the expert output, so config validation now rejects moe_apply_probs_on_input with the flashinfer backend. Test: TestInferenceGroupedMLPMatchesTraining runs one expert module through its inference and training paths on the same weights and hand-built routing (no router, so top-1 routing needs no router support) for each option on both backends; the option cases fail on main. Replay tests cover the new kernel variants, and the batch-invariant clamped SwiGLU is checked bit for bit against training's fused clamped weighted-swiglu. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lawrence McAfee <lmcafee@nvidia.com>
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
…BI tests - Test permute_and_quantize_mxfp8 and permute_tokens with scale_by_probs against a PyTorch reference: each row is BF16(prob * token) and the permuted probability becomes 1 (both run on H100). - Test the batch-invariant SwiGLU kernels' clamp, offset, interleave and unweighted variants against a reference of training's GLU, and run the InferenceGroupedMLP training-parity test with batch-invariant mode on too. - Keep NaN FC1 outputs NaN in the GLU clamp (propagate_nan=ALL), as training's torch.clamp does, instead of clamping them to a finite value. - Reject glu_linear_offset with use_te_activation_func, and for SwiGLU experts with use_transformer_engine_op_fuser: the Transformer Engine GLU activations have no linear offset, so those training paths silently dropped it while inference applies it. - Describe the interleaved FC1 layout in the SwiGLU kernel docstrings. - Make the shared Triton helpers public (glu_columns, glu_inputs) instead of importing private names across modules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lawrence McAfee <lmcafee@nvidia.com>
3 of 7 tasks
Resolve conflicts with main's vLLM fused-MoE and InferenceGroupedMLP changes: - vllm_fused_moe.py: main applies routing probabilities in FC2's GEMM epilogue and makes the topk reduction unweighted. With moe_apply_probs_on_input FC1 already scaled the input rows, so FC2's epilogue now skips the probabilities in that case as it does in batch-invariant mode. - experts.py: keep this branch's GLU options next to main's FlashInfer clamp limit; main now sets inference_grouped_gemm_backend earlier. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Lawrence McAfee <lmcafee@nvidia.com>
This was referenced Nov 1, 2026
This branch has not been deployed
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.
What does this PR do?
Makes the inference-optimized MoE experts (
InferenceGroupedMLP, torch and vLLM grouped-GEMM backends) applyactivation_func_clamp_value,glu_linear_offset,moe_mlp_glu_interleave_sizeandmoe_apply_probs_on_inputthe way training does. Before this change, the experts dropped these options without an error.The bug
InferenceGroupedMLP._resolve_mcore_activation_typemaps every gated-SiLU configuration to plain SwiGLU. Thetorchandvllm(default) backends then receive only the activation type and the tanh clamp scale.TEGroupedMLP, the training path of the same module, applies four options that inference never read:activation_func_clamp_value: caps the gate atvand clamps the linear half to[-v, v].glu_linear_offset:silu(gate) * (linear + offset).moe_mlp_glu_interleave_size: FC1 rows alternate gate and linear blocks.moe_apply_probs_on_input: the expert input is scaled by the routing probability, and the output is left unweighted.Config validation rejected these options only under
inference_moe_nvfp4_recipe. A model trained with any of them and served withtransformer_impl='inference_optimized'therefore produced wrong outputs. This affected BF16 and MXFP8 weights, and batch-invariant mode too. On H100 in the CI image, the expert outputs ofmaindiffer from training by a normwise relative error of 7.1 (clamp 0.5), 0.64 (offset 1.0) and 1.4 (interleave 32). A model-level matrix showed 0.8–1.7 nats of mean log-prob error.Root cause and fix
The inference kernels implemented only plain SwiGLU, and nothing passed the options to them.
activations.py,batch_invariant.py): a frozenGLUOptionsvalue holds the clamp, the linear offset and the interleave size. Two shared Triton helpers do the work.glu_columnsfinds the gate and linear columns of an output column in either FC1 layout.glu_inputsapplies the clamp and the offset to the FP32 inputs. All four SwiGLU kernels use them:padded_swiglu(torch backend and MXFP8),bounded_silu_mul(vLLM), and the batch-invariantswiglu_with_probsandweighted_silu_mul_bounded.torch.clamp, the clamp keeps NaN inputs NaN.moe_apply_probs_on_input: as in training, each routed input row is multiplied by its probability and rounded to BF16, and the expert output is not weighted again. The torch backend does this in the permute kernels (permute_tokensand the fused MXFP8permute_and_quantize_mxfp8), which then write unit probabilities, so the activation and unpermute code is unchanged. The vLLM backend scales the A rows in its FC1 GEMM (MUL_ROUTED_WEIGHT_ON_INPUT). It then skips the probability in_moe_sumand, in batch-invariant mode, in the activation (weighted_silu_mul_bounded/squared_relu_with_probsacceptNone).fused_moe.py,vllm_fused_moe.py,experts.py):InferenceGroupedMLPbuildsGLUOptionsfrom the config for gated units, which is the only case where training applies them. It passes the options andmoe_apply_probs_on_inputtomcore_fused_moeandvllm_fused_moe.transformer_config.py):moe_apply_probs_on_inputwithinference_grouped_gemm_backend='flashinfer'is now rejected with a message that points totorch/vllm. The GLU options were already excluded on FlashInfer: gated units reach it only under the NVFP4 recipe, which rejects them.glu_linear_offsetwhile inference now applies it. Those paths areuse_te_activation_funcand the op-fuserScaledSwiGLU(use_transformer_engine_op_fuserwith SwiGLU experts). Both combinations are now rejected, so training and inference cannot disagree on the offset.No new allocation, host sync or per-step setup, so the change is CUDA-graph safe.
In batch-invariant mode, the clamp keeps the inference kernels bit-identical to training's fused clamped weighted-SwiGLU (tested). For
glu_linear_offsetand the interleave, training always uses the unfused eager GLU. Its BF16 roundings are not reproduced; the same is true today for plain SwiGLU withbias_activation_fusion=False. Those cases now match training to BF16 tolerance but not bit for bit.Tests
tests/unit_tests/inference/test_moe_dispatching_and_routing.py::TestInferenceGroupedMLPMatchesTraining(1 GPU, TE): one expert module runs its inference path and its training path (TEGroupedMLP) on the same weights and hand-built routing.test_config_rejects_flashinfer_probs_on_input(CPU), andtests/unit_tests/fusions/test_swiglu_fusion.py::test_swiglu_config_rejects_linear_offset_on_te_glu(CPU; fails without the new validation).Kernel reference tests:
permute_tokensandpermute_and_quantize_mxfp8withscale_by_probsagainst PyTorch, bit-exact; the MXFP8 one runs on H100.test_vllm_fused_moe_batch_invariant.py).test_moe_permute.py).Determinism (
test_inference_kernels.py,test_moe_kernels.py,manifest.pynotes) adds:InferenceGroupedMLPreplay with the options on both backends;test_batch_invariant_clamped_swiglu_matches_training, a bit-exact check of both batch-invariant SwiGLU kernels againstclamped_weighted_swiglu.tools/check_kernel_determinism_coverage.py --base-ref origin/mainpasses.On H100 in the CI image, at the first commit of this PR:
mainin 13 cases: the 8 option cases of the parity test, the FlashInfer config test, and the 4 determinism tests that use the new kernel arguments. They pass with this PR: 39 passed, and 8 skipped. The skips are pre-existing Blackwell-only MXFP8/NVFP4 cases intest_inference_kernels.py, which also skip in the H100 CI bucket.test_vllm_fused_moe.py(117),test_vllm_fused_moe_batch_invariant.py(16) andtest_moe_permute.py(871).On H100 in the CI image (rerun at this PR's head), the regression tests pass (126 passed; 11 Blackwell-only MXFP8/NVFP4 cases skipped, as in the H100 CI bucket), and 40 of them fail on
main. The neighbor tests pass. The offset-validation tests pass on CPU and fail without the new check.Issue tracking
Linked issue: none; this PR's regression tests reproduce the bug.
Code provenance
No code was copied from other repositories.
Contribution process
Pre-checks
jit_fuser/torch.compile, CUDA extension, TE or external-library dispatch, or a scatter/index accumulation), I have added or updated its bit-exact determinism test and registered it intests/unit_tests/determinism/kernels/manifest.py(guide)🤖 Generated with Claude Code