Skip to content

[PyTorch] Reduce NVFP4 quantization work for the fused GroupedMLP fc2 input with activation recompute - #3837

Open
xrennvidia wants to merge 5 commits into
NVIDIA:mainfrom
xrennvidia:xren/fused_grouped_mlp_rowwise_fc2x_recompute
Open

xrennvidia wants to merge 5 commits into
NVIDIA:mainfrom
xrennvidia:xren/fused_grouped_mlp_rowwise_fc2x_recompute

Conversation

@xrennvidia

Copy link
Copy Markdown
Collaborator

Description

With activation recompute in the fused NVFP4 GroupedMLP, the backward rebuilds the fc2 input (fc2_x) with the dSReLU kernel and re-quantizes it column-wise for wgrad. The forward nevertheless quantized fc2_x both row- and column-wise and discarded the column-wise copy, and the backward re-quantization ran a full group Hadamard-amax pass even though the regenerated tensor is bit-identical to the forward one. This PR removes that redundant work and fixes an amax mismatch in the Hadamard-fused fc1 kernel path with randomized Hadamard transform (RHT).

Measured on GB300 with a fused grouped MLP (8 experts, NVFP4 + RHT): the group quantize kernel for the fc2 input drops from ~570us to ~330us per fused call on a 130B hybrid MoE proxy, and the forward and backward each launch one fewer Hadamard-amax kernel per fused call where the Hadamard-fused fc1 kernel is used. Recompute on and off give bitwise-equal output and gradients for SReLU, tanh-SReLU and SwiGLU.

Type of change

  • Documentation change (change only to the documentation, either a fix or a new content)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Infra/Build change
  • Code refactoring

Changes

  • Decide recompute_srelu_fc2_x before quantizing the fc2 input and request only row-wise NVFP4 data when the backward will rebuild the tensor; mark only the tensor actually saved for backward for paged stashing.
  • Keep the forward (pre-RHT, post-RHT) amax of fc2_x on the fc2 context and quantize the recomputed tensor with nvfp4_group_quantize_with_amax, dropping the backward amax pass. Add a nvfp4_group_hadamard_amax binding so the plain SReLU kernel path computes both amax values in the single forward pass. NVTE_FUSED_GROUPED_MLP_REUSE_FC2_X_AMAX=0 disables the reuse.
  • Route ScaledTanhSReLU through the Hadamard-fused fc1 kernel when the cuDNN frontend grouped_gemm_glu_hadamard_wrapper_sm100 accepts tanh_clamp_scale (detected by signature), so the forward amax pass disappears for tanh-SReLU as well.
  • Pass the quantizer's RHT sign mask (rht_sign_mask) to the Hadamard-fused kernel when the cuDNN frontend accepts it. Previously the kernel computed the post-RHT amax for the plain transform, so the NVFP4 global scale was off by the sign pattern (up to ~17% too large for SReLU-shaped activations). On frontends without the argument, the fused kernel is used only when the mask is zero.
  • tests/pytorch/test_grouped_mlp.py: the recompute test asserts a bitwise-equal fc2 weight gradient for tanh-SReLU with NVFP4; plain SReLU keeps its tolerance until the cuDNN frontend fix for the Hadamard-fused kernel is required.

Checklist:

  • I have read and followed the contributing guidelines
  • The functionality is complete
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

xrennvidia and others added 4 commits November 9, 2026 01:56
…s recomputed in backward

With activation recompute in the fused NVFP4 GroupedMLP, the backward
rebuilds fc2_x from the dSReLU kernel and re-quantizes it column-wise for
wgrad. The forward still quantized fc2_x row- and column-wise and then
dropped the column-wise copy at save time.

Decide recompute_srelu_fc2_x before the fc2 input quantization, request
only row-wise data when the backward will rebuild the tensor, and mark
for paged stashing only the tensor that is actually saved for backward.
Rowwise FP4 data and the fc2 GEMM result are unchanged; the group quantize
kernel for the fc2 input drops from ~570us to ~330us per fused call on
GB300 for a 130B hybrid MoE proxy.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Xiaowei Ren <xren@nvidia.com>
…uted in backward

With activation recompute in the fused NVFP4 GroupedMLP, the backward
regenerates fc2_x with the dSReLU kernel and re-quantizes it column-wise,
which first runs the group Hadamard-amax kernel over the whole tensor and
then the cast. The regenerated tensor is bit-identical to the forward one
(the plain SReLU kernel rounds the accumulator through C dtype before the
activation; the Hadamard-fused kernel does so with the matching cuDNN
frontend fix), so its per-group amax is the forward amax.

