Skip to content

[Common][PyTorch] Add NVFP4 row-scaled grouped cast with graph-safe path - #3649

Merged
timmoon10 merged 13 commits into
NVIDIA:mainfrom
cael-ling:pr/nvfp4-row-scaled-grouped-cast
Oct 27, 2026
Merged

timmoon10 merged 13 commits into
NVIDIA:mainfrom
cael-ling:pr/nvfp4-row-scaled-grouped-cast

Conversation

@cael-ling

Copy link
Copy Markdown
Contributor

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_offsets instead 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

  • 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:

  • Change A
  • Change B

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

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>
@github-actions github-actions Bot added the community-contribution PRs from external contributor outside the core maintainers, representing community-driven work. label Oct 8, 2026
@greptile-apps

greptile-apps Bot commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The changes appear safe to merge, with no outstanding findings.

Summary

Adds grouped row-scaled NVFP4 quantization with device-side routing for CUDA graph replay.

  • Grouped casts encode packed expert rows as row-scaled NVFP4.
  • Grouped amax calls compute per-row values for row-scaled NVFP4.
  • PyTorch grouped quantization routes row-scaled NVFP4 calls through the grouped cast.

The latest change adds the header that declares nvte_group_amax. No new issues were found. All six earlier findings are addressed: the per-expert fallback preserves supported shapes, empty inputs, and quantizer checks; grouped input is made contiguous; uniform expert sizes require 128-row alignment; and the operator test includes the required declaration.

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]
Loading

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>
cael-ling and others added 3 commits October 9, 2026 05:02
…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>
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>
@cael-ling cael-ling mentioned this pull request Oct 13, 2026
1 of 13 tasks
@timmoon10
timmoon10 self-requested a review October 14, 2026 03:01

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.

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.

Comment thread transformer_engine/pytorch/ep.py Resolved
Comment thread tests/cpp/test_common.h Resolved
cael-ling and others added 2 commits October 21, 2026 22:07
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>
@cael-ling

Copy link
Copy Markdown
Contributor Author

@timmoon10 Thanks, the reviews have been replied separately, please help review once you have a window.

@cael-ling
cael-ling requested a review from timmoon10 October 22, 2026 09:40
Comment on lines 2174 to 2190

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.

Now that we have the graph-safe path, robustness is more important than performance for the split-quantize path. I suggest simplifying the logic:

Suggested change
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;
}

Comment thread tests/cpp/test_common.h Resolved
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 Bot and others added 2 commits October 26, 2026 22:28
Review suggestion from @greptile-apps

Signed-off-by: Tim Moon <tmoon@nvidia.com>
@timmoon10

Copy link
Copy Markdown
Member

/te-ci

timmoon10
timmoon10 previously approved these changes Oct 26, 2026

@timmoon10 timmoon10 left a comment

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.

LGTM, pending CI

Signed-off-by: Tim Moon <tmoon@nvidia.com>
@timmoon10

Copy link
Copy Markdown
Member

/te-ci

@timmoon10
timmoon10 merged commit 89668d9 into NVIDIA:main Oct 27, 2026
24 of 29 checks passed
phu0ngng pushed a commit to phu0ngng/TransformerEngine that referenced this pull request Oct 27, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-contribution PRs from external contributor outside the core maintainers, representing community-driven work.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants