1515import torch
1616
1717import transformer_engine_torch as tex
18- from ...cpp_extensions import (
19- general_grouped_gemm_for_grouped_tensor ,
20- )
2118from ...module .base import get_dummy_wgrad
2219from ...quantization import Recipe
2320from ...tensor .grouped_tensor import GroupedTensor
2825from ..fuser import register_backward_fusion
2926from ..op import FusedOperation , FusibleOperation , OperationContext
3027from .._common import (
28+ _nvidia_cudnn_frontend_supports_wgrad ,
3129 fuse_grouped_mlp_ops ,
3230 maybe_dequantize ,
3331 validate_grouped_mlp_dims ,
3432)
33+ from ...cpp_extensions import general_grouped_gemm_for_grouped_tensor
34+ from ...module .base import _2X_ACC_WGRAD
3535from ...triton .grouped_dbias_dscales import _compute_grouped_dbias_dscales
3636
3737
38+ def _cudnn_compute_wgrad (
39+ grouped_x : GroupedTensor ,
40+ grouped_dy : GroupedTensor ,
41+ wgrad_output ,
42+ weight_shape : tuple ,
43+ offsets : torch .Tensor ,
44+ accumulate : bool ,
45+ wgrad_kernel_fn ,
46+ single_grouped_weight : bool ,
47+ ):
48+ """Compute wgrad using the cuDNN CuTe DSL grouped GEMM wgrad kernel.
49+
50+ The cuDNN wgrad kernel computes:
51+ wgrad[e] = a[:, tok_start:tok_end] @ b[tok_start:tok_end, :]
52+ where a = DY^T = (out_features, total_tokens) row-major and
53+ b = X = (total_tokens, in_features) column-major.
54+ """
55+ out_features , in_features = weight_shape
56+ total_tokens = grouped_dy .logical_shape [0 ]
57+
58+ fp8_dtype = torch .float8_e4m3fn
59+
60+ # a_tensor = DY^T = (out_features, total_tokens) row-major
61+ a_tensor = grouped_dy .columnwise_data .view (dtype = fp8_dtype ).view (total_tokens , out_features ).T
62+ # b_tensor = X = (total_tokens, in_features) column-major
63+ b_tensor = grouped_x .columnwise_data .view (dtype = fp8_dtype ).view (total_tokens , in_features )
64+
65+ sfa_tensor = grouped_dy .columnwise_scale_inv .view (out_features , - 1 ).view (
66+ dtype = torch .float8_e8m0fnu
67+ )
68+ sfb_tensor = grouped_x .columnwise_scale_inv .view (in_features , - 1 ).view (
69+ dtype = torch .float8_e8m0fnu
70+ )
71+ offsets_tensor = offsets .to (dtype = torch .int32 )
72+
73+ # Prepare wgrad output
74+ if single_grouped_weight :
75+ # Dense mode: single (num_groups, out_features, in_features) tensor
76+ wgrad_tensor = wgrad_output .rowwise_data .view (
77+ offsets_tensor .shape [0 ], out_features , in_features
78+ )
79+ wgrad_kernel_fn (
80+ a_tensor = a_tensor ,
81+ b_tensor = b_tensor ,
82+ sfa_tensor = sfa_tensor ,
83+ sfb_tensor = sfb_tensor ,
84+ offsets_tensor = offsets_tensor ,
85+ output_mode = "dense" ,
86+ wgrad_tensor = wgrad_tensor ,
87+ acc_dtype = torch .float32 ,
88+ wgrad_dtype = wgrad_tensor .dtype ,
89+ sf_vec_size = MXFP8_BLOCK_SCALING_SIZE ,
90+ accumulate_on_output = accumulate ,
91+ )
92+ else :
93+ # Discrete mode: per-expert wgrad device pointers
94+ (wgrad_ptrs ,) = tex .convert_host_pointers_to_tensor ([wgrad_output ])
95+ wgrad_kernel_fn (
96+ a_tensor = a_tensor ,
97+ b_tensor = b_tensor ,
98+ sfa_tensor = sfa_tensor ,
99+ sfb_tensor = sfb_tensor ,
100+ offsets_tensor = offsets_tensor ,
101+ output_mode = "discrete" ,
102+ wgrad_ptrs = wgrad_ptrs ,
103+ acc_dtype = torch .float32 ,
104+ wgrad_dtype = wgrad_output [0 ].dtype ,
105+ sf_vec_size = MXFP8_BLOCK_SCALING_SIZE ,
106+ accumulate_on_output = accumulate ,
107+ )
108+
109+
38110@functools .lru_cache (maxsize = 1 )
39111def _dglu_wrapper_has_generate_dbias_arg () -> bool :
40112 """True if cudnn-frontend SM100 dGLU wrapper accepts ``generate_dbias``."""
@@ -61,6 +133,9 @@ def _compute_grad_params(
61133 bias_grads ,
62134 bias_grad_packed ,
63135 label = "" ,
136+ * ,
137+ cudnn_wgrad_kernel_fn ,
138+ offsets ,
64139):
65140 """Compute weight gradients and build grad_params for a GroupedLinear layer.
66141 Returns the grad_params list in parameter registration order.
@@ -131,11 +206,23 @@ def _compute_grad_params(
131206 if ctx .weight_requires_grad :
132207 # Launch or defer the GEMM
133208 delay_wgrad = fc_op .wgrad_store is not None and fc_op .wgrad_store .delay_wgrad_compute ()
134- gemm_fn = functools .partial (
135- general_grouped_gemm_for_grouped_tensor ,
136- layout = "NT" ,
137- accumulate = accumulate_into_main_grad ,
138- )
209+ if cudnn_wgrad_kernel_fn is not None :
210+ gemm_fn = functools .partial (
211+ _cudnn_compute_wgrad ,
212+ weight_shape = weight_shape ,
213+ offsets = offsets ,
214+ accumulate = accumulate_into_main_grad ,
215+ wgrad_kernel_fn = cudnn_wgrad_kernel_fn ,
216+ single_grouped_weight = fc_op .single_grouped_weight ,
217+ )
218+ else :
219+ gemm_fn = functools .partial (
220+ general_grouped_gemm_for_grouped_tensor ,
221+ layout = "NT" ,
222+ accumulate = accumulate_into_main_grad ,
223+ use_split_accumulator = _2X_ACC_WGRAD ,
224+ )
225+
139226 if delay_wgrad :
140227 fc_op .wgrad_store .put ([grouped_x , grouped_dy , wgrad_output ], gemm_fn )
141228 else :
@@ -204,6 +291,19 @@ def grouped_gemm_quant_kernel(cls) -> Callable:
204291
205292 return grouped_gemm_quant_wrapper_sm100
206293
294+ @classmethod
295+ @functools .lru_cache (maxsize = None )
296+ def grouped_gemm_wgrad_kernel (cls ) -> Optional [Callable ]:
297+ """CuTe DSL kernel for grouped GEMM wgrad on SM100+.
298+ Returns ``None`` when the cuDNN front-end package is older than
299+ 1.23.0.
300+ """
301+ if not _nvidia_cudnn_frontend_supports_wgrad ():
302+ return None
303+ from cudnn import grouped_gemm_wgrad_wrapper_sm100 # pylint: disable=no-name-in-module
304+
305+ return grouped_gemm_wgrad_wrapper_sm100
306+
207307 @classmethod
208308 @functools .lru_cache (maxsize = None )
209309 def is_supported (cls ) -> bool :
@@ -477,10 +577,12 @@ def fuser_backward(
477577
478578 fc1_dy_row_data = fc2_dgrad_kernel_out ["d_row_tensor" ]
479579 fc1_dy_row_data = fc1_dy_row_data .view (out_shape [0 ], fc1_weight_shape [0 ])
480- fc1_dy_row_scale = fc2_dgrad_kernel_out ["sfd_row_tensor" ]
580+ # View scale in their actual swizzled shape
581+ fc1_dy_row_scale = fc2_dgrad_kernel_out ["sfd_row_tensor" ].permute (5 , 2 , 4 , 0 , 1 , 3 ).view (- 1 )
481582 fc1_dy_col_data = fc2_dgrad_kernel_out ["d_col_tensor" ]
482583 fc1_dy_col_data = fc1_dy_col_data .view (out_shape [0 ], fc1_weight_shape [0 ])
483- fc1_dy_col_scale = fc2_dgrad_kernel_out ["sfd_col_tensor" ]
584+ # View scale in their actual swizzled shape
585+ fc1_dy_col_scale = fc2_dgrad_kernel_out ["sfd_col_tensor" ].permute (5 , 2 , 4 , 0 , 1 , 3 ).view (- 1 )
484586 grad_scales = fc2_dgrad_kernel_out ["dprob_tensor" ].view (- 1 )
485587
486588 fc2_bias_grads : Optional [list [Optional [torch .Tensor ]]] = None
@@ -553,6 +655,8 @@ def fuser_backward(
553655 bias_grads = fc2_bias_grads ,
554656 bias_grad_packed = fc2_bias_grad_packed ,
555657 label = "FC2" ,
658+ cudnn_wgrad_kernel_fn = self .grouped_gemm_wgrad_kernel (),
659+ offsets = split_points ,
556660 )
557661
558662 # Clear FC2 input tensor if possible
@@ -648,6 +752,8 @@ def fuser_backward(
648752 bias_grads = fc1_bias_grads ,
649753 bias_grad_packed = fc1_bias_grad_packed ,
650754 label = "FC1" ,
755+ cudnn_wgrad_kernel_fn = self .grouped_gemm_wgrad_kernel (),
756+ offsets = split_points ,
651757 )
652758
653759 # Clear FC1 input tensor if possible
0 commit comments