1150 lines
44 KiB
Python
1150 lines
44 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 Type
|
|
import argparse
|
|
|
|
import cuda.bindings.driver as cuda
|
|
import cutlass
|
|
import cutlass.cute as cute
|
|
from cutlass.cute.runtime import from_dlpack
|
|
from cutlass._mlir.dialects import llvm
|
|
import cutlass.pipeline as pipeline
|
|
import cutlass.utils as utils
|
|
|
|
"""
|
|
A HSTU attention forward pass example for NVIDIA Ampere SM80 architecture using Cute DSL, based on the example of flash_attention_v2 for Ampere.
|
|
|
|
The example showcases an implementation of HSTU attention(https://arxiv.org/abs/2402.17152) within generative recommender system. It utilize the formula: `mask(silu(q@k+rab))@v`. The implementation includes the following features:
|
|
- efficient fast sigmoid implementation
|
|
- block rasterization to improve L2 cache hit rate.
|
|
- The correct approach to verify the results of the HSTU attention with a Pytorch implementation.
|
|
|
|
To run this example:
|
|
|
|
.. code-block:: bash
|
|
|
|
python examples/ampere/hstu_attention.py --batch_size 4 --seqlen_q 8192 --seqlen_kv 8192 --num_head 4 --head_dim 128 --m_block_size 128 --n_block_size 64 --is_causal --perf_test
|
|
|
|
The above example tests the performance of HSTU attention with batch size 4, sequence length 8192, 4 attention heads, and head dimension 128. The m_block_size is 128, and n_block_size is 64. The causal masking is enabled.
|
|
|
|
There are some constraints for this example:
|
|
* Only Float16 and BFloat16 are supported.
|
|
* The contiguous dimension of each tensor must be at least 16 bytes aligned.
|
|
* The values of `m_block_size`, `n_block_size`, and `head_dim` must be selected to stay within shared memory capacity limits.
|
|
* `m_block_size * 2` must be divisible by `num_threads`, otherwise the kernel will not be able to get the correct result.
|
|
* "seqlen_kv should be greater or equal to seqlen_q.
|
|
"""
|
|
|
|
|
|
class HSTUAttentionForwardAmpere(object):
|
|
def __init__(
|
|
self,
|
|
dtype,
|
|
batch_size,
|
|
seqlen_q,
|
|
seqlen_kv,
|
|
num_head,
|
|
head_dim,
|
|
m_block_size=128,
|
|
n_block_size=128,
|
|
num_threads=128,
|
|
enable_fast_sigmoid=False,
|
|
enable_block_rasterization=False,
|
|
is_causal=False,
|
|
):
|
|
self._dtype = dtype
|
|
self._batch_size = batch_size
|
|
self._seqlen_q = seqlen_q
|
|
self._seqlen_kv = seqlen_kv
|
|
self._num_head = num_head
|
|
self._head_dim = head_dim
|
|
self._m_block_size = m_block_size
|
|
self._n_block_size = n_block_size
|
|
# padded head_dim to 32 for cta tile.
|
|
self._head_dim_padded = (head_dim + 31) // 32 * 32
|
|
self._num_threads = num_threads
|
|
self._enable_fast_sigmoid = enable_fast_sigmoid
|
|
self._enable_block_rasterization = enable_block_rasterization
|
|
self._is_causal = is_causal
|
|
assert self._dtype == cutlass.Float16 or self._dtype == cutlass.BFloat16, (
|
|
"Only Float16 or BFloat16 is supported"
|
|
)
|
|
assert self._head_dim % 8 == 0, "head dim should be multiply of 8"
|
|
assert self._num_threads % 32 == 0, "num_threads should be multiply of 32"
|
|
assert self._m_block_size * self._head_dim_padded // self._num_threads >= 8, (
|
|
"Small m_block_size and too many threads"
|
|
)
|
|
assert self._n_block_size * self._head_dim_padded // self._num_threads >= 8, (
|
|
"Small n_block_size and too many threads"
|
|
)
|
|
assert seqlen_kv >= seqlen_q, "seqlen_kv should be greater or equal to seqlen_q"
|
|
self.cta_sync_barrier = pipeline.NamedBarrier(
|
|
barrier_id=1, num_threads=num_threads
|
|
)
|
|
|
|
@cute.jit
|
|
def __call__(
|
|
self,
|
|
mQ: cute.Tensor,
|
|
mK: cute.Tensor,
|
|
mV: cute.Tensor,
|
|
mO: cute.Tensor,
|
|
mRAB: cute.Tensor,
|
|
stream: cuda.CUstream,
|
|
):
|
|
"""Configures and launches the HSTU attention kernel.
|
|
mQ/mK/mV/mO/mRAB has same data types(supports fp16 and bf16).
|
|
mQ has layout: (batch_size, seqlen_q, num_head, head_dim):(seqlen_q * num_head * head_dim, num_head * head_dim, head_dim, 1)
|
|
mK/mV/mO has same layout: (batch_size, seqlen_kv, num_head, head_dim):(seqlen_kv * num_head * head_dim, num_head * head_dim, head_dim, 1)
|
|
mRAB has layout: (batch_size, num_head, seqlen_q, seqlen_kv):(seqlen_q*seqlen_kv*num_head, seqlen_q*seqlen_kv, seqlen_kv, 1)
|
|
|
|
Prepares the shared memory layout, tiled copy atoms, tiled mma and shared memory storage.
|
|
Then launches the kernel function with the prepared parameters.
|
|
|
|
:param mQ: query tensor
|
|
:type mQ: cute.Tensor
|
|
:param mK: key tensor
|
|
:type mK: cute.Tensor
|
|
:param mV: value tensor
|
|
:type mV: cute.Tensor
|
|
:param mO: output tensor
|
|
:type mO: cute.Tensor
|
|
:param mRAB: RAB tensor
|
|
:type mRAB: cute.Tensor
|
|
"""
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Shared memory layout: Q/K/V/RAB
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
smem_k_block_size = 64 if self._head_dim_padded % 64 == 0 else 32
|
|
swizzle_bits = 3 if smem_k_block_size == 64 else 2
|
|
sQ_layout_atom = cute.make_composed_layout(
|
|
cute.make_swizzle(swizzle_bits, 4, 3),
|
|
0,
|
|
cute.make_layout((8, smem_k_block_size), stride=(smem_k_block_size, 1)),
|
|
)
|
|
sQ_layout = cute.tile_to_shape(
|
|
sQ_layout_atom,
|
|
(self._m_block_size, self._head_dim_padded),
|
|
(0, 1),
|
|
)
|
|
sKV_layout_atom = sQ_layout_atom
|
|
sKV_layout = cute.tile_to_shape(
|
|
sKV_layout_atom,
|
|
(self._n_block_size, self._head_dim_padded),
|
|
(0, 1),
|
|
)
|
|
sRAB_layout_atom = sQ_layout_atom
|
|
sRAB_layout = cute.tile_to_shape(
|
|
sRAB_layout_atom, (self._m_block_size, self._n_block_size), (0, 1)
|
|
)
|
|
sO_layout = sQ_layout
|
|
|
|
@cute.struct
|
|
class SharedStorage:
|
|
sQ: cute.struct.Align[
|
|
cute.struct.MemRange[self._dtype, cute.cosize(sQ_layout)], 1024
|
|
]
|
|
sK: cute.struct.Align[
|
|
cute.struct.MemRange[self._dtype, cute.cosize(sKV_layout)], 1024
|
|
]
|
|
sV: cute.struct.Align[
|
|
cute.struct.MemRange[self._dtype, cute.cosize(sKV_layout)], 1024
|
|
]
|
|
sRAB: cute.struct.Align[
|
|
cute.struct.MemRange[self._dtype, cute.cosize(sRAB_layout)], 1024
|
|
]
|
|
|
|
assert SharedStorage.size_in_bytes() < utils.get_smem_capacity_in_bytes(
|
|
"sm_80"
|
|
), "insufficient shared memory"
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# GMEM Tiled copy:
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Thread layouts for copies
|
|
universal_copy_bits = 128
|
|
async_copy_elems = universal_copy_bits // self._dtype.width
|
|
# atom_async_copy: async copy atom for QKV load
|
|
atom_async_copy = cute.make_copy_atom(
|
|
cute.nvgpu.cpasync.CopyG2SOp(),
|
|
self._dtype,
|
|
num_bits_per_copy=universal_copy_bits,
|
|
)
|
|
# atom_universal_copy: universal copy atom for O store
|
|
atom_universal_copy = cute.make_copy_atom(
|
|
cute.nvgpu.CopyUniversalOp(),
|
|
self._dtype,
|
|
num_bits_per_copy=universal_copy_bits,
|
|
)
|
|
# tQKV_layout: thread layout for QKV load
|
|
tQKV_shape_dim_1 = sQ_layout_atom.outer.shape[1] // async_copy_elems
|
|
tQKV_layout = cute.make_layout(
|
|
(self._num_threads // tQKV_shape_dim_1, tQKV_shape_dim_1),
|
|
stride=(tQKV_shape_dim_1, 1),
|
|
)
|
|
# tO_layout: thread layout for O store
|
|
tO_layout = tQKV_layout
|
|
|
|
# Value layouts for copies
|
|
vQKV_layout = cute.make_layout((1, async_copy_elems))
|
|
vO_layout = vQKV_layout
|
|
|
|
# gmem_tiled_copy_QKV: tiled copy for QKV load
|
|
gmem_tiled_copy_QKV = cute.make_tiled_copy_tv(
|
|
atom_async_copy, tQKV_layout, vQKV_layout
|
|
)
|
|
|
|
# gmem_tiled_copy_O: tiled copy for O store
|
|
gmem_tiled_copy_O = cute.make_tiled_copy_tv(
|
|
atom_universal_copy, tO_layout, vO_layout
|
|
)
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Tiled mma
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
tiled_mma = cute.make_tiled_mma(
|
|
cute.nvgpu.warp.MmaF16BF16Op(self._dtype, cutlass.Float32, (16, 8, 16)),
|
|
(self._num_threads // 32, 1, 1),
|
|
permutation_mnk=(self._num_threads // 32 * 16, 16, 16),
|
|
)
|
|
|
|
# block rasterization
|
|
if cutlass.const_expr(self._enable_block_rasterization):
|
|
grid_dim = (
|
|
self._batch_size,
|
|
self._num_head,
|
|
cute.ceil_div(mQ.shape[1], self._m_block_size),
|
|
)
|
|
else:
|
|
grid_dim = (
|
|
cute.ceil_div(mQ.shape[1], self._m_block_size),
|
|
self._batch_size,
|
|
self._num_head,
|
|
)
|
|
|
|
self.kernel(
|
|
mQ,
|
|
mK,
|
|
mV,
|
|
mO,
|
|
mRAB,
|
|
sQ_layout,
|
|
sKV_layout,
|
|
sRAB_layout,
|
|
sO_layout,
|
|
gmem_tiled_copy_QKV,
|
|
gmem_tiled_copy_O,
|
|
tiled_mma,
|
|
SharedStorage,
|
|
).launch(
|
|
grid=grid_dim,
|
|
block=[self._num_threads, 1, 1],
|
|
stream=stream,
|
|
)
|
|
|
|
@cute.kernel
|
|
def kernel(
|
|
self,
|
|
mQ: cute.Tensor,
|
|
mK: cute.Tensor,
|
|
mV: cute.Tensor,
|
|
mO: cute.Tensor,
|
|
mRAB: cute.Tensor,
|
|
sQ_layout: cute.ComposedLayout,
|
|
sKV_layout: cute.ComposedLayout,
|
|
sRAB_layout: cute.ComposedLayout,
|
|
sO_layout: cute.ComposedLayout,
|
|
gmem_tiled_copy_QKV: cute.TiledCopy,
|
|
gmem_tiled_copy_O: cute.TiledCopy,
|
|
tiled_mma: cute.TiledMma,
|
|
SharedStorage: cutlass.Constexpr,
|
|
):
|
|
"""Kernel function for HSTU attention.
|
|
|
|
:param mQ: query tensor
|
|
:type mQ: cute.Tensor
|
|
:param mK: key tensor
|
|
:type mK: cute.Tensor
|
|
:param mV: value tensor
|
|
:type mV: cute.Tensor
|
|
:param mO: output tensor
|
|
:type mO: cute.Tensor
|
|
:param mRAB: RAB(Relative Attention Bias) tensor
|
|
:type mRAB: cute.Tensor
|
|
:param sQ_layout: shared memory layout for Q
|
|
:type sQ_layout: cute.ComposedLayout
|
|
:param sKV_layout: shared memory layout for K/V
|
|
:type sKV_layout: cute.ComposedLayout
|
|
:param sRAB_layout: shared memory layout for RAB
|
|
:type sRAB_layout: cute.ComposedLayout
|
|
:param sO_layout: shared memory layout for O
|
|
:type sO_layout: cute.ComposedLayout
|
|
:param gmem_tiled_copy_QKV: tiled copy for QKV load
|
|
:type gmem_tiled_copy_QKV: cute.TiledCopy
|
|
:param gmem_tiled_copy_O: tiled copy for O store
|
|
:type gmem_tiled_copy_O: cute.TiledCopy
|
|
:param tiled_mma: tiled mma
|
|
:type tiled_mma: cute.TiledMma
|
|
:param SharedStorage: shared storage
|
|
:type SharedStorage: cutlass.Constexpr
|
|
"""
|
|
# Thread index, block index
|
|
tidx, _, _ = cute.arch.thread_idx()
|
|
|
|
if cutlass.const_expr(self._enable_block_rasterization):
|
|
batch_size, num_head, m_block = cute.arch.block_idx()
|
|
else:
|
|
m_block, batch_size, num_head = cute.arch.block_idx()
|
|
|
|
# reverse the m_block index
|
|
m_block = cute.ceil_div(mQ.shape[1], self._m_block_size) - m_block - 1
|
|
|
|
if cutlass.const_expr(self._is_causal):
|
|
n_block = (
|
|
cute.ceil_div(
|
|
min((m_block + 1) * self._m_block_size, mK.shape[1]),
|
|
self._n_block_size,
|
|
)
|
|
- 1
|
|
) # for causal case, only process the first n_block tiles
|
|
else:
|
|
n_block = cute.ceil_div(mK.shape[1], self._n_block_size) - 1
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Get the appropriate tiles for this thread block.
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# (m_block_size, head_dim)
|
|
gQ = cute.local_tile(
|
|
mQ[batch_size, None, num_head, None],
|
|
(self._m_block_size, self._head_dim_padded),
|
|
(m_block, 0),
|
|
)
|
|
# (n_block_size, head_dim, n_block)
|
|
gK = cute.local_tile(
|
|
mK[batch_size, None, num_head, None],
|
|
(self._n_block_size, self._head_dim_padded),
|
|
(None, 0),
|
|
)
|
|
# (n_block_size, head_dim, n_block)
|
|
gV = cute.local_tile(
|
|
mV[batch_size, None, num_head, None],
|
|
(self._n_block_size, self._head_dim_padded),
|
|
(None, 0),
|
|
)
|
|
# (m_block_size, n_block_size)
|
|
gRAB = cute.local_tile(
|
|
mRAB[batch_size, num_head, None, None],
|
|
(self._m_block_size, self._n_block_size),
|
|
(m_block, None),
|
|
)
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Get shared memory buffer
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
smem = cutlass.utils.SmemAllocator()
|
|
storage = smem.allocate(SharedStorage)
|
|
sQ = storage.sQ.get_tensor(sQ_layout)
|
|
sK = storage.sK.get_tensor(sKV_layout)
|
|
sV = storage.sV.get_tensor(sKV_layout)
|
|
sRAB = storage.sRAB.get_tensor(sRAB_layout)
|
|
|
|
# Transpose view of V to tensor with layout (head_dim, n_block_size) for tiled mma
|
|
sVt = cute.composition(
|
|
sV,
|
|
cute.make_layout(
|
|
(self._head_dim_padded, self._n_block_size),
|
|
stride=(self._n_block_size, 1),
|
|
),
|
|
)
|
|
|
|
gmem_thr_copy_QKV = gmem_tiled_copy_QKV.get_slice(tidx)
|
|
# (CPY_Atom, CPY_M, CPY_K)
|
|
tQgQ = gmem_thr_copy_QKV.partition_S(gQ)
|
|
tQsQ = gmem_thr_copy_QKV.partition_D(sQ)
|
|
# (CPY_Atom, CPY_N, CPY_K, n_block)
|
|
tKgK = gmem_thr_copy_QKV.partition_S(gK)
|
|
tKsK = gmem_thr_copy_QKV.partition_D(sK)
|
|
# (CPY_Atom, CPY_N, CPY_K, n_block)
|
|
tVgV = gmem_thr_copy_QKV.partition_S(gV)
|
|
tVsV = gmem_thr_copy_QKV.partition_D(sV)
|
|
# (CPY_Atom, CPY_M, CPY_N, n_block)
|
|
tRABgRAB = gmem_tiled_copy_QKV.get_slice(tidx).partition_S(gRAB)
|
|
tRabsRAB = gmem_tiled_copy_QKV.get_slice(tidx).partition_D(sRAB)
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Tile MMA compute thread partitions and allocate accumulators
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
thr_mma = tiled_mma.get_slice(tidx)
|
|
tSrQ = thr_mma.make_fragment_A(thr_mma.partition_A(sQ))
|
|
tSrK = thr_mma.make_fragment_B(thr_mma.partition_B(sK))
|
|
tOrVt = thr_mma.make_fragment_B(thr_mma.partition_B(sVt))
|
|
acc_shape_O = thr_mma.partition_shape_C(
|
|
(self._m_block_size, self._head_dim_padded)
|
|
)
|
|
acc_O = cute.make_rmem_tensor(acc_shape_O, cutlass.Float32)
|
|
acc_O.fill(0.0)
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Smem copy atom tiling
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
smem_copy_atom_Q = cute.make_copy_atom(
|
|
cute.nvgpu.warp.LdMatrix8x8x16bOp(transpose=False, num_matrices=4),
|
|
self._dtype,
|
|
)
|
|
smem_copy_atom_K = cute.make_copy_atom(
|
|
cute.nvgpu.warp.LdMatrix8x8x16bOp(transpose=False, num_matrices=4),
|
|
self._dtype,
|
|
)
|
|
smem_copy_atom_V = cute.make_copy_atom(
|
|
cute.nvgpu.warp.LdMatrix8x8x16bOp(transpose=True, num_matrices=4),
|
|
self._dtype,
|
|
)
|
|
smem_copy_atom_RAB = cute.make_copy_atom(
|
|
cute.nvgpu.warp.LdMatrix8x8x16bOp(transpose=False, num_matrices=4),
|
|
self._dtype,
|
|
)
|
|
smem_tiled_copy_Q = cute.make_tiled_copy_A(smem_copy_atom_Q, tiled_mma)
|
|
smem_tiled_copy_K = cute.make_tiled_copy_B(smem_copy_atom_K, tiled_mma)
|
|
smem_tiled_copy_V = cute.make_tiled_copy_B(smem_copy_atom_V, tiled_mma)
|
|
smem_tiled_copy_RAB = cute.make_tiled_copy_C(smem_copy_atom_RAB, tiled_mma)
|
|
|
|
smem_thr_copy_Q = smem_tiled_copy_Q.get_slice(tidx)
|
|
smem_thr_copy_K = smem_tiled_copy_K.get_slice(tidx)
|
|
smem_thr_copy_V = smem_tiled_copy_V.get_slice(tidx)
|
|
smem_thr_copy_RAB = smem_tiled_copy_RAB.get_slice(tidx)
|
|
|
|
tSsQ = smem_thr_copy_Q.partition_S(sQ)
|
|
tSrQ_copy_view = smem_thr_copy_Q.retile(tSrQ)
|
|
tSsK = smem_thr_copy_K.partition_S(sK)
|
|
tSrK_copy_view = smem_thr_copy_K.retile(tSrK)
|
|
tOsVt = smem_thr_copy_V.partition_S(sVt)
|
|
tOrVt_copy_view = smem_thr_copy_V.retile(tOrVt)
|
|
tSsRAB = smem_thr_copy_RAB.partition_S(sRAB)
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Predicate: Mark indices that need to copy when problem_shape isn't a multiple
|
|
# of tile_shape
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Construct identity layout for Q, KV and RAB
|
|
mcQ = cute.make_identity_tensor(mQ.layout.shape)
|
|
mcKV = cute.make_identity_tensor(mK.layout.shape)
|
|
mcRAB = cute.make_identity_tensor(mRAB.layout.shape)
|
|
|
|
cQ = cute.local_tile(
|
|
mcQ[batch_size, None, num_head, None],
|
|
(self._m_block_size, self._head_dim_padded),
|
|
(m_block, 0),
|
|
)
|
|
cKV = cute.local_tile(
|
|
mcKV[batch_size, None, num_head, None],
|
|
(self._n_block_size, self._head_dim_padded),
|
|
(n_block, 0),
|
|
)
|
|
cRAB = cute.local_tile(
|
|
mcRAB[batch_size, num_head, None, None],
|
|
(self._m_block_size, self._n_block_size),
|
|
(m_block, None),
|
|
)
|
|
|
|
# Repeat the partitioning with identity layouts
|
|
tQcQ = gmem_thr_copy_QKV.partition_S(cQ)
|
|
tKVcKV = gmem_thr_copy_QKV.partition_S(cKV)
|
|
tRABcRAB = gmem_thr_copy_QKV.partition_S(cRAB)
|
|
|
|
tQpQ = cute.make_rmem_tensor(
|
|
cute.make_layout(
|
|
(
|
|
tQsQ.shape[0][1],
|
|
cute.size(tQsQ, mode=[1]),
|
|
cute.size(tQsQ, mode=[2]),
|
|
),
|
|
stride=(cute.size(tQsQ, mode=[2]), 0, 1),
|
|
),
|
|
cutlass.Boolean,
|
|
)
|
|
tKVpKV = cute.make_rmem_tensor(
|
|
cute.make_layout(
|
|
(
|
|
tKsK.shape[0][1],
|
|
cute.size(tKsK, mode=[1]),
|
|
cute.size(tKsK, mode=[2]),
|
|
),
|
|
stride=(cute.size(tKsK, mode=[2]), 0, 1),
|
|
),
|
|
cutlass.Boolean,
|
|
)
|
|
|
|
# Set predicates for head_dim bounds, seqlen_q/k/v bounds is processed at the first tile.
|
|
for rest_v in cutlass.range_constexpr(tQpQ.shape[0]):
|
|
for rest_k in cutlass.range_constexpr(tQpQ.shape[2]):
|
|
tQpQ[rest_v, 0, rest_k] = cute.elem_less(
|
|
tQcQ[(0, rest_v), 0, rest_k][3], mQ.layout.shape[3]
|
|
)
|
|
for rest_v in cutlass.range_constexpr(tKVpKV.shape[0]):
|
|
for rest_k in cutlass.range_constexpr(tKVpKV.shape[2]):
|
|
tKVpKV[rest_v, 0, rest_k] = cute.elem_less(
|
|
tKVcKV[(0, rest_v), 0, rest_k][3], mK.layout.shape[3]
|
|
)
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Prefetch Prologue
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Start async loads of the last mn-tile, where we take care of the mn residue
|
|
for m in cutlass.range_constexpr(cute.size(tQsQ.shape[1])):
|
|
if cute.elem_less(tQcQ[0, m, 0][1], mQ.layout.shape[1]):
|
|
cute.copy(
|
|
gmem_tiled_copy_QKV,
|
|
tQgQ[None, m, None],
|
|
tQsQ[None, m, None],
|
|
pred=tQpQ[None, m, None],
|
|
)
|
|
else:
|
|
# Clear the smem tiles to account for predicated off loads
|
|
tQsQ[None, m, None].fill(0)
|
|
|
|
for n in cutlass.range_constexpr(cute.size(tKsK.shape[1])):
|
|
if cute.elem_less(tKVcKV[0, n, 0][1], mK.layout.shape[1]):
|
|
cute.copy(
|
|
gmem_tiled_copy_QKV,
|
|
tKgK[None, n, None, n_block],
|
|
tKsK[None, n, None],
|
|
pred=tKVpKV[None, n, None],
|
|
)
|
|
else:
|
|
# Clear the smem tiles to account for predicated off loads
|
|
tKsK[None, n, None].fill(0)
|
|
|
|
for m in cutlass.range_constexpr(cute.size(tRABcRAB.shape[1])):
|
|
for n in cutlass.range_constexpr(cute.size(tRABcRAB.shape[2])):
|
|
if cute.elem_less(
|
|
tRABcRAB[0, m, n, n_block][1], mRAB.layout.shape[2]
|
|
) and cute.elem_less(
|
|
tRABcRAB[0, m, n, n_block][2], mRAB.layout.shape[3]
|
|
):
|
|
cute.copy(
|
|
gmem_tiled_copy_QKV,
|
|
tRABgRAB[None, m, n, n_block],
|
|
tRabsRAB[None, m, n],
|
|
)
|
|
else:
|
|
# Clear the smem tiles to account for predicated off loads
|
|
tRabsRAB[None, m, n].fill(0)
|
|
cute.arch.cp_async_commit_group()
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Mainloop
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
for n_block_idx in range(n_block, -1, -1):
|
|
# wait for smem tile QK before mma caculation for S
|
|
cute.arch.cp_async_wait_group(0)
|
|
self.cta_sync_barrier.arrive_and_wait()
|
|
|
|
if n_block_idx == n_block:
|
|
for n in cutlass.range_constexpr(cute.size(tVsV.shape[1])):
|
|
if cute.elem_less(tKVcKV[0, n, 0][1], mV.layout.shape[1]):
|
|
cute.copy(
|
|
gmem_tiled_copy_QKV,
|
|
tVgV[None, n, None, n_block_idx],
|
|
tVsV[None, n, None],
|
|
pred=tKVpKV[None, n, None],
|
|
)
|
|
else:
|
|
tVsV[None, n, None].fill(0)
|
|
else:
|
|
cute.copy(
|
|
gmem_tiled_copy_QKV,
|
|
tVgV[None, None, None, n_block_idx],
|
|
tVsV[None, None, None],
|
|
pred=tKVpKV[None, None, None],
|
|
)
|
|
cute.arch.cp_async_commit_group()
|
|
|
|
acc_shape_S = thr_mma.partition_shape_C(
|
|
(self._m_block_size, self._n_block_size)
|
|
)
|
|
acc_S = cute.make_rmem_tensor(acc_shape_S, cutlass.Float32)
|
|
|
|
rRAB_shape_S = thr_mma.partition_shape_C(
|
|
(self._m_block_size, self._n_block_size)
|
|
)
|
|
rRAB = cute.make_rmem_tensor(rRAB_shape_S, self._dtype)
|
|
tSrRAB_copy_view = smem_thr_copy_RAB.retile(rRAB)
|
|
cute.copy(
|
|
smem_tiled_copy_RAB,
|
|
tSsRAB[None, None, None],
|
|
tSrRAB_copy_view[None, None, None],
|
|
)
|
|
acc_S.store(rRAB.load().to(cutlass.Float32))
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# S gemm calculation
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# ldmatrix first QK k-block for mma
|
|
cute.copy(
|
|
smem_tiled_copy_Q,
|
|
tSsQ[None, None, 0],
|
|
tSrQ_copy_view[None, None, 0],
|
|
)
|
|
cute.copy(
|
|
smem_tiled_copy_K,
|
|
tSsK[None, None, 0],
|
|
tSrK_copy_view[None, None, 0],
|
|
)
|
|
for k in cutlass.range_constexpr(0, cute.size(tSsQ.shape[2])):
|
|
# ldmatrix next QK k-block for mma
|
|
if k < cute.size(tSsQ.shape[2]) - 1:
|
|
cute.copy(
|
|
smem_tiled_copy_Q,
|
|
tSsQ[None, None, k + 1],
|
|
tSrQ_copy_view[None, None, k + 1],
|
|
)
|
|
cute.copy(
|
|
smem_tiled_copy_K,
|
|
tSsK[None, None, k + 1],
|
|
tSrK_copy_view[None, None, k + 1],
|
|
)
|
|
# mma for S=Q@K
|
|
cute.gemm(
|
|
tiled_mma,
|
|
acc_S,
|
|
tSrQ[None, None, k],
|
|
tSrK[None, None, k],
|
|
acc_S,
|
|
)
|
|
|
|
# wait for smem tile V for O
|
|
cute.arch.cp_async_wait_group(0)
|
|
self.cta_sync_barrier.arrive_and_wait()
|
|
|
|
if n_block_idx > 0:
|
|
cute.copy(
|
|
gmem_tiled_copy_QKV,
|
|
tKgK[None, None, None, n_block_idx - 1],
|
|
tKsK[None, None, None],
|
|
pred=tKVpKV[None, None, None],
|
|
)
|
|
# m residue handling for RAB
|
|
for m in cutlass.range_constexpr(cute.size(tRABcRAB.shape[1])):
|
|
if cute.elem_less(
|
|
tRABcRAB[0, m, 0, n_block_idx - 1][1], mRAB.layout.shape[2]
|
|
):
|
|
cute.copy(
|
|
gmem_tiled_copy_QKV,
|
|
tRABgRAB[None, m, None, n_block_idx - 1],
|
|
tRabsRAB[None, m, None],
|
|
)
|
|
else:
|
|
tRabsRAB[None, m, None].fill(0)
|
|
|
|
cute.arch.cp_async_commit_group()
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# silu activation
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
if self._enable_fast_sigmoid:
|
|
t1 = acc_S.load()
|
|
t2 = t1 * 0.5
|
|
acc_S.store(t2)
|
|
for i in cutlass.range_constexpr(cute.size(acc_S.shape[0])):
|
|
for j in cutlass.range_constexpr(cute.size(acc_S.shape[1])):
|
|
for k in cutlass.range_constexpr(cute.size(acc_S.shape[2])):
|
|
ret = llvm.inline_asm(
|
|
cutlass.Float32.mlir_type,
|
|
[acc_S[i, j, k].ir_value()],
|
|
"tanh.approx.f32 $0, $1;",
|
|
"=f,f",
|
|
has_side_effects=False,
|
|
is_align_stack=False,
|
|
asm_dialect=llvm.AsmDialect.AD_ATT,
|
|
)
|
|
acc_S[i, j, k] = ret
|
|
t3 = acc_S.load()
|
|
t4 = t2 * t3 + t2
|
|
acc_S.store(t4)
|
|
else:
|
|
LOG2_E = 1.4426950408889634074
|
|
t1 = acc_S.load()
|
|
t2 = t1 * -LOG2_E
|
|
t3 = cute.math.exp2(t2, fastmath=True) + 1.0
|
|
t4 = t1 / t3
|
|
acc_S.store(t4)
|
|
|
|
mACC = cute.make_identity_tensor(
|
|
(mRAB.layout.shape[2], mRAB.layout.shape[3])
|
|
) # (seqlen_q, seqlen_kv)
|
|
cACC = cute.local_tile(
|
|
mACC[None, None],
|
|
(self._m_block_size, self._n_block_size),
|
|
(m_block, n_block_idx),
|
|
)
|
|
|
|
if self._is_causal and (n_block - n_block_idx) < cute.ceil_div(
|
|
self._m_block_size, self._n_block_size
|
|
):
|
|
tACCcACC = thr_mma.partition_C(cACC)
|
|
for i in cutlass.range_constexpr(cute.size(tACCcACC.shape[0])):
|
|
for j in cutlass.range_constexpr(cute.size(tACCcACC.shape[1])):
|
|
for k in cutlass.range_constexpr(cute.size(tACCcACC.shape[2])):
|
|
if cute.elem_less(
|
|
tACCcACC[i, j, k][0], tACCcACC[i, j, k][1]
|
|
):
|
|
acc_S[i, j, k] = 0.0
|
|
|
|
rP = cute.make_rmem_tensor_like(acc_S, self._dtype)
|
|
rP.store(acc_S.load().to(self._dtype))
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# O gemm calculation
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Convert layout of acc_S to gemm O accept layout.
|
|
# Due to the mma instruction shape is 16x8x16, we need to convert from (4, MMA_M, MMA_N) to ((4, 2), MMA_M, MMA_N / 2)
|
|
# (4, MMA_M, MMA_N) -> (4, MMA_M, (2, MMA_N / 2))
|
|
rP_layout_divided = cute.logical_divide(rP.layout, (None, None, 2))
|
|
rP_mma_view = cute.make_layout(
|
|
(
|
|
(rP_layout_divided.shape[0], rP_layout_divided.shape[2][0]),
|
|
rP_layout_divided.shape[1],
|
|
rP_layout_divided.shape[2][1],
|
|
),
|
|
stride=(
|
|
(rP_layout_divided.stride[0], rP_layout_divided.stride[2][0]),
|
|
rP_layout_divided.stride[1],
|
|
rP_layout_divided.stride[2][1],
|
|
),
|
|
)
|
|
tOrP = cute.make_tensor(rP.iterator, rP_mma_view)
|
|
|
|
# ldmatrix first V k-block for mma
|
|
cute.copy(
|
|
smem_tiled_copy_V,
|
|
tOsVt[None, None, 0],
|
|
tOrVt_copy_view[None, None, 0],
|
|
)
|
|
for k in cutlass.range_constexpr(0, cute.size(tOrP.shape[2])):
|
|
# ldmatrix next V k-block for mma
|
|
if k < cute.size(tOrP.shape[2]) - 1:
|
|
cute.copy(
|
|
smem_tiled_copy_V,
|
|
tOsVt[None, None, k + 1],
|
|
tOrVt_copy_view[None, None, k + 1],
|
|
)
|
|
# mma for O=P@V
|
|
cute.gemm(
|
|
tiled_mma,
|
|
acc_O,
|
|
tOrP[None, None, k],
|
|
tOrVt[None, None, k],
|
|
acc_O,
|
|
)
|
|
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# Epilogue
|
|
# ///////////////////////////////////////////////////////////////////////////////
|
|
# store acc_O
|
|
rO = cute.make_rmem_tensor(acc_O.layout, self._dtype)
|
|
rO.store(acc_O.load().to(self._dtype))
|
|
# reuse sQ's data iterator
|
|
sO_iter = cute.recast_ptr(sQ.iterator, sO_layout.inner)
|
|
sO = cute.make_tensor(sO_iter, sO_layout.outer)
|
|
smem_copy_atom_O = cute.make_copy_atom(
|
|
cute.nvgpu.CopyUniversalOp(), self._dtype
|
|
)
|
|
smem_tiled_copy_O = cute.make_tiled_copy_C(smem_copy_atom_O, tiled_mma)
|
|
smem_thr_copy_O = smem_tiled_copy_O.get_slice(tidx)
|
|
taccOrO = smem_thr_copy_O.retile(rO)
|
|
taccOsO = smem_thr_copy_O.partition_D(sO)
|
|
# copy acc O from rmem to smem with sts.32(auto vectorization)
|
|
cute.copy(
|
|
smem_copy_atom_O,
|
|
taccOrO,
|
|
taccOsO,
|
|
)
|
|
gO = cute.local_tile(
|
|
mO[batch_size, None, num_head, None],
|
|
(self._m_block_size, self._head_dim_padded),
|
|
(m_block, 0),
|
|
)
|
|
|
|
gmem_thr_copy_O = gmem_tiled_copy_O.get_slice(tidx)
|
|
tOsO = gmem_thr_copy_O.partition_S(sO)
|
|
tOgO = gmem_thr_copy_O.partition_D(gO)
|
|
tOrO = cute.make_fragment_like(tOgO, self._dtype)
|
|
# sync before all sts are done.
|
|
self.cta_sync_barrier.arrive_and_wait()
|
|
# load acc O from smem to rmem for wider vectorization
|
|
cute.copy(
|
|
gmem_tiled_copy_O,
|
|
tOsO,
|
|
tOrO,
|
|
)
|
|
# predicate for O
|
|
mcO = cute.make_identity_tensor(mO.layout.shape)
|
|
cO = cute.local_tile(
|
|
mcO[batch_size, None, num_head, None],
|
|
(self._m_block_size, self._head_dim_padded),
|
|
(m_block, 0),
|
|
)
|
|
tOcO = gmem_thr_copy_O.partition_D(cO)
|
|
tOpO = cute.make_rmem_tensor(
|
|
cute.make_layout(
|
|
(tOgO.shape[0][1], tOgO.shape[1], tOgO.shape[2]),
|
|
stride=(tOgO.shape[2], 0, 1),
|
|
),
|
|
cutlass.Boolean,
|
|
)
|
|
for rest_v in cutlass.range_constexpr(tOpO.shape[0]):
|
|
for rest_n in cutlass.range_constexpr(cute.size(tOpO.shape[2])):
|
|
tOpO[rest_v, 0, rest_n] = cute.elem_less(
|
|
tOcO[(0, rest_v), 0, rest_n][3], mO.layout.shape[3]
|
|
)
|
|
# copy acc O from rmem to gmem
|
|
for rest_m in cutlass.range_constexpr(cute.size(tOpO.shape[1])):
|
|
if cute.elem_less(tOcO[0, rest_m, 0][1], mO.layout.shape[1]):
|
|
cute.copy(
|
|
gmem_tiled_copy_O,
|
|
tOrO[None, rest_m, None],
|
|
tOgO[None, rest_m, None],
|
|
pred=tOpO[None, rest_m, None],
|
|
)
|
|
|
|
|
|
def run_pytorch_hstu_test(
|
|
dtype,
|
|
q,
|
|
k,
|
|
v,
|
|
rab,
|
|
is_causal: bool,
|
|
):
|
|
"""Generate the reference output of the HSTU attention with Pytorch.
|
|
|
|
:param dtype: data type of the input tensors
|
|
:param q: query tensor
|
|
:param k: key tensor
|
|
:param v: value tensor
|
|
:param rab: RAB tensor
|
|
:param is_causal: whether to use causal masking
|
|
:type is_causal: bool
|
|
"""
|
|
import torch
|
|
|
|
q = q.to(dtype)
|
|
k = k.to(dtype)
|
|
v = v.to(dtype)
|
|
rab = rab.to(dtype)
|
|
|
|
s_ = torch.matmul(q, k.transpose(-2, -1)) + rab
|
|
s_ = torch.nn.functional.silu(s_)
|
|
if is_causal:
|
|
mask = torch.ones(1, 1, q.shape[2], k.shape[2], dtype=dtype)
|
|
mask = torch.tril(mask)
|
|
s_ = s_ * mask.cuda()
|
|
|
|
o = torch.matmul(s_, v).permute(0, 2, 1, 3).contiguous()
|
|
return o
|
|
|
|
|
|
def run(
|
|
dtype: Type[cutlass.Numeric],
|
|
batch_size: int,
|
|
seqlen_q: int,
|
|
seqlen_kv: int,
|
|
num_head: int,
|
|
head_dim: int,
|
|
m_block_size: int = 128,
|
|
n_block_size: int = 128,
|
|
num_threads: int = 128,
|
|
enable_fast_sigmoid: bool = False,
|
|
enable_block_rasterization: bool = False,
|
|
is_causal: bool = False,
|
|
perf_test: bool = False,
|
|
**kwargs,
|
|
):
|
|
"""
|
|
Run the HSTU attention kernel.
|
|
|
|
:param dtype: data type of the input tensors
|
|
:type dtype: Type[cutlass.Numeric]
|
|
:param batch_size: batch size
|
|
:type batch_size: int
|
|
:param seqlen_q: sequence length of the query
|
|
:type seqlen_q: int
|
|
:param seqlen_kv: sequence length of the key
|
|
:type seqlen_kv: int
|
|
:param num_head: number of attention heads
|
|
:type num_head: int
|
|
:param head_dim: dimension of the head
|
|
:type head_dim: int
|
|
:param m_block_size: block size for the m dimension of computation
|
|
:type m_block_size: int
|
|
:param n_block_size: block size for the n dimension of computation
|
|
:type n_block_size: int
|
|
:param num_threads: number of threads
|
|
:type num_threads: int
|
|
:param enable_fast_sigmoid: whether to use fast sigmoid
|
|
:type enable_fast_sigmoid: bool
|
|
:param enable_block_rasterization: whether to use block rasterization
|
|
:type enable_block_rasterization: bool
|
|
:param is_causal: whether to use causal masking
|
|
:type is_causal: bool
|
|
"""
|
|
assert dtype == cutlass.Float16 or dtype == cutlass.BFloat16
|
|
|
|
import torch
|
|
import cutlass.torch as cutlass_torch
|
|
|
|
torch_stream = torch.cuda.current_stream()
|
|
stream = cuda.CUstream(torch_stream.cuda_stream)
|
|
|
|
print("Running Ampere SM80 HSTUAttentionForward test with:")
|
|
print("batch_size: ", batch_size)
|
|
print("seqlen_q: ", seqlen_q)
|
|
print("seqlen_kv: ", seqlen_kv)
|
|
print("num_head: ", num_head)
|
|
print("head_dim: ", head_dim)
|
|
print("m_block_size: ", m_block_size)
|
|
print("n_block_size: ", n_block_size)
|
|
print("num_threads: ", num_threads)
|
|
print("is_causal: ", is_causal)
|
|
print("enable_fast_sigmoid: ", enable_fast_sigmoid)
|
|
print("enable_block_rasterization: ", enable_block_rasterization)
|
|
print("dtype: ", dtype)
|
|
|
|
# reduced tensor num and iter num for functionality test
|
|
TENSOR_NUM = 1
|
|
ITER_NUM = 1
|
|
WARMUP_NUM = 0
|
|
if perf_test:
|
|
TENSOR_NUM = 3
|
|
ITER_NUM = 100
|
|
WARMUP_NUM = 10
|
|
|
|
# Create tensor Q/K/V/O
|
|
qs = [
|
|
torch.randn(
|
|
batch_size, seqlen_q, num_head, head_dim, dtype=cutlass_torch.dtype(dtype)
|
|
).cuda()
|
|
for _ in range(TENSOR_NUM)
|
|
]
|
|
ks = [
|
|
torch.randn(
|
|
batch_size, seqlen_kv, num_head, head_dim, dtype=cutlass_torch.dtype(dtype)
|
|
).cuda()
|
|
for _ in range(TENSOR_NUM)
|
|
]
|
|
vs = [
|
|
torch.randn(
|
|
batch_size, seqlen_kv, num_head, head_dim, dtype=cutlass_torch.dtype(dtype)
|
|
).cuda()
|
|
for _ in range(TENSOR_NUM)
|
|
]
|
|
os = [
|
|
torch.randn(
|
|
batch_size, seqlen_q, num_head, head_dim, dtype=cutlass_torch.dtype(dtype)
|
|
).cuda()
|
|
for _ in range(TENSOR_NUM)
|
|
]
|
|
|
|
rabs = [
|
|
torch.randn(
|
|
batch_size, num_head, seqlen_q, seqlen_kv, dtype=cutlass_torch.dtype(dtype)
|
|
).cuda()
|
|
for _ in range(TENSOR_NUM)
|
|
]
|
|
|
|
fa2_fwd = HSTUAttentionForwardAmpere(
|
|
dtype,
|
|
batch_size,
|
|
seqlen_q,
|
|
seqlen_kv,
|
|
num_head,
|
|
head_dim,
|
|
m_block_size,
|
|
n_block_size,
|
|
num_threads,
|
|
enable_fast_sigmoid=enable_fast_sigmoid,
|
|
enable_block_rasterization=enable_block_rasterization,
|
|
is_causal=is_causal,
|
|
)
|
|
# assume input is 16B align.
|
|
mqs = [
|
|
(
|
|
from_dlpack(qs[i], assumed_align=16)
|
|
.mark_layout_dynamic(leading_dim=3)
|
|
.mark_compact_shape_dynamic(
|
|
mode=3,
|
|
stride_order=qs[i].dim_order(),
|
|
divisibility=(128 // dtype.width),
|
|
)
|
|
)
|
|
for i in range(TENSOR_NUM)
|
|
]
|
|
mks = [
|
|
(
|
|
from_dlpack(ks[i], assumed_align=16)
|
|
.mark_layout_dynamic(leading_dim=3)
|
|
.mark_compact_shape_dynamic(
|
|
mode=3,
|
|
stride_order=ks[i].dim_order(),
|
|
divisibility=(128 // dtype.width),
|
|
)
|
|
)
|
|
for i in range(TENSOR_NUM)
|
|
]
|
|
mvs = [
|
|
(
|
|
from_dlpack(vs[i], assumed_align=16)
|
|
.mark_layout_dynamic(leading_dim=3)
|
|
.mark_compact_shape_dynamic(
|
|
mode=3,
|
|
stride_order=vs[i].dim_order(),
|
|
divisibility=(128 // dtype.width),
|
|
)
|
|
)
|
|
for i in range(TENSOR_NUM)
|
|
]
|
|
mos = [
|
|
(
|
|
from_dlpack(os[i], assumed_align=16)
|
|
.mark_layout_dynamic(leading_dim=3)
|
|
.mark_compact_shape_dynamic(
|
|
mode=3,
|
|
stride_order=os[i].dim_order(),
|
|
divisibility=(128 // dtype.width),
|
|
)
|
|
)
|
|
for i in range(TENSOR_NUM)
|
|
]
|
|
mrabs = [
|
|
(
|
|
from_dlpack(rabs[i], assumed_align=16)
|
|
.mark_layout_dynamic(leading_dim=3)
|
|
.mark_compact_shape_dynamic(
|
|
mode=3,
|
|
stride_order=rabs[i].dim_order(),
|
|
divisibility=(128 // dtype.width),
|
|
)
|
|
)
|
|
for i in range(TENSOR_NUM)
|
|
]
|
|
start_event = torch.cuda.Event(enable_timing=True)
|
|
end_event = torch.cuda.Event(enable_timing=True)
|
|
|
|
kernel = cute.compile(
|
|
fa2_fwd,
|
|
mqs[0],
|
|
mks[0],
|
|
mvs[0],
|
|
mos[0],
|
|
mrabs[0],
|
|
stream,
|
|
)
|
|
|
|
for i in range(0, ITER_NUM):
|
|
if i == WARMUP_NUM:
|
|
start_event.record(torch_stream)
|
|
# Run the kernel
|
|
kernel(
|
|
mqs[i % TENSOR_NUM],
|
|
mks[i % TENSOR_NUM],
|
|
mvs[i % TENSOR_NUM],
|
|
mos[i % TENSOR_NUM],
|
|
mrabs[i % TENSOR_NUM],
|
|
stream,
|
|
)
|
|
|
|
end_event.record(torch_stream)
|
|
torch.cuda.synchronize(torch_stream)
|
|
|
|
elapsed_time = start_event.elapsed_time(end_event)
|
|
elapsed_time_avg = elapsed_time / (ITER_NUM - WARMUP_NUM)
|
|
|
|
LAST_USED_TENSOR = (ITER_NUM - 1) % TENSOR_NUM
|
|
q = qs[LAST_USED_TENSOR].permute(0, 2, 1, 3).contiguous()
|
|
k = ks[LAST_USED_TENSOR].permute(0, 2, 1, 3).contiguous()
|
|
v = vs[LAST_USED_TENSOR].permute(0, 2, 1, 3).contiguous()
|
|
rab = rabs[LAST_USED_TENSOR]
|
|
|
|
kernel_out = os[LAST_USED_TENSOR].cpu()
|
|
|
|
with torch.cuda.stream(torch_stream):
|
|
ref_bf16 = run_pytorch_hstu_test(torch.bfloat16, q, k, v, rab, is_causal).cpu()
|
|
ref_fp32 = run_pytorch_hstu_test(torch.float32, q, k, v, rab, is_causal).cpu()
|
|
torch.cuda.synchronize(torch_stream)
|
|
|
|
assert (kernel_out - ref_fp32).abs().max().item() <= 4 * (
|
|
ref_bf16 - ref_fp32
|
|
).abs().max().item()
|
|
print("Results verified successfully!")
|
|
|
|
if perf_test:
|
|
print(f"Elapsed time: {elapsed_time_avg:.3f} ms")
|
|
return elapsed_time_avg * 1000 # return in microseconds
|
|
|
|
|
|
if __name__ == "__main__":
|
|
parser = argparse.ArgumentParser(description="example of HSTU attention with CuTe")
|
|
parser.add_argument("--dtype", type=cutlass.dtype, default=cutlass.BFloat16)
|
|
parser.add_argument("--batch_size", type=int, default=4)
|
|
parser.add_argument("--seqlen_q", type=int, default=2048)
|
|
parser.add_argument("--seqlen_kv", type=int, default=2048)
|
|
parser.add_argument("--num_head", type=int, default=4)
|
|
parser.add_argument("--head_dim", type=int, default=128)
|
|
parser.add_argument("--m_block_size", type=int, default=64)
|
|
parser.add_argument("--n_block_size", type=int, default=64)
|
|
parser.add_argument("--num_threads", type=int, default=128)
|
|
parser.add_argument(
|
|
"--no_fast_sigmoid", action="store_false", dest="enable_fast_sigmoid"
|
|
)
|
|
parser.add_argument(
|
|
"--no_block_rasterization",
|
|
action="store_false",
|
|
dest="enable_block_rasterization",
|
|
)
|
|
parser.add_argument("--is_causal", action="store_true", dest="is_causal")
|
|
parser.add_argument("--perf_test", action="store_true", dest="perf_test")
|
|
args = parser.parse_args()
|
|
|
|
run(
|
|
args.dtype,
|
|
args.batch_size,
|
|
args.seqlen_q,
|
|
args.seqlen_kv,
|
|
args.num_head,
|
|
args.head_dim,
|
|
args.m_block_size,
|
|
args.n_block_size,
|
|
args.num_threads,
|
|
args.enable_fast_sigmoid,
|
|
args.enable_block_rasterization,
|
|
args.is_causal,
|
|
args.perf_test,
|
|
)
|
|
print("PASS")
|