Skip to content

Commit 6e646e0

Browse files
Kevin-Li-2025Kevin-Li-2025drisspg
authored
Fix CuTe SM120 compile-time argument handling (#2671)
* Fix CuTe SM120 compile-time argument handling * clean up * guard empty SM120 local backward tiles --------- Co-authored-by: Kevin-Li-2025 <2242139@qq.com> Co-authored-by: drisspg <drisspguessous@gmail.com>
1 parent 1f7ce2f commit 6e646e0

5 files changed

Lines changed: 91 additions & 68 deletions

File tree

flash_attn/cute/flash_bwd.py

Lines changed: 79 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from flash_attn.cute.mask import AttentionMask
2222
from flash_attn.cute.softmax import call_score_mod, call_score_mod_bwd
2323
from flash_attn.cute.seqlen_info import SeqlenInfoQK
24+
from flash_attn.cute.block_info import BlockInfo
2425
from quack.cute_dsl_utils import ParamsBase
2526
from flash_attn.cute.tile_scheduler import SingleTileScheduler, SingleTileVarlenScheduler, TileSchedulerArguments
2627
from flash_attn.cute.block_sparsity import BlockSparseTensors
@@ -41,6 +42,7 @@ def __init__(
4142
num_threads: int = 256,
4243
pack_gqa: bool = False,
4344
is_causal: bool = False,
45+
is_local: bool = False,
4446
SdP_swapAB: bool = False,
4547
dKV_swapAB: bool = False,
4648
dQ_swapAB: bool = False,
@@ -83,6 +85,7 @@ def __init__(
8385
self.num_threads = num_threads
8486
self.pack_gqa = pack_gqa
8587
self.is_causal = is_causal
88+
self.is_local = is_local
8689
self.num_stages_Q = num_stages_Q
8790
self.num_stages_dO = num_stages_dO
8891
self.SdP_swapAB = SdP_swapAB
@@ -436,7 +439,9 @@ def __call__(
436439
tile_sched_params = TileScheduler.to_underlying_arguments(tile_sched_args)
437440
grid_dim = TileScheduler.get_grid_shape(tile_sched_params)
438441

439-
softmax_scale_log2, softmax_scale = utils.compute_softmax_scale_log2(softmax_scale, self.score_mod)
442+
softmax_scale_log2, _ = utils.compute_softmax_scale_log2(
443+
softmax_scale, self.score_mod
444+
)
440445
self.kernel(
441446
mQ,
442447
mK,
@@ -453,6 +458,8 @@ def __call__(
453458
mSeqUsedK,
454459
softmax_scale,
455460
softmax_scale_log2,
461+
window_size_left,
462+
window_size_right,
456463
self.sQ_layout,
457464
self.sK_layout,
458465
self.sV_layout,
@@ -498,6 +505,8 @@ def kernel(
498505
mSeqUsedK: Optional[cute.Tensor],
499506
softmax_scale: cutlass.Float32,
500507
softmax_scale_log2: cutlass.Float32,
508+
window_size_left: Optional[Int32],
509+
window_size_right: Optional[Int32],
501510
sQ_layout: cute.ComposedLayout,
502511
sK_layout: cute.ComposedLayout,
503512
sV_layout: cute.ComposedLayout,
@@ -540,13 +549,16 @@ def kernel(
540549
tile_n=self.n_block_size,
541550
)
542551

543-
m_block_max = cute.ceil_div(seqlen.seqlen_q, self.m_block_size)
544-
m_block_min = 0
545-
if cutlass.const_expr(self.is_causal):
546-
m_block_min = max(
547-
(n_block * self.n_block_size + seqlen.seqlen_q - seqlen.seqlen_k) // self.m_block_size,
548-
m_block_min,
549-
)
552+
block_info = BlockInfo(
553+
self.m_block_size,
554+
self.n_block_size,
555+
self.is_causal,
556+
self.is_local,
557+
False,
558+
window_size_left,
559+
window_size_right,
560+
)
561+
m_block_min, m_block_max = block_info.get_m_block_min_max(seqlen, n_block)
550562
# TODO: return early if m_block_max == 0
551563

552564
# ///////////////////////////////////////////////////////////////////////////////
@@ -786,61 +798,68 @@ def kernel(
786798
aux_data=aux_data,
787799
)
788800

789-
# ///////////////////////////////////////////////////////////////////////////////
790-
# Prologue
791-
# ///////////////////////////////////////////////////////////////////////////////
792-
# Start async loads of the last mn-tile, where we take care of the mn residue
793-
self.load_V(gmem_thr_copy_VdO, tVgV, tVsV, n_block, seqlen=seqlen.seqlen_k,
794-
headdim=d_head_v)
795-
if cutlass.const_expr(self.V_in_regs):
796-
cute.arch.cp_async_commit_group()
797-
self.load_K(gmem_thr_copy_QK, tKgK, tKsK, n_block, seqlen=seqlen.seqlen_k,
798-
headdim=d_head)
799-
cute.arch.cp_async_commit_group()
800-
801-
if cutlass.const_expr(self.V_in_regs):
802-
cute.arch.cp_async_wait_group(1)
803-
cute.arch.barrier()
804-
tdPrV_copy_view = smem_thr_copy_KV.retile(tdPrV)
805-
cute.copy(smem_thr_copy_KV, tdPsV, tdPrV_copy_view)
806-
# Sync to avoid loading Q to smem_q, which overlaps with smem_v
807-
cute.arch.barrier()
808-
809-
m_block = m_block_min
810-
assert self.num_stages_Q >= self.num_stages_dO
811-
for stage in cutlass.range_constexpr(self.num_stages_Q):
812-
if cutlass.const_expr(self.num_stages_Q == 1 or stage < self.num_stages_Q - 1):
813-
if stage == 0 or m_block + stage < m_block_max:
814-
load_Q_LSE(m_block + stage, smem_pipe_write_q=stage)
815-
cute.arch.cp_async_commit_group()
816-
if cutlass.const_expr(stage < self.num_stages_dO):
817-
if stage == 0 or m_block + stage < m_block_max:
818-
load_dO_dPsum(m_block + stage, smem_pipe_write_q=stage)
801+
if m_block_min < m_block_max:
802+
# ///////////////////////////////////////////////////////////////////////////////
803+
# Prologue
804+
# ///////////////////////////////////////////////////////////////////////////////
805+
# Start async loads of the last mn-tile, where we take care of the mn residue
806+
self.load_V(gmem_thr_copy_VdO, tVgV, tVsV, n_block, seqlen=seqlen.seqlen_k,
807+
headdim=d_head_v)
808+
if cutlass.const_expr(self.V_in_regs):
819809
cute.arch.cp_async_commit_group()
810+
self.load_K(gmem_thr_copy_QK, tKgK, tKsK, n_block, seqlen=seqlen.seqlen_k,
811+
headdim=d_head)
812+
cute.arch.cp_async_commit_group()
820813

821-
# ///////////////////////////////////////////////////////////////////////////////
822-
# Mainloop
823-
# ///////////////////////////////////////////////////////////////////////////////
824-
# Start processing of the first n-block.
825-
mask = AttentionMask(self.m_block_size, self.n_block_size, seqlen)
826-
mask_fn = partial(
827-
mask.apply_mask, n_block=n_block, thr_mma=thr_mma_sdp,
828-
batch_idx=batch_idx, head_idx=head_idx,
829-
mask_seqlen=True, mask_causal=self.is_causal
830-
)
831-
smem_pipe_read_q = cutlass.Int32(0)
832-
smem_pipe_read_do = cutlass.Int32(0)
833-
smem_pipe_write_q = cutlass.Int32(self.num_stages_Q - 1)
834-
smem_pipe_write_do = cutlass.Int32(0)
835-
for m_tile in cutlass.range(m_block_min, m_block_max, unroll=1):
836-
compute_one_m_block(
837-
m_tile, smem_pipe_read_q, smem_pipe_read_do, smem_pipe_write_q, smem_pipe_write_do,
838-
mask_fn=mask_fn,
814+
if cutlass.const_expr(self.V_in_regs):
815+
cute.arch.cp_async_wait_group(1)
816+
cute.arch.barrier()
817+
tdPrV_copy_view = smem_thr_copy_KV.retile(tdPrV)
818+
cute.copy(smem_thr_copy_KV, tdPsV, tdPrV_copy_view)
819+
# Sync to avoid loading Q to smem_q, which overlaps with smem_v
820+
cute.arch.barrier()
821+
822+
m_block = m_block_min
823+
assert self.num_stages_Q >= self.num_stages_dO
824+
for stage in cutlass.range_constexpr(self.num_stages_Q):
825+
if cutlass.const_expr(self.num_stages_Q == 1 or stage < self.num_stages_Q - 1):
826+
if stage == 0 or m_block + stage < m_block_max:
827+
load_Q_LSE(m_block + stage, smem_pipe_write_q=stage)
828+
cute.arch.cp_async_commit_group()
829+
if cutlass.const_expr(stage < self.num_stages_dO):
830+
if stage == 0 or m_block + stage < m_block_max:
831+
load_dO_dPsum(m_block + stage, smem_pipe_write_q=stage)
832+
cute.arch.cp_async_commit_group()
833+
834+
# ///////////////////////////////////////////////////////////////////////////////
835+
# Mainloop
836+
# ///////////////////////////////////////////////////////////////////////////////
837+
# Start processing of the first n-block.
838+
mask = AttentionMask(
839+
self.m_block_size,
840+
self.n_block_size,
841+
seqlen,
842+
window_size_left,
843+
window_size_right,
844+
)
845+
mask_fn = partial(
846+
mask.apply_mask, n_block=n_block, thr_mma=thr_mma_sdp,
847+
batch_idx=batch_idx, head_idx=head_idx,
848+
mask_seqlen=True, mask_causal=self.is_causal, mask_local=self.is_local
839849
)
840-
smem_pipe_read_q = self.advance_pipeline(smem_pipe_read_q, self.num_stages_Q)
841-
smem_pipe_read_do = self.advance_pipeline(smem_pipe_read_do, self.num_stages_dO)
842-
smem_pipe_write_q = self.advance_pipeline(smem_pipe_write_q, self.num_stages_Q)
843-
smem_pipe_write_do = self.advance_pipeline(smem_pipe_write_do, self.num_stages_dO)
850+
smem_pipe_read_q = cutlass.Int32(0)
851+
smem_pipe_read_do = cutlass.Int32(0)
852+
smem_pipe_write_q = cutlass.Int32(self.num_stages_Q - 1)
853+
smem_pipe_write_do = cutlass.Int32(0)
854+
for m_tile in cutlass.range(m_block_min, m_block_max, unroll=1):
855+
compute_one_m_block(
856+
m_tile, smem_pipe_read_q, smem_pipe_read_do, smem_pipe_write_q, smem_pipe_write_do,
857+
mask_fn=mask_fn,
858+
)
859+
smem_pipe_read_q = self.advance_pipeline(smem_pipe_read_q, self.num_stages_Q)
860+
smem_pipe_read_do = self.advance_pipeline(smem_pipe_read_do, self.num_stages_dO)
861+
smem_pipe_write_q = self.advance_pipeline(smem_pipe_write_q, self.num_stages_Q)
862+
smem_pipe_write_do = self.advance_pipeline(smem_pipe_write_do, self.num_stages_dO)
844863

845864
# ///////////////////////////////////////////////////////////////////////////////
846865
# Epilogue

flash_attn/cute/flash_fwd.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -633,8 +633,8 @@ def __call__(
633633
mSeqUsedQ: Optional[cute.Tensor] = None,
634634
mSeqUsedK: Optional[cute.Tensor] = None,
635635
mPageTable: Optional[cute.Tensor] = None,
636-
window_size_left: Optional[Int32] = None,
637-
window_size_right: Optional[Int32] = None,
636+
window_size_left: Int32 | int | None = None,
637+
window_size_right: Int32 | int | None = None,
638638
learnable_sink: Optional[cute.Tensor] = None,
639639
blocksparse_tensors: Optional[BlockSparseTensors] = None,
640640
aux_data: AuxData = AuxData(),

flash_attn/cute/flash_fwd_sm120.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,16 @@
77

88
import cutlass
99
import cutlass.utils as utils_basic
10+
from cutlass.base_dsl.arch import Arch
1011

1112
from flash_attn.cute.flash_fwd import FlashAttentionForwardSm80
1213

1314

1415
class FlashAttentionForwardSm120(FlashAttentionForwardSm80):
15-
# Keep arch = 80 to use CpAsync code paths (no TMA for output).
16-
# The compilation target is determined by the GPU at compile time, not this field.
17-
arch = 80
16+
def __init__(self, *args, **kwargs):
17+
"""Force SM80 code paths while the DSL still targets the resident SM120 GPU."""
18+
super().__init__(*args, **kwargs)
19+
self.arch = Arch.sm_80
1820

1921
@staticmethod
2022
def can_implement(

flash_attn/cute/interface.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,8 @@ def _flash_attn_fwd(
459459
qhead_per_kvhead = num_head // num_head_kv
460460
if pack_gqa is None:
461461
pack_gqa = qhead_per_kvhead > 1
462+
if arch // 10 == 12:
463+
pack_gqa = False
462464

463465
is_fp8 = v.dtype in (torch.float8_e4m3fn, torch.float8_e5m2)
464466
requires_grad = any(t is not None and t.requires_grad for t in [q, k, v, qv])
@@ -1364,6 +1366,7 @@ def _flash_attn_bwd(
13641366
AtomLayoutNdKV = 4
13651367
AtomLayoutMdQ = 4
13661368
V_in_regs = False
1369+
dQ_single_wg = False
13671370
cluster_size = 1
13681371
use_2cta_instrs = False
13691372
num_threads = 128
@@ -1797,6 +1800,7 @@ def _flash_attn_bwd(
17971800
num_threads,
17981801
pack_gqa,
17991802
causal,
1803+
local,
18001804
SdP_swapAB,
18011805
dKV_swapAB,
18021806
dQ_swapAB,

flash_attn/cute/utils.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -472,9 +472,7 @@ def atomic_add_fp32(a: float | Float32, gmem_ptr: cute.Pointer, *, loc=None, ip=
472472
# is_align_stack=False,
473473
# asm_dialect=llvm.AsmDialect.AD_ATT,
474474
# )
475-
nvvm.atomicrmw(
476-
res=T.f32(), op=nvvm.AtomicOpKind.FADD, ptr=gmem_ptr.llvm_ptr, a=Float32(a).ir_value()
477-
)
475+
nvvm.atomicrmw(op=nvvm.AtomicOpKind.FADD, ptr=gmem_ptr.llvm_ptr, a=Float32(a).ir_value())
478476

479477

480478
@dsl_user_op

0 commit comments

Comments
 (0)