Repository navigation
[Common][PyTorch] Add NVFP4 row-scaled grouped cast with graph-safe path - #3649
Conversation
Single-launch grouped row-scaled NVFP4 cast + fused amax, plus graph-safe amax/cast variants driven by device first_dims/tensor_offsets for capture-once/replay MoE. Wired into PyTorch (ep.nvfp4_group_row_scaled_cast) with C++ and PyTorch capture/replay tests. Signed-off-by: Cael Ling <caell@nvidia.com>
[Medium impact] The changes appear safe to merge, with no outstanding findings. SummaryAdds grouped row-scaled NVFP4 quantization with device-side routing for CUDA graph replay.
The latest change adds the header that declares Diagram%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[Packed BF16 input] --> B[Grouped amax]
R[Device routing] --> B
B --> C[Grouped NVFP4 cast]
R --> C
C --> D[Packed FP4 data and scales]
Reviews (10) · Last reviewed commit: "Add missing header include" · Reviewed by Greptile |
Restrict the FUSED_NVFP4_ROW_SCALED selection to shapes the grouped kernels accept (128-aligned last dim, <= 64 experts) so unsupported requests fall back to the per-expert path. Return early for empty routing, make the graph-safe binding's input contiguous, and reject with_rht/with_2d_quantization/with_amax_reduction on both row-scaled entries. Add regression tests for the four guarded paths. Signed-off-by: Cael Ling <caell@nvidia.com>
…uting The grouped row-scaled cast treats its input as 2D [rows, last_dim] indexed by split counts, so route higher-rank inputs and partial splits (split sum below the row count) to the per-expert path instead of the grouped kernel. Make first_dims and tensor_offsets contiguous in the graph-safe cast so a strided routing view is not misread. Add fallback and strided-routing tests. Signed-off-by: Cael Ling <caell@nvidia.com>
for more information, see https://pre-commit.ci
The bulk NVFP4 split_quantize allocation sizes its amax buffer from the first quantizer's row-scale setting, so a group mixing row-scaled and tensor-scaled quantizers writes per-row amax into a per-tensor buffer and corrupts neighbors. Detect the mixed case and fall back to the unfused per-split path. Signed-off-by: Cael Ling <caell@nvidia.com>
There was a problem hiding this comment.
This file is dedicated to NCCL EP support. The new helper function is not related to EP, and in fact is general enough that it could be used beyond MoEs. transformer_engine/pytorch/tensor/grouped_tensor.py would be a more natural place.
@phu0ngng We should consider renaming this file ncclep.py to avoid confusion.
Add the kNVTEGroupedRowScaledNVFP4 grouped tensor param and dispatch row-scaled NVFP4 outputs in nvte_group_quantize to the graph-safe amax and cast kernels. Handle row-scaled quantizers in tex.group_quantize. Remove the row-scaled-specific C APIs, the host-split row-scaled cast kernel, tex.nvfp4_group_row_scaled_cast_graph_safe, the ep.py helper, and the fused row-scaled split_quantize path. Add a top-level static_assert for unregistered types in TypeInfo. Signed-off-by: Cael Ling <caell@nvidia.com>
for more information, see https://pre-commit.ci
Signed-off-by: Cael Ling <caell@nvidia.com>
for more information, see https://pre-commit.ci
|
@timmoon10 Thanks, the reviews have been replied separately, please help review once you have a window. |
There was a problem hiding this comment.
Now that we have the graph-safe path, robustness is more important than performance for the split-quantize path. I suggest simplifying the logic:
| const bool has_row_scaled_nvfp4 = | |
| std::any_of(quantizer_cpp_list.begin(), quantizer_cpp_list.end(), | |
| [](const std::unique_ptr<Quantizer> &quantizer) { | |
| return static_cast<NVFP4Quantizer *>(quantizer.get())->row_scaled_nvfp4; | |
| }); | |
| if (has_row_scaled_nvfp4) { | |
| allocation_method = AllocationMethod::UNFUSED; | |
| quantization_method = QuantizationMethod::UNFUSED; | |
| } else { | |
| allocation_method = AllocationMethod::BULK_NVFP4; | |
| quantization_method = QuantizationMethod::FUSED_NVFP4; | |
| } |
Dispatch nvte_group_amax and nvte_group_amax_graph_safe on the output tensor format so that row-scaled NVFP4 outputs get per-row amaxes, and deprecate nvte_group_nvfp4_compute_amax in favor of nvte_group_amax. Rename the row-scaled amax helpers to group_row_amax(_graph_safe). In split_quantize, fall back to unfused allocation and quantization whenever any NVFP4 quantizer is row-scaled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tim Moon <tmoon@nvidia.com>
for more information, see https://pre-commit.ci
Review suggestion from @greptile-apps Signed-off-by: Tim Moon <tmoon@nvidia.com>
|
/te-ci |
Signed-off-by: Tim Moon <tmoon@nvidia.com>
|
/te-ci |
…ath (NVIDIA#3649) * [Common][PyTorch] Add NVFP4 row-scaled grouped cast with graph-safe path Single-launch grouped row-scaled NVFP4 cast + fused amax, plus graph-safe amax/cast variants driven by device first_dims/tensor_offsets for capture-once/replay MoE. Wired into PyTorch (ep.nvfp4_group_row_scaled_cast) with C++ and PyTorch capture/replay tests. Signed-off-by: Cael Ling <caell@nvidia.com> * Address review: guard grouped row-scaled cast dispatch and inputs Restrict the FUSED_NVFP4_ROW_SCALED selection to shapes the grouped kernels accept (128-aligned last dim, <= 64 experts) so unsupported requests fall back to the per-expert path. Return early for empty routing, make the graph-safe binding's input contiguous, and reject with_rht/with_2d_quantization/with_amax_reduction on both row-scaled entries. Add regression tests for the four guarded paths. Signed-off-by: Cael Ling <caell@nvidia.com> * Guard row-scaled grouped cast against non-2D, partial, and strided routing The grouped row-scaled cast treats its input as 2D [rows, last_dim] indexed by split counts, so route higher-rank inputs and partial splits (split sum below the row count) to the per-expert path instead of the grouped kernel. Make first_dims and tensor_offsets contiguous in the graph-safe cast so a strided routing view is not misread. Add fallback and strided-routing tests. Signed-off-by: Cael Ling <caell@nvidia.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fall back to per-expert cast for mixed NVFP4 row/tensor-scaled splits The bulk NVFP4 split_quantize allocation sizes its amax buffer from the first quantizer's row-scale setting, so a group mixing row-scaled and tensor-scaled quantizers writes per-row amax into a per-tensor buffer and corrupts neighbors. Detect the mixed case and fall back to the unfused per-split path. Signed-off-by: Cael Ling <caell@nvidia.com> * Route row-scaled NVFP4 grouped quantize through nvte_group_quantize Add the kNVTEGroupedRowScaledNVFP4 grouped tensor param and dispatch row-scaled NVFP4 outputs in nvte_group_quantize to the graph-safe amax and cast kernels. Handle row-scaled quantizers in tex.group_quantize. Remove the row-scaled-specific C APIs, the host-split row-scaled cast kernel, tex.nvfp4_group_row_scaled_cast_graph_safe, the ep.py helper, and the fused row-scaled split_quantize path. Add a top-level static_assert for unregistered types in TypeInfo. Signed-off-by: Cael Ling <caell@nvidia.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Require 128-row experts for uniform row-scaled NVFP4 group_quantize Signed-off-by: Cael Ling <caell@nvidia.com> * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fold row-scaled NVFP4 amax into nvte_group_amax Dispatch nvte_group_amax and nvte_group_amax_graph_safe on the output tensor format so that row-scaled NVFP4 outputs get per-row amaxes, and deprecate nvte_group_nvfp4_compute_amax in favor of nvte_group_amax. Rename the row-scaled amax helpers to group_row_amax(_graph_safe). In split_quantize, fall back to unfused allocation and quantization whenever any NVFP4 quantizer is row-scaled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> 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 * Fix missing include Review suggestion from @greptile-apps Signed-off-by: Tim Moon <tmoon@nvidia.com> * Add missing header include Signed-off-by: Tim Moon <tmoon@nvidia.com> --------- Signed-off-by: Cael Ling <caell@nvidia.com> Signed-off-by: Tim Moon <tmoon@nvidia.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Tim Moon <tmoon@nvidia.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Description
Add a single-launch grouped row-scaled NVFP4 cast to the common layer, replacing the per-expert quantize loop used by MoE activations, together with a CUDA-graph-safe variant for dynamic-routing experts.
The grouped cast runs in two single-launch kernels: a grouped amax kernel over the packed (sum_M, K) buffer that produces per-expert rowwise [sum_M] and columnwise [num_tensors*K] amax vectors (both directions fused into one pass), followed by a grouped cast that consumes them.
Both steps have a graph-safe counterpart that reads routing from the grouped tensor's device
first_dims/tensor_offsetsinstead of host split metadata, so an MoE cast can be captured once and replayed with changed routing. The host-split and graph-safe kernels share the same device body, so their outputs are byte-identical.Type of change
Changes
Please list the changes introduced in this PR:
Checklist: