Skip to content

Implement 4over6 NVFP4 recipe - #2972

Merged
timmoon10 merged 72 commits into
NVIDIA:mainfrom
zianglih:4over6
May 22, 2026
Merged

Implement 4over6 NVFP4 recipe#2972
timmoon10 merged 72 commits into
NVIDIA:mainfrom
zianglih:4over6

Conversation

@zianglih

@zianglihzianglih commented May 9, 2026

Copy link
Copy Markdown
Contributor

Description

@HumansAnd

Implement 4over6 nvfp4 from:

FlashInfer PR:

Enable per-block map-to-4 versus map-to-6 candidate selection for 1D/2D NVFP4 quantization in the NVFP4BlockScaling recipe. This mode currently requires RHT to be disabled. Both original per-tensor scaling and row-scaling NVFP4 introduced by #2931 are supported.

This PR also fixes a few minor bugs for row-scaled NVFP4 from #2931.

Currently, the 4over6 quantization kernel is heavily compute bounded, caused by fp32 dequantization math required for error computation. To allieviate register pressure we did not fold the implementation into existing quantization kernel which mainly optimizes memory latency which may lead to higher register pressure. We can add other error heuristics and optimize performance in future PRs.

With fast math:
image
Without fast math:
image

Type of change

  • Documentation change (change only to the documentation, either a fix or a new content)
  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Infra/Build change
  • Code refactoring

Changes

Please list the changes introduced in this PR:

  • Adds scoped NVFP4 4over6 control through NVTE_NVFP4_4OVER6=none|weights|activations|all, with unset preserving existing behavior, and threads the selected scope through recipes, quantizers, tensor metadata, split quantization, single-tensor quantization, and C++ tensor/config APIs.
  • Implements 1D & 2D NVFP4 4over6 quantization in the existing NVFP4 CUDA paths by comparing TE-style map-to-4 and map-to-6 FP4 candidates with the original 4over6 MSE rule, choosing map-to-6 on ties, error compute fast math configurable by NVTE_NVFP4_4OVER6_ERR_USE_FAST_MATH=0|1 (default to 0), and rejecting unsupported combinations such as grouped tensors, fprop RHT.
  • Updates dequantization and NVFP4 GEMM scaling to respect per-tensor 4over6 metadata without requiring callers to do hidden rescaling. 4over6 can either use 256 or 448 as E4M3 bound, configurable by NVTE_NVFP4_4OVER6_E4M3_USE_256=none|weights|activations|all (default to all).
  • Extends the Python reference implementation to mirror the intended ground truth, meaning TE-style candidate quantization plus original 4over6 MSE/compare logic, and uses this reference for bitwise exact tests where fast math is disabled.
  • Expands C++ and Python coverage across exact NVFP4 quantization, GEMM, dequantization, recipe scope resolution, quantized tensor handling, numerics, sanity, CUDA graph, torch compile, CPU offload, fusible ops, and backward override paths, while documenting the new environment variable and known unsupported modes.

Checklist:

  • I have read and followed the contributing guidelines
  • The functionality is complete
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

@zianglih
zianglih marked this pull request as draft May 9, 2026 03:50
@zianglihzianglih changed the title Implement 4over6 nvfp4Implement 4over6 nvfp4 recipeMay 9, 2026
@zianglihzianglih changed the title Implement 4over6 nvfp4 recipeImplement 4over6 NVFP4 recipeMay 9, 2026
@greptile-apps

greptile-appsBot commented May 9, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR implements the 4over6 NVFP4 quantization scheme from the FourOverSix paper, enabling per-block selection between map-to-4 and map-to-6 FP4 candidates based on a configurable error metric (MAE or MSE). The feature is opt-in via NVTE_NVFP4_4OVER6 and is scoped to weights, activations, or both; RHT and stochastic rounding are explicitly rejected when 4over6 is active.

  • Adds a new dedicated CUDA kernel (quantize_4over6_nvfp4.cuh) with a 2-stage software pipeline, warp-level error reductions for 2D quantization, and Blackwell PTX round-trip for bitwise-exact dequantization error; threads nvfp4_4over6_mode and nvfp4_e4m3_max through the full Python→C++ tensor/quantizer/config chain.
  • Updates the per-tensor GEMM scale kernel in nvfp4.cu to accept per-tensor fp8_max values (256 or 448) instead of a hardcoded 448, enabling correct mixed-bound GEMM scaling for 4over6 operands.
  • Changes backward quantizer selection in quantization.py from mode==\"backward\" to explicit tensor-type checking (\"grad_output\" / \"grad_input\"), which is required for correct 4over6 scoping but is a broader behavioral change affecting all backward-mode quantization.

Confidence Score: 4/5

The PR is safe to merge for the primary 4over6 path; the only notable concern is a backward-pass quantizer selection change that broadens in scope beyond 4over6.

The 4over6 CUDA kernel logic, warp-level reductions, and scale-pair computation are correct. The full Python→C++ metadata chain is consistently threaded across all tensor lifecycle paths. The GEMM scaling fix is mathematically sound for mixed E4M3 bounds. The backward quantizer dispatch change (from mode=="backward" to tensor_type in (...)) is intentional per the PR description but is broader than 4over6: any backward-mode tensor typed "weight" now silently receives forward weight quantization params, which could include RHT if enabled. Additionally, the RHT split-quantization helper contains dead code that sets 4over6 configs after an unconditional early-exit check.

transformer_engine/pytorch/quantization.py (backward dispatch change) and transformer_engine/pytorch/csrc/extensions/cast.cpp (dead config-setting code in RHT helper)

Important Files Changed

FilenameOverview
transformer_engine/common/cast/nvfp4/quantize_4over6_nvfp4.cuhNew 668-line CUDA kernel implementing 4over6 adaptive block-scale selection; 2-stage pipeline and warp-level reductions are logically correct; minor performance issue: the initial stage-0 async copy is waited synchronously before the main loop, negating pipeline overlap for the first stage.
transformer_engine/pytorch/quantization.pyChanges _qparams() from mode-based dispatch to tensor_type-based dispatch; intentional for 4over6 scoping but silently shifts backward-mode "weight"-typed tensors from fp4_quant_bwd_grad to fp4_quant_fwd_weight (which may have RHT enabled).
transformer_engine/pytorch/csrc/extensions/cast.cppAdds 4over6 guards to grouped, RHT split, and standard split quantization paths; contains dead code in split_quantize_nvfp4_impl_with_rht_helper where 4over6 mode configs are set after the early-exit NVTE_CHECK.
transformer_engine/common/cast/nvfp4/dequantize_nvfp4.cuhPromotes row_scaled_nvfp4 and E4M3_MAX to compile-time template parameters; correctly dispatches E4M3_MAX=256 and E4M3_MAX=448 kernel variants with appropriate static_assert guards.
transformer_engine/common/recipe/nvfp4.cuUpdates per-tensor GEMM scale kernel to accept per-tensor fp8_max_A/fp8_max_B rather than hardcoded 448, correctly supporting mixed 256/448 E4M3 bounds across operand tensors.
transformer_engine/common/recipe/init.pyAdds nvfp4_4over6, nvfp4_4over6_e4m3_use_256, and nvfp4_4over6_err_mode fields to NVFP4BlockScaling; validation asserts are present and env-var defaults are correct.
transformer_engine/pytorch/tensor/nvfp4_tensor.pyPropagates nvfp4_use_4over6 and nvfp4_e4m3_max through tensor construction, view/reshape autograd functions, AG split metadata, and reduce_ex; coverage appears complete.
transformer_engine/pytorch/csrc/quantizer.cppCorrectly reads nvfp4_4over6_mode and nvfp4_e4m3_max from Python, validates supported values, and propagates them to C++ tensor wrappers and quant configs.
transformer_engine/pytorch/custom_recipes/quantization_ref_nvfp4.pyExtends the Python reference quantizer with a correct 4over6 implementation that mirrors the CUDA path: FP8-clipped block scale, TE-style FP4 quantization per candidate, input-domain error comparison, and tie-breaking towards map-to-6.
transformer_engine/common/include/transformer_engine/transformer_engine.hAdds kNVTENVFP4E4M3Max tensor param enum value and NVTENVFP44Over6Mode enum; extends TensorWrapper and QuantizationConfigWrapper with corresponding get/set helpers.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
A["Python: NVFP4BlockScaling(nvfp4_4over6='all')"] --> B["quantization.py: _make(tensor_type)"]
B -->|"grad_output / grad_input"| C["fp4_quant_bwd_grad\nnvfp4_use_4over6=False"]
B -->|"weight"| D["fp4_quant_fwd_weight\nnvfp4_use_4over6=True"]
B -->|"input / output"| E["fp4_quant_fwd_inp\nnvfp4_use_4over6=True"]
D --> F["NVFP4Quantizer\nnvfp4_4over6_mode=MinMAE\nnvfp4_e4m3_max=256"]
E --> F
F --> G["quantizer.cpp: quantize_impl()"]
G -->|"4over6 enabled"| H["quantize.cuh: nvfp4_use_4over6 branch"]
H -->|"2D"| I["quantize_4over6<use_2d=true>"]
H -->|"1D"| J["quantize_4over6<use_2d=false>"]
I & J --> K["quantize_4over6_kernel\n(128 threads, 2-stage pipeline)"]
K --> L["compute_scale_pair\nmap4: scale×1.5, map6: scale×1.0"]
L --> M["make_candidates\nFP4-quant + dequant error per candidate"]
M --> N["reduce_group_sum_16 / max_16\n(warp-level, 16-thread groups)"]
N --> O{"err_map4 < err_map6?"}
O -->|"Yes"| P["Write map4 packed + scale"]
O -->|"No (tie→map6)"| Q["Write map6 packed + scale"]
P & Q --> R["NVFP4Tensor\n_nvfp4_use_4over6=True\n_nvfp4_e4m3_max=256"]
R --> S["nvfp4.cu: compute_per_tensor_scale\nfactor = fp4² × fp8_max_A × fp8_max_B"]
S --> T["NVFP4 GEMM"]
Loading

Reviews (21): Last reviewed commit: "Merge branch 'main' into 4over6" | Re-trigger Greptile

Comment threadtransformer_engine/pytorch/csrc/extensions/cast.cpp Outdated
Comment threadtransformer_engine/common/recipe/__init__.py Outdated
Comment threadtests/pytorch/test_sanity.py Outdated
@zianglih

