Skip to content

Commit f45c939

Browse files
committed
ad tcgen.ld.red support to sm103a arch
1 parent 002cce0 commit f45c939

2 files changed

Lines changed: 42 additions & 4 deletions

File tree

flash_attn/cute/flash_fwd_sm100.py

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,11 @@ 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 tcgen05.ld.red fuses the S TMEM load with a per-x32-tile max computed
213+
# in the TMEM controller, making row max nearly free. Only valid when the
214+
# loaded values are used unmodified, so score_mod (incl. softcap) disables it;
215+
# masked iterations additionally fall back to the software max (see softmax_step).
216+
self.use_ldred_rowmax = is_sm103 and self.score_mod is None
212217
# enable_ex2_emu is derived: True if tuning config has freq > 0, else fallback to default logic
213218
_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
214219
self.enable_ex2_emu = _default_enable_ex2_emu
@@ -1920,9 +1925,12 @@ def softmax_loop(
19201925
)
19211926
tStP = cute.make_tensor(tSAcc.iterator + self.tmem_s_to_p_offset, tStP_layout)
19221927

1923-
tmem_load_atom = cute.make_copy_atom(
1924-
tcgen05.copy.Ld32x32bOp(tcgen05.copy.Repetition(32)), self.qk_acc_dtype
1928+
tmem_load_op = (
1929+
tcgen05.copy.LdRed32x32bOp(tcgen05.copy.Repetition(32))
1930+
if const_expr(self.use_ldred_rowmax)
1931+
else tcgen05.copy.Ld32x32bOp(tcgen05.copy.Repetition(32))
19251932
)
1933+
tmem_load_atom = cute.make_copy_atom(tmem_load_op, self.qk_acc_dtype)
19261934
thr_tmem_load = tcgen05.make_tmem_copy(tmem_load_atom, tSAcc).get_slice(tidx)
19271935
tStS_t2r = thr_tmem_load.partition_S(tSAcc) # (((32,32),1),1,4)
19281936

@@ -2169,6 +2177,7 @@ def softmax_loop(
21692177
else:
21702178
mma_si_consumer_phase, sm_stats_producer_phase, s0_s1_sequence_phase = softmax_step(
21712179
mma_si_consumer_phase, sm_stats_producer_phase, s0_s1_sequence_phase, n_block,
2180+
mask_is_noop=True,
21722181
)
21732182
# Separate iterations with local masking on the left
21742183
if const_expr(self.is_local and block_info.window_size_left is not None):
@@ -2256,6 +2265,9 @@ def softmax_step(
22562265
head_divmod=None,
22572266
mask_fn: Optional[Callable] = None,
22582267
is_first: bool = False,
2268+
# True when this call site applies no masking (no causal/local edge, no
2269+
# mask_mod, no seqlen boundary) — gates use of the tcgen05.ld.red hardware max.
2270+
mask_is_noop: cutlass.Constexpr[bool] = False,
22592271
) -> Tuple[cute.Int32, cute.Int32, cute.Int32]:
22602272
"""Perform a single step of the softmax computation on a block of attention scores.
22612273
@@ -2284,7 +2296,16 @@ def softmax_step(
22842296
# Wait for Si
22852297
pipeline_s_p_o.consumer_wait_w_index_phase(stage, mma_si_consumer_phase)
22862298
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)
2299+
hw_row_max = Float32(-Float32.inf)
2300+
if const_expr(self.use_ldred_rowmax):
2301+
# ld.red returns each x32 tile's max in an extra register alongside the
2302+
# 32 values; the reduction tensor collapses the V-mode to a single element.
2303+
tSrS_red = cute.make_rmem_tensor(((1, 1), *tSrS_t2r.shape[1:]), self.qk_acc_dtype)
2304+
cute.copy(thr_tmem_load, tStS_t2r, (tSrS_t2r, tSrS_red))
2305+
for i in cutlass.range_constexpr(cute.size(tSrS_red.shape)):
2306+
hw_row_max = cute.arch.fmax(hw_row_max, tSrS_red[i])
2307+
else:
2308+
cute.copy(thr_tmem_load, tStS_t2r, tSrS_t2r)
22882309
# tSrS_t2r = copy_utils.load_t2r(thr_tmem_load, tScS_shape, tStS_t2r)
22892310
if cutlass.const_expr(self.score_mod is not None):
22902311
self.apply_score_mod(
@@ -2304,7 +2325,12 @@ def softmax_step(
23042325

23052326
if const_expr(mask_fn is not None):
23062327
mask_fn(tSrS_t2r, n_block=n_block)
2307-
row_max, acc_scale = softmax.update_row_max(tSrS_t2r.load(), is_first)
2328+
# The hardware max is only valid when masking left the values untouched;
2329+
# masked iterations reduce over the post-mask values in software.
2330+
if const_expr(self.use_ldred_rowmax and mask_is_noop):
2331+
row_max, acc_scale = softmax.update_row_max_precomputed(hw_row_max, is_first)
2332+
else:
2333+
row_max, acc_scale = softmax.update_row_max(tSrS_t2r.load(), is_first)
23082334

23092335
if const_expr(not is_first):
23102336
# 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)