[Common][PyTorch] Fuse NVFP4 quantization launches for MoE experts#3261
Open
liujshi wants to merge 3 commits into
Open
[Common][PyTorch] Fuse NVFP4 quantization launches for MoE experts#3261liujshi wants to merge 3 commits into
liujshi wants to merge 3 commits into
Conversation
Summary
-------
Two complementary fusions that reduce NVFP4 quantization kernel
launches by ~96% for MoE expert layers while keeping outputs
bit-identical to the per-tensor loop.
1. Row-scaled activations (fused whole-buffer quantization):
Row-scaled NVFP4 quantization is row-local (per-row amax, 1x16
block scales and 4/6 candidate selection never cross rows), so the
whole expert input buffer can be quantized in a single kernel
launch and split into zero-copy per-expert row views.
- Add NVFP4TensorStorage.slice_rows: zero-copy row-range view of a
rowwise-quantized NVFP4 tensor, with optional scale padding to
the 128-row cuBLAS block-scaling layout.
- grouped_linear: fused input quantization for row-scaled NVFP4
(env: NVTE_ROW_SCALED_FUSED_QUANTIZE, default on).
2. Batched 4over6 weight quantization (per-tensor-scaled tensors):
A new batched kernel quantizes all same-shaped tensors in two
launches (one batched amax pass + one batched quantize pass, with
grid.z tensor segments) instead of two launches per tensor. Each
tensor keeps its own scalar amax and global scale, so results are
bit-identical to the per-tensor loop. This also fills the grouped
quantization gap for the non-RHT path noted in
split_quantize_nvfp4_impl_helper.
- batched_amax_kernel (per-segment atomicMax; max is exact and
order-independent) and quantize_4over6_batched_kernel (reuses
the single-tensor pipeline per segment).
- New C API nvte_nvfp4_quantize_4over6_multi and torch extension
entry nvfp4_quantize_4over6_multi, with optional caller-provided
output workspaces to preserve weight-cache semantics.
- grouped_linear: batched weight quantization
(env: NVTE_NVFP4_BATCHED_WEIGHT_QUANTIZE, default on).
Benchmarks (Qwen3-30B-A3B, 48 layers, seq 8192, 4xB300, mock data,
nsys over 2 steady-state training steps)
---------------------------------------------------------------
before after delta
quantize_4over6 kernel time 2053 ms 832 ms -59.5%
quantization launches (2 steps) 71564 3072 -95.7%
quantization total (amax + swizzle
+ backward dequantize) 3061 ms 1529 ms -50.0%
total compute time 9.197 s 7.606 s -17.3%
GEMM time (sanity check) 1598 ms 1603 ms unchanged
Correctness
-----------
- Kernel-level bitwise equality vs the per-tensor loop for 64 weight
tensors (packed fp4 data, e4m3 block scales, scalar amax).
- Workspace-write path bitwise equality (32/32 tensors).
- Identical first-iteration loss and grad norm in mock training
(5-layer and 48-layer); later-iteration deviations match
run-to-run noise.
liujshi
requested review from
Oleg-Goncharov,
ksivaman and
ptrendx
as code owners
July 25, 2026 16:53
Contributor
Greptile SummaryThe PR reduces NVFP4 quantization launches for MoE expert layers.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Sequence DiagramsequenceDiagram
participant GL as GroupedLinear
participant Q as NVFP4 Quantizer
participant Ext as PyTorch Extension
participant Core as CUDA Core
participant GEMM as Grouped GEMM
GL->>Q: Quantize full activation buffer
Q-->>GL: Row-scaled NVFP4 tensor
GL->>GL: Create per-expert row views
GL->>Ext: Batch same-shaped expert weights
Ext->>Core: nvte_nvfp4_quantize_4over6_multi
Core->>Core: Batched amax pass
Core->>Core: Batched 4over6 quantization
Core-->>GL: Per-expert NVFP4 weights
GL->>GEMM: Expert activation and weight tensors
GEMM-->>GL: Grouped output
Reviews (3): Last reviewed commit: "Merge branch 'main' into nvfp4-fused-qua..." | Re-trigger Greptile |
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.
Summary
Two complementary fusions that reduce NVFP4 quantization kernel launches by ~96% for MoE expert layers while keeping outputs bit-identical to the per-tensor loop.
1. Row-scaled activations (fused whole-buffer quantization)
Row-scaled NVFP4 quantization is row-local (per-row amax, 1x16 block scales and 4/6 candidate selection never cross rows), so the whole expert input buffer can be quantized in a single kernel launch and split into zero-copy per-expert row views.
NVFP4TensorStorage.slice_rows: zero-copy row-range view of a rowwise-quantized NVFP4 tensor, with optional scale padding to the 128-row cuBLAS block-scaling layout.grouped_linear: fused input quantization for row-scaled NVFP4 (env:NVTE_ROW_SCALED_FUSED_QUANTIZE, default on).2. Batched 4over6 weight quantization (per-tensor-scaled tensors)
A new batched kernel quantizes all same-shaped tensors in two launches (one batched amax pass + one batched quantize pass, with
grid.ztensor segments) instead of two launches per tensor. Each tensor keeps its own scalar amax and global scale, so results are bit-identical to the per-tensor loop. This also fills the grouped quantization gap for the non-RHT path noted insplit_quantize_nvfp4_impl_helper.batched_amax_kernel(per-segmentatomicMax; max is exact and order-independent) andquantize_4over6_batched_kernel(reuses the single-tensor pipeline per segment).nvte_nvfp4_quantize_4over6_multiand torch extension entrynvfp4_quantize_4over6_multi, with optional caller-provided output workspaces to preserve weight-cache semantics.grouped_linear: batched weight quantization (env:NVTE_NVFP4_BATCHED_WEIGHT_QUANTIZE, default on).Benchmarks
Setup: Qwen3-30B-A3B, 48 layers, seq_len=8192, 4xB300, mock data, nsys over 2 steady-state training steps.
Correctness
Fixes # (issue)
Type of change
Changes
NVFP4TensorStorage.slice_rows(zero-copy row-range view, optional 128-row scale padding).grouped_linear: fused whole-buffer input quantization for row-scaled NVFP4 activations (NVTE_ROW_SCALED_FUSED_QUANTIZE=1default).batched_amax_kernelandquantize_4over6_batched_kernelincommon/cast/nvfp4/quantize_4over6_nvfp4.cuh(grid.z segmented, per-segment scalar amax).nvte_nvfp4_quantize_4over6_multi(common/cast/cast.cu,cast.h).tex.nvfp4_quantize_4over6_multi(pytorch/csrc/extensions/nvfp4_multi_quantize.cpp) with optional output workspaces.grouped_linear: batched 4over6 weight quantization for same-shaped expert weights (NVTE_NVFP4_BATCHED_WEIGHT_QUANTIZE=1default).Checklist