zianglih commented May 11, 2026

Copy link
Copy Markdown
ContributorAuthor

Functionality has been verified by internal RL experiments.
We may want to allow separate 4over6 config for weights and activations, maybe NVTE_NVFP4_ENABLE_4OVER6=weights|activations|all.

@ptrendx
ptrendx requested a review from negvetMay 11, 2026 17:12
@ptrendxptrendx added community-contribution PRs from external contributor outside the core maintainers, representing community-driven work. fp4 labels May 11, 2026
@zianglih

Copy link
Copy Markdown
ContributorAuthor

Need to rebase.

@zianglih
zianglih marked this pull request as draft May 11, 2026 21:17
@zianglih
zianglih marked this pull request as ready for review May 11, 2026 22:36
* its values are populated during quantization.
*/
kNVTERowScaledNVFP4 = 8,
kNVTENVFP44Over6 = 9, /*!< Whether an NVFP4 tensor uses 4over6 scaling */

@timmoon10timmoon10May 11, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are specifying this redundantly in NVTETensor and NVTEQuantizationConfig. If this option can be isolated to quantization, then we should not add clutter to the tensor. If the option is needed for downstream consumers (dequantization, GEMM), then it should be treated as part of the tensor data. I'm not especially familiar, but 4over6 seems like it should be specific to quantization.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4over6 changes the decode convention from 1 / (6 * 448) to 1 / (6 * 256). Therefore, for our current representation 4over6 is part of the tensor data contract, not just a quantization option.

using namespace detail;
constexpr float fp8_max = TypeExtrema<fp8e4m3>::max; // 448.0f;
constexpr float fp4_max = TypeExtrema<fp4e2m1>::max; // 6.0f;
constexpr float fp8_max = USE_4OVER6 ? 256.0f : TypeExtrema<fp8e4m3>::max; // 448.0f;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How much benefit does changing the FP8 scale have on convergence? If we don't see a clear benefit, then it would be nicer to use the same scale for 4over6 and non-4over6. That way keep can keep this logic confined to quantization, and downstream consumers are completely unaffected.

If there is an impact on training quality, we should still consider disentangling the FP8 scaling from 4over6. I don't see why other NVFP4 recipes might not benefit from tweaking the scaling.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the original paper:

Finally, we make one modification to the computation of the tensor scale α (Equation 1) when
quantizing to NVFP4 with 4/6. When MFP4 ×MFP8 is used to compute the tensor scale, it ensures
that all quantized values will be less than 6 ×448. However, this makes it impossible to select a scale
of 4 for the blocks that contain a tensor’s largest values, because the block’s scale would need to be
448 × 6/4 = 672, which would overflow since 448 is the maximum value that can be represented by
E4M3. As a result, when computing the tensor scale, we replace MFP8 to 256 in Equation 1, since
256 is the largest E4M3 that can be multiplied by 6/4 and represented without error in E4M3, as 384.

Also:

In Section 3.1, we propose calculating the FP32 global tensor scale using 256 as the maximum FP8
E4M3 value rather than the default of 448, as this allows blocks with a tensor’s largest value to have
the option to have a largest FP4 value of 4. In Figure 6, we find that this provides a marginal benefit
over using the standard tensor scale calculation. Even though this adjustment only affects a small
number of large values, this performance gain may come from the fact that larger activation values
can have an outsize impact on model performance. This adjustment is incorporated into the remaining
experiments in this section.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if there are internal or external studies about the convergence. But this is required to make it work. We need the largest value that is smaller than 448/1.5 and which is itself, and its multiplication by 1.5 is represented by E4M3 exactly. This would help to avoid quantization noise on both map to 4 and map to 6 paths.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We did find the use of 256 to calculate the second level scaling factor helped convergence vs 448, but only slightly.

It's possible that the premise of the paper's argument (prevent saturations when 4 scaling effectively multiplies the block decode scale by 1.5) is sound, but a value larger than 256 can achieve this and the perfect representation of the block with the global amax value with both scalings is not worth the extra range loss.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me make 256 scaling a separate env var disabled by default

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

448, 320, 288, 256 are all potential candidates for map-to-6:

  • 448: effectively disable map-to-4 option above 256, preserve range
  • 320, 288: map-to-4 uses 448, no precise 1.5x
  • 256: map-to-4 uses 384, precise 1.5x

For now let me refactor the interface to NVTE_NVFP4_4OVER6_E4M3="448"|"256", default to "448" and dispatches to a number in template parameter in C++ code instead of a boolean toggle. People can add support for other values or make it more generic (like directly parsing the env var digits) in the future.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NVTE_NVFP4_4OVER6_E4M3_USE_256=weights|activations|all is a cleaner pattern and allows separate configuration.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For our RL experiments we do observe 256 leads to less mismatch vs 448.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since 256 is slightly better for our internal and RL (and paper) studies, what about enabling it by default? Keeping 448 as an option (via NVTE_NVFP4_4OVER6_E4M3_USE_256=None) for those who want to trade off precision for range.

cc @kwyss-nvidia

