# Copyright (c) 2025, Wentao Guo, Ted Zadouri, Tri Dao. import cutlass import cutlass.cute as cute from cutlass import Int32, const_expr def transpose_view(a: cute.Tensor) -> cute.Tensor: """Transpose the first two dimensions of a tensor on smem.""" shape = (a.shape[1], a.shape[0], *a.shape[2:]) order = (1, 0, *range(2, cute.rank(a))) return cute.composition(a, cute.make_ordered_layout(shape, order=order)) def select(a: cute.Tensor, mode: list[int]) -> cute.Tensor: return cute.make_tensor(a.iterator, cute.select(a.layout, mode)) def expand(a: cute.Tensor, dim: int, size: Int32 | int) -> cute.Tensor: shape = (*a.shape[:dim], size, *a.shape[dim:]) stride = (*a.layout.stride[:dim], 0, *a.layout.stride[dim:]) return cute.make_tensor(a.iterator, cute.make_layout(shape, stride=stride)) @cute.jit def permute_gated_Cregs_b16(t: cute.Tensor) -> None: assert t.element_type.width == 16 assert cute.size(t.shape) % 4 == 0, "Tensor size must be a multiple of 4 for b16 permutation" t_u32 = cute.recast_tensor(t, Int32) quad_idx = cute.arch.lane_idx() % 4 lane_03 = quad_idx == 0 or quad_idx == 3 selector_upper = Int32(0x5410) if lane_03 else Int32(0x1054) selector_lower = Int32(0x7632) if lane_03 else Int32(0x3276) # upper_map = [0, 3, 1, 2] # lower_map = [1, 2, 0, 3] # upper_idx = upper_map[quad_idx] # indexing isn't supported so we have to do arithmetic upper_idx = quad_idx // 2 if quad_idx % 2 == 0 else 3 - quad_idx // 2 lower_idx = upper_idx ^ 1 # 1 -> 0b11111, 2 -> 0b11110, 4 -> 0b11100, 8 -> 0b11000, 16 -> 0b10000, 32 -> 0b00000 width = 4 mask = cute.arch.WARP_SIZE - width clamp = cute.arch.WARP_SIZE - 1 mask_and_clamp = mask << 8 | clamp for i in cutlass.range(cute.size(t_u32.shape) // 2, unroll_full=True): upper, lower = t_u32[i * 2 + 0], t_u32[i * 2 + 1] upper0 = upper if lane_03 else lower lower0 = lower if lane_03 else upper upper0 = cute.arch.shuffle_sync(upper0, offset=upper_idx, mask_and_clamp=mask_and_clamp) lower0 = cute.arch.shuffle_sync(lower0, offset=lower_idx, mask_and_clamp=mask_and_clamp) t_u32[i * 2 + 0] = cute.arch.prmt(upper0, lower0, selector_upper) t_u32[i * 2 + 1] = cute.arch.prmt(upper0, lower0, selector_lower) @cute.jit def permute_Cregs_b32_for_stsm(t: cute.Tensor) -> None: """Permute and shuffle within 4 threads to change the layout from T0 | T1 | T2 | T3 a b | c d | e f | g h to T0 | T1 | T2 | T3 | T0 | T1 | T2 | T3 a | b | c | d | e | f | g | h This is so that we can use STSM (instead of STS.64) to store C registers without bank conflict. """ assert t.element_type.width == 32 assert cute.size(t.shape) % 4 == 0, "Tensor size must be a multiple of 4 for b32 permutation" quad_idx = cute.arch.lane_idx() % 4 # left_map = [0, 2, 1, 3] # right_map = [2, 0, 3, 1] # indexing isn't supported so we have to do arithmetic left_idx = quad_idx // 2 if quad_idx % 2 == 0 else 2 + quad_idx // 2 right_idx = left_idx ^ 0b10 # 1 -> 0b11111, 2 -> 0b11110, 4 -> 0b11100, 8 -> 0b11000, 16 -> 0b10000, 32 -> 0b00000 width = 4 mask = cute.arch.WARP_SIZE - width clamp = cute.arch.WARP_SIZE - 1 mask_and_clamp = mask << 8 | clamp for i in cutlass.range(cute.size(t.shape) // 4, unroll_full=True): for r in cutlass.range(2, unroll_full=True): left, right = t[i * 4 + r * 2 + 0], t[i * 4 + r * 2 + 1] # a b | c d | e f | g h -> a b | c d | f e | h g left0 = left if quad_idx < 2 else right right0 = right if quad_idx < 2 else left # a b | c d | f e | h g -> a b | f d | c e | h g left0 = cute.arch.shuffle_sync(left0, offset=left_idx, mask_and_clamp=mask_and_clamp) # a b | f d | c e | h g -> a e | f b | c g | h d right0 = cute.arch.shuffle_sync(right0, offset=right_idx, mask_and_clamp=mask_and_clamp) # a e | f b | c g | h d -> a e | b f | c g | d h t[i * 4 + r * 2 + 0] = left0 if quad_idx % 2 == 0 else right0 t[i * 4 + r * 2 + 1] = right0 if quad_idx % 2 == 0 else left0 t[i * 4 + 1], t[i * 4 + 2] = t[i * 4 + 2], t[i * 4 + 1] @cute.jit def permute_Cregs_b32_for_ldsm(t: cute.Tensor) -> None: """Permute and shuffle within 4 threads to change the layout from T0 | T1 | T2 | T3 | T0 | T1 | T2 | T3 a | b | c | d | e | f | g | h to T0 | T1 | T2 | T3 a b | c d | e f | g h This is so that we can use LDSM (instead of LDS.64) to store C registers without bank conflict. """ assert t.element_type.width == 32 assert cute.size(t.shape) % 4 == 0, "Tensor size must be a multiple of 4 for b32 permutation" quad_idx = cute.arch.lane_idx() % 4 # left_map = [0, 2, 1, 3] # right_map = [1, 3, 0, 2] # indexing isn't supported so we have to do arithmetic left_idx = quad_idx // 2 if quad_idx % 2 == 0 else 2 + quad_idx // 2 right_idx = left_idx ^ 0b01 # 1 -> 0b11111, 2 -> 0b11110, 4 -> 0b11100, 8 -> 0b11000, 16 -> 0b10000, 32 -> 0b00000 width = 4 mask = cute.arch.WARP_SIZE - width clamp = cute.arch.WARP_SIZE - 1 mask_and_clamp = mask << 8 | clamp # This is just the inverse of permute_Cregs_b32_for_stsm for i in cutlass.range(cute.size(t.shape) // 4, unroll_full=True): t[i * 4 + 1], t[i * 4 + 2] = t[i * 4 + 2], t[i * 4 + 1] for r in cutlass.range(2, unroll_full=True): left, right = t[i * 4 + r * 2 + 0], t[i * 4 + r * 2 + 1] # a e | b f | c g | d h -> a e | f b | c g | h d left0 = left if quad_idx % 2 == 0 else right right0 = right if quad_idx % 2 == 0 else left # a e | f b | c g | h d -> a b | f d | c e | h g right0 = cute.arch.shuffle_sync(right0, offset=right_idx, mask_and_clamp=mask_and_clamp) # a b | f d | c e | h g -> a b | c d | f e | h g left0 = cute.arch.shuffle_sync(left0, offset=left_idx, mask_and_clamp=mask_and_clamp) # a b | c d | f e | h g -> a b | c d | e f | g h t[i * 4 + r * 2 + 0] = left0 if quad_idx < 2 else right0 t[i * 4 + r * 2 + 1] = right0 if quad_idx < 2 else left0 @cute.jit def concat_layout(*layouts: cute.Layout) -> cute.Layout: return cute.make_layout( tuple(l.shape for l in layouts), stride=tuple(l.stride for l in layouts), ) def convert_layout_acc_mn(acc_layout: cute.Layout, transpose: bool = False) -> cute.Layout: """ For Sm80, convert ((2, 2), MMA_M, MMA_N, ...) to ((2, MMA_M), (2, MMA_N), ...). For Sm90, convert ((2, 2, V), MMA_M, MMA_N, ...) to ((2, MMA_M), (2, V, MMA_N), ...). """ acc_layout_col_major = cute.make_layout(acc_layout.shape) shape = ( (acc_layout_col_major.shape[0][1], acc_layout_col_major.shape[1]), # MMA_M ( acc_layout_col_major.shape[0][0], *acc_layout_col_major.shape[0][2:], acc_layout_col_major.shape[2], ), # MMA_N *acc_layout_col_major.shape[3:], ) stride = ( (acc_layout_col_major.stride[0][1], acc_layout_col_major.stride[1]), # MMA_M ( acc_layout_col_major.stride[0][0], *acc_layout_col_major.stride[0][2:], acc_layout_col_major.stride[2], ), # MMA_N *acc_layout_col_major.stride[3:], ) if const_expr(transpose): shape = (shape[1], shape[0], *shape[2:]) stride = (stride[1], stride[0], *stride[2:]) acc_layout_mn = cute.make_layout(shape, stride=stride) return cute.composition(acc_layout, acc_layout_mn) def make_acc_tensor_mn_view(acc: cute.Tensor, transpose: bool = False) -> cute.Tensor: return cute.make_tensor(acc.iterator, convert_layout_acc_mn(acc.layout, transpose=transpose)) def reshape_acc_to_mn(acc: cute.Tensor, transpose: bool = False) -> cute.Tensor: return cute.make_tensor(acc.iterator, convert_layout_acc_mn(acc.layout, transpose=transpose)) @cute.jit def convert_layout_acc_frgA(acc_layout: cute.Layout) -> cute.Layout: # For back to back gemm, convert layout of acc0 to gemm 1 accept layout. # For Sm80, as the mma instruction shape is 16x8x16, we need to convert from (4, MMA_M, MMA_N) to ((4, 2), MMA_M, MMA_N / 2) # For Sm90, FP16/BF16, convert acc_layout from ((2, 2, N / 8), MMA_M, MMA_N) to ((2, 2, 2), MMA_M, (N / 16, MMA_N)) # If N / 8 is odd, we'll convert to ((2, 2, 1), MMA_M, N / 8, MMA_N). # TODO: Sm90 FP8 if const_expr(cute.rank(acc_layout.shape[0]) == 3): # Sm90 div = 2 if const_expr(acc_layout.shape[0][2] % 2 == 0) else 1 l = cute.logical_divide( acc_layout, ((None, None, div), None, None) ) # ((2, 2, (2, N / 16)), MMA_M, MMA_N) rA_mma_view = cute.make_layout( ( (l.shape[0][0], l.shape[0][1], l.shape[0][2][0]), l.shape[1], (l.shape[0][2][1], l.shape[2]), ), stride=( (l.stride[0][0], l.stride[0][1], l.stride[0][2][0]), l.stride[1], (l.stride[0][2][1], l.stride[2]), ), ) else: # Sm80 # (4, MMA_M, MMA_N) -> (4, MMA_M, (2, MMA_N / 2)) l = cute.logical_divide(acc_layout, (None, None, 2)) rA_mma_view = cute.make_layout( ( (l.shape[0], l.shape[2][0]), l.shape[1], l.shape[2][1], ), stride=( (l.stride[0], l.stride[2][0]), l.stride[1], l.stride[2][1], ), ) return rA_mma_view def reshape_acc_to_frgA(acc: cute.Tensor) -> cute.Tensor: return cute.make_tensor(acc.iterator, convert_layout_acc_frgA(acc.layout)) def convert_layout_zero_stride( input: cute.Tensor | cute.Layout, ref_layout: cute.Layout ) -> cute.Layout: layout = input.layout if const_expr(isinstance(input, cute.Tensor)) else input # Group the modes with non-zero stride in the ref_layout together, # and the modes with zero stride together layout_flat = cute.flatten(layout) ref_layout_flat = cute.flatten(ref_layout) nonzero_modes = [i for i in range(cute.rank(layout_flat)) if ref_layout_flat[i].stride != 0] zero_modes = [i for i in range(cute.rank(layout_flat)) if ref_layout_flat[i].stride == 0] # There's an edge case when all modes are zero stride new_shape = ( tuple(layout_flat[i].shape for i in nonzero_modes) if len(nonzero_modes) > 0 else (1,), tuple(layout_flat[i].shape for i in zero_modes), ) new_stride = ( tuple(layout_flat[i].stride for i in nonzero_modes) if len(nonzero_modes) > 0 else (0,), tuple(layout_flat[i].stride for i in zero_modes), ) out_layout = cute.make_layout(new_shape, stride=new_stride) if const_expr(isinstance(input, cute.Tensor)): return cute.make_tensor(input.iterator, out_layout) else: return out_layout def mma_partition_C_vec( sVec: cute.Tensor, thr_mma: cute.core.ThrMma, expand_shape: int, is_colvec: bool ) -> cute.Tensor: assert cute.rank(sVec) == 2 assert sVec.stride[0] == 1 stage = sVec.shape[1] shape = ( (sVec.shape[0], expand_shape, stage) if const_expr(is_colvec) else (expand_shape, sVec.shape[0], stage) ) stride = (1, 0, sVec.stride[1]) if const_expr(is_colvec) else (0, 1, sVec.stride[1]) sVec_mma = cute.make_tensor(sVec.iterator, cute.make_layout(shape, stride=stride)) tC_sVec = make_acc_tensor_mn_view(thr_mma.partition_C(sVec_mma)) return tC_sVec[None, 0, None] if const_expr(is_colvec) else tC_sVec[0, None, None] def mma_partition_A_vec( sVec: cute.Tensor, thr_mma: cute.core.ThrMma, expand_shape: int, is_colvec: bool ) -> cute.Tensor: assert cute.rank(sVec) == 2 assert sVec.stride[0] == 1 stage = sVec.shape[1] shape = ( (sVec.shape[0], expand_shape, stage) if const_expr(is_colvec) else (expand_shape, sVec.shape[0], stage) ) stride = (1, 0, sVec.stride[1]) if const_expr(is_colvec) else (0, 1, sVec.stride[1]) sVec_mma = cute.make_tensor(sVec.iterator, cute.make_layout(shape, stride=stride)) tC_sVec = make_acc_tensor_mn_view(thr_mma.partition_A(sVec_mma)) return tC_sVec[None, 0, None] if const_expr(is_colvec) else tC_sVec[0, None, None] def copy_partition_S_vec( sVec: cute.Tensor, thr_copy: cute.core.ThrCopy, expand_shape: int, is_colvec: bool ) -> cute.Tensor: assert cute.rank(sVec) == 2 assert sVec.stride[0] == 1 stage = sVec.shape[1] shape = ( (sVec.shape[0], expand_shape, stage) if const_expr(is_colvec) else (expand_shape, sVec.shape[0], stage) ) stride = (1, 0, sVec.stride[1]) if const_expr(is_colvec) else (0, 1, sVec.stride[1]) sVec_thr = cute.make_tensor(sVec.iterator, cute.make_layout(shape, stride=stride)) tC_sVec = reshape_acc_to_mn(thr_copy.partition_S(sVec_thr)) return tC_sVec[None, 0, None] if const_expr(is_colvec) else tC_sVec[0, None, None] def copy_partition_D_vec( sVec: cute.Tensor, thr_copy: cute.core.ThrCopy, expand_shape: int, is_colvec: bool ) -> cute.Tensor: assert cute.rank(sVec) == 2 assert sVec.stride[0] == 1 stage = sVec.shape[1] shape = ( (sVec.shape[0], expand_shape, stage) if const_expr(is_colvec) else (expand_shape, sVec.shape[0], stage) ) stride = (1, 0, sVec.stride[1]) if const_expr(is_colvec) else (0, 1, sVec.stride[1]) sVec_thr = cute.make_tensor(sVec.iterator, cute.make_layout(shape, stride=stride)) tC_sVec = reshape_acc_to_mn(thr_copy.partition_D(sVec_thr)) return tC_sVec[None, 0, None] if const_expr(is_colvec) else tC_sVec[0, None, None]