Fuse NVFP4 fake-QAT quantize-dequantize with CuTe DSL - #88
Draft
zianglih wants to merge 3 commits into
Draft
Conversation
Signed-off-by: Ziang Li <ziangli@umich.edu>
Signed-off-by: Ziang Li <ziangli@umich.edu>
Signed-off-by: Ziang Li <ziangli@umich.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.
What does this PR do?
@HumansAnd
Adds a fused CuTe DSL NVFP4 quantize-dequantize path for fake QAT, reimplemented on the
current
miles-maingrouped-linear integration after the upstream-dev sync.TEGroupedLinear._get_weight_tensors()path obtains TE's native discrete weight list anddelegates to one env-gated helper. There is no constructor cache, packed-parameter adapter,
or grouped rank-3 kernel.
has no assumption that the list length is 4, 8, or another fixed multiple; eight weights are
only the requested performance proxy.
and packed E2M1 values in registers, and writes only the dequantized BF16/FP16 result. This
removes the quantized-data and block-scale global-memory round trips in the former TE
quantize(...).dequantize(...)fake-QAT path.one
mul.f32x2. Adjacent values multiplied by one common scale also sharemul.f32x2acrossstandard/4over6 quantization, FP16-error comparison, and final dequantization. TE's term order
for sequential error accumulation remains unchanged, and no runtime mode branch is added.
candidate-error behavior are compile-time CuTe specializations cached outside the steady-state
path. The grid-stride kernels do not branch on those modes at runtime.
simultaneous INT4 QAT, gradient-accumulation fusion, delayed wgrad, FSDP-patched weights, and
moe_single_grouped_weight=True, matching the safety rationale of current upstream INT4 QAT.per-tensor amax;
NVTE_USE_FAST_MATH=0; standard NVFP4 plus the complete 4over6 MAE/MSE,E4M3 256/448, exact-error/FP16-error matrix. The legacy-named
NVTE_NVFP4_4OVER6_ERR_USE_FAST_MATHswitch follows TE 2.17's current FP16candidate-error contract, not its older instruction-fast-math meaning.
output, row scaling, ordinary quantization fast math, pre-TE-2.17 compatibility, TE's
experimental single-grouped-parameter path, and a grouped/persistent rank-3 QDQ kernel.
The CuTe arithmetic and test design follow the bit-exact work in
FlashInfer #3448,
Transformer Engine #2972, and
Transformer Engine #3068. The strict
oracle follows TE's
test_nvfp4_quantize_exact.py;the four data families mirror FlashInfer's
test_fp4_quantize.py.The timing method follows
TE commit
83e23087.Issue tracking
Supersedes draft #87 after
miles-mainwas rewritten to the synced upstream-dev base. The earlierdraft remains open and draft for history. This remains a standalone follow-up to #75.
Linked issue: N/A (fork follow-up)
Validation
61b2c1170dd41d5094e81d918d8d3a95fc56cde1235952df607b3820716e5e67728a5ab470ca33ae(miles-main)radixark/miles:dev-202608241212(
sha256:c60775b5b11e6aac47c1f076fbef7a29506de3442ac8bbfef1efeef29e1c90b2)matrix, two GPUs for device restoration, four GPUs for the CI-shaped
torchrunlaunch2.11.0+cu130, CUDA13.0, Transformer Engine2.17.0, Cutlass DSL4.6.2The Cartesian test covers
2 dtypes x 12 shapes x 4 data patterns x 9 numerical modes = 864strict TE-oracle cases. It compares BF16/FP16 integer views to retain signed-zero sensitivity and
also calls
torch.testing.assert_close(..., rtol=0, atol=0). Tolerance is never relaxed.The single-GPU run intentionally skips only the cross-device state-restoration case; the next
command runs that exact case with two visible GPUs and passes. The four-worker run intentionally
skips it in each worker to avoid cross-worker device contention.
Formatter/linter command:
autoformat.shcompleted successfully. Its internally non-gating mypy invocation reportedmissing optional CUDA-package stubs and CuTe DSL tuple-inference limitations in the isolated
formatter environment; Black, isort, pylint, and Ruff all passed.
Performance
The target is the complete loop over eight independently stored TE-style parameters. Each weight
is contiguous
[4096, 6144]; the reported logical shape is6144 x 4096, so 1x16 blocks runalong the 6144 input-feature axis.
--num-weightsis configurable and the production helperaccepts arbitrary list lengths.
quantizer.quantize(x_padded).dequantize(dtype=x.dtype)[:m, :n].contiguous().maybe_fake_quantize_nvfp4_weight_tensors(...)call,including env/config validation, one FP32 PyTorch amax reduction per weight, STE wrapping,
output allocation, TVM-FFI argument marshalling, kernel launches, and the full weight loop.
in the speedup or geomean.
torch.utils.benchmark.Timer.blocked_autorange(min_run_time=1); three repeats;interleaved naive/fused/naive order. The naive median uses all six A/B/A samples and the fused
median uses three samples.
Exact-error remains fully covered by zero-tolerance tests but is deliberately excluded from the
benchmark target and geomean.
Geomean history
Exact-error 4over6 is excluded from every row. New optimization commits will append rows while
retaining earlier exact-commit results.
30d5fd6321e63642d761b2c1170The first exact-commit run of
1e63642d7measured1.626xoverall (1.635xBF16,1.617xFP16). The confirmation table above is the latest full run; both runs reproduce theMSE precomputed-amax reduction from approximately
229.3 usat baseline to227.8-228.0 usBF16 and
226.6-226.8 usFP16. The end-to-end geomean range is retained here rather thanpresenting its sub-percent change as noise-free.
The first exact-commit run of
61b2c1170measured1.545xoverall (1.527xBF16,1.563xFP16), with the standard TE loop in a distinct faster host/launch regime. The repeatabove returned to the longer-running regime seen in earlier tables. Both exact runs reproduce the
kernel result: MSE precomputed-amax falls from
227.8-228.0 usto216.1-216.4 usBF16 and from226.6-226.8 usto213.6 usFP16. End-to-end geomeans are retained as measured rather thannormalizing away the host-heavy standard-path variability.
Packed-MSE mechanism check
Nsight Compute
2025.3.1.0captured one BF16[4096,6144], 4over6 MSE,E4M3-448 FP16-error launch with precomputed amax. This is a diagnostic mechanism check, not a
replacement for the full-loop benchmark above.
30d5fd632baseline1e63642d7packed squaresFMUL/FMUL2rowsThe only opcode-count changes are 32 fewer static
FMULrows and 16 newFMUL2rows. Atruntime, 1,572,864 scalar multiplies become 786,432 packed multiplies, exactly accounting for the
786,432-instruction reduction. Report SHA256s are
4236ce067e74895d3cb0edeb0d92bebc78a210591043e72c51a666edbf620374(baseline) andbaf3190683d89d433a2166a8b6f1a8f281360fdfd05f4fe207d634c60f32bdac(packed squares).Packed-scale mechanism check
The next one-launch BF16 NCU comparison uses the same shape and precomputed amax. Standard and
MAE compare to
30d5fd632; MSE compares to1e63642d7, which already contains packed errorsquares.
61b2c1170Standard replaces 32 static scalar
FMULinstructions with 16FMUL2; MAE and MSE eachreplace 64 with 32. Branch counts are unchanged and all reports have zero local spilling. The MSE
specialization crosses from the 64-register to the 48-register allocation bucket. Candidate
report SHA256s are
0979b9c9764913eb911b6693d0307930288ae09c8e7913bf103e0a011b67d2cb(standard),
8e70b863a79673f7e778b754e8b6a30b3b527b99bb5d24afda6fc790319f9429(MAE), and
1a169563b920c1ec62202ca7322ec4bb1b6c94e10bf2bb096f71444d7a2d41b3(MSE).
Limitations and pending work
development lock still names an older TE source revision, so generic older environments fail
clearly when the flag is enabled; the validated Miles runtime supplies TE 2.17.0.
16 and at most
INT32_MAXelements per tensor.FSDP-patched weights and
moe_single_grouped_weight=Trueare rejected.It does not depend on
NVTE_GROUPED_LINEAR_SINGLE_PARAMand does not include the abandonedgrouped rank-3 kernel direction.
not performance-validated here.
Contribution process
Pre-checks
Code review
Feel free to message or comment @NVIDIA/mcore-oncall to help accelerate your merge into main. The less complex your PR is, the faster it will be approved and merged!
All PRs start as draft. If you open a non-draft PR, it will be automatically converted to draft.
Step 1: Mark PR as "Ready for Review"
.github/CODEOWNERS.Final Review might get declined if these requirements are not fulfilled.
Step 2: Final Review
For PRs that change
megatron/core, once all expert reviewers have approved, theFinal Reviewlabel is applied automatically and final reviewers are assigned.For PRs outside
megatron/core, this step is skipped.Step 3: Approved
Once all required reviewers have approved, the
Approvedlabel is applied automatically.Merge
Any member of mcore-engineers will be able to merge your PR.