Comment threadtests/pytorch/utils.py Outdated
Comment threadtransformer_engine/common/cast/dispatch/quantize.cuh Outdated
Comment threadtests/cpp/operator/test_dequantize_nvfp4.cu
@zianglih
zianglih marked this pull request as draft May 12, 2026 02:01
@zianglih
zianglih marked this pull request as ready for review May 12, 2026 06:45
@zianglih
zianglih requested a review from timmoon10May 12, 2026 06:47
@zianglih
zianglih marked this pull request as draft May 12, 2026 09:03
@zianglih
zianglih marked this pull request as ready for review May 12, 2026 10:10
Comment threadtransformer_engine/common/recipe/__init__.py Outdated
using namespace detail;
constexpr float fp8_max = TypeExtrema<fp8e4m3>::max; // 448.0f;
constexpr float fp4_max = TypeExtrema<fp4e2m1>::max; // 6.0f;
constexpr float fp8_max = USE_4OVER6 ? 256.0f : TypeExtrema<fp8e4m3>::max; // 448.0f;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if there are internal or external studies about the convergence. But this is required to make it work. We need the largest value that is smaller than 448/1.5 and which is itself, and its multiplication by 1.5 is represented by E4M3 exactly. This would help to avoid quantization noise on both map to 4 and map to 6 paths.

@Oleg-Goncharov
Oleg-Goncharov self-requested a review May 12, 2026 16:37
@timmoon10

timmoon10 commented May 21, 2026

Copy link
Copy Markdown
Member

The dropout test failure doesn't show up in the nightly build, so something is going on. I'll try digging into it.

Edit: I reproduce the bug if I add extra NVFP4 test cases in the main branch. I think the extra test cases affected RNG in the dropout test and it got unlucky (p-value <0.01 is low, but not impossibly low). I've changed the RNG logic so that RNG state is reset for each test case.

Adding tests affected RNG in unrelated tests.
Signed-off-by: Tim Moon <tmoon@nvidia.com>
timmoon10
timmoon10 previously approved these changes May 21, 2026
@timmoon10

Copy link
Copy Markdown
Member

/te-ci pytorch

@zianglih

Copy link
Copy Markdown
ContributorAuthor

Now a different failure, likely relevant to the RNG reset:
https://github.com/NVIDIA/TransformerEngine/actions/runs/26263025517/job/77300412018#step:2:2871

FAILED ../../tests/pytorch/test_fusible_ops.py::TestSequentialModules::test_layernorm_mlp[nvfp4-dtype2-False-True-True-False] - AssertionError: Tensor-likes are not close!
Mismatched elements: 1 / 384 (0.3%)
Greatest absolute difference: 0.5362055950809819 at index (153,) (up to 0.5 allowed)
Greatest relative difference: 21.897700308376375 at index (153,) (up to 0.25 allowed)
FAILED ../../tests/pytorch/test_fusible_ops.py::TestSequentialModules::test_layernorm_mlp[nvfp4-dtype2-False-True-True-True] - AssertionError: Tensor-likes are not close!
Mismatched elements: 1 / 384 (0.3%)
Greatest absolute difference: 0.5362055950809819 at index (153,) (up to 0.5 allowed)
Greatest relative difference: 21.897700308376375 at index (153,) (up to 0.25 allowed)
FAILED ../../tests/pytorch/test_fusible_ops.py::TestSequentialModules::test_layernorm_mlp[nvfp4-dtype2-True-True-True-False] - AssertionError: Tensor-likes are not close!
Mismatched elements: 1 / 384 (0.3%)
Greatest absolute difference: 0.5362055950809819 at index (153,) (up to 0.5 allowed)
Greatest relative difference: 21.897700308376375 at index (153,) (up to 0.25 allowed)
FAILED ../../tests/pytorch/test_fusible_ops.py::TestSequentialModules::test_layernorm_mlp[nvfp4-dtype2-True-True-True-True] - AssertionError: Tensor-likes are not close!
Mismatched elements: 1 / 384 (0.3%)
Greatest absolute difference: 0.5362055950809819 at index (153,) (up to 0.5 allowed)
Greatest relative difference: 21.897700308376375 at index (153,) (up to 0.25 allowed)
=== 4 failed, 3946 passed, 9607 skipped, 2966 warnings in 415.33s (0:06:55) ====
Error: sub-test failed: test_fusible_ops.py

Let me relax and resolve conflict.

Signed-off-by: Ziang Li <ziangli@umich.edu>
@zianglih

zianglih commented May 22, 2026

Copy link
Copy Markdown
ContributorAuthor

Resolved merge conflicts and relaxed tolerance for nvfp4 test_layernorm_mlp:

https://github.com/zianglih/TransformerEngine/blob/5f2d761c9b8aa8239a7ebd079b44abc29a5ced9d/tests/pytorch/test_fusible_ops.py#L3666-L3667

root@B200-114:~/TransformerEngine# NVTE_GROUPED_LINEAR_SINGLE_PARAM=1 NVTE_CUTEDSL_FUSED_GROUPED_MLP=1 python3 -m pytest --tb=auto tests/pytorch/test_fusible_ops.py -k test_layernorm_mlp
=============================== test session starts ===============================
platform linux -- Python 3.12.3, pytest-9.0.2, pluggy-1.6.0
rootdir: /root/TransformerEngine
configfile: pyproject.toml
plugins: anyio-4.13.0, asyncio-1.3.0, typeguard-4.5.1
asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 15872 items / 15584 deselected / 288 selected tests/pytorch/test_fusible_ops.py ....ssssssssssss....ssssssssssss....sssss [ 14%]
sssssssssss............ssss............ssss............ssss............ssss [ 40%]
............ssss............ssss............ssss............ssss........... [ 66%]
.ssssssssssssssssssssssssssssssssssss............ssssssssssssssssssssssssss [ 92%]
ssssssssss............ [100%]
================================ warnings summary =================================
tests/pytorch/test_fusible_ops.py: 44 warnings
/root/TransformerEngine/transformer_engine/pytorch/quantized_tensor.py:126: UserWarning: Quantizer is being updated, this may affect model behavior
warnings.warn("Quantizer is being updated, this may affect model behavior")
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
========= 144 passed, 144 skipped, 15584 deselected, 44 warnings in 9.98s =========
root@B200-114:~/TransformerEngine# NVTE_GROUPED_LINEAR_SINGLE_PARAM=1 NVTE_CUTEDSL_FUSED_GROUPED_MLP=1 python3 -m pytest --tb=auto tests/pytorch/test_fusible_ops.py -k test_dropout
=============================== test session starts ===============================
platform linux -- Python 3.12.3, pytest-9.0.2, pluggy-1.6.0
rootdir: /root/TransformerEngine
configfile: pyproject.toml
plugins: anyio-4.13.0, asyncio-1.3.0, typeguard-4.5.1
asyncio: mode=Mode.STRICT, debug=False, asyncio_default_fixture_loop_scope=None, asyncio_default_test_loop_scope=function
collected 15872 items / 15764 deselected / 108 selected tests/pytorch/test_fusible_ops.py ......ssssss......ssssss................. [ 37%]
.ssssss......ssssss..................ssssss......ssssss............ [100%]
================================ warnings summary =================================
tests/pytorch/test_fusible_ops.py: 18 warnings
/root/TransformerEngine/transformer_engine/pytorch/tensor/float8_tensor.py:751: UserWarning: A function call(aten._to_copy.default) in <class 'transformer_engine.pytorch.tensor.float8_tensor.Float8Tensor'> may not return <class 'transformer_engine.pytorch.tensor.float8_tensor.Float8Tensor'> tensor as an output. It might cause an error in torch FSDP2!
warnings.warn(
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
========== 72 passed, 36 skipped, 15764 deselected, 18 warnings in 8.33s ==========

timmoon10and others added 2 commits May 22, 2026 05:58
Make sure tensors are representable in quantized format.
Signed-off-by: Tim Moon <tmoon@nvidia.com>
@timmoon10

Copy link
Copy Markdown
Member

Loosening tolerances to get tests to pass is kind of defeating their purpose. The root cause is that some of the reference values in the LayerNorm + MLP test were not perfectly representable in NVFP4, so we got more numerical error than expected and the test would fail if we got unlucky. I've pushed a fix. Hopefully this takes us over the finish line.

@timmoon10

Copy link
Copy Markdown
Member

/te-ci pytorch

timmoon10
timmoon10 previously approved these changes May 22, 2026
Oleg-Goncharov
Oleg-Goncharov previously approved these changes May 22, 2026

@Oleg-GoncharovOleg-Goncharov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. The only remaining point from my side is kernel performance, but we can revisit that in more detail later since it does not seem to slow down the end-to-end runtime too much.

negvet
negvet previously approved these changes May 22, 2026
Signed-off-by: Tim Moon <tmoon@nvidia.com>
@timmoon10
timmoon10 dismissed stale reviews from negvet, Oleg-Goncharov, and themself via dd2e4f8May 22, 2026 18:36
@timmoon10

Copy link
Copy Markdown
Member

/te-ci pytorch

@timmoon10
timmoon10 merged commit dc9af4a into NVIDIA:mainMay 22, 2026
14 of 26 checks passed
Baibaifan pushed a commit to Baibaifan/TransformerEngine that referenced this pull request Jun 1, 2026
* Initial implementation
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Make 4over6 compile time for dequant
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Expand 1d fwd+bwd test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add gemm test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add more tests and fix offload
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Fix offload
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up arg
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add more test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add more tests
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor cuh kernel impl
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Further extract
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add recipe_id
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Fix failing unit tests
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor ref
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Update comments and docs
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Drop unnecessary test_sanity workaround
The following tests passed:
`NVTE_GROUPED_LINEAR_SINGLE_PARAM=1 python3 -m pytest --tb=auto tests/pytorch/test_sanity.py
`
`NVTE_GROUPED_LINEAR_SINGLE_PARAM=1 NVTE_TEST_NVINSPECT_ENABLED=1 NVTE_TEST_NVINSPECT_CONFIG_FILE=tests/pytorch/debug/test_configs/dummy_feature.yaml NVTE_TEST_NVINSPECT_FEATURE_DIRS=transformer_engine/debug/features PYTORCH_JIT=0 NVTE_TORCH_COMPILE=0 NVTE_ALLOW_NONDETERMINISTIC_ALGO=0 python3 -m pytest --tb=auto tests/pytorch/test_sanity.py
`
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor `QuantizerRole`
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Allow separate recipe 4over6 config
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Support 2d
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor 2d
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up anti pattern
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Enforce 4over6 consistency
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Update comments
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Update docs
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Fix test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Drop test_fusible_ops
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Revert "Drop test_fusible_ops"
This reverts commit 69f9ccc.
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor test_fusible_ops
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor ref and extend cpp test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up cpp test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Minor comment
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Drop doc
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Explicit handle conditional smem buffer
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Further clean up
Signed-off-by: Ziang Li <ziangli@umich.edu>
* More templates
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Simplify cpp
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Drop write back lifting
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add MAE and dedicated fast math env var
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Harden cpp test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add warning and err fast math coverage
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Fold test case and clean up cpp test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Initial 448 vs 256 implementation
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Use e4m3 max instead of boolean, more template
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add benchmark script and minor optimization
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Use standalone kernels
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Use cp async
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add benchmark script
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Minor fix after rebase
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Naming consistency
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Remove 4over6 benchmark
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor modes
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Relax tol for `test_layernorm_mlp` for `nvfp4_4over6`
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Minor fix recipe naming
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Remove gradient 4over6 quantization and partially allow SR/RHT
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Allow RHT in pytorch ref
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Update transformer_engine/pytorch/csrc/quantizer.cpp
Signed-off-by: Tim Moon <4406448+timmoon10@users.noreply.github.com>
* Minor fix TODO lint
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Use standard nvfp4 for grad ref in test_fusible_ops.py since 4over6 is not applied to gradient quantizers
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Minor fix test-fusible_ops 4over6 helper
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Default to 256 for 4over6
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Reset RNG state for each TE ops test
Adding tests affected RNG in unrelated tests.
Signed-off-by: Tim Moon <tmoon@nvidia.com>
* Remove loosened NVFP4 tols in layernorm MLP test.
Make sure tensors are representable in quantized format.
Signed-off-by: Tim Moon <tmoon@nvidia.com>
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Signed-off-by: Ziang Li <ziangli@umich.edu>
Signed-off-by: Tim Moon <4406448+timmoon10@users.noreply.github.com>
Signed-off-by: Tim Moon <tmoon@nvidia.com>
Co-authored-by: Tim Moon <4406448+timmoon10@users.noreply.github.com>
Co-authored-by: Tim Moon <tmoon@nvidia.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Signed-off-by: yangfan.bai <yangfan.bai@shopee.com>
Baibaifan pushed a commit to Baibaifan/TransformerEngine that referenced this pull request Jun 1, 2026
* Initial implementation
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Make 4over6 compile time for dequant
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Expand 1d fwd+bwd test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add gemm test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add more tests and fix offload
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Fix offload
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up arg
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add more test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add more tests
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor cuh kernel impl
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Further extract
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add recipe_id
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Fix failing unit tests
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor ref
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Update comments and docs
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Drop unnecessary test_sanity workaround
The following tests passed:
`NVTE_GROUPED_LINEAR_SINGLE_PARAM=1 python3 -m pytest --tb=auto tests/pytorch/test_sanity.py
`
`NVTE_GROUPED_LINEAR_SINGLE_PARAM=1 NVTE_TEST_NVINSPECT_ENABLED=1 NVTE_TEST_NVINSPECT_CONFIG_FILE=tests/pytorch/debug/test_configs/dummy_feature.yaml NVTE_TEST_NVINSPECT_FEATURE_DIRS=transformer_engine/debug/features PYTORCH_JIT=0 NVTE_TORCH_COMPILE=0 NVTE_ALLOW_NONDETERMINISTIC_ALGO=0 python3 -m pytest --tb=auto tests/pytorch/test_sanity.py
`
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor `QuantizerRole`
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Allow separate recipe 4over6 config
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Support 2d
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor 2d
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up anti pattern
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Enforce 4over6 consistency
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Update comments
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Update docs
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Fix test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Drop test_fusible_ops
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Revert "Drop test_fusible_ops"
This reverts commit 69f9ccc.
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor test_fusible_ops
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor ref and extend cpp test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Clean up cpp test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Minor comment
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Drop doc
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Explicit handle conditional smem buffer
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Further clean up
Signed-off-by: Ziang Li <ziangli@umich.edu>
* More templates
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Simplify cpp
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Drop write back lifting
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add MAE and dedicated fast math env var
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Harden cpp test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add warning and err fast math coverage
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Fold test case and clean up cpp test
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Initial 448 vs 256 implementation
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Use e4m3 max instead of boolean, more template
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add benchmark script and minor optimization
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Use standalone kernels
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Use cp async
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Add benchmark script
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Minor fix after rebase
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Naming consistency
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Remove 4over6 benchmark
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Refactor modes
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Relax tol for `test_layernorm_mlp` for `nvfp4_4over6`
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Minor fix recipe naming
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Remove gradient 4over6 quantization and partially allow SR/RHT
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Allow RHT in pytorch ref
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Update transformer_engine/pytorch/csrc/quantizer.cpp
Signed-off-by: Tim Moon <4406448+timmoon10@users.noreply.github.com>
* Minor fix TODO lint
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Use standard nvfp4 for grad ref in test_fusible_ops.py since 4over6 is not applied to gradient quantizers
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Minor fix test-fusible_ops 4over6 helper
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Default to 256 for 4over6
Signed-off-by: Ziang Li <ziangli@umich.edu>
* Reset RNG state for each TE ops test
Adding tests affected RNG in unrelated tests.
Signed-off-by: Tim Moon <tmoon@nvidia.com>
* Remove loosened NVFP4 tols in layernorm MLP test.
Make sure tensors are representable in quantized format.
Signed-off-by: Tim Moon <tmoon@nvidia.com>
* [pre-commit.ci] auto fixes from pre-commit.com hooks
for more information, see https://pre-commit.ci
---------
Signed-off-by: Ziang Li <ziangli@umich.edu>
Signed-off-by: Tim Moon <4406448+timmoon10@users.noreply.github.com>
Signed-off-by: Tim Moon <tmoon@nvidia.com>
Co-authored-by: Tim Moon <4406448+timmoon10@users.noreply.github.com>
Co-authored-by: Tim Moon <tmoon@nvidia.com>
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Signed-off-by: yangfan.bai <yangfan.bai@shopee.com>
aleozlx pushed a commit to flashinfer-ai/flashinfer that referenced this pull request Jun 3, 2026
<!-- .github/pull_request_template.md -->
## 📌 Description
@HumansAnd
Adds a CuTe DSL NVFP4 quantization backend with support for per-token
activation quantization and NVFP4 4over6 scale-candidate selection.
The 4over6 configuration remains environment-driven to match the
existing kernel configuration flow:
- `FLASHINFER_NVFP4_4OVER6=1`
- `FLASHINFER_NVFP4_4OVER6_ERR_MODE=MAE|MSE`
- `FLASHINFER_NVFP4_4OVER6_ERR_USE_FAST_MATH=0|1`
- `FLASHINFER_NVFP4_4OVER6_E4M3_USE_256=0|1`
- `FLASHINFER_DISABLE_FP4_QUANT_FAST_MATH=0|1`
- C++ dispatch also accepts `TRTLLM_DISABLE_FP4_QUANT_FAST_MATH=1` as a
compatibility fallback. Python/CuTe DSL paths use the FlashInfer env
name.
The public 4over6 error-mode surface is now only `MAE` and `MSE`. The
former FP16-specific public modes were removed. Instead,
`FLASHINFER_NVFP4_4OVER6_ERR_USE_FAST_MATH` selects the candidate-error
contract:
- `0`: strict full-dequant candidate scoring with explicit RN
arithmetic.
- Per-token NVFP4 uses the online row amax and scores candidates with
the TE-style expression `(e2m1 * sf * global_amax) / (6 * e4m3_max)`.
- Per-tensor NVFP4 uses the global decode scale path derived from the
provided global scale, rather than materializing a separate global amax
in the Python API. This avoids adding an always-on input reduction for
per-tensor callers.
- CUDA and CuTe DSL follow the same per-token/per-tensor split.
- `1`: FP16-domain candidate scoring. Candidates are decoded through the
E2M1 x E4M3 FP16 conversion path and compared against `x *
global_encode_scale`; this path also uses explicit RN arithmetic for
candidate error scoring.
~~Earlier revisions treated the strict full-dequant path as a single
TE-style expression for both per-token and per-tensor NVFP4.~~ The
current contract only requires the TE expression for the per-token path
where amax is already computed online; per-tensor uses the existing
global decode scale expression to avoid extra overhead.
CUDA, CuTe DSL, and fused MoE dispatch all route through the shared
`NVFP44Over6Config<e4m3_max, err_mode, err_use_fast_math>`
configuration. The old 4over6 candidate-error paths that used
unqualified fast-math arithmetic were removed.
## 🔍 Related Issues
TE PR that implements the same contract:
- NVIDIA/TransformerEngine#3068
Other relevant PRs:
- #3387
- NVIDIA/TransformerEngine#2972
- #3264
- #3027
## 🚀 Pull Request Checklist
Thank you for contributing to FlashInfer! Before we review your pull
request, please make sure the following items are complete.
### ✅ Pre-commit Checks
- [x] I have installed `pre-commit` by running `pip install pre-commit`
(or used your preferred method).
- [x] I have installed the hooks with `pre-commit install`.
- [x] I have run the hooks manually with `pre-commit run --all-files`
and fixed any reported issues.
> If you are unsure about how to set up `pre-commit`, see [the
pre-commit documentation](https://pre-commit.com/).
## 🧪 Tests
- [x] Tests have been added or updated as needed.
- [x] All tests are passing (`unittest`, etc.).
Unit tests extended:
- `tests/test_helpers/utils_fp4.py` extends the TE-style NVFP4/4over6
reference path for both full-dequant scoring and FP16-domain scoring.
- `tests/utils/test_fp4_quantize.py` extends
`test_nvfp4_quantize_te_reference` to cover strict bitwise CUDA and CuTe
DSL agreement for NVFP4, per-token activation, scale-factor layouts,
zero inputs, and 4over6 MAE/MSE configurations.
Commands run:
```bash
pre-commit run --all-files
```
Passed.
```bash
python3 -m compileall \
flashinfer/quantization/nvfp4_quantization_utils.py \
tests/test_helpers/utils_fp4.py
```
Passed.
```bash
CUDA_VISIBLE_DEVICES=0 \
python3 -m pytest -q --tb=short tests/utils/test_fp4_quantize.py::test_nvfp4_quantize_te_reference
```
Passed: `9504 passed in 756.79s (0:12:36)`.
```bash
CUDA_VISIBLE_DEVICES=0 \
python3 -m pytest -q --tb=short tests/moe/test_trtllm_cutlass_fused_moe.py
```
Passed: `75 passed, 25 skipped, 1 warning in 74.79s (0:01:14)`.
```bash
CUDA_VISIBLE_DEVICES=0 \
python3 -m pytest -q --tb=short tests/moe/test_trtllm_gen_per_token_moe.py
```
Passed: `108 passed in 785.08s (0:13:05)`.
## Benchmarking
Updated benchmark run compares PR head (`b44e847b`) against the earliest
PR commit with CUDA 4over6 support (`e30a0b14`). The denominator for
every 4over6 speedup is the baseline CUDA 4over6 MSE no-fast time for
the same `(M, K)` case.
Common config:
- dtype: `bfloat16`
- scale-factor layout: `swizzled_128x4`
- `FLASHINFER_NVFP4_4OVER6=1`
- `FLASHINFER_NVFP4_4OVER6_ERR_MODE=MSE`
- `FLASHINFER_NVFP4_4OVER6_E4M3_USE_256=1`
- `FLASHINFER_DISABLE_FP4_QUANT_FAST_MATH=1`
- CUDA graph disabled, CUPTI timing enabled, cold L2 cache enabled
Per-token runs add `--per-token-activation`; per-tensor runs omit it.
`FLASHINFER_NVFP4_4OVER6_ERR_USE_FAST_MATH` is set to `0` for no-fast
rows and `1` for fast rows.
Each table cell is `geomean / min / max` speedup over the scanned M/K
cases.
| Activation scale | CUDA no fast math | CUDA fast math | CuTe DSL no
fast math | CuTe DSL fast math |
| --- | ---: | ---: | ---: | ---: |
| Per-tensor | 2.83x / 1.65x / 5.81x | 3.22x / 1.67x / 7.25x | 2.17x /
1.21x / 8.26x | 4.12x / 2.45x / 20.92x |
| Per-token | 0.89x / 0.71x / 0.98x | 1.41x / 1.05x / 3.40x | 2.05x /
1.03x / 2.89x | 3.00x / 1.80x / 4.17x |
Pure per-token NVFP4 without 4over6, same dtype/layout/quant-fast-math
setting:
| Mode | CuTe DSL vs CUDA geomean/min/max |
| --- | ---: |
| Pure per-token no 4over6 | 2.24x / 1.04x / 3.76x |
Heat maps:
![NVFP4 4over6 MSE per-tensor speedup heat
map](https://gist.githubusercontent.com/zianglih/1e1fdd42d27244692d66cd6a5b2b904f/raw/2f4f7965c0b57e7b148acdde64a632daa11072a6/nvfp4_4over6_mse_speedup_vs_baseline_per_tensor.svg)
![NVFP4 4over6 MSE per-token speedup heat
map](https://gist.githubusercontent.com/zianglih/1e1fdd42d27244692d66cd6a5b2b904f/raw/23fc9c84605795b9c17febac47f0175fc14c3a07/nvfp4_4over6_mse_speedup_vs_baseline_per_token.svg)
![NVFP4 pure per-token no-4over6 CuTe DSL vs CUDA heat
map](https://gist.githubusercontent.com/zianglih/1e1fdd42d27244692d66cd6a5b2b904f/raw/34e161c89dc0a23378c5d23041567953b8f7c9b2/nvfp4_pure_per_token_no_4over6_cute_vs_cuda.svg)
Previous benchmark snapshot kept for review context:
| Activation scale | Baseline CUDA no-fast geomean (ms) | CUDA no-fast |
CUDA fast | CuTe DSL no-fast | CuTe DSL fast |
| --- | ---: | ---: | ---: | ---: | ---: |
| Per-tensor | ~~0.074336~~ | ~~1.000x~~ | ~~1.668x~~ | ~~1.434x~~ |
~~1.738x~~ |
| Per-token | ~~0.065345~~ | ~~0.974x~~ | ~~1.554x~~ | ~~2.297x~~ |
~~3.267x~~ |
- ~~Previous benchmark run compared current PR head (`e5349fc2`) against
a pre-FP16-refactor baseline (`99a21fcd`).~~
- ~~Previous common config used
`FLASHINFER_NVFP4_4OVER6_E4M3_USE_256=0`.~~
- ~~Previous table reported only geomean speedup.~~
Full 22x15 per-case data was generated separately and kept out of the PR
body.
## Reviewer Notes
The 4over6 path can become compute-bound at large inputs. The benchmark
path keeps the built-in sweep and uses environment variables for 4over6
mode selection rather than adding 4over6-specific shape or mode CLI
arguments.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added 4over6 quantization mode for NVFP4 with configurable error
metrics (MAE/MSE)
* Added per-token activation support for NVFP4 quantization
* Extended CuTe-DSL backend capabilities for improved quantization
operations
* **Documentation**
* Updated quantization benchmarks with new 4over6 mode configuration
examples
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Signed-off-by: Ziang Li <ziangli@umich.edu>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contributionPRs from external contributor outside the core maintainers, representing community-driven work.fp4

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants

@zianglih@Oleg-Goncharov@negvet@timmoon10@kwyss-nvidia@ptrendx