Instructions to use Efficient-Large-Model/Sol-Attn-Kernel-Source with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Kernels
How to use Efficient-Large-Model/Sol-Attn-Kernel-Source with Kernels:
# !pip install kernels from kernels import get_kernel kernel = get_kernel("Efficient-Large-Model/Sol-Attn-Kernel-Source") - Notebooks
- Google Colab
- Kaggle
| """Sol-Attn forward kernel for Blackwell SM100. | |
| The kernel routes two physical N64 halves at a time and accumulates their exact | |
| indices into one logical G256 stream. Per-column additive masks are built once | |
| in shared memory and reused by the approximate and exact score paths. | |
| """ | |
| import math | |
| import cuda.bindings.driver as cuda | |
| import cutlass | |
| import cutlass.cute as cute | |
| import cutlass.pipeline as pipeline | |
| import cutlass.utils as utils | |
| import cutlass.utils.blackwell_helpers as sm100_utils | |
| from .._vendor.flash_attn.cute import pipeline as fa_pipeline | |
| from .._vendor.flash_attn.cute import utils as fa_utils | |
| from cutlass import BFloat16, Float32, Int32 | |
| from cutlass._mlir.dialects import llvm | |
| from cutlass.cute.nvgpu import cpasync, tcgen05 | |
| from cutlass.cutlass_dsl import T, dsl_user_op | |
| from .._vendor.flash_attn.cute.cute_dsl_utils import assume_tensor_aligned | |
| from .softmax import ( | |
| _load_m64_n128_score as _load_pair_score, | |
| _online_update_one_half as _online_update_pair, | |
| _rescale_m64_partial_o as _rescale_pair_o, | |
| ) | |
| from . import math as mma_utils | |
| from ..common import layout_utils | |
| from ..common.selector import ( | |
| sol_attn_popc_b32, | |
| sol_attn_route_is_exact, | |
| ) | |
| from .tmem import ( | |
| _add_physical_tmem_base, | |
| _zero_based_tmem_tensor, | |
| load_m64_o_fp32_256b, | |
| tcgen05_wait_st, | |
| ) | |
| M = 64 | |
| N_MEMBER = 64 | |
| N_PACK_HALF = 128 | |
| D = 128 | |
| DV = 128 | |
| THREADS = 192 | |
| PAIR_STAGES = 1 | |
| TMEM_COLS = 256 | |
| PAIR_SCORE_OFFSET = 0 | |
| PAIR_P_OFFSET = 64 | |
| O_OFFSET = 128 | |
| PACK_QK_INST = (M, N_PACK_HALF, 16) | |
| PACK_QK_TILE = (M, N_PACK_HALF, D) | |
| PACK_PV_INST = (M, DV, 16) | |
| PACK_PV_TILE = (M, DV, N_PACK_HALF) | |
| PACK_QK_QUARTER_INST = (M, N_MEMBER, 16) | |
| PACK_PV_QUARTER_INST = (M, 64, 16) | |
| PACK_QK_GATHER_TILE = (M, N_MEMBER, 64) | |
| PACK_PV_GATHER_TILE = (M, 64, 64) | |
| LOG2E = math.log2(math.e) | |
| LN2 = math.log(2.0) | |
| SEMANTIC_ROW_OFFSET = 16 | |
| LOGICAL_GROUP_SIZE = 256 | |
| ROUTE_TILE_SIZE = 128 | |
| ROUTE_HALVES_PER_GROUP = LOGICAL_GROUP_SIZE // ROUTE_TILE_SIZE | |
| ROUTE_MASK_WORDS = 4 | |
| # masks[0:4], current-half exact count, append base, cumulative exact count, | |
| # logical-terminal-half flag | |
| PACKET_WORDS = 8 | |
| ROUTE_INDEX_CAPACITY = LOGICAL_GROUP_SIZE | |
| PAIR_P_CHUNKS = 4 | |
| PAIR_P_CHUNK_PACKED_COLUMNS = (N_PACK_HALF // 2) // PAIR_P_CHUNKS | |
| PAIR_P_PACKED_REGISTERS_PER_THREAD_PER_CHUNK = 8 | |
| O_PACKED_STORE_VALUES_PER_WORD = 2 | |
| O_PACKED_STORE_ALIGNMENT_BYTES = 4 | |
| O_PACKED_STORE_WRITER_THREADS = 4 * 32 | |
| O_ROWS_PER_OWNER_THREAD = 2 | |
| O_PACKED_WORDS_PER_ROW_PER_THREAD = 16 | |
| O_PACKED_COLUMN_STRIDE = 8 | |
| def _cvt_bf16x2_f32( | |
| hi: Float32, | |
| lo: Float32, | |
| *, | |
| loc=None, | |
| ip=None, | |
| ) -> Int32: | |
| """Round two FP32 values and pack them as ``{lo, hi}`` BF16 bits.""" | |
| return Int32( | |
| llvm.inline_asm( | |
| T.i32(), | |
| [ | |
| Float32(hi).ir_value(loc=loc, ip=ip), | |
| Float32(lo).ir_value(loc=loc, ip=ip), | |
| ], | |
| "cvt.rn.bf16x2.f32 $0, $1, $2;", | |
| "=r,f,f", | |
| has_side_effects=False, | |
| is_align_stack=False, | |
| asm_dialect=llvm.AsmDialect.AD_ATT, | |
| ) | |
| ) | |
| def _store_global_u32_inline( | |
| ptr: cute.Pointer, | |
| value: Int32, | |
| *, | |
| loc=None, | |
| ip=None, | |
| ) -> None: | |
| """Store one aligned same-row BF16 pair as a single 32-bit word.""" | |
| llvm.inline_asm( | |
| None, | |
| [ | |
| ptr.toint().ir_value(), | |
| Int32(value).ir_value(loc=loc, ip=ip), | |
| ], | |
| "st.global.u32 [$0], $1;", | |
| "l,r", | |
| has_side_effects=True, | |
| is_align_stack=False, | |
| asm_dialect=llvm.AsmDialect.AD_ATT, | |
| ) | |
| def _prmt_b32( | |
| a: Int32, | |
| b: Int32, | |
| sel: Int32, | |
| *, | |
| loc=None, | |
| ip=None, | |
| ) -> Int32: | |
| """Select four bytes from packed words ``a`` and ``b``.""" | |
| return Int32( | |
| llvm.inline_asm( | |
| T.i32(), | |
| [ | |
| Int32(a).ir_value(loc=loc, ip=ip), | |
| Int32(b).ir_value(loc=loc, ip=ip), | |
| Int32(sel).ir_value(loc=loc, ip=ip), | |
| ], | |
| "prmt.b32 $0, $1, $2, $3;", | |
| "=r,r,r,r", | |
| has_side_effects=False, | |
| is_align_stack=False, | |
| asm_dialect=llvm.AsmDialect.AD_ATT, | |
| ) | |
| ) | |
| def _store_pair_probability_chunked_tmemp( | |
| o_template: cute.Tensor, | |
| probabilities: cute.Tensor, | |
| tmem_base: Int32, | |
| p_offset: Int32, | |
| owner_tidx: Int32, | |
| ): | |
| """Store M64xN128 BF16 P as four live-range-bounded x8 chunks. | |
| Probabilities remain FP32 until each x8 fragment is converted, and every | |
| chunk waits for its St16x64b store before the fragment goes out of scope. | |
| """ | |
| assert o_template.element_type == Float32 | |
| assert cute.size(o_template) == M * DV | |
| p_chunk_layout = cute.composition( | |
| o_template.layout, | |
| cute.make_layout((M, PAIR_P_CHUNK_PACKED_COLUMNS)), | |
| ) | |
| relative_chunk = _zero_based_tmem_tensor(Float32, p_chunk_layout) | |
| store_atom = cute.make_copy_atom( | |
| tcgen05.copy.St16x64bOp(tcgen05.copy.Repetition(8)), | |
| Float32, | |
| ) | |
| tiled_store = tcgen05.make_tmem_copy(store_atom, relative_chunk) | |
| thread_store = tiled_store.get_slice(owner_tidx) | |
| destination_relative = thread_store.partition_D(relative_chunk) | |
| destination = _add_physical_tmem_base( | |
| destination_relative, tmem_base + p_offset | |
| ) | |
| p_store_coordinates = thread_store.partition_S( | |
| cute.make_identity_tensor((M, PAIR_P_CHUNK_PACKED_COLUMNS)) | |
| ) | |
| lane = owner_tidx % Int32(32) | |
| for chunk_idx in cutlass.range_constexpr(PAIR_P_CHUNKS): | |
| p_store_registers = cute.make_rmem_tensor( | |
| p_store_coordinates.shape, Float32 | |
| ) | |
| assert ( | |
| cute.size(p_store_registers) | |
| == PAIR_P_PACKED_REGISTERS_PER_THREAD_PER_CHUNK | |
| ) | |
| assert ( | |
| cute.size(probabilities) | |
| == 2 * cute.size(p_store_registers) * PAIR_P_CHUNKS | |
| ) | |
| p_store_words = cute.make_tensor( | |
| cute.recast_ptr(p_store_registers.iterator, dtype=Int32), | |
| p_store_registers.layout, | |
| ) | |
| probability_base = chunk_idx * (2 * cute.size(p_store_registers)) | |
| for i in cutlass.range( | |
| cute.size(p_store_registers), unroll_full=True | |
| ): | |
| low = probability_base + i * 2 | |
| high = low + 1 | |
| own = _cvt_bf16x2_f32( | |
| Float32(probabilities[high]), | |
| Float32(probabilities[low]), | |
| ) | |
| peer = cute.arch.shuffle_sync_bfly(own, offset=2) | |
| if (lane & Int32(2)) == Int32(0): | |
| p_store_words[i] = _prmt_b32( | |
| own, peer, Int32(0x5410) | |
| ) | |
| else: | |
| p_store_words[i] = _prmt_b32( | |
| own, peer, Int32(0x3276) | |
| ) | |
| destination_chunk = cute.make_tensor( | |
| destination.iterator | |
| + chunk_idx * PAIR_P_CHUNK_PACKED_COLUMNS, | |
| destination.layout, | |
| ) | |
| cute.copy(tiled_store, p_store_registers, destination_chunk) | |
| tcgen05_wait_st() | |
| cute.arch.fence_view_async_tmem_store() | |
| def _load_pack_k_half( | |
| tma_atom_pack_k: cute.CopyAtom, | |
| tPackKgK: cute.Tensor, | |
| tPackKsK: cute.Tensor, | |
| block0: Int32, | |
| block1: Int32, | |
| quarter0: Int32, | |
| barrier, | |
| ): | |
| """Gather one canonical N128 K tile as K0/N0,K0/N1,K1/N0,K1/N1.""" | |
| cute.copy( | |
| tma_atom_pack_k, | |
| tPackKgK[(None, block0, Int32(0))], | |
| tPackKsK[(None, quarter0)], | |
| tma_bar_ptr=barrier, | |
| ) | |
| cute.copy( | |
| tma_atom_pack_k, | |
| tPackKgK[(None, block1, Int32(0))], | |
| tPackKsK[(None, quarter0 + Int32(1))], | |
| tma_bar_ptr=barrier, | |
| ) | |
| cute.copy( | |
| tma_atom_pack_k, | |
| tPackKgK[(None, block0, Int32(1))], | |
| tPackKsK[(None, quarter0 + Int32(2))], | |
| tma_bar_ptr=barrier, | |
| ) | |
| cute.copy( | |
| tma_atom_pack_k, | |
| tPackKgK[(None, block1, Int32(1))], | |
| tPackKsK[(None, quarter0 + Int32(3))], | |
| tma_bar_ptr=barrier, | |
| ) | |
| def _load_pack_v_half( | |
| tma_atom_pack_v: cute.CopyAtom, | |
| tPackVgV: cute.Tensor, | |
| tPackVsV: cute.Tensor, | |
| block0: Int32, | |
| block1: Int32, | |
| quarter0: Int32, | |
| barrier, | |
| ): | |
| """Gather one canonical N128 V tile as D0/N0,D0/N1,D1/N0,D1/N1.""" | |
| cute.copy( | |
| tma_atom_pack_v, | |
| tPackVgV[(None, Int32(0), block0)], | |
| tPackVsV[(None, quarter0)], | |
| tma_bar_ptr=barrier, | |
| ) | |
| cute.copy( | |
| tma_atom_pack_v, | |
| tPackVgV[(None, Int32(0), block1)], | |
| tPackVsV[(None, quarter0 + Int32(1))], | |
| tma_bar_ptr=barrier, | |
| ) | |
| cute.copy( | |
| tma_atom_pack_v, | |
| tPackVgV[(None, Int32(1), block0)], | |
| tPackVsV[(None, quarter0 + Int32(2))], | |
| tma_bar_ptr=barrier, | |
| ) | |
| cute.copy( | |
| tma_atom_pack_v, | |
| tPackVgV[(None, Int32(1), block1)], | |
| tPackVsV[(None, quarter0 + Int32(3))], | |
| tma_bar_ptr=barrier, | |
| ) | |
| class SharedStorage: | |
| q_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2] | |
| pack_k_mbar_ptr: cute.struct.MemRange[ | |
| cutlass.Int64, PAIR_STAGES * 2 | |
| ] | |
| pack_v_mbar_ptr: cute.struct.MemRange[ | |
| cutlass.Int64, PAIR_STAGES * 2 | |
| ] | |
| pair_score_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2] | |
| pair_o_mbar_ptr: cute.struct.MemRange[cutlass.Int64, 2] | |
| final_stats: cute.struct.Align[ | |
| cute.struct.MemRange[Float32, M * 2], 128 | |
| ] | |
| route_partial: cute.struct.Align[ | |
| cute.struct.MemRange[Float32, 4 * ROUTE_TILE_SIZE], 16 | |
| ] | |
| column_masks: cute.struct.Align[ | |
| cute.struct.MemRange[Float32, ROUTE_TILE_SIZE], 16 | |
| ] | |
| route_packet: cute.struct.Align[ | |
| cute.struct.MemRange[Int32, PACKET_WORDS], 16 | |
| ] | |
| tmem_holding_buf: Int32 | |
| # Owner-warp 0 lane 0 appends both N128 route masks. The full-CTA | |
| # pre-exact join publishes the completed list to warp 5; no HBM indices. | |
| route_indices: cute.struct.Align[ | |
| cute.struct.MemRange[Int32, ROUTE_INDEX_CAPACITY], 16 | |
| ] | |
| def _sol_attn_sm100_bf16_kernel( | |
| tiled_pack_qk: cute.TiledMma, | |
| tiled_pack_pv: cute.TiledMma, | |
| tma_atom_q: cute.CopyAtom, | |
| mQ_mkl: cute.Tensor, | |
| tma_atom_pack_k: cute.CopyAtom, | |
| mPackK_nkl: cute.Tensor, | |
| tma_atom_pack_v: cute.CopyAtom, | |
| mPackV_nkl: cute.Tensor, | |
| tma_atom_kc: cute.CopyAtom, | |
| mKC_nkl: cute.Tensor, | |
| tma_atom_vc: cute.CopyAtom, | |
| mVC_nkl: cute.Tensor, | |
| mThreshold_bnh: cute.Tensor, | |
| mO_bthd: cute.Tensor, | |
| mLSE_bth: cute.Tensor, | |
| token_count: Int32, | |
| route_valid_total: Int32, | |
| num_route_tiles: Int32, | |
| softmax_scale: Float32, | |
| sink_start_block: Int32, | |
| sink_end_block: Int32, | |
| q_layout: cute.ComposedLayout, | |
| pack_k_layout: cute.ComposedLayout, | |
| pack_k_gather_layout: cute.ComposedLayout, | |
| pack_p_layout: cute.ComposedLayout, | |
| pack_v_layout: cute.ComposedLayout, | |
| pack_v_gather_layout: cute.ComposedLayout, | |
| route_k_layout: cute.ComposedLayout, | |
| route_v_layout: cute.ComposedLayout, | |
| ): | |
| tidx, _, _ = cute.arch.thread_idx() | |
| warp_idx = cute.arch.make_warp_uniform(cute.arch.warp_idx()) | |
| q_block_idx_raw, head_idx_raw, batch_idx_raw = cute.arch.block_idx() | |
| q_block_idx = Int32(q_block_idx_raw) | |
| head_idx = Int32(head_idx_raw) | |
| batch_idx = Int32(batch_idx_raw) | |
| softmax_scale_log2 = softmax_scale * Float32(LOG2E) | |
| smem = utils.SmemAllocator() | |
| storage = smem.allocate(SharedStorage) | |
| sFinalStats = storage.final_stats.get_tensor( | |
| cute.make_layout((M, 2)) | |
| ) | |
| route_partial = storage.route_partial.get_tensor( | |
| cute.make_layout((4, ROUTE_TILE_SIZE)) | |
| ) | |
| column_masks = storage.column_masks.get_tensor( | |
| cute.make_layout((ROUTE_TILE_SIZE,)) | |
| ) | |
| route_packet = storage.route_packet.get_tensor( | |
| cute.make_layout((PACKET_WORDS,)) | |
| ) | |
| route_indices = storage.route_indices.get_tensor( | |
| cute.make_layout((ROUTE_INDEX_CAPACITY,)) | |
| ) | |
| sQ = smem.allocate_tensor( | |
| element_type=BFloat16, | |
| layout=q_layout.outer, | |
| byte_alignment=128, | |
| swizzle=q_layout.inner, | |
| ) | |
| sPackK = smem.allocate_tensor( | |
| element_type=BFloat16, | |
| layout=pack_k_layout.outer, | |
| byte_alignment=128, | |
| swizzle=pack_k_layout.inner, | |
| ) | |
| sPackV = smem.allocate_tensor( | |
| element_type=BFloat16, | |
| layout=pack_v_layout.outer, | |
| byte_alignment=128, | |
| swizzle=pack_v_layout.inner, | |
| ) | |
| # One independent physical N128 K stage and one N128 V stage. Every | |
| # runtime route/exact transaction stays in this completion domain. | |
| sPackKGather = cute.make_tensor( | |
| cute.recast_ptr( | |
| sPackK.iterator, pack_k_gather_layout.inner, BFloat16 | |
| ), | |
| pack_k_gather_layout.outer, | |
| ) | |
| sPackVGather = cute.make_tensor( | |
| cute.recast_ptr( | |
| sPackV.iterator, pack_v_gather_layout.inner, BFloat16 | |
| ), | |
| pack_v_gather_layout.outer, | |
| ) | |
| # KC/VC and exact K/V have disjoint lifetimes within each runtime group. | |
| # They reuse the same independent N128 K and V allocations without a | |
| # cross-operand alias barrier. | |
| sKC = cute.make_tensor( | |
| cute.recast_ptr(sPackK.iterator, route_k_layout.inner, BFloat16), | |
| route_k_layout.outer, | |
| ) | |
| sVC = cute.make_tensor( | |
| cute.recast_ptr(sPackV.iterator, route_v_layout.inner, BFloat16), | |
| route_v_layout.outer, | |
| ) | |
| tmem_barrier = pipeline.NamedBarrier(barrier_id=1, num_threads=THREADS) | |
| score_loaded_barrier = pipeline.NamedBarrier( | |
| barrier_id=2, num_threads=4 * 32 | |
| ) | |
| final_stats_ready_barrier = pipeline.NamedBarrier( | |
| barrier_id=3, num_threads=4 * 32 | |
| ) | |
| pack_score_loaded_barrier = pipeline.NamedBarrier( | |
| barrier_id=4, num_threads=4 * 32 | |
| ) | |
| route_packet_ready_barrier = pipeline.NamedBarrier( | |
| barrier_id=5, num_threads=5 * 32 | |
| ) | |
| exact_pair_p_ready_barrier = pipeline.NamedBarrier( | |
| barrier_id=6, num_threads=5 * 32 | |
| ) | |
| tmem = utils.TmemAllocator( | |
| storage.tmem_holding_buf.ptr, | |
| barrier_for_retrieve=tmem_barrier, | |
| ) | |
| tmem.allocate(TMEM_COLS) | |
| one_thread = pipeline.CooperativeGroup(pipeline.Agent.Thread, 1) | |
| pack_owner_threads = pipeline.CooperativeGroup( | |
| pipeline.Agent.Thread, 4 * 32 | |
| ) | |
| q_bytes = cute.size_in_bytes( | |
| BFloat16, cute.select(q_layout, mode=[0, 1, 2]) | |
| ) | |
| route_k_bytes = cute.size_in_bytes( | |
| BFloat16, cute.select(route_k_layout, mode=[0, 1, 2]) | |
| ) | |
| route_v_bytes = cute.size_in_bytes( | |
| BFloat16, cute.select(route_v_layout, mode=[0, 1, 2]) | |
| ) | |
| pack_k_bytes = cute.size_in_bytes( | |
| BFloat16, cute.select(pack_k_layout, mode=[0, 1, 2]) | |
| ) | |
| pack_v_bytes = cute.size_in_bytes( | |
| BFloat16, cute.select(pack_v_layout, mode=[0, 1, 2]) | |
| ) | |
| assert route_k_bytes == pack_k_bytes | |
| assert route_v_bytes == pack_v_bytes | |
| q_pipe = fa_pipeline.PipelineTmaUmma.create( | |
| num_stages=1, | |
| producer_group=one_thread, | |
| consumer_group=one_thread, | |
| tx_count=q_bytes, | |
| barrier_storage=storage.q_mbar_ptr.data_ptr(), | |
| ) | |
| pack_k_pipe = fa_pipeline.PipelineTmaUmma.create( | |
| num_stages=PAIR_STAGES, | |
| producer_group=one_thread, | |
| consumer_group=one_thread, | |
| tx_count=pack_k_bytes, | |
| barrier_storage=storage.pack_k_mbar_ptr.data_ptr(), | |
| ) | |
| pack_v_pipe = fa_pipeline.PipelineTmaUmma.create( | |
| num_stages=PAIR_STAGES, | |
| producer_group=one_thread, | |
| consumer_group=one_thread, | |
| tx_count=pack_v_bytes, | |
| barrier_storage=storage.pack_v_mbar_ptr.data_ptr(), | |
| ) | |
| pair_score_pipe = fa_pipeline.PipelineUmmaAsync.create( | |
| num_stages=1, | |
| producer_group=one_thread, | |
| consumer_group=pack_owner_threads, | |
| barrier_storage=storage.pair_score_mbar_ptr.data_ptr(), | |
| ) | |
| pair_o_pipe = fa_pipeline.PipelineUmmaAsync.create( | |
| num_stages=1, | |
| producer_group=one_thread, | |
| consumer_group=pack_owner_threads, | |
| barrier_storage=storage.pair_o_mbar_ptr.data_ptr(), | |
| ) | |
| mQ_cur = mQ_mkl[None, None, head_idx, batch_idx] | |
| mPackK_cur = mPackK_nkl[None, None, head_idx, batch_idx] | |
| mPackV_cur = mPackV_nkl[None, None, head_idx, batch_idx] | |
| mKC_cur = mKC_nkl[None, None, head_idx, batch_idx] | |
| mVC_cur = mVC_nkl[None, None, head_idx, batch_idx] | |
| gQ = cute.local_tile(mQ_cur, (M, D), (None, 0)) | |
| gPackK = cute.local_tile( | |
| mPackK_cur, (N_MEMBER, 64), (None, None) | |
| ) | |
| gPackV = cute.local_tile( | |
| mPackV_cur, (64, N_MEMBER), (None, None) | |
| ) | |
| gKC = cute.local_tile(mKC_cur, (N_PACK_HALF, D), (None, 0)) | |
| gVC = cute.local_tile(mVC_cur, (DV, N_PACK_HALF), (0, None)) | |
| thr_pack_qk = tiled_pack_qk.get_slice(0) | |
| thr_pack_pv = tiled_pack_pv.get_slice(0) | |
| tCgQ = thr_pack_qk.partition_A(gQ) | |
| tCgKC = thr_pack_qk.partition_B(gKC) | |
| tCgVC = thr_pack_pv.partition_B(gVC) | |
| tCrKC = tiled_pack_qk.make_fragment_B(sKC) | |
| tCrVC = tiled_pack_pv.make_fragment_B(sVC) | |
| tCrPackQ = tiled_pack_qk.make_fragment_A(sQ) | |
| tCrPackK = tiled_pack_qk.make_fragment_B(sPackK) | |
| tCrPackV = tiled_pack_pv.make_fragment_B(sPackV) | |
| tQsQ, tQgQ = cpasync.tma_partition( | |
| tma_atom_q, | |
| 0, | |
| cute.make_layout(1), | |
| cute.group_modes(sQ, 0, 3), | |
| cute.group_modes(tCgQ, 0, 3), | |
| ) | |
| tPackKsK, tPackKgK = cpasync.tma_partition( | |
| tma_atom_pack_k, | |
| 0, | |
| cute.make_layout(1), | |
| cute.group_modes(sPackKGather, 0, 3), | |
| cute.group_modes(gPackK, 0, 2), | |
| ) | |
| tPackVsV, tPackVgV = cpasync.tma_partition( | |
| tma_atom_pack_v, | |
| 0, | |
| cute.make_layout(1), | |
| cute.group_modes(sPackVGather, 0, 3), | |
| cute.group_modes(gPackV, 0, 2), | |
| ) | |
| tKCsKC, tKCgKC = cpasync.tma_partition( | |
| tma_atom_kc, | |
| 0, | |
| cute.make_layout(1), | |
| cute.group_modes(sKC, 0, 3), | |
| cute.group_modes(tCgKC, 0, 3), | |
| ) | |
| tVCsVC, tVCgVC = cpasync.tma_partition( | |
| tma_atom_vc, | |
| 0, | |
| cute.make_layout(1), | |
| cute.group_modes(sVC, 0, 3), | |
| cute.group_modes(tCgVC, 0, 3), | |
| ) | |
| pack_score_shape = tiled_pack_qk.partition_shape_C( | |
| PACK_QK_TILE[:2] | |
| ) | |
| pack_score_template = tiled_pack_qk.make_fragment_C(pack_score_shape) | |
| pack_o_shape = tiled_pack_pv.partition_shape_C(PACK_PV_TILE[:2]) | |
| pack_o_template = tiled_pack_pv.make_fragment_C(pack_o_shape) | |
| tmem.wait_for_alloc() | |
| tmem_ptr = tmem.retrieve_ptr(Float32) | |
| # The 256-column allocation leaves the second half of SM TMEM available to | |
| # another CTA. The live allocation remains owned after permit release. | |
| tmem.relinquish_alloc_permit() | |
| tmem_base = tmem_ptr.toint() | |
| pair_tScore = cute.make_tensor( | |
| cute.make_ptr( | |
| Float32, | |
| tmem_base + Int32(PAIR_SCORE_OFFSET), | |
| cute.AddressSpace.tmem, | |
| assumed_align=16, | |
| ), | |
| pack_score_template.layout, | |
| ) | |
| pair_tO = cute.make_tensor( | |
| cute.make_ptr( | |
| Float32, | |
| tmem_base + Int32(O_OFFSET), | |
| cute.AddressSpace.tmem, | |
| assumed_align=16, | |
| ), | |
| pack_o_template.layout, | |
| ) | |
| # make_fragment_A drops the physical TMEM allocation base and addresses | |
| # packed BF16 columns in half-column units. Restore both facts so | |
| # 2*tmem_base + 2*PAIR_P_OFFSET names columns 64..127. | |
| pair_tP_storage = cute.make_tensor( | |
| pair_tScore.iterator, pack_p_layout.outer | |
| ) | |
| pair_tP_base = tiled_pack_pv.make_fragment_A(pair_tP_storage)[ | |
| None, None, None, 0 | |
| ] | |
| pair_tP = cute.make_tensor( | |
| pair_tP_base.iterator | |
| + tmem_base | |
| + tmem_base | |
| + Int32(PAIR_P_OFFSET * 2), | |
| pair_tP_base.layout, | |
| ) | |
| q_producer = fa_pipeline.make_pipeline_state( | |
| pipeline.PipelineUserType.Producer, 1 | |
| ) | |
| q_consumer = fa_pipeline.make_pipeline_state( | |
| pipeline.PipelineUserType.Consumer, 1 | |
| ) | |
| pack_k_producer = pipeline.make_pipeline_state( | |
| pipeline.PipelineUserType.Producer, PAIR_STAGES | |
| ) | |
| pack_k_consumer = pipeline.make_pipeline_state( | |
| pipeline.PipelineUserType.Consumer, PAIR_STAGES | |
| ) | |
| pack_v_producer = pipeline.make_pipeline_state( | |
| pipeline.PipelineUserType.Producer, PAIR_STAGES | |
| ) | |
| pack_v_consumer = pipeline.make_pipeline_state( | |
| pipeline.PipelineUserType.Consumer, PAIR_STAGES | |
| ) | |
| pair_score_producer = fa_pipeline.make_pipeline_state( | |
| pipeline.PipelineUserType.Producer, 1 | |
| ) | |
| pair_score_consumer = fa_pipeline.make_pipeline_state( | |
| pipeline.PipelineUserType.Consumer, 1 | |
| ) | |
| pair_o_producer = fa_pipeline.make_pipeline_state( | |
| pipeline.PipelineUserType.Producer, 1 | |
| ) | |
| pair_o_consumer = fa_pipeline.make_pipeline_state( | |
| pipeline.PipelineUserType.Consumer, 1 | |
| ) | |
| route_start_base = Int32(0) | |
| q_len = token_count - q_block_idx * Int32(M) | |
| if q_len > Int32(M): | |
| q_len = Int32(M) | |
| threshold = Float32( | |
| mThreshold_bnh[batch_idx, q_block_idx, head_idx] | |
| ) | |
| if warp_idx == Int32(5): | |
| cpasync.prefetch_descriptor(tma_atom_q) | |
| cpasync.prefetch_descriptor(tma_atom_pack_k) | |
| cpasync.prefetch_descriptor(tma_atom_pack_v) | |
| cpasync.prefetch_descriptor(tma_atom_kc) | |
| cpasync.prefetch_descriptor(tma_atom_vc) | |
| q_pipe.producer_acquire(q_producer) | |
| q_barrier = q_pipe.producer_get_barrier(q_producer) | |
| cute.copy( | |
| tma_atom_q, | |
| tQgQ[(None, q_block_idx)], | |
| tQsQ[(None, q_producer.index)], | |
| tma_bar_ptr=q_barrier, | |
| ) | |
| q_producer.advance() | |
| is_owner = warp_idx >= Int32(1) and warp_idx <= Int32(4) | |
| is_score_consumer = warp_idx <= Int32(4) | |
| owner_tidx = tidx - Int32(32) | |
| # One register-resident online state and one TMEM-O initialization bit span | |
| # every route/exact transaction in every runtime group. | |
| running_max = -Float32.inf | |
| running_sum = Float32(0.0) | |
| owner_o_initialized = Int32(0) | |
| mma_o_initialized = Int32(0) | |
| if warp_idx == Int32(0): | |
| q_pipe.consumer_wait(q_consumer) | |
| # The outer loop owns one logical G256 exact-index lifetime. The inner | |
| # loop consumes each physical score/PV half immediately; it appends only | |
| # integer indices, never a second score or probability fragment. | |
| num_logical_groups = ( | |
| num_route_tiles + Int32(ROUTE_HALVES_PER_GROUP - 1) | |
| ) // Int32(ROUTE_HALVES_PER_GROUP) | |
| # BEGIN_G256_CURSOR_UNIFORM_INDUCTION | |
| # arch_make_warp_uniform is a lowering hint, not a value broadcast. Both | |
| # values are CTA-invariant integer scalars before the hint. | |
| logical_group_idx = cute.arch.make_warp_uniform(Int32(0)) | |
| remaining_group_tiles = cute.arch.make_warp_uniform(num_route_tiles) | |
| while logical_group_idx < num_logical_groups: | |
| is_final_logical_group = ( | |
| logical_group_idx + Int32(1) == num_logical_groups | |
| ) | |
| group_route_tile_base = logical_group_idx * Int32( | |
| ROUTE_HALVES_PER_GROUP | |
| ) | |
| physical_halves_this_group = remaining_group_tiles | |
| if physical_halves_this_group > Int32(ROUTE_HALVES_PER_GROUP): | |
| physical_halves_this_group = Int32(ROUTE_HALVES_PER_GROUP) | |
| for half_idx in cutlass.range( | |
| physical_halves_this_group, unroll=1 | |
| ): | |
| route_tile_idx = cute.arch.make_warp_uniform( | |
| group_route_tile_base + half_idx | |
| ) | |
| is_final_route_tile = ( | |
| route_tile_idx + Int32(1) == num_route_tiles | |
| ) | |
| is_logical_terminal_half = ( | |
| half_idx + Int32(1) == physical_halves_this_group | |
| ) | |
| route_start = cute.arch.make_warp_uniform( | |
| route_start_base | |
| + route_tile_idx * Int32(ROUTE_TILE_SIZE) | |
| ) | |
| remaining_route_count = cute.arch.make_warp_uniform( | |
| route_valid_total | |
| - route_tile_idx * Int32(ROUTE_TILE_SIZE) | |
| ) | |
| valid_route_count = remaining_route_count | |
| if valid_route_count > Int32(ROUTE_TILE_SIZE): | |
| valid_route_count = Int32(ROUTE_TILE_SIZE) | |
| if valid_route_count < Int32(0): | |
| valid_route_count = Int32(0) | |
| # One native N128 route transaction shares the independent K/V stages | |
| # with the exact-pair engine. Route and exact are separated by | |
| # a full-CTA phase boundary, so no K<->V alias handoff is required. | |
| if warp_idx == Int32(5): | |
| pack_k_pipe.producer_acquire(pack_k_producer) | |
| route_k_barrier = pack_k_pipe.producer_get_barrier( | |
| pack_k_producer | |
| ) | |
| cute.copy( | |
| tma_atom_kc, | |
| tKCgKC[(None, route_tile_idx)], | |
| tKCsKC[(None, pack_k_producer.index)], | |
| tma_bar_ptr=route_k_barrier, | |
| ) | |
| pack_k_producer.advance() | |
| pack_v_pipe.producer_acquire(pack_v_producer) | |
| route_v_barrier = pack_v_pipe.producer_get_barrier( | |
| pack_v_producer | |
| ) | |
| cute.copy( | |
| tma_atom_vc, | |
| tVCgVC[(None, route_tile_idx)], | |
| tVCsVC[(None, pack_v_producer.index)], | |
| tma_bar_ptr=route_v_barrier, | |
| ) | |
| pack_v_producer.advance() | |
| if warp_idx == Int32(0): | |
| pack_k_pipe.consumer_wait(pack_k_consumer) | |
| pair_score_pipe.producer_acquire(pair_score_producer) | |
| mma_utils.gemm( | |
| tiled_pack_qk, | |
| pair_tScore, | |
| tCrPackQ[None, None, None, q_consumer.index], | |
| tCrKC[None, None, None, pack_k_consumer.index], | |
| zero_init=True, | |
| ) | |
| pair_score_pipe.producer_commit(pair_score_producer) | |
| pair_score_producer.advance() | |
| pack_k_pipe.consumer_release(pack_k_consumer) | |
| pack_k_consumer.advance() | |
| # BEGIN_RUNTIME_GROUP_BODY | |
| # Route generation: four physical owner warps reduce the native N128 | |
| # score tile into one four-word mask. HBM receives only the diagnostic | |
| # copy; the compacted exact stream remains resident in SMEM. | |
| if is_owner: | |
| pair_score_pipe.consumer_wait(pair_score_consumer) | |
| score_raw, score_coords = _load_pair_score( | |
| pack_score_template, | |
| thr_pack_qk, | |
| tmem_base, | |
| Int32(PAIR_SCORE_OFFSET), | |
| owner_tidx, | |
| ) | |
| pack_score_loaded_barrier.arrive_and_wait() | |
| pair_score_pipe.consumer_release(pair_score_consumer) | |
| pair_score_consumer.advance() | |
| owner_warp = owner_tidx // Int32(32) | |
| lane = owner_tidx % Int32(32) | |
| semantic_row = ( | |
| score_coords[0][0] + Int32(SEMANTIC_ROW_OFFSET) | |
| ) & Int32(M - 1) | |
| row_valid = semantic_row < q_len | |
| lane_col_parity = (lane // Int32(2)) % Int32(2) | |
| # Column-pair reduction: parity-0 lanes carry column 2*pair and | |
| # parity-1 lanes carry column 2*pair+1. The XOR-1/16/8/4 | |
| # butterfly tree never crosses lane column-parity classes | |
| # ((l^k)//2 keeps (l//2)%2 for k in {1,16,8,4}), so one tree | |
| # reduces both columns at once; every surviving addition chain | |
| # sees the same zero-padded operand streams, and the removed | |
| # chains only ever accumulated 0.0. Writer lanes 0 and 2 equal | |
| # 2*(col%2). | |
| for pair_idx in cutlass.range_constexpr( | |
| 0, ROUTE_TILE_SIZE // 2, 2 | |
| ): | |
| my_col0 = Int32(2 * pair_idx) + lane_col_parity | |
| partial0 = Float32(0.0) | |
| if row_valid and my_col0 < valid_route_count: | |
| partial0 = Float32(score_raw[pair_idx]) | |
| my_col1 = Int32(2 * (pair_idx + 1)) + lane_col_parity | |
| partial1 = Float32(0.0) | |
| if row_valid and my_col1 < valid_route_count: | |
| partial1 = Float32(score_raw[pair_idx + 1]) | |
| raw_partial0 = partial0 | |
| raw_partial1 = partial1 | |
| scaled0, scaled1 = cute.arch.mul_packed_f32x2( | |
| (raw_partial0, raw_partial1), | |
| (softmax_scale_log2, softmax_scale_log2), | |
| ) | |
| peer_scaled0 = cute.arch.shuffle_sync_bfly( | |
| scaled0, offset=1 | |
| ) | |
| peer_scaled1 = cute.arch.shuffle_sync_bfly( | |
| scaled1, offset=1 | |
| ) | |
| partial0, partial1 = cute.arch.fma_packed_f32x2( | |
| (raw_partial0, raw_partial1), | |
| (softmax_scale_log2, softmax_scale_log2), | |
| (peer_scaled0, peer_scaled1), | |
| ) | |
| peer0 = cute.arch.shuffle_sync_bfly( | |
| partial0, offset=16 | |
| ) | |
| peer1 = cute.arch.shuffle_sync_bfly( | |
| partial1, offset=16 | |
| ) | |
| partial0, partial1 = cute.arch.add_packed_f32x2( | |
| (partial0, partial1), (peer0, peer1) | |
| ) | |
| peer0 = cute.arch.shuffle_sync_bfly( | |
| partial0, offset=8 | |
| ) | |
| peer1 = cute.arch.shuffle_sync_bfly( | |
| partial1, offset=8 | |
| ) | |
| partial0, partial1 = cute.arch.add_packed_f32x2( | |
| (partial0, partial1), (peer0, peer1) | |
| ) | |
| peer0 = cute.arch.shuffle_sync_bfly( | |
| partial0, offset=4 | |
| ) | |
| peer1 = cute.arch.shuffle_sync_bfly( | |
| partial1, offset=4 | |
| ) | |
| partial0, partial1 = cute.arch.add_packed_f32x2( | |
| (partial0, partial1), (peer0, peer1) | |
| ) | |
| if lane == Int32(0): | |
| route_partial[owner_warp, 2 * pair_idx] = partial0 | |
| route_partial[owner_warp, 2 * (pair_idx + 1)] = ( | |
| partial1 | |
| ) | |
| if lane == Int32(2): | |
| route_partial[owner_warp, 2 * pair_idx + 1] = partial0 | |
| route_partial[ | |
| owner_warp, 2 * (pair_idx + 1) + 1 | |
| ] = partial1 | |
| cute.arch.fence_view_async_shared() | |
| score_loaded_barrier.arrive_and_wait() | |
| if owner_warp == Int32(0): | |
| mask0 = Int32(0) | |
| mask1 = Int32(0) | |
| mask2 = Int32(0) | |
| mask3 = Int32(0) | |
| # Half 0 starts a fresh G256 stream and half 1 appends to | |
| # lane 0's cumulative packet word. The preceding packet | |
| # barrier makes the base warp-uniform before the vote. | |
| append_base = Int32(0) | |
| if half_idx != Int32(0): | |
| append_base = Int32(route_packet[6]) | |
| # A positive signed shift avoids materializing 1<<31: | |
| # lane 0 gets zero and lane 31 gets 0x7fffffff. | |
| lane_mask_lt = Int32(0x7FFFFFFF) >> ( | |
| Int32(31) - lane | |
| ) | |
| preceding_word_count = Int32(0) | |
| for word in cutlass.range_constexpr(ROUTE_MASK_WORDS): | |
| off = Int32(word * 32) + lane | |
| valid = off < valid_route_count | |
| exact_pred = False | |
| if valid: | |
| pair_02 = Float32(route_partial[0, off]) + Float32( | |
| route_partial[2, off] | |
| ) | |
| pair_13 = Float32(route_partial[1, off]) + Float32( | |
| route_partial[3, off] | |
| ) | |
| col_mean = (pair_02 + pair_13) / Float32(q_len) | |
| exact_pred = sol_attn_route_is_exact( | |
| q_block_idx, | |
| route_start + off, | |
| col_mean, | |
| threshold, | |
| valid, | |
| ) | |
| # Sink is a KV-only contract. Text queries remain | |
| # a caller-side dense operation in MMDiT models. | |
| exact_pred = ( | |
| exact_pred | |
| or ( | |
| route_start + off >= sink_start_block | |
| and route_start + off < sink_end_block | |
| ) | |
| ) | |
| word_mask = Int32( | |
| cute.arch.vote_ballot_sync(exact_pred) | |
| ) | |
| # Site 2: preserve the route decision and its four | |
| # ordered ballots, but materialize the resulting | |
| # approximate-column mask exactly once. Dedicated | |
| # SMEM holds the two N64 mask halves so the reduction | |
| # scratch remains non-aliasing for ptxas scheduling. | |
| # The existing shared fence and owner barrier below | |
| # publish them to every score owner. | |
| if valid and not exact_pred: | |
| column_masks[off] = Float32(0.0) | |
| else: | |
| column_masks[off] = -Float32.inf | |
| lane_rank = ( | |
| append_base | |
| + preceding_word_count | |
| + sol_attn_popc_b32(word_mask & lane_mask_lt) | |
| ) | |
| if exact_pred: | |
| route_indices[lane_rank] = route_start + off | |
| if cutlass.const_expr(word == 0): | |
| mask0 = word_mask | |
| elif cutlass.const_expr(word == 1): | |
| mask1 = word_mask | |
| elif cutlass.const_expr(word == 2): | |
| mask2 = word_mask | |
| else: | |
| mask3 = word_mask | |
| preceding_word_count = ( | |
| preceding_word_count | |
| + sol_attn_popc_b32(word_mask) | |
| ) | |
| # Every selected lane has a unique rank; lane 0 publishes | |
| # the packet after reconvergence. | |
| exact_count = preceding_word_count | |
| if lane == Int32(0): | |
| route_rank = append_base + exact_count | |
| route_packet[0] = mask0 | |
| route_packet[1] = mask1 | |
| route_packet[2] = mask2 | |
| route_packet[3] = mask3 | |
| route_packet[4] = exact_count | |
| route_packet[5] = append_base | |
| route_packet[6] = route_rank | |
| terminal_half_word = Int32(0) | |
| if is_logical_terminal_half: | |
| terminal_half_word = Int32(1) | |
| route_packet[7] = terminal_half_word | |
| cute.arch.fence_view_async_shared() | |
| # The selector packet is now immutable. Reuse the already resident | |
| # route scores for the non-exact transaction; no offset list or second | |
| # route-score load is introduced. | |
| score_loaded_barrier.arrive_and_wait() | |
| route_exact_count = Int32(route_packet[4]) | |
| has_route_approx = route_exact_count < valid_route_count | |
| if has_route_approx: | |
| row_mask = -Float32.inf | |
| if row_valid: | |
| row_mask = Float32(0.0) | |
| # Route generation has consumed every raw score. Apply | |
| # the shared mask in place so raw and masked N128 | |
| # fragments never overlap in registers; the same object | |
| # remains available for the later route-mass scratch. | |
| route_scores = score_raw | |
| assert cute.size(score_raw) % 2 == 0 | |
| for i in cutlass.range_constexpr( | |
| 0, cute.size(score_raw), 2 | |
| ): | |
| group_col0 = score_coords[i][1] | |
| group_col1 = score_coords[i + 1][1] | |
| mask0 = Float32(column_masks[group_col0]) | |
| mask1 = Float32(column_masks[group_col1]) | |
| mask0, mask1 = cute.arch.add_packed_f32x2( | |
| (mask0, mask1), (row_mask, row_mask) | |
| ) | |
| mask0, mask1 = cute.arch.add_packed_f32x2( | |
| ( | |
| Float32(score_raw[i]), | |
| Float32(score_raw[i + 1]), | |
| ), | |
| (mask0, mask1), | |
| ) | |
| route_scores[i] = mask0 | |
| route_scores[i + 1] = mask1 | |
| local_max = fa_utils.fmax_reduce( | |
| route_scores.load(), arch=100 | |
| ) | |
| local_max = Float32(local_max) * softmax_scale | |
| peer_max = cute.arch.shuffle_sync_bfly(local_max, offset=2) | |
| pair_max = local_max | |
| if peer_max > pair_max: | |
| pair_max = peer_max | |
| old_max = running_max | |
| old_sum = running_sum | |
| new_max = old_max | |
| if old_max == -Float32.inf or pair_max > old_max: | |
| new_max = pair_max | |
| row_alpha = Float32(0.0) | |
| if old_max != -Float32.inf: | |
| row_alpha = cute.math.exp2( | |
| (old_max - new_max) * Float32(LOG2E), | |
| fastmath=True, | |
| ) | |
| route_probabilities = cute.make_rmem_tensor( | |
| route_scores.shape, Float32 | |
| ) | |
| if new_max == -Float32.inf: | |
| for i in cutlass.range( | |
| cute.size(route_scores), unroll_full=True | |
| ): | |
| route_probabilities[i] = Float32(0.0) | |
| else: | |
| for i in cutlass.range( | |
| cute.size(route_scores), unroll_full=True | |
| ): | |
| route_probabilities[i] = cute.math.exp2( | |
| Float32(route_scores[i]) * softmax_scale_log2 | |
| - new_max * Float32(LOG2E), | |
| fastmath=True, | |
| ) | |
| # ``route_scores`` is dead after the exponentials above. Use | |
| # it as mass scratch so the compiler does not need a second | |
| # full N128-shaped fragment while probabilities remain live | |
| # for the chunked TMEM-P store below. Keeping the same shape, | |
| # index order, and fadd_reduce preserves floating-point | |
| # reduction order and every phase edge. | |
| assert cute.size(route_probabilities) % 2 == 0 | |
| for i in cutlass.range_constexpr( | |
| 0, cute.size(route_probabilities), 2 | |
| ): | |
| block_idx0 = route_start + score_coords[i][1] | |
| raw_length0 = ( | |
| token_count - block_idx0 * Int32(N_MEMBER) | |
| ) | |
| block_length0 = max( | |
| Int32(0), min(raw_length0, Int32(N_MEMBER)) | |
| ) | |
| block_idx1 = route_start + score_coords[i + 1][1] | |
| raw_length1 = ( | |
| token_count - block_idx1 * Int32(N_MEMBER) | |
| ) | |
| block_length1 = max( | |
| Int32(0), min(raw_length1, Int32(N_MEMBER)) | |
| ) | |
| mass0, mass1 = cute.arch.mul_packed_f32x2( | |
| ( | |
| Float32(route_probabilities[i]), | |
| Float32(route_probabilities[i + 1]), | |
| ), | |
| ( | |
| Float32(block_length0), | |
| Float32(block_length1), | |
| ), | |
| ) | |
| route_scores[i] = mass0 | |
| route_scores[i + 1] = mass1 | |
| current_sum = fa_utils.fadd_reduce( | |
| route_scores.load(), arch=100 | |
| ) | |
| current_sum += cute.arch.shuffle_sync_bfly( | |
| current_sum, offset=2 | |
| ) | |
| # KC is a block mean and VC a valid-token sum. Route mass uses | |
| # the true block length while PV still consumes p*VC once. | |
| running_sum = old_sum * row_alpha + current_sum | |
| running_max = new_max | |
| if owner_o_initialized != Int32(0): | |
| _rescale_pair_o( | |
| pack_o_template, | |
| thr_pack_pv, | |
| tmem_base, | |
| Int32(O_OFFSET), | |
| owner_tidx, | |
| row_alpha, | |
| ) | |
| _store_pair_probability_chunked_tmemp( | |
| pack_o_template, | |
| route_probabilities, | |
| tmem_base, | |
| Int32(PAIR_P_OFFSET), | |
| owner_tidx, | |
| ) | |
| owner_o_initialized = Int32(1) | |
| # Publish the mask/P decision to warp 0. The route PV is deliberately | |
| # drained before exact work so all-exact, all-approx, odd, and | |
| # partial-tail paths share one phase boundary. | |
| if is_score_consumer: | |
| route_packet_ready_barrier.arrive_and_wait() | |
| if warp_idx == Int32(0): | |
| route_exact_count = Int32(route_packet[4]) | |
| route_has_approx = route_exact_count < valid_route_count | |
| pack_v_pipe.consumer_wait(pack_v_consumer) | |
| if route_has_approx: | |
| mma_utils.gemm( | |
| tiled_pack_pv, | |
| pair_tO, | |
| pair_tP, | |
| tCrVC[None, None, None, pack_v_consumer.index], | |
| zero_init=mma_o_initialized == Int32(0), | |
| ) | |
| # Half 0 is followed by half-1 route QK. The terminal | |
| # route half is followed by exact QK0 whenever the fused | |
| # G256 index stream is nonempty. Those score completions | |
| # prove this PV complete; only a final route-only CTA needs | |
| # an explicit O completion here. | |
| if ( | |
| is_final_route_tile | |
| and Int32(route_packet[6]) == Int32(0) | |
| ): | |
| pair_o_pipe.producer_commit(pair_o_producer) | |
| mma_o_initialized = Int32(1) | |
| pack_v_pipe.consumer_release(pack_v_consumer) | |
| pack_v_consumer.advance() | |
| if is_owner: | |
| cumulative_exact_count = Int32(route_packet[6]) | |
| if ( | |
| is_final_route_tile | |
| and cumulative_exact_count == Int32(0) | |
| ): | |
| pair_o_pipe.consumer_wait(pair_o_consumer) | |
| # route_packet may be reused by the next physical half without a | |
| # CTA join. Warp 0 reads this half's packet before it can issue | |
| # next-half QK; owner-warp 0 cannot overwrite the packet until | |
| # that QK's pair-score completion has released all owners. | |
| # Both route halves have published their packet/index data and drained | |
| # approximate PV. This is the only CTA-wide pre-exact join in the | |
| # logical G256 group; it publishes the combined list to warp 5. | |
| cute.arch.barrier() | |
| # The cumulative count covers half 0 followed by half 1. Pairing this | |
| # one ordered stream removes cross-half odd padding without retaining | |
| # either physical score fragment. | |
| exact_block_count = Int32(route_packet[6]) | |
| exact_pair_count = (exact_block_count + Int32(1)) // Int32(2) | |
| pair_count = exact_pair_count | |
| has_pair_exact = exact_block_count > Int32(0) | |
| # BEGIN_GENERAL_N128_PAIR | |
| # Every executable exact count, including a logical-group terminal | |
| # exact1, stays in the N128 domain. | |
| # Warp 5 streams one physical N128 K stage and one physical N128 V | |
| # stage. A missing odd peer duplicates block0 only for the physical | |
| # transaction; owners mask all upper-64 scores before softmax. | |
| if warp_idx == Int32(5) and has_pair_exact: | |
| for pair_idx in cutlass.range(pair_count, unroll=1): | |
| ordinal0 = pair_idx * Int32(2) | |
| block0 = Int32(route_indices[ordinal0]) | |
| block1 = block0 | |
| if ordinal0 + Int32(1) < exact_block_count: | |
| block1 = Int32(route_indices[ordinal0 + Int32(1)]) | |
| pack_k_pipe.producer_acquire(pack_k_producer) | |
| pair_k_barrier = pack_k_pipe.producer_get_barrier( | |
| pack_k_producer | |
| ) | |
| _load_pack_k_half( | |
| tma_atom_pack_k, | |
| tPackKgK, | |
| tPackKsK, | |
| block0, | |
| block1, | |
| pack_k_producer.index * Int32(4), | |
| pair_k_barrier, | |
| ) | |
| pack_k_producer.advance() | |
| pack_v_pipe.producer_acquire(pack_v_producer) | |
| pair_v_barrier = pack_v_pipe.producer_get_barrier( | |
| pack_v_producer | |
| ) | |
| _load_pack_v_half( | |
| tma_atom_pack_v, | |
| tPackVgV, | |
| tPackVsV, | |
| block0, | |
| block1, | |
| pack_v_producer.index * Int32(4), | |
| pair_v_barrier, | |
| ) | |
| pack_v_producer.advance() | |
| if warp_idx == Int32(0) and has_pair_exact: | |
| # QK0 prologue. K and score cursors advance exactly once per QK; | |
| # neither V nor O state is touched until the steady-state PV path. | |
| pack_k_pipe.consumer_wait(pack_k_consumer) | |
| pair_score_pipe.producer_acquire(pair_score_producer) | |
| mma_utils.gemm( | |
| tiled_pack_qk, | |
| pair_tScore, | |
| tCrPackQ[None, None, None, q_consumer.index], | |
| tCrPackK[None, None, None, pack_k_consumer.index], | |
| zero_init=True, | |
| ) | |
| pair_score_pipe.producer_commit(pair_score_producer) | |
| pair_score_producer.advance() | |
| # PipelineTmaUmma release is tcgen05-completion-backed. | |
| pack_k_pipe.consumer_release(pack_k_consumer) | |
| pack_k_consumer.advance() | |
| for pair_idx in cutlass.range(pair_count, unroll=1): | |
| # P aliases the drained upper half of S. PV must therefore be | |
| # issued before QK(i+1) overwrites S. Both instructions are | |
| # emitted back-to-back by warp 0, retaining the full-G128 | |
| # tcgen05 dependency order without its K/V alias barriers. | |
| pack_v_pipe.consumer_wait(pack_v_consumer) | |
| # All four owners have completed their synchronous chunked | |
| # TMEM stores and the helper's TMEM store fence before this | |
| # five-warp rendezvous releases the single MMA warp. | |
| exact_pair_p_ready_barrier.arrive_and_wait() | |
| mma_utils.gemm( | |
| tiled_pack_pv, | |
| pair_tO, | |
| pair_tP, | |
| tCrPackV[None, None, None, pack_v_consumer.index], | |
| zero_init=mma_o_initialized == Int32(0), | |
| ) | |
| # QK(i+1) completion dominates PV(i) completion for every | |
| # nonterminal transaction on this tcgen05 issuer. Commit one | |
| # explicit O-full generation only for the CTA's final PV. | |
| if ( | |
| is_final_logical_group | |
| and pair_idx + Int32(1) == pair_count | |
| ): | |
| pair_o_pipe.producer_commit(pair_o_producer) | |
| mma_o_initialized = Int32(1) | |
| pack_v_pipe.consumer_release(pack_v_consumer) | |
| pack_v_consumer.advance() | |
| if pair_idx + Int32(1) < pair_count: | |
| pack_k_pipe.consumer_wait(pack_k_consumer) | |
| pair_score_pipe.producer_acquire(pair_score_producer) | |
| mma_utils.gemm( | |
| tiled_pack_qk, | |
| pair_tScore, | |
| tCrPackQ[None, None, None, q_consumer.index], | |
| tCrPackK[ | |
| None, None, None, pack_k_consumer.index | |
| ], | |
| zero_init=True, | |
| ) | |
| pair_score_pipe.producer_commit(pair_score_producer) | |
| pair_score_producer.advance() | |
| pack_k_pipe.consumer_release(pack_k_consumer) | |
| pack_k_consumer.advance() | |
| if is_owner and has_pair_exact: | |
| exact_owner_warp = owner_tidx // Int32(32) | |
| exact_lane = owner_tidx % Int32(32) | |
| for pair_idx in cutlass.range(pair_count, unroll=1): | |
| ordinal0 = pair_idx * Int32(2) | |
| block0 = Int32(route_indices[ordinal0]) | |
| has_peer = ordinal0 + Int32(1) < exact_block_count | |
| block1 = block0 | |
| if has_peer: | |
| block1 = Int32(route_indices[ordinal0 + Int32(1)]) | |
| valid0 = token_count - block0 * Int32(N_MEMBER) | |
| valid1 = Int32(0) | |
| if has_peer: | |
| valid1 = token_count - block1 * Int32(N_MEMBER) | |
| # Keep packed-select integer min/max lowering and exact-pair | |
| # bookkeeping unchanged. | |
| valid0 = max(Int32(0), min(valid0, Int32(N_MEMBER))) | |
| valid1 = max(Int32(0), min(valid1, Int32(N_MEMBER))) | |
| # Site 1: owner warp 0 builds two 64-column gates once for | |
| # this exact N128 pair. The existing score-load barrier below | |
| # both protects the S/P alias and publishes these stores; no | |
| # barrier or shared allocation is added. | |
| if exact_owner_warp == Int32(0): | |
| for cohort in cutlass.range_constexpr(4): | |
| column = Int32(cohort * 32) + exact_lane | |
| if cutlass.const_expr(cohort < 2): | |
| if column >= valid0: | |
| column_masks[column] = -Float32.inf | |
| else: | |
| column_masks[column] = Float32(0.0) | |
| else: | |
| if column - Int32(N_MEMBER) >= valid1: | |
| column_masks[column] = -Float32.inf | |
| else: | |
| column_masks[column] = Float32(0.0) | |
| cute.arch.fence_view_async_shared() | |
| pair_score_pipe.consumer_wait(pair_score_consumer) | |
| # Keep the exact ae9 score-load helper and fragment scope. | |
| pair_scores, pair_coords = _load_pair_score( | |
| pack_score_template, | |
| thr_pack_qk, | |
| tmem_base, | |
| Int32(PAIR_SCORE_OFFSET), | |
| owner_tidx, | |
| ) | |
| # Every owner retires the complete score load before the | |
| # packed P store aliases columns 64..127 of S. | |
| pack_score_loaded_barrier.arrive_and_wait() | |
| pair_score_pipe.consumer_release(pair_score_consumer) | |
| pair_score_consumer.advance() | |
| semantic_row = ( | |
| pair_coords[0][0] + Int32(SEMANTIC_ROW_OFFSET) | |
| ) & Int32(M - 1) | |
| row_valid = semantic_row < q_len | |
| row_mask = -Float32.inf | |
| if row_valid: | |
| row_mask = Float32(0.0) | |
| assert cute.size(pair_scores) % 2 == 0 | |
| for i in cutlass.range_constexpr( | |
| 0, cute.size(pair_scores), 2 | |
| ): | |
| column0 = pair_coords[i][1] | |
| column1 = pair_coords[i + 1][1] | |
| mask0 = Float32(column_masks[column0]) | |
| mask1 = Float32(column_masks[column1]) | |
| mask0, mask1 = cute.arch.add_packed_f32x2( | |
| (mask0, mask1), (row_mask, row_mask) | |
| ) | |
| mask0, mask1 = cute.arch.add_packed_f32x2( | |
| ( | |
| Float32(pair_scores[i]), | |
| Float32(pair_scores[i + 1]), | |
| ), | |
| (mask0, mask1), | |
| ) | |
| pair_scores[i] = mask0 | |
| pair_scores[i + 1] = mask1 | |
| probabilities, next_max, next_sum, row_alpha = ( | |
| _online_update_pair( | |
| pair_scores, | |
| running_max, | |
| running_sum, | |
| softmax_scale, | |
| ) | |
| ) | |
| # For i>0, pair-score completion comes from QK(i), issued | |
| # after PV(i-1) on the same tcgen05 issuer. The score wait and | |
| # load above therefore retire PV(i-1) before this O rescale. | |
| # Pair0 similarly follows either route QK or route PV->QK0. | |
| if owner_o_initialized != Int32(0): | |
| _rescale_pair_o( | |
| pack_o_template, | |
| thr_pack_pv, | |
| tmem_base, | |
| Int32(O_OFFSET), | |
| owner_tidx, | |
| row_alpha, | |
| ) | |
| # The one TMEM P image is free once PV(i-1) completes. Keep | |
| # probabilities FP32 until the live-range-bounded chunked R2T. | |
| _store_pair_probability_chunked_tmemp( | |
| pack_o_template, | |
| probabilities, | |
| tmem_base, | |
| Int32(PAIR_P_OFFSET), | |
| owner_tidx, | |
| ) | |
| # The preceding helper performs tcgen05.wait::st for every | |
| # chunk and a TMEM-store fence. Publish P to warp 0 with one | |
| # uniform generation shared by warps 0-4; warp 5 is excluded. | |
| exact_pair_p_ready_barrier.arrive_and_wait() | |
| running_max = next_max | |
| running_sum = next_sum | |
| owner_o_initialized = Int32(1) | |
| # There is no successor QK after the CTA's final exact PV. Keep | |
| # exactly one completion-backed wait before the epilogue; all | |
| # earlier groups flow into a successor route QK completion. | |
| if is_final_logical_group and pair_count > Int32(0): | |
| pair_o_pipe.consumer_wait(pair_o_consumer) | |
| # route_indices reuse HB proof for the next logical group: | |
| # (1) warp 5 reads both indices before producing each pair's K/V, and | |
| # final-pair score completion therefore dominates its last read; | |
| # (2) all owner index reads precede the final exact-P NamedBarrier; | |
| # (3) owner-warp0/lane0 is the sole next-group writer and reaches it | |
| # only after that same exact loop. For exact_count==0 there are no | |
| # readers. Therefore no group-tail CTA barrier is required. | |
| # Cross-group progress is carried by the existing K/V buffer-free | |
| # phases and pair-score ready phase. There is no CTA-wide group-tail | |
| # join: the next producer acquire cannot overwrite a live K/V stage, | |
| # and the next owner score load cannot precede QK completion. | |
| # END_GENERAL_N128_PAIR | |
| logical_group_idx = cute.arch.make_warp_uniform( | |
| logical_group_idx + Int32(1) | |
| ) | |
| remaining_group_tiles = cute.arch.make_warp_uniform( | |
| remaining_group_tiles - Int32(ROUTE_HALVES_PER_GROUP) | |
| ) | |
| # END_RUNTIME_GROUP_BODY | |
| # END_G256_CURSOR_UNIFORM_INDUCTION | |
| if warp_idx == Int32(0): | |
| q_pipe.consumer_release(q_consumer) | |
| q_consumer.advance() | |
| if is_owner: | |
| lane = owner_tidx % Int32(32) | |
| owner_warp = owner_tidx // Int32(32) | |
| owner_row = ( | |
| owner_warp * Int32(16) | |
| + lane // Int32(4) | |
| + (lane % Int32(2)) * Int32(8) | |
| + Int32(SEMANTIC_ROW_OFFSET) | |
| ) & Int32(M - 1) | |
| # Register state remains owner-local for the entire exact stream. It | |
| # is published only once here because the final Ld16x256b epilogue | |
| # remaps rows differently from the Ld16x64b xor-2 score ownership. | |
| if (lane & Int32(2)) == Int32(0): | |
| sFinalStats[owner_row, 0] = running_sum | |
| sFinalStats[owner_row, 1] = running_max | |
| cute.arch.fence_view_async_shared() | |
| final_stats_ready_barrier.arrive_and_wait() | |
| o_regs, o_coords = load_m64_o_fp32_256b( | |
| pack_o_template, | |
| thr_pack_pv, | |
| tmem_base, | |
| owner_tidx, | |
| ) | |
| assert cute.size(o_regs) == 64 | |
| assert cute.size(o_coords) == 64 | |
| # B7's device inversion proves that 4*w/4*w+1 belong to one | |
| # semantic row and 4*w+2/4*w+3 to its row-plus-eight peer. Hoist | |
| # validity, final-sum LDS, reciprocal, and row base once per stratum. | |
| semantic_row0 = ( | |
| owner_warp * Int32(16) | |
| + lane // Int32(4) | |
| + Int32(SEMANTIC_ROW_OFFSET) | |
| ) & Int32(M - 1) | |
| semantic_row1 = (semantic_row0 + Int32(8)) & Int32(M - 1) | |
| even_col_base = (lane % Int32(4)) * Int32(2) | |
| if semantic_row0 < q_len: | |
| inv_sum0 = cute.arch.rcp_approx( | |
| Float32(sFinalStats[semantic_row0, 0]) | |
| ) | |
| query_idx0 = q_block_idx * Int32(M) + semantic_row0 | |
| destination_row0 = cute.domain_offset( | |
| (batch_idx, query_idx0, head_idx, Int32(0)), mO_bthd | |
| ) | |
| for word_i in cutlass.range( | |
| O_PACKED_WORDS_PER_ROW_PER_THREAD, unroll_full=True | |
| ): | |
| even_i = word_i * 4 | |
| odd_i = even_i + 1 | |
| even_value = Float32(o_regs[even_i]) * inv_sum0 | |
| odd_value = Float32(o_regs[odd_i]) * inv_sum0 | |
| packed_word = _cvt_bf16x2_f32( | |
| Float32(odd_value), Float32(even_value) | |
| ) | |
| even_col = ( | |
| even_col_base + word_i * O_PACKED_COLUMN_STRIDE | |
| ) | |
| _store_global_u32_inline( | |
| destination_row0.iterator + even_col, packed_word | |
| ) | |
| if semantic_row1 < q_len: | |
| inv_sum1 = cute.arch.rcp_approx( | |
| Float32(sFinalStats[semantic_row1, 0]) | |
| ) | |
| query_idx1 = q_block_idx * Int32(M) + semantic_row1 | |
| destination_row1 = cute.domain_offset( | |
| (batch_idx, query_idx1, head_idx, Int32(0)), mO_bthd | |
| ) | |
| for word_i in cutlass.range( | |
| O_PACKED_WORDS_PER_ROW_PER_THREAD, unroll_full=True | |
| ): | |
| even_i = word_i * 4 + 2 | |
| odd_i = even_i + 1 | |
| even_value = Float32(o_regs[even_i]) * inv_sum1 | |
| odd_value = Float32(o_regs[odd_i]) * inv_sum1 | |
| packed_word = _cvt_bf16x2_f32( | |
| Float32(odd_value), Float32(even_value) | |
| ) | |
| even_col = ( | |
| even_col_base + word_i * O_PACKED_COLUMN_STRIDE | |
| ) | |
| _store_global_u32_inline( | |
| destination_row1.iterator + even_col, packed_word | |
| ) | |
| if (lane & Int32(2)) == Int32(0) and owner_row < q_len: | |
| query_idx = q_block_idx * Int32(M) + owner_row | |
| mLSE_bth[batch_idx, query_idx, head_idx] = ( | |
| running_max | |
| + cute.math.log2(running_sum, fastmath=True) * Float32(LN2) | |
| ) | |
| cute.arch.barrier() | |
| tmem.free(tmem_ptr) | |
| def _sol_attn_sm100_bf16_host( | |
| q: cute.Tensor, | |
| k: cute.Tensor, | |
| v: cute.Tensor, | |
| o: cute.Tensor, | |
| kc: cute.Tensor, | |
| vc: cute.Tensor, | |
| threshold: cute.Tensor, | |
| lse: cute.Tensor, | |
| softmax_scale: Float32, | |
| sink_start_block: Int32, | |
| sink_end_block: Int32, | |
| stream: cuda.CUstream = None, | |
| ): | |
| q, k, v, o, kc, vc = tuple( | |
| assume_tensor_aligned(t) for t in (q, k, v, o, kc, vc) | |
| ) | |
| q_mkl, k_nkl, kc_nkl = [ | |
| layout_utils.select(t, [1, 3, 2, 0]) for t in (q, k, kc) | |
| ] | |
| v_nkl, vc_nkl = [ | |
| layout_utils.select(t, [3, 1, 2, 0]) for t in (v, vc) | |
| ] | |
| token_count = cute.size(q_mkl.shape[0]) | |
| num_blocks = cute.size(kc_nkl.shape[0]) | |
| num_heads = cute.size(q_mkl.shape[2]) | |
| num_batches = cute.size(q_mkl.shape[3]) | |
| num_route_tiles = cute.ceil_div(num_blocks, ROUTE_TILE_SIZE) | |
| pack_qk_op = tcgen05.MmaF16BF16Op( | |
| BFloat16, | |
| Float32, | |
| PACK_QK_INST, | |
| tcgen05.CtaGroup.ONE, | |
| tcgen05.OperandSource.SMEM, | |
| cute.nvgpu.OperandMajorMode.K, | |
| cute.nvgpu.OperandMajorMode.K, | |
| ) | |
| tiled_pack_qk = cute.make_tiled_mma(pack_qk_op) | |
| pack_pv_op = tcgen05.MmaF16BF16Op( | |
| BFloat16, | |
| Float32, | |
| PACK_PV_INST, | |
| tcgen05.CtaGroup.ONE, | |
| tcgen05.OperandSource.TMEM, | |
| cute.nvgpu.OperandMajorMode.K, | |
| cute.nvgpu.OperandMajorMode.MN, | |
| ) | |
| tiled_pack_pv = cute.make_tiled_mma(pack_pv_op) | |
| pack_qk_quarter_op = tcgen05.MmaF16BF16Op( | |
| BFloat16, | |
| Float32, | |
| PACK_QK_QUARTER_INST, | |
| tcgen05.CtaGroup.ONE, | |
| tcgen05.OperandSource.SMEM, | |
| cute.nvgpu.OperandMajorMode.K, | |
| cute.nvgpu.OperandMajorMode.K, | |
| ) | |
| tiled_pack_qk_gather = cute.make_tiled_mma(pack_qk_quarter_op) | |
| pack_pv_quarter_op = tcgen05.MmaF16BF16Op( | |
| BFloat16, | |
| Float32, | |
| PACK_PV_QUARTER_INST, | |
| tcgen05.CtaGroup.ONE, | |
| tcgen05.OperandSource.TMEM, | |
| cute.nvgpu.OperandMajorMode.K, | |
| cute.nvgpu.OperandMajorMode.MN, | |
| ) | |
| tiled_pack_pv_gather = cute.make_tiled_mma(pack_pv_quarter_op) | |
| q_layout = sm100_utils.make_smem_layout_a( | |
| tiled_pack_qk, PACK_QK_TILE, BFloat16, 1 | |
| ) | |
| pack_k_layout = sm100_utils.make_smem_layout_b( | |
| tiled_pack_qk, PACK_QK_TILE, BFloat16, PAIR_STAGES | |
| ) | |
| pack_v_layout = sm100_utils.make_smem_layout_b( | |
| tiled_pack_pv, PACK_PV_TILE, BFloat16, PAIR_STAGES | |
| ) | |
| pack_k_gather_layout = sm100_utils.make_smem_layout_b( | |
| tiled_pack_qk_gather, | |
| PACK_QK_GATHER_TILE, | |
| BFloat16, | |
| PAIR_STAGES * 4, | |
| ) | |
| pack_v_gather_layout = sm100_utils.make_smem_layout_b( | |
| tiled_pack_pv_gather, | |
| PACK_PV_GATHER_TILE, | |
| BFloat16, | |
| PAIR_STAGES * 4, | |
| ) | |
| pack_p_layout = sm100_utils.make_smem_layout_a( | |
| tiled_pack_pv, PACK_PV_TILE, BFloat16, 1 | |
| ) | |
| route_k_layout = sm100_utils.make_smem_layout_b( | |
| tiled_pack_qk, PACK_QK_TILE, BFloat16, PAIR_STAGES | |
| ) | |
| route_v_layout = sm100_utils.make_smem_layout_b( | |
| tiled_pack_pv, PACK_PV_TILE, BFloat16, PAIR_STAGES | |
| ) | |
| copy_op = cpasync.CopyBulkTensorTileG2SOp(tcgen05.CtaGroup.ONE) | |
| q_tma_atom, q_tma_tensor = cute.nvgpu.make_tiled_tma_atom_A( | |
| copy_op, | |
| q_mkl, | |
| cute.select(q_layout, mode=[0, 1, 2]), | |
| PACK_QK_TILE, | |
| tiled_pack_qk, | |
| ) | |
| pack_k_tma_layout = cute.make_composed_layout( | |
| pack_k_gather_layout.inner, | |
| 0, | |
| cute.make_layout((64, 64), stride=(64, 1)), | |
| ) | |
| pack_k_tma_atom, pack_k_tma_tensor = cpasync.make_tiled_tma_atom( | |
| copy_op, | |
| k_nkl, | |
| pack_k_tma_layout, | |
| (64, 64), | |
| ) | |
| pack_v_tma_layout = cute.make_composed_layout( | |
| pack_v_gather_layout.inner, | |
| 0, | |
| cute.make_layout((64, 64), stride=(1, 64)), | |
| ) | |
| pack_v_tma_atom, pack_v_tma_tensor = cpasync.make_tiled_tma_atom( | |
| copy_op, | |
| v_nkl, | |
| pack_v_tma_layout, | |
| (64, 64), | |
| ) | |
| kc_tma_atom, kc_tma_tensor = cute.nvgpu.make_tiled_tma_atom_B( | |
| copy_op, | |
| kc_nkl, | |
| cute.select(route_k_layout, mode=[0, 1, 2]), | |
| PACK_QK_TILE, | |
| tiled_pack_qk, | |
| ) | |
| vc_tma_atom, vc_tma_tensor = cute.nvgpu.make_tiled_tma_atom_B( | |
| copy_op, | |
| vc_nkl, | |
| cute.select(route_v_layout, mode=[0, 1, 2]), | |
| PACK_PV_TILE, | |
| tiled_pack_pv, | |
| ) | |
| _sol_attn_sm100_bf16_kernel( | |
| tiled_pack_qk, | |
| tiled_pack_pv, | |
| q_tma_atom, | |
| q_tma_tensor, | |
| pack_k_tma_atom, | |
| pack_k_tma_tensor, | |
| pack_v_tma_atom, | |
| pack_v_tma_tensor, | |
| kc_tma_atom, | |
| kc_tma_tensor, | |
| vc_tma_atom, | |
| vc_tma_tensor, | |
| threshold, | |
| o, | |
| lse, | |
| Int32(token_count), | |
| Int32(num_blocks), | |
| Int32(num_route_tiles), | |
| softmax_scale, | |
| sink_start_block, | |
| sink_end_block, | |
| q_layout, | |
| pack_k_layout, | |
| pack_k_gather_layout, | |
| pack_p_layout, | |
| pack_v_layout, | |
| pack_v_gather_layout, | |
| route_k_layout, | |
| route_v_layout, | |
| ).launch( | |
| grid=(num_blocks, num_heads, num_batches), | |
| block=(THREADS, 1, 1), | |
| stream=stream, | |
| min_blocks_per_mp=2, | |
| ) | |
| def forward( | |
| q: cute.Tensor, | |
| k: cute.Tensor, | |
| v: cute.Tensor, | |
| o: cute.Tensor, | |
| kc: cute.Tensor, | |
| vc: cute.Tensor, | |
| threshold: cute.Tensor, | |
| lse: cute.Tensor, | |
| softmax_scale: Float32, | |
| sink_start_block: Int32, | |
| sink_end_block: Int32, | |
| stream: cuda.CUstream = None, | |
| ): | |
| return _sol_attn_sm100_bf16_host( | |
| q, | |
| k, | |
| v, | |
| o, | |
| kc, | |
| vc, | |
| threshold, | |
| lse, | |
| softmax_scale, | |
| sink_start_block, | |
| sink_end_block, | |
| stream, | |
| ) | |
| __all__ = ["forward"] | |