Skip to content

Improve device-init grouped linear module with single grouped weight support - #3224

Open
zhongbozhu wants to merge 21 commits into
NVIDIA:mainfrom
zhongbozhu:improve_device_grouped_linear
Open

Improve device-init grouped linear module with single grouped weight support #3224
zhongbozhu wants to merge 21 commits into
NVIDIA:mainfrom
zhongbozhu:improve_device_grouped_linear

Conversation

@zhongbozhu

@zhongbozhu zhongbozhu commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

Description

Fixes numerical issues when using single weight for TE module grouped linear. Limit the single weight feature to the grouped tensor API instead of the legacy path.

TODO: test E2E convergence, unit test directly from Mcore.

Note: needs to pay extra attention to whether bias grad and weight grad are generated properly.

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

@github-actions github-actions Bot added the community-contribution PRs from external contributor outside the core maintainers, representing community-driven work. label Jul 20, 2026
@zhongbozhu
zhongbozhu force-pushed the improve_device_grouped_linear branch from aa3b9d1 to 47ba66a Compare July 20, 2026 22:36
Comment thread transformer_engine/pytorch/module/grouped_linear.py Outdated
@zhongbozhu
zhongbozhu force-pushed the improve_device_grouped_linear branch from ff7eee2 to a43f70f Compare July 20, 2026 22:53
Comment thread transformer_engine/pytorch/module/grouped_linear.py Outdated
is_grad_enabled = torch.is_grad_enabled()
num_gemms = self.num_gemms

if FP8GlobalStateManager.fp8_graph_capturing():

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Note: this code block was deleted because it was duplicated

@zhongbozhu
zhongbozhu marked this pull request as ready for review July 23, 2026 10:52
@zhongbozhu

Copy link
Copy Markdown
Collaborator Author

/te-ci pytorch

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR improves the device-init grouped linear module by adding full single grouped weight/bias support restricted to the grouped tensor API, fixing numerical correctness issues where the old code allowed the single-weight path to silently fall through to the incompatible split-quantize path.

  • Grouped tensor path gating: introduces is_module_grouped_tensor_path_supported / is_op_fuser_grouped_tensor_path_supported predicates (now exported) that subsume the old private static method and add a backward_override check, with a RuntimeError guard in fuser_forward blocking single_grouped_weight/bias when use_grouped_tensor_path is false.
  • MXFP8 kernel fix: group_quantize_mxfp8.cuh corrects column-wise scale computation for SAME_BOTH_DIMS shape representation by dividing first_logical_dim by num_tensors to get per-member row strides; cublaslt_grouped_gemm.cu adds a zero-row early-return to launch_grouped_bias_add before accessing data pointers.
  • group_quantize output reuse: the C++ extension gains an output parameter enabling in-place update of pre-allocated GroupedTensor storage, making CUDA-graph-safe weight caching possible across microbatches.

Confidence Score: 4/5

Safe to merge with one minor concern about an implicit divisibility assumption in the MXFP8 kernel.

The PR is a well-scoped correctness fix with extensive new test coverage (8+ new tests). The numerical correctness issues it addresses — silent fall-through to the split-quantize path, zero-row null-pointer crash, and incorrect MXFP8 column-wise scale strides — are all properly fixed. The only remaining concern is the integer division first_logical_dim / num_tensors in the MXFP8 kernel which lacks a divisibility assertion; in practice the grouped linear module always creates same-shape experts so this is unlikely to trigger, but it is a latent silent-correctness hazard.

Files Needing Attention: transformer_engine/common/cast/mxfp8/group_quantize_mxfp8.cuh — the new tensor_rows_for_scales calculation lacks a divisibility guard.

Important Files Changed

Filename Overview
transformer_engine/pytorch/module/grouped_linear.py Major refactor adding single grouped weight/bias support to the module-level GroupedLinear; introduces is_module_grouped_tensor_path_supported, _prepare_weights/bias_for_grouped_tensor_gemm, make_grouped_weights, backward_dw single-weight path, and checkpoint remap helpers. Logic is well-structured with good test coverage.
transformer_engine/pytorch/ops/basic/grouped_linear.py Adds single grouped weight/bias support to the ops-level GroupedLinear fuser; exports is_op_fuser_grouped_tensor_path_supported; fixes _apply_delay_wgrad_param_hooks meta-device crash via _parameters.get(); grad_params ordering intentionally matches PyTorch ordered _parameters dict.
transformer_engine/common/cast/mxfp8/group_quantize_mxfp8.cuh Fixes column-wise scale computation for SAME_BOTH_DIMS / GEMM-swizzled single grouped weight; uses first_logical_dim/num_tensors as per-member row stride with no divisibility assertion — silent truncation is possible if row counts are not exact multiples.
transformer_engine/common/gemm/cublaslt_grouped_gemm.cu Adds total_rows==0 early-return guard to launch_grouped_bias_add before any data-pointer dereference, correctly fixing the null-pointer crash for zero-token batches.
transformer_engine/pytorch/csrc/extensions/cast.cpp Adds output-reuse path to group_quantize: validates num_tensors and logical_shape match, propagates noop_flag for FP8 block-scaling path, enabling CUDA-graph-safe in-place weight caching.
transformer_engine/pytorch/csrc/extensions.h Adds const py::object &output parameter to group_quantize declaration; straightforward signature update matching the implementation change.
transformer_engine/pytorch/csrc/extensions/pybind.cpp Registers py::arg("output") = py::none() for group_quantize binding; backward-compatible default keeps all existing call sites working.
transformer_engine/pytorch/ops/fused/grouped_mlp.py Minor updates to adopt is_op_fuser_grouped_tensor_path_supported; no substantive logic changes to the MLP fuser itself.
transformer_engine/pytorch/ops/basic/init.py Exports the new is_op_fuser_grouped_tensor_path_supported predicate; trivial change.
transformer_engine/pytorch/module/init.py Exports the new is_module_grouped_tensor_path_supported predicate; trivial change.
tests/pytorch/test_grouped_linear.py Adds 8 new tests covering zero-work bias, host m_splits rejection, numerical equivalence of single vs discrete grouped tensor path, workspace caching, swizzle bypass, primary MxFP8 bypass, parameter layout, and grouped bias return; replaces SM-cap guards with is_module_grouped_tensor_path_supported.
tests/pytorch/test_grouped_mlp.py Adds test for single grouped bias packed storage; replaces SM capability guards with is_op_fuser_grouped_tensor_path_supported predicate.
tests/pytorch/test_grouped_tensor.py Adds two tests for group_quantize output-reuse and noop semantics; loosens one tolerance from exact equality to rtol=1e-5/atol=4e-3 for a numerical stability fix.
tests/pytorch/test_sanity.py Adds shape alignment checks, use_grouped_tensor flag, cuda synchronize, and empty-split+FP8 skip guards to test_sanity_grouped_linear; also adds synchronize to test_sanity_linear_with_zero_tokens.

Sequence Diagram

sequenceDiagram
    participant User as Caller
    participant GL_mod as GroupedLinear (module)
    participant GL_ops as GroupedLinear (ops)
    participant PrepW as _prepare_weights_for_grouped_tensor_gemm
    participant GQ as group_quantize (C++ ext)
    participant GEMM as grouped_gemm kernel
    participant BiasAdd as launch_grouped_bias_add

    User->>GL_mod: forward(inp, m_splits, single_grouped_weight)
    GL_mod->>GL_mod: validate single_grouped_weight requires use_grouped_tensor
    GL_mod->>GL_ops: fuser_forward(...)
    GL_ops->>GL_ops: check is_op_fuser_grouped_tensor_path_supported()
    alt "single_grouped_weight=True"
        GL_ops->>PrepW: "_get_grouped_weight_for_gemm(single=True)"
        PrepW->>GQ: "group_quantize(..., output=cached_workspace)"
        GQ-->>PrepW: GroupedTensor (quantized, in-place reuse)
    else discrete weights
        GL_ops->>PrepW: "_prepare_weights_for_grouped_tensor_gemm(single=False)"
        PrepW->>GQ: group_quantize(weight_i...)
        GQ-->>PrepW: GroupedTensor
    end
    PrepW-->>GL_ops: grouped weight tensor
    GL_ops->>GEMM: general_grouped_gemm_for_grouped_tensor(...)
    GEMM-->>GL_ops: output activations
    GL_ops->>BiasAdd: launch_grouped_bias_add(...)
    Note over BiasAdd: early-return if total_rows==0
    BiasAdd-->>GL_ops: biased output
    GL_ops-->>GL_mod: output
    GL_mod-->>User: output

    User->>GL_mod: backward(grad_output)
    GL_mod->>GL_ops: fuser_backward(...)
    alt "single_grouped_weight=True"
        GL_ops->>GL_ops: compute wgrad (single grouped)
        GL_ops->>GL_mod: grad_weights (stacked or rowwise_data view)
        GL_mod->>GL_mod: set self.weight.grad
    else discrete weights
        GL_ops->>GL_mod: grad_weights list
        GL_mod->>GL_mod: set weight_i.grad for each expert
    end
    GL_ops-->>GL_mod: (dinp, dweights, dbiases)
    GL_mod-->>User: dinput
Loading

Reviews (20): Last reviewed commit: "add another specific case guard" | Re-trigger Greptile

Comment thread transformer_engine/pytorch/module/grouped_linear.py
Comment thread tests/pytorch/test_grouped_linear.py Outdated
Comment thread tests/pytorch/test_grouped_mlp.py Outdated

@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.

The biggest change in this PR is that TE is abandoning any attempt to make single_grouped_weight=True a general feature. Things must be exactly right, or we crash. Given how delicate and experimental this feature has been, I'm not opposed.

The second change is that users must opt-in to access the grouped GEMM kernel. This is also reasonable, since it has alignment requirements for m_splits and it's helpful having a way for users to accept that stricter contract.

We are experiencing many test failures. Given that single_grouped_weight is no longer a general feature, I think it's reasonable we move the corresponding tests to test_grouped_linear.py and test_grouped_mlp.py.

Comment on lines +761 to +764
raise ValueError(
"The native grouped_tensor path requires CUDA m_splits. Pass a CUDA int64 "
"tensor, or select grouped_gemm_backend='legacy'."
)

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.

I get that the h2d memcpy is suboptimal, but it's trivially easy to handle. Erroring out seems excessively rigid.

Suggested change
raise ValueError(
"The native grouped_tensor path requires CUDA m_splits. Pass a CUDA int64 "
"tensor, or select grouped_gemm_backend='legacy'."
)
m_splits = m_splits.to(device=device)

We need to handle the d2h case anyways when the user has specified grouped_gemm_backend="grouped_tensor", but it's not supported and we fallback to split-quantize.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

But if the alignment is not provided in the first place, converting it to a device tensor also wouldn't work right, I am okay with another alignment check before adding this H2D copy.

Comment thread transformer_engine/pytorch/module/grouped_linear.py Outdated
@zhongbozhu
zhongbozhu force-pushed the improve_device_grouped_linear branch 3 times, most recently from b6a9482 to 25314b8 Compare July 25, 2026 07:08
Comment thread tests/pytorch/test_grouped_linear.py Outdated
@zhongbozhu

Copy link
Copy Markdown
Collaborator Author

/te-ci pytorch

zhongbozhu and others added 6 commits July 27, 2026 20:47
Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>

# Conflicts:
#	tests/pytorch/test_grouped_mlp.py
Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
zhongbozhu and others added 11 commits July 27, 2026 20:49
Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
Signed-off-by: zhongboz <zhongboz@nvidia.com>
Signed-off-by: zhongboz <zhongboz@nvidia.com>
Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
@zhongbozhu
zhongbozhu force-pushed the improve_device_grouped_linear branch from 07b2f18 to bb6c1b9 Compare July 28, 2026 03:50
@zhongbozhu

Copy link
Copy Markdown
Collaborator Author

/te-ci pytorch

: tensor_base;
size_t tensor_base_for_scales = tensor_base;
size_t tensor_rows_for_scales = rows;
if constexpr (WITH_GEMM_SWIZZLED_SCALES && SHAPE_REP == ShapeRepresentation::SAME_BOTH_DIMS) {

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Note: this is for single weight quantize for mxfp8.

Before this change, the weight quantizer didn't have the first_dims because moe weights are uniform shape for both dimension. This will then trigger a CUDA illegal access because offsets_ptr=nullptr

Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
@zhongbozhu
zhongbozhu force-pushed the improve_device_grouped_linear branch from 71a29ba to 13c60fb Compare July 28, 2026 08:34
Signed-off-by: zhongboz <zhongboz@nvidia.com>
@zhongbozhu
zhongbozhu force-pushed the improve_device_grouped_linear branch from 2734746 to 5fc5db7 Compare July 28, 2026 09:21
@zhongbozhu

Copy link
Copy Markdown
Collaborator Author

/te-ci pytorch

Signed-off-by: Zhongbo Zhu <zhongboz@nvidia.com>
Signed-off-by: zhongboz <zhongboz@nvidia.com>
@zhongbozhu

Copy link
Copy Markdown
Collaborator Author

/te-ci pytorch

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.

3 participants