Repository navigation
[Common, PyTorch] Add grouped NVFP4 dequantization - #3591
harshithkantamneni wants to merge 7 commits into
Conversation
nvte_group_dequantize had no NVFP4 path (NVIDIA#2726). Add one for rowwise data with compact (non-swizzled) E4M3 scales, as written by the grouped NVFP4 quantizer: one FP32 amax for the group or one per tensor, and tensors that share the last dimension (equal shapes or a varying first dimension). Each thread dequantizes one 16-element block with the same arithmetic as the single-tensor NVFP4 kernel, so the results match it bitwise. Fix the PyTorch group_dequantize binding for NVFP4: size the FP4 data in elements rather than bytes, pass the amax and the swizzled-scale flag, and reject row-scaled NVFP4 and an E4M3 max other than 448, which the grouped tensor cannot describe. Add a C++ test that compares grouped against per-tensor dequantization bitwise, and PyTorch tests against the grouped NVFP4 quantizer, including an empty tensor, the swizzled-scale error and CUDA graph capture. Signed-off-by: Harshith Kantamneni <hkantamneni2@wisc.edu>
[Medium impact] The PR appears safe to merge; no new blocking issues were found. SummaryAdds grouped NVFP4 dequantization for rowwise data with compact E4M3 scales.
No new actionable issues were found. The previous threads were unnumbered, so they have no entries in Reviews (7) · Last reviewed commit: "Merge branch 'main' into harshith-group-..." · Reviewed by Greptile |
Look up the owning tensor with common::find_tensor_from_offsets instead of a local binary search, and report per-tensor first dimensions that are not multiples of 128 through common::get_tensor_rows_num, once per tensor in the first block, as group_quantize_mxfp8.cuh does. The dense scale indexing depends on that alignment, which was only checked on the host for equal shapes. Signed-off-by: Harshith Kantamneni <hkantamneni2@wisc.edu>
Merge NVIDIA/TransformerEngine main at bba2d2b without rewriting the existing PR commits. Restore the is_nvfp4 declaration removed by the automatic merge with the scale_inv_dtype changes from NVIDIA#3325. Accept nvfp4_e4m3_max=0 as the E4M3 default as well as explicit 448, and cover both values in the grouped dequantization numerical test. Keep the existing compact E4M3 scale and FP32 amax requirements. Validation: compile the complete cast.cpp translation unit with CUDA 13.4 and PyTorch 2.13; run pre-commit on both changed paths, the C++ portion of L0_pytorch_lint, and L0_license. GPU numerical tests were not run: the local SM120 GPU cannot execute grouped NVFP4 quantization, which supports SM100-SM110. Signed-off-by: Przemek Tredak <ptredak@nvidia.com>
group_quantize with disable_second_level_scale=True produces a grouped NVFP4 tensor without an amax. Treat a missing amax as a unit global scale, as dequantize_nvfp4.cuh does, instead of rejecting the tensor. Cover it with a NO_AMAX mode in the C++ grouped test and with disable_second_level_scale in the PyTorch grouped round trip. Signed-off-by: Harshith Kantamneni <hkantamneni2@wisc.edu>
|
Thanks for merging main and restoring the The Greptile P1 on 8375386 was right: Your merge message mentions the GPU numerical tests could not run on SM120, so I ran them on a B200 (
To check that the new tests exercise the new path, I changed the default amax from 6 * 448 (global scale 1) to 1.0: exactly the 18 |
Signed-off-by: Harshith Kantamneni <hkantamneni2@wisc.edu>
Signed-off-by: Harshith Kantamneni <hkantamneni2@wisc.edu>
|
@ptrendx I merged main into the branch again, since it conflicted after #3614. The only conflict was in
Whenever you have time for another look, thanks. |
Description
nvte_group_dequantize/tex.group_dequantizesupported MXFP8 and FP8 block scaling but not NVFP4. This adds NVFP4 for rowwise data with compact (non-swizzled) E4M3 scales, as #2726 asks, and fixes the PyTorch binding so it passes an NVFP4 grouped tensor's element count, amax and swizzle flag to the kernel.Fixes #2726
Type of change
Changes
common/cast/nvfp4/group_dequantize_nvfp4.cuh(new): one thread per 16-element block, with the same arithmetic asdequantize_nvfp4.cuh, so output matches per-tensor dequantization bitwise. The owning tensor comes fromrow / rows_per_tensor(equal shapes) orcommon::find_tensor_from_offsetsovertensor_offsets(varying first dimension, empty tensors included).tensor_offsetsis read on the device, so there is no device-to-host copy and the call can be captured in a CUDA graph.common/cast/dispatch/dequantize.cuh: routeNVTE_NVFP4_1D_SCALINGto the new kernel.common/include/transformer_engine/cast.h: document NVFP4 support and its requirements.pytorch/csrc/extensions/cast.cpp(group_dequantize), for NVFP4 only:CheckInputGroupedTensoraccepts it;amaxand_with_gemm_swizzled_scales, which were not set before;tests/cpp/operator/test_dequantize_nvfp4_grouped.cu(new, registered in CMake): grouped vs. per-tensornvte_dequantize, bitwise, over 6 shapes x 3 output types x shared/per-tensor amax.tests/pytorch/test_grouped_tensor.py: grouped vs. per-tensor dequantize fromtex.group_quantizeoutput (including an empty tensor), the swizzled-scale error, and CUDA graph capture.Scope
graph_safe_group_row_cast_col_hadamard_transform_cast_fusion.cu) requires. This makes the padded per-tensor scale layout one dense[rows, cols / 16]array. For equal shapes the per-tensor row count is checked on the host. With a varying first dimension the kernel reports a misaligned tensor throughcommon::get_tensor_rows_num, once per tensor in the first block asgroup_quantize_mxfp8.cuhdoes (NVTE_DEVICE_ERROR, so it stops only in debug builds).VARYING_LAST_DIMandVARYING_BOTH_DIMSare rejected, because the grouped NVFP4 quantizer on main requires a constant last dimension. The kernel is a plain one and does not depend on [Common] Group NVFP4 Quantize Kernels #3458.Testing
Both commits were tested on one B300 (SM103,
NVTE_CUDA_ARCHS=103a) at fb0fed6. The first commit was also tested on one B200 (SM100,100a) at 1770259, with the same results. torch 2.8.0+cu129, cuDNN 9.26, CUDA 12.9. Current main does not build with CUDA 12.8: ptxas rejects theld.global.nc.L2::evict_firstfallback inutil/ptx.cuh(from #3459) with "'.L1::eviction_priority' syntax expected", whiledocs/installation.rstlists 12.8+ for Blackwell.test_operator --gtest_filter='*GroupedDequantizeNVFP4*': 33 passed, 3 skipped (the one-tensor per-tensor-amax cases, which duplicate the shared-amax ones), 0 failed. The reference, per-tensornvte_dequantize, is itself checked against a CPU reference intest_dequantize_nvfp4.cu.--gtest_filter='*Dequantize*': 777 passed, 219 skipped (including the 144 grouped FP8-blockwise cases, which run on SM90 only), 0 failed.Row-count report: a grouped tensor with first dimensions 128 and 384 prints no message; one with 192 and 320 prints the
get_tensor_rows_nummessage exactly twice, once per tensor, and in this release build the call continues, as for MXFP8.New PyTorch tests: 8 passed.
NVTE_GROUPED_LINEAR_SINGLE_PARAM=1 pytest tests/pytorch/test_grouped_tensor.py: 167 passed, 71 skipped, 0 failed.Each planted bug below was a temporary edit, rebuilt and run, then restored; all new tests pass again afterwards.
row * cols - 1passed tofind_tensor_from_offsets)The pre-commit hooks (black 24.4.2, clang-format 18.1.6), cpplint 1.6.0 on the changed
transformer_engine/files, andqa/L0_licensepass. No new build warnings in the changed files.Not run: the full
qa/L0_cppunittestandqa/L0_pytorch_unittestlaunchers, only the subsets above.Notes for reviewers
group_dequantizelines incast.cppand changes two things this PR relies on: the defaultnvfp4_e4m3_maxbecomes 0 (the scale dtype's max), which thenvfp4_e4m3_max == 448check here would reject, anddisable_second_level_scaleallows NVFP4 without an amax, which this kernel rejects. Whichever lands second needs to handle both, and UE5M3 needs a scale-type template parameter in this kernel.cols / 16scales per data row) needs to follow it.tex.group_quantizefor NVFP4 needsfirst_dims. Without it the grouped tensor has notensor_offsets(build_grouped_tensor_offsetsreturns nullopt), and both graph-safe kernels readoffsets[num_tensors]without a check (graph_safe_group_hadamard_transform.cu:241,graph_safe_group_row_cast_col_hadamard_transform_cast_fusion.cu:224), which fails with an illegal memory access. So the PyTorch tests passfirst_dims, and the equal-shape path is covered by the C++ test.Checklist:
AI usage: I used an AI assistant (Claude Code) to write code, tests, the validation scripts and this description. I reviewed every change, directed the tests, and ran the B200 validation.