Keep the forward (pre-RHT, post-RHT) amax of fc2_x on the fc2 context and
quantize the regenerated tensor with nvfp4_group_quantize_with_amax, which
drops the backward amax pass. The Hadamard-fused fc1 kernel already returns
both amax values; for the plain SReLU kernel add nvfp4_group_hadamard_amax,
a binding that runs only the group Hadamard-amax kernel, so the forward
computes both values in the single pass it needs for the row-wise cast.
Without the binding the previous behavior is kept.
NVTE_FUSED_GROUPED_MLP_REUSE_FC2_X_AMAX=0 disables the reuse.

Verified on GB300 with a fused grouped MLP (8 experts, NVFP4 + RHT): with
recompute on and off, output, dgrad, dprob and both weight gradients match
bitwise for tanh-SReLU and plain SReLU, and the backward launches one fewer
Hadamard-amax kernel per fused call where the forward amax is available.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

The recompute test now asserts a bitwise-equal FC2 weight gradient for
tanh-SReLU with NVFP4; plain SReLU keeps its tolerance until the cuDNN
frontend fix for the Hadamard-fused kernel is required.

Signed-off-by: Xiaowei Ren <xren@nvidia.com>
…uDNN frontend supports it

ScaledTanhSReLU was routed to the plain grouped SReLU kernel because the
Hadamard-fused kernel had no soft-clamp support. With NVFP4 RHT that costs
a separate Hadamard-amax pass over the fc2 input in every forward. The
cuDNN frontend now accepts tanh_clamp_scale on
grouped_gemm_glu_hadamard_wrapper_sm100; detect that by signature and let
the clamped op take the same fused path as the unclamped one. The kernel
then returns both NVFP4 amax values, so the forward amax pass disappears
and the backward re-quantization of the recomputed fc2 input reuses them.

Verified on GB300 with a fused grouped MLP (8 experts, NVFP4 + RHT): the
tanh-SReLU forward launches one Hadamard-amax kernel fewer per fused call,
the backward one fewer with recompute, and recompute on/off give bitwise-
equal output and gradients.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Xiaowei Ren <xren@nvidia.com>
…GEMM kernel

The fused fc1 GEMM + activation + Hadamard kernel returns the post-RHT amax
that sets the global scale of the column-wise NVFP4 cast of fc2 input. The
NVFP4 quantizer applies a randomized Hadamard transform with a per-quantizer
sign mask, but the kernel computed the amax for the plain transform, so the
scale was off by the sign pattern: up to ~17% too large for SReLU-shaped
activations and possibly too small for GLUs. cuDNN frontend now accepts
rht_sign_mask on grouped_gemm_glu_hadamard_wrapper_sm100 and also rounds
its amax values like the quantizer does; pass the quantizer mask when the
wrapper takes it. On frontends without it, use the fused kernel only when
the mask is zero, so a wrong amax is never fed to the cast.

Verified on GB300 (8 experts, NVFP4 + RHT): with the matching cuDNN
frontend the fused amax values are bit-identical to the standalone group
Hadamard-amax kernel for SReLU, tanh-SReLU and SwiGLU, and tanh-SReLU gives
bitwise-equal output and gradients on the fused and the plain kernel path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Xiaowei Ren <xren@nvidia.com>
@github-actions github-actions Bot added the community-contribution PRs from external contributor outside the core maintainers, representing community-driven work. label Nov 9, 2026
@greptile-apps

greptile-apps Bot commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The PR appears safe to merge; no actionable defects were found.

Summary

The PR reduces repeated NVFP4 work when the fused grouped MLP rebuilds its second-layer input during backward.

  • Recomputed FC2 inputs reuse forward NVFP4 amax values during backward.
  • Tanh-SReLU uses the fused Hadamard path when cuDNN supports its clamp.
  • Fused FC1 kernels measure amax using the quantizer’s randomized Hadamard transform.

No actionable issues were found. xrennvidia explicitly documented the existing plain SReLU rounding difference on older frontends; this review does not report it as a new issue.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[FC1 and activation] --> B[Get pre-RHT and post-RHT amax]
  B --> C[Quantize row-wise for FC2]
  B --> D[Keep amax for backward]
  C --> E[FC2 forward]
  E --> F[Backward rebuilds FC2 input]
  D --> G[Quantize rebuilt input column-wise]
  F --> G
  G --> H[FC2 weight gradient]
Loading

Reviews (1) · Last reviewed commit: "[pre-commit.ci] auto fixes from pre-comm..." · Reviewed by Greptile

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

community-contribution PRs from external contributor outside the core maintainers, representing community-driven work.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant