Files
cutlass/examples/python/CuTeDSL/blackwell/mixed_input_fmha/prefill_helpers.py
2026-02-13 23:27:58 -05:00

401 lines
15 KiB
Python

# Copyright (c) 2025 - 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: BSD-3-Clause
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from typing import Tuple, Optional
import cutlass
import cutlass.cute as cute
import cutlass.cute.nvgpu.tcgen05 as tcgen05
import cutlass.pipeline as pipeline
@cute.jit
def load_qk(
iterations: int,
kv_step: cutlass.Int32,
k_args: Tuple,
scale_k_args: Optional[Tuple] = None,
q_args: Optional[Tuple] = None,
) -> Tuple[pipeline.PipelineProducer, pipeline.PipelineProducer]:
if cutlass.const_expr(q_args is not None):
tQgQ, tQsQ, tma_atom_q, load_q_producer = q_args
else:
tQgQ, tQsQ, tma_atom_q, load_q_producer = None, None, None, None
tKgK, tKsK, tma_atom_k, load_k_producer = k_args
tKgScaleK, tKsScaleK, tma_atom_scale_k, load_scale_k_producer = scale_k_args
scale_k_handle = load_scale_k_producer.acquire_and_advance()
cute.copy(
tma_atom_scale_k,
tKgScaleK[None, kv_step],
tKsScaleK[None, scale_k_handle.index],
tma_bar_ptr=scale_k_handle.barrier,
)
for iter in cutlass.range(iterations, unroll=1):
if cutlass.const_expr(q_args is not None):
q_handle = load_q_producer.acquire_and_advance()
cute.copy(
tma_atom_q,
tQgQ[None, iter],
tQsQ[None, q_handle.index],
tma_bar_ptr=q_handle.barrier,
)
k_handle = load_k_producer.acquire_and_advance()
cute.copy(
tma_atom_k,
tKgK[None, kv_step, iter],
tKsK[None, k_handle.index],
tma_bar_ptr=k_handle.barrier,
)
if cutlass.const_expr(q_args is not None):
return load_k_producer, load_scale_k_producer, load_q_producer
else:
return load_k_producer, load_scale_k_producer
@cute.jit
def load_v(
iterations: int,
kv_step: cutlass.Int32,
v_args: Tuple,
scale_v_args: Tuple,
) -> pipeline.PipelineProducer:
tVgV, tVsV, tma_atom_v, load_v_producer = v_args
tScaleVgV, tScaleVsV, tma_atom_scale_v, load_scale_v_producer = scale_v_args
scale_v_handle = load_scale_v_producer.acquire_and_advance()
cute.copy(
tma_atom_scale_v,
tScaleVgV[None, kv_step],
tScaleVsV[None, scale_v_handle.index],
tma_bar_ptr=scale_v_handle.barrier,
)
for iter in cutlass.range(iterations, unroll=1):
v_handle = load_v_producer.acquire_and_advance()
cute.copy(
tma_atom_v,
tVgV[None, iter, kv_step],
tVsV[None, v_handle.index],
tma_bar_ptr=v_handle.barrier,
)
return load_v_producer, load_scale_v_producer
@cute.jit
def get_scale_smem_layout(
scale_granularity: int,
d_r: int,
mma_tiler: cute.Tile,
major_mode: tcgen05.OperandMajorMode,
) -> Tuple[cute.Layout, cute.Tile]:
size_mn = mma_tiler[1] // 2 # 2cta by default
if cutlass.const_expr(major_mode == tcgen05.OperandMajorMode.MN): # v
scale_tiler = (mma_tiler[2] * d_r,)
tma_view_layout = cute.make_layout(
(mma_tiler[2] * d_r),
)
assert scale_granularity % mma_tiler[1] == 0, (
"scale_granularity must be divisible by mma_tiler[1]"
)
rest_l = scale_granularity // mma_tiler[1]
s2r_view_layout = cute.make_layout(
(size_mn, mma_tiler[2], (rest_l, d_r)),
stride=(0, d_r, (0, 1)),
)
else: # k
scale_tiler = (mma_tiler[1] * d_r,)
tma_view_layout = cute.make_layout((size_mn * d_r))
assert scale_granularity % mma_tiler[2] == 0, (
"scale_granularity must be divisible by mma_tiler[2]"
)
rest_l = scale_granularity // mma_tiler[2]
s2r_view_layout = cute.make_layout(
(size_mn, mma_tiler[2], (rest_l, d_r)),
stride=(d_r, 0, (0, 1)),
)
# Apply a trivial swizzle to make it a composed layout, which could be used to construct TMA atom
tma_view_smem_layout = cute.make_composed_layout(
cute.make_swizzle(0, 4, 3), 0, tma_view_layout
)
return tma_view_smem_layout, scale_tiler, s2r_view_layout
@cute.jit
def mma_qk(
iterations: int,
qk_tiled_mma: cute.TiledMma,
tensor_args: Tuple,
pipeline_args: Tuple,
):
tStS, tSrQ, tSrK_trans = tensor_args
mma_s_producer, load_q_consumer, load_q_releaser, dequant_kv_consumer = (
pipeline_args
)
cta_rank_in_cluster = cute.arch.make_warp_uniform(cute.arch.block_idx_in_cluster())
is_leader_cta = cta_rank_in_cluster % 2 == 0
if is_leader_cta:
s_handle = mma_s_producer.acquire_and_advance()
tStS_slice = tStS[None, None, None, s_handle.index]
qk_tiled_mma.set(tcgen05.Field.ACCUMULATE, False)
for iter in cutlass.range(iterations, unroll=1):
if cutlass.const_expr(load_q_consumer is not None):
load_q_consumer.wait_and_advance()
tSrQ_slice = tSrQ[None, None, None, iter]
k_trans_handle = dequant_kv_consumer.wait_and_advance()
tSrK_trans_slice = tSrK_trans[None, None, None, k_trans_handle.index]
num_kphases = cute.size(tSrQ_slice, mode=[2])
for kphase_idx in cutlass.range(num_kphases, unroll_full=True):
kphase_coord = (None, None, kphase_idx)
cute.gemm(
qk_tiled_mma,
tStS_slice,
tSrQ_slice[kphase_coord],
tSrK_trans_slice[kphase_coord],
tStS_slice,
)
qk_tiled_mma.set(tcgen05.Field.ACCUMULATE, True)
k_trans_handle.release()
if cutlass.const_expr(load_q_releaser is not None):
load_q_releaser.release()
load_q_releaser.advance()
s_handle.commit()
return mma_s_producer, load_q_consumer, dequant_kv_consumer
@cute.jit
def dequant_k(
iterations: int,
transform_warp_ids: Tuple,
dtype_args: Tuple,
tensor_args: Tuple,
pipeline_args: Tuple,
):
(k_dtype, q_dtype) = dtype_args
(sOrig, sScale, sTrans) = tensor_args
(load_kv_consumer, load_scale_consumer, dequant_kv_producer) = pipeline_args
tidx, _, _ = cute.arch.thread_idx()
THREADS_PER_WARP = 32
thread_idx = tidx % (THREADS_PER_WARP * len(transform_warp_ids))
r2s_copy_atom = cute.make_copy_atom(
cute.nvgpu.CopyUniversalOp(), k_dtype, num_bits_per_copy=32
)
# Construct tiled_copy satisfying 16 contiguous elts per copy atom
r2s_tiled_copy = cute.make_cotiled_copy(
r2s_copy_atom,
cute.make_layout((256, 16), stride=(16, 1)),
sTrans[(None, None, None, 0)].layout,
)
thr_r2s_tiled_copy = r2s_tiled_copy.get_slice(thread_idx)
tOsOrig = thr_r2s_tiled_copy.partition_S(sOrig)
tTsTrans = thr_r2s_tiled_copy.partition_D(sTrans)
tOrOrig = cute.make_rmem_tensor_like(
cute.append(
tOsOrig[None, None, None, None, 0].layout,
cute.make_layout(
2, stride=cute.cosize(tOsOrig[None, None, None, None, 0].layout)
),
),
k_dtype,
)
tTrTrans = cute.make_rmem_tensor_like(
cute.append(
tTsTrans[None, None, None, None, 0].layout,
cute.make_layout(
2, stride=cute.cosize(tTsTrans[None, None, None, None, 0].layout)
),
),
q_dtype,
)
tSsScale = thr_r2s_tiled_copy.partition_S(sScale)
tSrScale = cute.make_rmem_tensor_like(tSsScale[None, None, None, None, None, 0])
scale_handle = load_scale_consumer.wait_and_advance()
cute.autovec_copy(
tSsScale[None, None, None, None, None, scale_handle.index], tSrScale
)
cute.arch.fence_view_async_shared()
scale_handle.release()
# prefetch iter = 0
kv_handle = load_kv_consumer.wait_and_advance()
cute.autovec_copy(
tOsOrig[None, None, None, None, kv_handle.index],
tOrOrig[None, None, None, None, 0],
)
transformed_tensor = tOrOrig[None, None, None, None, 0].load().to(q_dtype)
scale = cute.TensorSSA(
tSrScale[None, None, None, None, 0].load(),
transformed_tensor.shape,
q_dtype,
)
transformed_tensor = transformed_tensor * scale
tTrTrans[None, None, None, None, 0].store(transformed_tensor)
cute.arch.fence_view_async_shared()
kv_handle.release()
for iter in cutlass.range(1, iterations, unroll_full=True):
kv_trans_handle = dequant_kv_producer.acquire_and_advance()
cute.autovec_copy(
tTrTrans[None, None, None, None, (iter - 1) % 2],
tTsTrans[None, None, None, None, kv_trans_handle.index],
)
cute.arch.fence_view_async_shared()
kv_trans_handle.commit()
kv_handle = load_kv_consumer.wait_and_advance()
cute.autovec_copy(
tOsOrig[None, None, None, None, kv_handle.index],
tOrOrig[None, None, None, None, iter % 2],
)
transformed_tensor = (
tOrOrig[None, None, None, None, iter % 2].load().to(q_dtype)
)
scale = cute.TensorSSA(
tSrScale[None, None, None, None, iter].load(),
transformed_tensor.shape,
q_dtype,
)
transformed_tensor = transformed_tensor * scale
tTrTrans[None, None, None, None, iter % 2].store(transformed_tensor)
cute.arch.fence_view_async_shared()
kv_handle.release()
kv_trans_handle = dequant_kv_producer.acquire_and_advance()
cute.autovec_copy(
tTrTrans[None, None, None, None, (iterations - 1) % 2],
tTsTrans[None, None, None, None, kv_trans_handle.index],
)
cute.arch.fence_view_async_shared()
kv_trans_handle.commit()
return load_kv_consumer, load_scale_consumer, dequant_kv_producer
@cute.jit
def dequant_v(
iterations: int,
transform_warp_ids: Tuple,
dtype_args: Tuple,
tensor_args: Tuple,
pipeline_args: Tuple,
):
(v_dtype, q_dtype) = dtype_args
(sOrig, sScale, sTrans) = tensor_args
(load_kv_consumer, load_scale_consumer, dequant_kv_producer) = pipeline_args
tidx, _, _ = cute.arch.thread_idx()
THREADS_PER_WARP = 32
thread_idx = tidx % (THREADS_PER_WARP * len(transform_warp_ids))
r2s_copy_atom = cute.make_copy_atom(
cute.nvgpu.CopyUniversalOp(), v_dtype, num_bits_per_copy=32
)
# Construct tiled_copy satisfying 16 contiguous elts per copy atom
r2s_tiled_copy = cute.make_cotiled_copy(
r2s_copy_atom,
cute.make_layout((256, 16), stride=(16, 1)),
sTrans[(None, None, None, 0)].layout,
)
thr_r2s_tiled_copy = r2s_tiled_copy.get_slice(thread_idx)
tOsOrig = thr_r2s_tiled_copy.partition_S(sOrig)
tTsTrans = thr_r2s_tiled_copy.partition_D(sTrans)
# double buffer for better perf
tOrOrig = cute.make_rmem_tensor_like(
cute.append(
tOsOrig[None, None, None, None, 0].layout,
cute.make_layout(
2, stride=cute.cosize(tOsOrig[None, None, None, None, 0].layout)
),
),
v_dtype,
)
tTrTrans = cute.make_rmem_tensor_like(
cute.append(
tTsTrans[None, None, None, None, 0].layout,
cute.make_layout(
2, stride=cute.cosize(tTsTrans[None, None, None, None, 0].layout)
),
),
q_dtype,
)
tSsScale = thr_r2s_tiled_copy.partition_S(sScale)
tSrScale = cute.make_rmem_tensor_like(tSsScale[None, None, None, None, None, 0])
scale_v_handle = load_scale_consumer.wait_and_advance()
cute.autovec_copy(
tSsScale[None, None, None, None, None, scale_v_handle.index],
tSrScale,
)
cute.arch.fence_view_async_shared()
scale_v_handle.release()
# prefetch iter = 0
kv_handle = load_kv_consumer.wait_and_advance()
cute.autovec_copy(
tOsOrig[None, None, None, None, kv_handle.index],
tOrOrig[None, None, None, None, 0],
)
transformed_tensor = tOrOrig[None, None, None, None, 0].load().to(q_dtype)
scale = cute.TensorSSA(
tSrScale[None, None, None, None, 0].load(),
transformed_tensor.shape,
q_dtype,
)
transformed_tensor = transformed_tensor * scale
tTrTrans[None, None, None, None, 0].store(transformed_tensor)
cute.arch.fence_view_async_shared()
kv_handle.release()
for iter in cutlass.range(1, iterations, unroll_full=True):
kv_trans_handle = dequant_kv_producer.acquire_and_advance()
cute.autovec_copy(
tTrTrans[None, None, None, None, (iter - 1) % 2],
tTsTrans[None, None, None, None, kv_trans_handle.index],
)
cute.arch.fence_view_async_shared()
kv_trans_handle.commit()
kv_handle = load_kv_consumer.wait_and_advance()
cute.autovec_copy(
tOsOrig[None, None, None, None, kv_handle.index],
tOrOrig[None, None, None, None, iter % 2],
)
transformed_tensor = (
tOrOrig[None, None, None, None, iter % 2].load().to(q_dtype)
)
scale = cute.TensorSSA(
tSrScale[
None,
None,
None,
None,
iter,
].load(),
transformed_tensor.shape,
q_dtype,
)
transformed_tensor = transformed_tensor * scale
tTrTrans[None, None, None, None, iter % 2].store(transformed_tensor)
cute.arch.fence_view_async_shared()
kv_handle.release()
kv_trans_handle = dequant_kv_producer.acquire_and_advance()
cute.autovec_copy(
tTrTrans[None, None, None, None, (iterations - 1) % 2],
tTsTrans[None, None, None, None, kv_trans_handle.index],
)
cute.arch.fence_view_async_shared()
kv_trans_handle.commit()
return load_kv_consumer, load_scale_consumer, dequant_kv_producer