You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
At num_groups == 1, the fused grouped MLP produces NaN expert weight gradients whenever the token buffer is padded past sum(split_sizes) and the cuDNN single-group kernel specialization is unavailable. num_groups == 1 selects a family of dense shortcuts that quantize and scale-swizzle the buffer as one tensor over tensor.shape[0] rows and read it back the same way, which is only coherent if the cuDNN kernels also write densely over tensor.shape[0]. That request is already guarded because it can be refused, but its siblings are gated on num_groups == 1 alone, so when the specialization is unavailable the kernels pack their output to sum(split_sizes) rows while TE still reads everything densely. Since the columnwise swizzled MXFP8 scale layout is [k/128][m/128][32][4][4] with the m-tile as an inner stride, that extent mismatch misindexes every k-tile past the first: the head rows pick up the wrong scale factors and high k-tiles read scale memory the producer never wrote. The data is read correctly and only the scales move, so the error is order 1 and usually NaN.
Steps/Code to reproduce bug
Single GPU, no Megatron, no distributed. NVTE_CUTEDSL_FUSED_GROUPED_MLP=1 must be set before the TE import.
num_groups >= 2 already behaves this way at the same geometry, and so does num_groups == 1 with a GLU activation on cuDNN frontend >= 1.27.0 when the padded rows are zero.
Environment overview (please complete the following information)
Which configurations are affected. The trigger is _cudnn_frontend_supports_single_group_runtime_offsets, which returns not issubclass(activation_type, ScaledSReLU) and cudnn-frontend >= 1.27.0. So:
activation
cudnn-frontend
affected
ScaledSReLU
any version
yes
any activation
< 1.27.0
yes
ScaledSwiGLU, ScaledClampedQGeGLU, ScaledSiTUGLU
>= 1.27.0
see below
A second, related exposure on the GLU path. Where the specialization is available, producer and consumer agree and the scale misindex does not occur — but the plain wgrad GEMM in _single_group_wgrad_gemm still contracts over logical_shape[0] rows, so padding past sum(split_sizes) is summed into the weight gradient. That part is value-dependent: a zeroed tail contributes an all-zero outer product and is bit-exact (verified over 10 launches), while a garbage tail gives rel_err around 0.67 with realistic values. Since test_grouped_linear_cuda_graph_safe documents an uninitialized tail as permitted, this is arguably the same bug wearing a different mask, and a complete fix should address both.
Scope. Measured on MXFP8 only. Both gated code paths also accept NVFP4Quantizer, and the mechanism by which only wgrad breaks — wgrad contracts over the token axis, whereas forward and dgrad have M as a free dimension and merely produce extra output rows the caller discards — is quantizer-independent, so NVFP4 is plausibly affected as well. Not measured.
Why existing tests miss it.test_grouped_mlp builds in_shape = (split_sizes.sum(), hidden), so no test pads the input at all; the two tests that do pad (test_grouped_linear_cuda_graph_safe, test_grouped_mlp_cuda_graph_safe_mxfp8) both hard-code group_size = 4 and do not parametrize it. Padding and num_groups == 1 never co-occur.
When it was introduced.3e7ae6ce ("Single group mxfp8 grouped mlp", #3267) widened both the dense-quantize guard and the single-group wgrad gate from NVFP4Quantizer to (MXFP8Quantizer, NVFP4Quantizer). It is the only commit between the 2.19 and 2.20 version bumps that touches those guards. 91bb9cfe (#3117) is not the cause: it moved the code but left the guard NVFP4-only. TE 2.18.0 measures 0.0000e+00 in every arm of the reproducer above.
Symptoms in a real run. Forward output and dX are bitwise clean, so the loss curve shows nothing while the expert weight gradients are wrong. In an MoE setting this is reachable whenever a rank has one local expert — N experts at EP=N.
Reproducibility. Deterministic. The reproducer gives identical non-finite counts on repeated fresh processes, and reproduces unchanged on stock nvidia-cudnn-frontend 1.27.0 as published.
I have a fix and can open a PR: gate the whole shortcut family on the same predicate the kernels use, so producer and consumer always agree on the row extent. It is host-side (an activation type and a package version), so it costs nothing and stays CUDA-graph capturable, and it leaves the GLU activations byte-for-byte on their existing fast path — which matters, because removing the shortcuts outright measurably slows a dense shared expert (GroupedLinear(num_groups=1), as Megatron's FusedSharedExpertMLP builds).
Describe the bug
At
num_groups == 1, the fused grouped MLP produces NaN expert weight gradients whenever the token buffer is padded pastsum(split_sizes)and the cuDNN single-group kernel specialization is unavailable.num_groups == 1selects a family of dense shortcuts that quantize and scale-swizzle the buffer as one tensor overtensor.shape[0]rows and read it back the same way, which is only coherent if the cuDNN kernels also write densely overtensor.shape[0]. That request is already guarded because it can be refused, but its siblings are gated onnum_groups == 1alone, so when the specialization is unavailable the kernels pack their output tosum(split_sizes)rows while TE still reads everything densely. Since the columnwise swizzled MXFP8 scale layout is[k/128][m/128][32][4][4]with the m-tile as an inner stride, that extent mismatch misindexes every k-tile past the first: the head rows pick up the wrong scale factors and high k-tiles read scale memory the producer never wrote. The data is read correctly and only the scales move, so the error is order 1 and usually NaN.Steps/Code to reproduce bug
Single GPU, no Megatron, no distributed.
NVTE_CUTEDSL_FUSED_GROUPED_MLP=1must be set before the TE import.Output on
main(ace1873f):The two rows are identical — the corruption does not depend on what the padding holds.
Expected behavior
Rows past
sum(split_sizes)are outside every group and must not affect any weight gradient, so both arms should match the unpadded reference:num_groups >= 2already behaves this way at the same geometry, and so doesnum_groups == 1with a GLU activation on cuDNN frontend >= 1.27.0 when the padded rows are zero.Environment overview (please complete the following information)
Environment location: Bare-metal (Slurm cluster node, RHEL 9.8)
Method of Transformer Engine install: from source, into a venv, at commit
ace1873f("Fix linter error (Fix linter error #3435)"):Docker: not used.
Environment details
ace1873f)nvidia-cudnn-cu13), frontendnvidia-cudnn-frontend1.27.0;nvidia-cutlass-dsl4.7.1Device details
Additional context
Which configurations are affected. The trigger is
_cudnn_frontend_supports_single_group_runtime_offsets, which returnsnot issubclass(activation_type, ScaledSReLU) and cudnn-frontend >= 1.27.0. So:ScaledSReLUScaledSwiGLU,ScaledClampedQGeGLU,ScaledSiTUGLUA second, related exposure on the GLU path. Where the specialization is available, producer and consumer agree and the scale misindex does not occur — but the plain wgrad GEMM in
_single_group_wgrad_gemmstill contracts overlogical_shape[0]rows, so padding pastsum(split_sizes)is summed into the weight gradient. That part is value-dependent: a zeroed tail contributes an all-zero outer product and is bit-exact (verified over 10 launches), while a garbage tail givesrel_erraround 0.67 with realistic values. Sincetest_grouped_linear_cuda_graph_safedocuments an uninitialized tail as permitted, this is arguably the same bug wearing a different mask, and a complete fix should address both.Scope. Measured on MXFP8 only. Both gated code paths also accept
NVFP4Quantizer, and the mechanism by which only wgrad breaks — wgrad contracts over the token axis, whereas forward and dgrad have M as a free dimension and merely produce extra output rows the caller discards — is quantizer-independent, so NVFP4 is plausibly affected as well. Not measured.Why existing tests miss it.
test_grouped_mlpbuildsin_shape = (split_sizes.sum(), hidden), so no test pads the input at all; the two tests that do pad (test_grouped_linear_cuda_graph_safe,test_grouped_mlp_cuda_graph_safe_mxfp8) both hard-codegroup_size = 4and do not parametrize it. Padding andnum_groups == 1never co-occur.When it was introduced.
3e7ae6ce("Single group mxfp8 grouped mlp", #3267) widened both the dense-quantize guard and the single-group wgrad gate fromNVFP4Quantizerto(MXFP8Quantizer, NVFP4Quantizer). It is the only commit between the 2.19 and 2.20 version bumps that touches those guards.91bb9cfe(#3117) is not the cause: it moved the code but left the guard NVFP4-only. TE 2.18.0 measures0.0000e+00in every arm of the reproducer above.Symptoms in a real run. Forward output and dX are bitwise clean, so the loss curve shows nothing while the expert weight gradients are wrong. In an MoE setting this is reachable whenever a rank has one local expert —
Nexperts atEP=N.Reproducibility. Deterministic. The reproducer gives identical non-finite counts on repeated fresh processes, and reproduces unchanged on stock
nvidia-cudnn-frontend1.27.0 as published.I have a fix and can open a PR: gate the whole shortcut family on the same predicate the kernels use, so producer and consumer always agree on the row extent. It is host-side (an activation type and a package version), so it costs nothing and stays CUDA-graph capturable, and it leaves the GLU activations byte-for-byte on their existing fast path — which matters, because removing the shortcuts outright measurably slows a dense shared expert (
GroupedLinear(num_groups=1), as Megatron'sFusedSharedExpertMLPbuilds).