Repository navigation
[PyTorch] Add export_components to MXFP8 tensors - #3790
bhargavchintam wants to merge 4 commits into
Conversation
Frameworks that sync MXFP8 training weights to inference engines read Transformer Engine's internal buffers to get the FP8 values and E8M0 scales, and each one handles the scale padding and the GEMM swizzle on its own. Add MXFP8TensorStorage.export_components(). It returns the row-scaled FP8 values with the tensor's shape and compact E8M0 scales with shape (*shape[:-1], shape[-1] // 32). The values are always a view of the tensor's data. The scales are a view too, unless their padding or GEMM swizzle has to be removed, in which case they are copied. Signed-off-by: Bindu Bhargava R Chintam <bindubhargavareddy@gmail.com>
[Medium impact] The PR appears safe to merge; no new actionable issues were found. SummaryAdds
The earlier unnumbered test-coverage finding is fixed: the test now checks saved views after a Reviews (3) · Last reviewed commit: "[PyTorch] Export a scale for a partial l..." · Reviewed by Greptile |
test_mxfp8_export_components_of_quantized_weight now exports the weight before a FusedAdam step with FP32 master weights. After the step, the exported values and scales must equal a fresh rowwise quantization of the new master weights, and must differ from what they decoded to before the step. Signed-off-by: Bindu Bhargava R Chintam <bindubhargavareddy@gmail.com>
…t-components Signed-off-by: Bindu Bhargava R Chintam <bindubhargavareddy@gmail.com>
Since NVIDIA#2894, MXFP8 accepts a last dimension that is a multiple of 16, and a partial last block keeps its own scale, so rowwise tensors carry ceil(K / 32) scale columns. export_components now returns all of them instead of K // 32. Tests cover K = 48 and K = 16, including rows that are not a multiple of 32 and GEMM-swizzled scales. Signed-off-by: Bindu Bhargava R Chintam <bindubhargavareddy@gmail.com>
|
Merged main after #2894. Now that MXFP8 allows partial blocks, a last block of 16 values keeps its own scale, so |
Description
MXFP8TensorStorage.export_components()returns the FP8 values and E8M0 scales of an MXFP8 tensor in a layout that does not depend on how Transformer Engine stores them. It is for integrations that hand MXFP8 weights to another library, for example RL weight sync to an inference engine. Today NeMo RL #3908, Megatron-Bridge #5917, Miles and verl #8129 each build this by hand from_rowwise_dataand_rowwise_scale_inv.It returns
MXFP8Components(data, scale_inv):datais_rowwise_dataviewed astorch.float8_e4m3fnortorch.float8_e5m2, with the tensor's shape.scale_invholds E8M0 biased exponents astorch.uint8, with shape(*shape[:-1], ceil(shape[-1] / 32)), without padding or GEMM swizzle. Since Improve the dimension checks for the FP8 recipes #2894 the last block can be shorter than 32 values, and it keeps its own scale.The values are always a view of the tensor's data, so they change when the tensor is updated in place, for example by an optimizer step. Clone them to keep a snapshot. The scales are a view too, except in two cases:
ceil(K/32)is not a multiple of 4), oroptimize_for_gemm=True).In those cases they are copied, and the swizzle is undone with one permute in PyTorch, so no new kernel is needed. A tensor without row-scaled data raises
ValueError. It works the same for quantized parameters and for tensors returned by a quantizer, with no dequantize or requantize.On #3644 I asked whether a standalone function that also quantizes a BF16 weight first would be preferred. This PR uses a method. I expect the export logic itself would stay the same in a function, but I have not tried that version.
Fixes #3644
Type of change
Changes
transformer_engine/pytorch/tensor/storage/mxfp8_tensor_storage.py: theMXFP8ComponentsNamedTuple andMXFP8TensorStorage.export_components().MXFP8Tensorinherits it.tests/pytorch/test_quantized_tensor.py, 29 cases in all:TestQuantizedTensor, which need no MXFP8 kernels: layout (including a partial last block,(100, 48)), views, empty tensor, and no row-scaled data.TestMXFP8Tensor:dequantize()exactly for E4M3, E5M2 and 2D quantization;dequantize()exactly;optimize_for_gemm=Truegives the same export, including for(100, 48);te.Linearweight fromquantized_model_initexports correctly after a forward and backward pass, and after aFusedAdamstep the views exported before the step hold exactly the rowwise quantization of the new FP32 master weights._swizzle_mxfp8_rowwise_scale_invbuilds the cuBLAS-swizzled scale layout from the kernel's byte-index formula, independently of the code under test;_decode_mxfp8_componentsdecodes the exported values and scales in plain PyTorch.docs/features/low_precision_training/mxfp8/mxfp8.rst: an "Exporting values and scales" section.Not covered here: column-scaled data, per-expert export from
GroupedTensor, and the other recipes.Testing
The GPU runs use one NVIDIA B200 (compute capability 10.0, driver 580.173.02) in
nvcr.io/nvidia/pytorch:26.10-py3(PyTorch 2.15.0a0+875d815502.nv26.10, CUDA 13.4). TE was built from current main (c1c2d4d) withNVTE_FRAMEWORK=pytorch NVTE_CUDA_ARCHS=100a MAX_JOBS=20 python -m pip install -e . -v --no-build-isolation, which givestransformer_engine-2.22.0.dev0+c1c2d4d2andis_mxfp8_available: (True, ''). This PR's diff was then applied on top, and the tested tree is identical to this branch's head (tree97416833).The new tests:
The 29 cases
With
scale_colsput back toK // 32, the 6 partial-block cases fail (both(100, 48)layout cases and all 4test_mxfp8_export_components_partial_blockcases), and the other 23 pass.The suites around this code, on main and then with this change:
That is 29 more passed, with the same skips and the same warnings.
Lint and the license check:
pylint reports one informational message, I1101 in
transformer_engine/pytorch/attention/fused_mla_q_uproj.py:207, a file this PR does not touch.Checklist:
qa/L0_pytorch_unittestlauncher.