Skip to content

Commit 9566d8d

Browse files
committed
ad tcgen.ld.red support to sm103a arch
stack-info: PR: #2696, branch: drisspg/stack/47
1 parent 002cce0 commit 9566d8d

2 files changed

Lines changed: 43 additions & 4 deletions

File tree

flash_attn/cute/flash_fwd_sm100.py

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,13 @@ def __init__(
209209
# despite the literal `is_sm103` name.
210210
is_sm103 = self.arch.is_family_of(Arch.sm_103f)
211211
self.is_sm103 = is_sm103
212+
# SM103 ld.red is profitable except for D32 when scores are unmodified.
213+
self.use_ldred_rowmax = (
214+
is_sm103
215+
and self.score_mod is None
216+
and self.mask_mod is None
217+
and self.head_dim_padded != 32
218+
)
212219
# enable_ex2_emu is derived: True if tuning config has freq > 0, else fallback to default logic
213220
_default_enable_ex2_emu = (self.head_dim_padded <= 128 or (self.head_dim_padded == 192 and self.use_2cta_instrs and not self.is_causal and not self.is_local)) and not is_sm103
214221
self.enable_ex2_emu = _default_enable_ex2_emu
@@ -731,6 +738,9 @@ class SharedStorage:
731738
head_divmod = FastDivmodDivisor(self.qhead_per_kvhead)
732739

733740
self.use_block_sparsity = cutlass.const_expr(blocksparse_tensors is not None)
741+
self.use_ldred_rowmax = cutlass.const_expr(
742+
self.use_ldred_rowmax and not self.use_block_sparsity
743+
)
734744
if cutlass.const_expr(self.use_block_sparsity and mPageTable is not None):
735745
raise NotImplementedError("Block sparsity + paged KV not supported on SM100")
736746
if cutlass.const_expr(self.use_block_sparsity and self.is_varlen_q):
@@ -1920,9 +1930,12 @@ def softmax_loop(
19201930
)
19211931
tStP = cute.make_tensor(tSAcc.iterator + self.tmem_s_to_p_offset, tStP_layout)
19221932

1923-
tmem_load_atom = cute.make_copy_atom(
1924-
tcgen05.copy.Ld32x32bOp(tcgen05.copy.Repetition(32)), self.qk_acc_dtype
1933+
tmem_load_op = (
1934+
tcgen05.copy.LdRed32x32bOp(tcgen05.copy.Repetition(32))
1935+
if const_expr(self.use_ldred_rowmax)
1936+
else tcgen05.copy.Ld32x32bOp(tcgen05.copy.Repetition(32))
19251937
)
1938+
tmem_load_atom = cute.make_copy_atom(tmem_load_op, self.qk_acc_dtype)
19261939
thr_tmem_load = tcgen05.make_tmem_copy(tmem_load_atom, tSAcc).get_slice(tidx)
19271940
tStS_t2r = thr_tmem_load.partition_S(tSAcc) # (((32,32),1),1,4)
19281941

@@ -2271,6 +2284,8 @@ def softmax_step(
22712284
4. Transforming scores using exp2(x*scale - max*scale)
22722285
5. Computing row sums for normalization
22732286
6. Coordinating pipeline synchronization between different processing stages
2287+
2288+
A None mask_fn means the tcgen05.ld.red hardware max is valid.
22742289
"""
22752290
warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx()) % 4
22762291
tilePlikeFP32 = self.mma_tiler_qk[1] // Float32.width * self.v_dtype.width
@@ -2284,7 +2299,15 @@ def softmax_step(
22842299
# Wait for Si
22852300
pipeline_s_p_o.consumer_wait_w_index_phase(stage, mma_si_consumer_phase)
22862301
tSrS_t2r = cute.make_rmem_tensor(thr_tmem_load.partition_D(tScS).shape, self.qk_acc_dtype)
2287-
cute.copy(thr_tmem_load, tStS_t2r, tSrS_t2r)
2302+
hw_row_max = Float32(-Float32.inf)
2303+
if const_expr(self.use_ldred_rowmax):
2304+
# ld.red returns each x32 tile's max in an extra register.
2305+
tSrS_red = cute.make_rmem_tensor(((1, 1), *tSrS_t2r.shape[1:]), self.qk_acc_dtype)
2306+
cute.copy(thr_tmem_load, tStS_t2r, (tSrS_t2r, tSrS_red))
2307+
for i in cutlass.range_constexpr(cute.size(tSrS_red.shape)):
2308+
hw_row_max = cute.arch.fmax(hw_row_max, tSrS_red[i])
2309+
else:
2310+
cute.copy(thr_tmem_load, tStS_t2r, tSrS_t2r)
22882311
# tSrS_t2r = copy_utils.load_t2r(thr_tmem_load, tScS_shape, tStS_t2r)
22892312
if cutlass.const_expr(self.score_mod is not None):
22902313
self.apply_score_mod(
@@ -2304,7 +2327,11 @@ def softmax_step(
23042327

23052328
if const_expr(mask_fn is not None):
23062329
mask_fn(tSrS_t2r, n_block=n_block)
2307-
row_max, acc_scale = softmax.update_row_max(tSrS_t2r.load(), is_first)
2330+
# Masked iterations reduce over post-mask values in software.
2331+
if const_expr(self.use_ldred_rowmax and mask_fn is None):
2332+
row_max, acc_scale = softmax.update_row_max_precomputed(hw_row_max, is_first)
2333+
else:
2334+
row_max, acc_scale = softmax.update_row_max(tSrS_t2r.load(), is_first)
23082335

23092336
if const_expr(not is_first):
23102337
# tSrScale_r2t = cute.make_rmem_tensor(thr_tmem_store_scale.partition_S(tScScale).shape, Float32)

flash_attn/cute/softmax.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,18 @@ def update_row_max_from_local(
298298
self.row_max[0] = row_max_new
299299
return row_max_safe, acc_scale
300300

301+
@cute.jit
302+
def update_row_max_precomputed(
303+
self, hw_row_max: Float32, is_first: int
304+
) -> Tuple[Float32, Float32]:
305+
"""Row max already reduced in hardware (SM103 tcgen05.ld.red): skip the
306+
software fmax tree — the TMEM controller computed the max during the S load."""
307+
if cutlass.const_expr(is_first):
308+
row_max_new = hw_row_max
309+
else:
310+
row_max_new = cute.arch.fmax(hw_row_max, self.row_max[0])
311+
return self.update_row_max_from_local(row_max_new, is_first)
312+
301313
@cute.jit
302314
def update_row_max(self, acc_S_row: cute.TensorSSA, is_first: int) -> Tuple[Float32, Float32]:
303315
if cutlass.const_expr(is_first):

0 commit comments

Comments
 (0)