Repository navigation
[PyTorch] Add MXFP8 dgrad and gradient 4over6 NVFP4 options - #3573
Draft
vincentzed wants to merge 1 commit into
Draft
vincentzed wants to merge 1 commit into
vincentzed wants to merge 1 commit into
Conversation
Add two NVFP4BlockScaling fields. Both can be set through env vars, so frameworks that construct a default recipe (e.g. Megatron-Core) can opt in without code changes. * dgrad_mxfp8 (NVTE_NVFP4_DGRAD_MXFP8): the dgrad GEMM uses MXFP8 operands while fprop and wgrad keep NVFP4 operands. The weight and grad_output slots become HybridQuantizers; the weight columnwise MXFP8 data is requantized from the NVFP4 fprop weight. Rejected together with backward_override and by quantized_model_init; quantized NVFP4 primary weights are rejected under this recipe. The graph-safe grouped GEMM path and the fused grouped MLP are skipped in this mode. * nvfp4_4over6_grad (NVTE_NVFP4_4OVER6_GRAD): 4over6 for gradient quantizers. Like the existing 4over6 scopes, it requires RHT and stochastic rounding to be disabled for those quantizers. Hybrid gradient quantizers now work in paths that assumed non-hybrid quantizers: * LayerNormMLP takes the unfused bgrad path, since tex.bgrad_quantize does not accept HybridQuantizer. * te.ops.GroupedLinear splits and quantizes grad_output with the hybrid-aware grouped split helper instead of tex.split_quantize. * The cuBLASMp wgrad re-gather in Linear and LayerNormLinear quantizes the original gradient for HybridQuantizer, as Linear already does for MXFP8, instead of requantizing dequantized rowwise data. Tests cover env var parsing, validation, quantizer layouts, te.Linear against GEMMs on dequantized operands for both options (with negative controls), bitwise parity with the equivalent CustomRecipe across modules, module options and te.ops, LayerNormMLP, the quantized primary weight rejections, the dgrad error reduction, and all-zero gradients and inputs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015LRZMCLfqm7g6gHe49iBPg Signed-off-by: Vincent Zhong <v4zhong@uwaterloo.ca>
Collaborator
|
Rebased NVIDIA/Megatron-LM#7176 and opened it for review (it is functional). |
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
NVFP4BlockScalingsupports NVFP4 backward GEMMs, or higher-precision backward GEMMs throughbackward_override.Some RL post-training setups (i.e a possible recipe in Miles) use the latter to keep gradient quantization error below the noise of small policy grad updates.
We already know that the error contrib is not equal:
dX = dY W) error propagates into every earlier layer.dW = dY^T X) error stays in one weight gradient.Meeting this requirement in the middle, we present:
MXFP8 operands for dgrad, with NVFP4 operands for fprop and wgrad. It also lets
gradient quantizers use 4over6 (we found this to be beneficial), which is currently limited to weights and activations.
Both options are recipe fields with env vars. Frameworks that construct a default
NVFP4BlockScaling, such asMegatron-Core, can opt in from the environment, the same way they use
NVTE_NVFP4_ROW_SCALED_ACTIVATIONandNVTE_BACKWARD_OVERRIDE. Megatron-Core cannot pass aCustomRecipeyet (Add format-neutral TE custom recipe support (--custom-recipe) Megatron-LM#7176).The code style should be relatively aligned w/ the TE requirements.
Codex below>
Type of change
Changes
NVFP4BlockScaling.dgrad_mxfp8/NVTE_NVFP4_DGRAD_MXFP8.NVFP4BlockScalingRecipeState.make_quantizersreturns aHybridQuantizerfor two slots:weight: NVFP4 rowwise (fprop) plus MXFP8 columnwise (dgrad), requantized from the dequantized NVFP4 weight(
columnwise_source="rowwise_dequantized").grad_output: MXFP8 rowwise (dgrad) plus NVFP4 columnwise (wgrad).nvfp4_4over6_gradsettings.NVFP4BlockScaling.nvfp4_4over6_grad/NVTE_NVFP4_4OVER6_GRAD.nvfp4_4over6_e4m3_use_256is"all"and 448 otherwise."activations"keepsmeaning forward activations.
nvfp4_4over6="all"still leaves gradients unchanged unless this flag is set.dgrad_mxfp8withbackward_overridefails in__post_init__.quantized_model_initrejectsdgrad_mxfp8recipes, because distributed master-weight updates reject hybridweights whose columnwise data is derived from rowwise data.
dgrad_mxfp8autocast are rejected in the module's weight/recipecheck, because they carry no MXFP8 columnwise data for dgrad.
GroupedLinearimplementations use their per-group paths, as they already do forrow_scaled_activation.CustomRecipehybrids:LayerNormMLPtakes the unfused bias-gradient path forHybridQuantizer, sincetex.bgrad_quantizedoes notaccept it.
te.ops.GroupedLinearbackward splits and quantizesgrad_outputwith the hybrid-aware grouped split helperinstead of
tex.split_quantize, which cannot convert hybrid quantizers.LinearandLayerNormLinearquantizes the original gradient forHybridQuantizer, asLinearalready does for MXFP8. Previously it requantized the columnwise operand fromdequantized rowwise data.
docs/envvars.rstentries for both env vars;NVTE_NVFP4_4OVER6_E4M3_USE_256description now covers gradients;make_quantizersdocstrings.tests/pytorch/nvfp4/test_nvfp4_backward_options.pyhas 81 tests.backward_overrideconflict. These run without FP4 hardware.te.Linearis compared against FP32 GEMMs on dequantized operands, with tolerancesset by BF16 output rounding:
dgrad_mxfp8: fprop and wgrad on NVFP4 operands; dgrad on MXFP8 of the dequantized NVFP4 weight.Negative controls asserted: NVFP4 dgrad, and an MXFP8 weight quantized from the original weight.
nvfp4_4over6_grad: dgrad and wgrad on 4over6 gradients. Negative control asserted: standard NVFP4gradients.
CustomRecipefor:Linear,LayerNormLinearandGroupedLinear, with and without bias and row-scaled activations;fuse_wgrad_accumulation(main_grad),delay_wgrad_compute,save_original_input,is_first_microbatchweight caching, andGroupedLinear(single_grouped_weight=True)for both the module and the op;te.ops.BasicLinearandte.ops.GroupedLinear.LayerNormLinearnorm-weight gradient depends on dgrad, so itis excluded);
backward_override="dequantized"gradient is under half.LayerNormMLP(gelu, swiglu, with and without bias):micro-batches:
grad_output;LinearandGroupedLinearwith five recipe configurations, plus all-zero input rows;all-zero
grad_output.Numerics on RL gradients
These numbers are not part of the test suite.
Setup:
te.Linear.backward_override="dequantized"gradient.gradient norm for the RL loss and 0.096 for an SFT loss on the same tokens.
backward_override="high_precision")nvfp4_row_scaled_fwd_mxfp8_bwd_factory)dgrad_mxfp8=Truenvfp4_4over6_grad=True(bound 448 / 256)dgrad_mxfp8=True, nvfp4_4over6_grad=Truegrad_output(not in this PR)dgrad_mxfp8halves the NVFP4 backward error.dgrad_mxfp8.grad_outputdoes not change the error. The gradient error comes from 4-bit element resolutionwithin 16-element blocks, not from the per-tensor scale range.
Performance
These numbers are not part of the test suite.
Setup:
quantize(dY)+ dgrad + wgrad per linear per step.flashinfer.testing.bench_gpu_time_with_cuptiwith CUDA graphs and cold L2; median of 50 iterations after10 warm-up iterations.
Times are in ms.
Benchmark script
dgrad_mxfp8dgrad_mxfp8is 1.1–1.25x faster than an MXFP8 backward and 1.6–2.2x faster than BF16.small-GEMM latency dominate.
grad_outputquantization is two casts that each read dY: 0.53 ms vs 0.35 ms for the single NVFP4 castat gate_up. A fused MXFP8-rowwise + NVFP4-columnwise cast would remove that overhead; it is left as follow-up.
Testing
B300 (sm_103), CUDA 13.0, PyTorch 2.13, cuDNN 9.20, driver 615.71.09, single GPU.
main (9c33764 + this change): editable build with
NVTE_CUDA_ARCHS="100a;103a"andNVTE_WITH_NCCL_EP=0; thecontainer's NCCL 2.29 lacks the NCCL EP APIs.
tests/pytorch/nvfp4/test_nvfp4_backward_options.pytests/pytorch/nvfp4test_backward_override.pytest_fusible_ops.pytest_sanity.pytest_grouped_linear.pytest_identity_quantizer.pytest_recipe.pytest_weight_swizzle_in_layers.pytest_grouped_mlp.py -k 'not prequantized'test_grouped_mlp.py(full)test_grouped_linear_prequantized_mxfp8_gradcascades into 387 errorstest_custom_recipe.py+test_hybrid_quantization.pytest_checkpoint.pyqa/L0_license/test.shqa/L0_pytorch_lint/test.sh(PYTHON_ONLY=1)fused_mla_q_uproj.pyv2.19 backport: installed TE 2.19 with the patched Python files.
tests/pytorch/nvfp4passes (9380), andtest_grouped_mlp -k 'not prequantized'passes(438).
test_backward_override,test_grouped_linearandtest_recipepass.test_fusible_ops.pydoes not import in that image (libtorch_cuda.so: undefined symbol ncclCommResume).Limitations
grad_outputis not supported.fill_userbuffers_buffer_for_all_gatherhandles onlyFP8 and MXFP8 quantizers, which is already the case for NVFP4 gradient quantizers. The failure is an explicit
Unsupported quantizer for Userbufferserror.dgrad_mxfp8; this is an explicit error. This includes Megatron-Core'sTEFusedMLPWithGroupedLineardense MLP, which creates its weights underquantized_model_init.dgrad_mxfp8ornvfp4_4over6_gradbetween steps on a module that already built its quantizers does not rebuild them. The same holds for every other
NVFP4BlockScalingfield; [PyTorch] Support dynamic recipe updates during training #3343 addresses recipe changes in general.Checklist: