[PyTorch] MXFP4 weight QAT on MXFP8 and FP8 block-scaling recipes - #3264
Open
xiuhu17 wants to merge 3 commits into
Open
[PyTorch] MXFP4 weight QAT on MXFP8 and FP8 block-scaling recipes#3264xiuhu17 wants to merge 3 commits into
xiuhu17 wants to merge 3 commits into
Conversation
xiuhu17
requested review from
Oleg-Goncharov,
ksivaman,
ptrendx and
timmoon10
as code owners
July 27, 2026 07:41
Contributor
Greptile SummaryAdds weight-only MXFP4 quantization-aware training on MXFP8 and block-scaled FP8 recipes.
Confidence Score: 5/5The PR appears safe to merge based on the currently established evidence. No blocking failure remains established; the previously reported cached-weight issue cannot be classified from the available backend evidence. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
W["BF16/FP32 master weight"] --> QAT["MXFP4 fake projection<br/>E2M1 + 1x32 UE8M0 scale"]
QAT --> HOST["Existing MXFP8 or FP8 block-scaling quantizer"]
HOST --> GEMM["Quantized GEMM"]
GEMM --> BW["Backward pass"]
BW --> STE["Straight-through gradient"]
STE --> W
Reviews (8): Last reviewed commit: "[PyTorch] Restyle MXFP4-QAT tests and ad..." | Re-trigger Greptile |
xiuhu17
marked this pull request as draft
July 27, 2026 08:38
xiuhu17
marked this pull request as ready for review
July 29, 2026 05:49
Contributor
|
Want your agent to iterate on Greptile's feedback? Try greploops. |
Weight-only MXFP4 quantization-aware training: weights are projected onto the MXFP4 grid (E2M1 payloads, 1x32 power-of-two UE8M0 scales) before the host recipe quantizes them, so training sees exactly the deployment weight while activations and gradients keep the base recipe untouched. The only new numerics is the fused projection bf16/fp32 -> MXFP4 grid -> bf16/fp32 (tex.mxfp4_fake_quantize): per 1x32 block, scale 2^clamp(ceil(log2(amax/6)), -126, 125) derived from the amax bit pattern, RTNE onto E2M1, straight-through-estimator gradient. All MXFP8 and 128x128 blockwise FP8 encoding/decoding of the projected weight runs through the existing quantizer pipeline unchanged. Since every projected value is a grid point p * 2^e, the existing encodings are decoded-value exact: MXFP8 rowwise unconditionally, 128x128 blockwise for tile scale spreads up to 2^14. Recipes: MXFP4QATMXFP8BlockScaling(MXFP8BlockScaling) and MXFP4QATFloat8BlockScaling(Float8BlockScaling); QAT can also be enabled on the stock host recipes via the mxfp4_qat_weights field (NVTE_MXFP4_QAT=1). backward_override keeps its base-recipe semantics. Also fixes ptx::exp2f(e8m0) to decode UE8M0 code 0 as 2^-127 and code 255 as NaN. Known limitations: for amax > 6*2^125 the fake-dequant saturates at the cap while packed deployment moves to scale 2^126; fp16 weights and fp16 activation/dequantize dtypes are rejected (the MXFP4 grid exceeds fp16 range); distributed end-to-end verification with a real optimizer master step and a fused projection+quantize kernel are future work.
…suite Convert test_mxfp4_qat.py to standard pytest (module-level availability gate, parametrized e2e/override matrices, no custom runner or prints, CUDA-free collection) and wire the three single-GPU QAT files into qa L0. Add directed tests for the 2^-128 RTNE tie and the CPU/non-2D/misaligned-inner-dim rejections. Trim docstrings across the QAT test files. New run_fsdp2_mxfp4_qat_parity.py: each rank trains its slice of a deterministic global batch under FSDP2 and must match local single-GPU references -- forward/dgrad bitwise against the full-batch run, wgrad against a split-sum reference that reproduces the data-parallel summation order without distributed code. Signed-off-by: zhihaow6 <zhihaow6@illinois.edu>
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
Weight-only MXFP4 quantization-aware training: weights are projected onto the MXFP4 grid (E2M1 payloads, 1x32 power-of-two UE8M0 scales) before the host recipe quantizes them, so training sees exactly the deployment weight while activations and gradients keep the base recipe untouched.
The only new numerics is the fused projection
bf16/fp32 -> MXFP4 grid -> bf16/fp32(tex.mxfp4_fake_quantize: per 1x32 block, scale2^clamp(ceil(log2(amax/6)), -126, 125)derived from the amax bit pattern, RTNE onto E2M1, straight-through-estimator gradient). All MXFP8 and 128x128 blockwise FP8 encoding/decoding of the projected weight runs through the existing quantizer pipeline unchanged. Since every projected value is a grid pointp * 2^e, the existing encodings are decoded-value exact: MXFP8 rowwise unconditionally, 128x128 blockwise for tile scale spreads up to 2^14.Recipes:
MXFP4QATMXFP8BlockScaling(MXFP8BlockScaling)andMXFP4QATFloat8BlockScaling(Float8BlockScaling); QAT can also be enabled on the stock host recipes via themxfp4_qat_weightsfield (NVTE_MXFP4_QAT=1).backward_overridekeeps its base-recipe semantics. Also fixesptx::exp2f(e8m0)to decode UE8M0 code 0 as 2^-127 and code 255 as NaN.Test coverage
All suites run on B200 (CUDA 13.0 build, cuBLAS 13.0; 2 ranks for the distributed suites). Skips are environment gates (perf benchmark opt-in; graph-safe grouped GEMM and the CuTe DSL fused grouped MLP need cuBLAS 13.3+).
tests/pytorch/test_mxfp4_qat.py--use_fast_mathrebuild bit-identical; UE8M0 code 0→2^-127 / 255→NaN through real dequantize; MXFP8 rowwise losslessness + columnwise bound; 128×128 blockwise exactness boundary; STE identity; input rejections (CPU/non-2D/inner%32/fp16)te.Linear/te.GroupedLinear× both QAT recipes × 3 backward overrides × fused/unfused wgrad, dgrad-operand discriminators (projected vs master); weight-workspace caching and microbatch lifecycle; base↔QAT recipe-switch cache invalidationtest_ops_grouped_linear_mxfp4_qat.py,test_ops_grouped_mlp_mxfp4_qat.pyte.ops.GroupedLinearQAT None on both backends with a projection-count contract; fused grouped-MLP QAT None bitwise vs projected pure-MXFP8 reference; loud rejection of unsupported overrides and primary-quantized weightsdistributed/fsdp2_tests/run_fsdp2_mxfp4_qat_ops.pyBasicLinear/Linearall 3 overrides,ops.GroupedLinearNone, fused grouped-MLP None underfully_shard; forward/dgrad bitwise vs projected reference, sharded wgrad bitwise vs the reference modulerun_fsdp2_mxfp4_qat_modules.pyte.Linear/te.LayerNormLinear/te.GroupedLinear× 3 overrides andte.LayerNormMLP(None) × both QAT recipes underfully_shard, same discriminator schemerun_fsdp2_mxfp4_qat_parity.pyrun_gtp_mxfp4_qat.pyte.Linear/te.LayerNormLinear/te.GroupedLinear× 3 overrides × both recipes with protocol-hook call assertions, plus loud rejection of the unsupported GTP surfaces (ops API, single grouped weight,LayerNormMLP, delayed wgrad)Launchers for the four distributed suites live in
distributed/test_torch_fsdp2.py(qa L1); the three single-GPU files are wired into qa L0.Type of change
Checklist: