Grouped GEMM + WGrad (Unified)#
GroupedGemmWgradSm100 and grouped_gemm_wgrad_wrapper_sm100 are experimental
SM100+ APIs for grouped MoE weight gradients. The same public surface dispatches
BF16 inputs to the BF16 kernel and preserves the legacy FP4/FP8 block-scaled
backend.
Install the optional CuTe DSL dependencies before importing either API:
pip install nvidia-cudnn-frontend[cutedsl]
Operation#
For expert e, let begin = 0 for the first expert and
begin = offsets_tensor[e - 1] otherwise, and let
end = offsets_tensor[e]. The API computes
Wgrad[e] = A[:, begin:end] @ B[begin:end, :]
When accumulate_on_output=True, that result is accumulated into the existing
output. The caller must therefore initialize every output allocation. When it
is false, the kernel overwrites the output; an empty expert produces zero.
BF16 contract#
The BF16 backend accepts:
Argument |
Shape |
Supported stride/major |
Dtype |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
contiguous |
|
dense |
|
|
BF16, FP16, or FP32 |
one discrete output |
|
|
BF16, FP16, or FP32 |
|
|
contiguous |
|
offsets_tensor is a non-decreasing cumulative sum. Every expert token count
(offsets[e] - offsets[e - 1]) must be a multiple of 256, and the final offset
must equal tokens_sum. Inputs, metadata, and outputs must reside on the same
CUDA device and satisfy the API’s alignment checks.
BF16 uses FP32 accumulation and requires sf_vec_size=16. Pass None for
sfa_tensor, sfb_tensor, global_scale_a, and global_scale_b. BF16 rejects
every non-None scale or global-scale control with ValueError; it never falls
through to another backend. Only a supported FP4/FP8 operand pair selects the
legacy block-scaled backend, which continues to support its existing scale
tensors and global scales.
input_order describes how the token dimension is stored:
"tensor2d"(default) uses one global 2-D tensor and its declared strides."tensor_ragged"uses per-expert K-contiguous blocks concatenated in memory. In this mode only each input’s unit-stride axis is meaningful; non-unit host strides are ignored when per-expert TMA descriptors are built.
Output modes#
With output_mode="dense", provide or let the wrapper allocate the contiguous
stacked wgrad_tensor. wgrad_ptrs is forbidden.
With output_mode="discrete", either:
omit both output arguments and let the wrapper allocate a stacked tensor and construct an internal pointer array; or
provide a CUDA
torch.int64wgrad_ptrsarray containing one non-null, 16-byte-aligned output address per expert.
For explicit pointer-only output, result["wgrad_tensor"] is None. The caller
owns all pointed-to output allocations and must keep both those allocations and
the pointer tensor alive until work on current_stream completes. The API
records the pointer tensor on the launch stream, but it cannot manage the
lifetime of allocations represented only by integer addresses.
The wrapper always returns TupleDict(wgrad_tensor=...); it contains exactly
one item and supports either keyed access or tuple unpacking.
Block-scaled contract#
The legacy block-scaled backend is selected only by a supported matching FP4/FP8
operand pair. It preserves the pre-existing scale-factor contract: provide
sfa_tensor and sfb_tensor, and provide global_scale_a and
global_scale_b where the selected low-precision format requires them. BF16
does not reinterpret these controls; it rejects them instead.
API usage#
BF16#
Wrapper#
Dense BF16 output:
import cudnn
import torch
result = cudnn.grouped_gemm_wgrad_wrapper_sm100(
a_tensor=a_tensor,
b_tensor=b_tensor,
sfa_tensor=None,
sfb_tensor=None,
offsets_tensor=offsets_tensor,
output_mode="dense",
wgrad_dtype=torch.bfloat16,
input_order="tensor2d",
)
wgrad_tensor = result["wgrad_tensor"]
Discrete BF16 outputs owned by the caller:
expert_outputs = [
torch.empty(
(hidden, intermediate), dtype=torch.bfloat16, device="cuda"
)
for _ in range(offsets_tensor.numel())
]
wgrad_ptrs = torch.tensor(
[output.data_ptr() for output in expert_outputs],
dtype=torch.int64,
device="cuda",
)
result = cudnn.grouped_gemm_wgrad_wrapper_sm100(
a_tensor=a_tensor,
b_tensor=b_tensor,
sfa_tensor=None,
sfb_tensor=None,
offsets_tensor=offsets_tensor,
output_mode="discrete",
wgrad_ptrs=wgrad_ptrs,
wgrad_dtype=torch.bfloat16,
input_order="tensor_ragged",
)
assert result["wgrad_tensor"] is None
Reusable class lifecycle#
The class API requires output descriptors at construction and output storage at
execution. This dense BF16 example compiles once and accepts later calls with a
different tokens_sum when static dimensions, dtypes, majors, and configuration
remain compatible:
op = cudnn.GroupedGemmWgradSm100(
sample_a=a_tensor,
sample_b=b_tensor,
sample_sfa=None,
sample_sfb=None,
sample_offsets=offsets_tensor,
sample_wgrad=wgrad_tensor,
acc_dtype=torch.float32,
input_order="tensor2d",
)
op.check_support()
op.compile()
op.execute(
a_tensor=a_tensor,
b_tensor=b_tensor,
sfa_tensor=None,
sfb_tensor=None,
offsets_tensor=offsets_tensor,
wgrad_tensor=wgrad_tensor,
)
For a discrete class instance, replace sample_wgrad with
sample_wgrad_expert=expert_outputs[0], num_experts, wgrad_shape, and
wgrad_dtype, then pass wgrad_ptrs to execute.
Block-scaled#
Wrapper#
result = cudnn.grouped_gemm_wgrad_wrapper_sm100(
a_tensor=a_tensor,
b_tensor=b_tensor,
sfa_tensor=sfa_tensor,
sfb_tensor=sfb_tensor,
offsets_tensor=offsets_tensor,
output_mode="dense",
wgrad_dtype=torch.bfloat16,
input_order="tensor_ragged",
)
Reusable class lifecycle#
op = cudnn.GroupedGemmWgradSm100(
sample_a=a_tensor,
sample_b=b_tensor,
sample_sfa=sfa_tensor,
sample_sfb=sfb_tensor,
sample_offsets=offsets_tensor,
sample_wgrad=sample_wgrad_tensor,
acc_dtype=torch.float32,
)
assert op.check_support()
op.compile()
op.execute(
a_tensor=a_tensor,
b_tensor=b_tensor,
sfa_tensor=sfa_tensor,
sfb_tensor=sfb_tensor,
offsets_tensor=offsets_tensor,
wgrad_tensor=wgrad_tensor,
)
Scheduling, cache, and errors#
The BF16 kernel uses dynamic persistent scheduling. The token dimension is compiled dynamically, and the wrapper cache abstracts the token-sized axes of A and B while retaining static dimensions, layouts, dtypes, output descriptors, tiling, cluster shape, input order, and accumulation mode in its key. A changed static contract creates a different cached operator or fails validation.
The APIs reject unsupported dtypes or layouts, malformed/unaligned offsets or
pointers, mixed devices, forbidden BF16 scale controls, unsupported tiling, use
before compile(), unavailable CUDA, and devices below SM100. Support and
validation errors are reported as ValueError or RuntimeError; callers should
not rely on this experimental API remaining source-compatible across releases.