Fuse NVFP4 fake-QAT quantize-dequantize with CuTe DSL - #87
Draft
ziang-and wants to merge 20 commits into
Draft
Conversation
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.
What does this PR do ?
@HumansAnd
Replaces the fake-QAT NVFP4 quantize/dequantize global-memory round trip from #75
with a standalone CuTe DSL kernel that quantizes and immediately dequantizes in
registers, while preserving Transformer Engine output bit-for-bit. Grouped expert
weights use one packed rank-3 API and one grouped launch rather than a Python QDQ
loop.
Scope and numerical contract
[G, M, N]BF16 or FP16 tensor,1 <= G <= 2048, homogeneousM/N,N % 16 == 0, and one FP32 amax pertensor.
G=1uses the same API and kernel as larger groups.torch.linalg.vector_norm(..., dim=(1, 2), ord=inf, dtype=torch.float32)callproduces the
[G]per-tensor amax vector consumed directly by the kernel.MAE/MSE, E4M3 max 256/448, and exact/FP16-rounded candidate error.
NVTE_NVFP4_4OVER6_ERR_USE_FAST_MATH=1selects currentTE's FP16 candidate-error contract. It is not the stale general fast-math
toggle. Ordinary
NVTE_USE_FAST_MATHmust be 0.activation/gradient roles, mixed shapes in one group, and non-SM10x GPUs.
Implementation
per-tensor expression association and final dequantization expression exactly.
blockIdx.x = group,blockIdx.y = persistent CTA. Int64 group-base addressingis separated from validated Int32 within-weight offsets.
G, caps it by natural work,and keeps dtype, standard/4over6, error mode, E4M3 max, error precision, thread
count, and occupancy bound compile-time static. Shape and
Gdo not enter thecompilation-cache key.
global memory. The STE has identity backward and preserves
main_grad.TEGroupedLinearrequests one packedparameter with
NVTE_GROUPED_LINEAR_SINGLE_PARAM=1, QDQs its rank-3 backingstorage once, and exposes zero-copy member views to TE. Older discrete-weight
paths call the same API with
G=1.m_splitscontract. That path is rejected explicitly rather than silentlyclaiming compatibility; supporting it would require a broader MCore/TE adapter.
Numerical references
ff22228d2fa144e9ac6a0d841f2e9ba767ba0f0a:flashinfer/quantization/kernels/nvfp4_quantize.pyandtests/utils/test_fp4_quantize.py.ea1a165decdd09c1ef98b273d965796c89c47473:tests/pytorch/nvfp4/test_nvfp4_quantize_exact.pyand current native/referenceNVFP4 implementations.
Native TE QDQ is the oracle. Every numerical case compares FP32 amax integer bits
and BF16/FP16 output integer bits, then also runs
rtol=0,atol=0. Tolerance isnever relaxed. Per-token FlashInfer agreement is not used as the per-tensor oracle.
Performance history
The retained history uses the current
8 x 6144 x 4096proxy wherever stated.Each matrix is stored
[4096, 6144], so 1x16 blocks lie along 6144. The geomeancovers BF16 and FP16 across standard NVFP4 and the four 4over6 FP16-error modes;
exact-error remains in correctness coverage but is excluded from performance.
Older obsolete versions were not rerun.
d8c78ebf3for w in weight_tensorsloop4e005bb600a213eb34fa826213430814f6024480b2ffff1cba72d3[G,M,N]API, vectorized amax, and one grouped persistent launchf25fbfa40Absolute native-TE timings vary across runs more than the fused kernel timings.
Every speedup above is an interleaved same-run ratio; cross-commit geomean changes
are not attributed solely to code. The narrower grid-tuning evidence is below.
Current best full mode table
radixark/miles:dev-202608230022sha256:44795dc9abafaa584a81742803541f287b5c7c6ff938c1ad991d59c706b1362ff25fbfa408e4912945097bef60561e7f6f6d60d6BF16 then FP16 sequentially in one process
repeats per row; input/view/quantizer construction and warmup excluded
allocation, one QDQ launch, and packed
main_gradassignment. The zero-copyTE 2.17 post-QDQ unbind adapter is not included.
78304b03bc22f295a91350e45c483daa81a361e285070bae5e728bf3bab5ec36[G,rows,block-axis][A/B/A raw](us)[raw](us)[raw](us)[8,4096,6144][674.959, 677.612, 678.795, 675.515, 672.407, 673.794][245.742, 245.707, 245.727][384.402, 382.832, 381.341][8,4096,6144][676.108, 674.198, 670.338, 665.662, 668.290, 669.821][294.275, 294.248, 294.270][411.087, 411.102, 411.154][8,4096,6144][672.268, 672.971, 665.585, 669.977, 668.782, 666.783][294.271, 294.264, 294.248][411.170, 411.102, 411.106][8,4096,6144][669.919, 674.634, 671.372, 670.526, 679.971, 675.079][303.179, 303.126, 303.234][426.259, 426.490, 426.222][8,4096,6144][673.447, 668.276, 677.700, 674.806, 668.006, 672.762][303.215, 303.221, 303.200][426.557, 426.209, 426.087][8,4096,6144][658.334, 655.053, 657.002, 662.231, 656.236, 658.117][241.779, 241.774, 241.773][380.226, 380.689, 384.022][8,4096,6144][668.166, 672.196, 666.377, 664.374, 665.410, 666.578][290.196, 290.280, 290.279][410.406, 410.302, 410.319][8,4096,6144][668.155, 669.047, 670.236, 665.090, 667.222, 663.661][290.253, 290.247, 290.202][410.344, 410.364, 410.250][8,4096,6144][668.179, 663.657, 663.525, 667.665, 665.049, 673.444][298.737, 298.736, 298.785][412.341, 412.011, 411.927][8,4096,6144][674.430, 669.154, 663.611, 663.500, 665.304, 667.494][298.701, 298.707, 298.728][412.013, 412.261, 412.356]Derived geomeans: 2.344x vs naive TE QDQ and 1.431x vs eight fused
singleton calls. BF16 and FP16 geomeans versus naive are 2.340x and 2.349x.
cd /root/Megatron-LM-grouped3d CUDA_VISIBLE_DEVICES=2 PYTHONPATH=. \ python tests/unit_tests/fusions/benchmark_fused_nvfp4_qdq.py \ --groups 8 --shape 6144x4096 --dtype both \ --min-run-time 1 --repeats 3 \ --image radixark/miles:dev-202608230022 \ --commit f25fbfa408e4912945097bef60561e7f6f6d60d6Persistent-grid tuning
Full target runs varied only the runtime total-grid targets. These are geometric
means of grouped-call microseconds; lower is better. The retained
96/64pair isthe smallest observed best plateau before each mode regresses.
Validation
The strict Cartesian is 2 dtypes x 3 group counts (
G=1,3,8) x 12 shapes x4 data patterns (
random,boundary,zeros,maxes) x 9 numerical modes =2,592 TE-oracle cases. Group-distinct values and amaxes exercise non-power-of-two
G; an additional random TE-oracle case covers theG=2048upper bound.Additional tests cover STE/main-grad, config parsing, invalid layouts/bounds,
non-current-device restoration, discrete
G=1fallback, and an actual TE 2.17packed GroupedLinear forward/backward.
cd /root/Megatron-LM-grouped3d CUDA_VISIBLE_DEVICES=0 PYTHONPATH=. \ pytest -q tests/unit_tests/fusions/test_fused_nvfp4_qdq.py CUDA_VISIBLE_DEVICES=0,1 PYTHONPATH=. \ pytest -q tests/unit_tests/fusions/test_fused_nvfp4_qdq.py \ -k uses_and_restores_non_current_deviceTest-log SHA256: full
5903489ee223f3db240a6ff6ec8b14c0c72fd4cd45cb202e65075aee110e3c02;two-device
7fe6c09244cb893c2ceea6529c9a409f5c0a56ff4307e2c5a3e27b89fad0d4e7.Static checks at
f25fbfa40:Limitations and pending work
been validated.
doing so also requires preserving CUDA
m_splitsthrough MCore.zero-copy output-view adapter.
main_gradpropagation is covered; MCore FSDP and a full training jobhave not been run.
Contribution process
flowchart LR A[Pre-checks] --> B[PR Tests] subgraph Code Review/Approval C1[Expert Review] --> C2[Final Review] end B --> C1 C2 --> D[Merge]Pre-checks
Core 0.8)Code review
The following process is enforced via the CODEOWNERS file for changes into
megatron/core. For changes outside ofmegatron/core, it is up to the PR author whether or not to tag the Final Reviewer team.For MRs into `main` branch
Feel free to message or comment on the @mcore-oncall to help accelerate your merge into main. The less complex your PR is, the faster it will be approved and merged!
(Step 1): Add PR label
Expert Review(Step 2): Collect the expert reviewers reviews
Expert Reviewlabel when your PR is ready for review.Final Review might get declined if these requirements are not fulfilled.
(Step 3): Final Review
Final Reviewlabel(Optional Step 4): Cherry-pick into release branch
If this PR also needs to be merged into
core_r*release branches after merge, selectCherry-pickto open a new PR into the release branch.For MRs into `dev` branch
The proposed review process for `dev` branch is under active discussion.MRs are mergable after one approval by either
eharper@nvidia.comorzijiey@nvidia.com.Merging your PR
Any member of core-adlr and
core-nemowill be able to merge this PR.