Repository navigation
[PyTorch] Reduce NVFP4 quantization work for the fused GroupedMLP fc2 input with activation recompute - #3837
Open
xrennvidia wants to merge 5 commits into
Conversation
…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>
for more information, see https://pre-commit.ci
Contributor
[Medium impact] The PR appears safe to merge; no actionable defects were found. SummaryThe PR reduces repeated NVFP4 work when the fused grouped MLP rebuilds its second-layer input during backward.
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]
Reviews (1) · Last reviewed commit: "[pre-commit.ci] auto fixes from pre-comm..." · Reviewed by Greptile |
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.
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 quantizedfc2_xboth 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
Changes
recompute_srelu_fc2_xbefore 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.fc2_xon the fc2 context and quantize the recomputed tensor withnvfp4_group_quantize_with_amax, dropping the backward amax pass. Add anvfp4_group_hadamard_amaxbinding so the plain SReLU kernel path computes both amax values in the single forward pass.NVTE_FUSED_GROUPED_MLP_REUSE_FC2_X_AMAX=0disables the reuse.ScaledTanhSReLUthrough the Hadamard-fused fc1 kernel when the cuDNN frontendgrouped_gemm_glu_hadamard_wrapper_sm100acceptstanh_clamp_scale(detected by signature), so the forward amax pass disappears for tanh-SReLU as well.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: