#include "attention_api.cuh"
#include <cassert>
#include <cmath>
#include <cuda.h>
#include <cuda_runtime.h>
#include <iostream>
#include <stdio.h>
#include <cutlass/numeric_conversion.h>
#include <cutlass/numeric_types.h>
#include <cute/tensor.hpp>
#include <torch/extension.h>
#include <torch/python.h>
#include <vector>
#include "static_switch.h"
#include "kernel_traits.h"
#include "flash.h"
#include "utils.h"
namespace flash {
using namespace cute;
template <int kBlockM, int kBlockN, int kNWarps,typename Engine, typename Layout>
inline __device__ void mask_within_nblock(Tensor<Engine, Layout> &tensor, const int m_block, const int nbi) {
// tensor has shape (nrow=(2, MMA_M), ncol=(2, MMA_N))
static_assert(Layout::rank == 2, "Only support 2D Tensor");
// NOTE:
// 确定一个MMA内的index也是一个难点
// (nrow=(2, MMA_M), ncol=(2, MMA_N))形如:
// T1.V0 T1.V1
// 根据mma_tile的示意图来确定col和row值
// 计算thread的处理范围, mask掉超出范围的部分
//
// % 32表示32做组, 因为SM80_16x8x16_F32F16F16F32_TN _1_2_1中最大线程数id是32
// (lane_id % 4) * 2表示在哪个"颜色"的col(thread)中, *2是为了靠右(即处理的哪个value2)
// 因此col_idx_offset表示当前thread所处理的单个Atom中4列的哪列
// lane_id表示一个MMA tile中的"线程组"
const int lane_id = threadIdx.x % 32;
const int col_idx_offset = kBlockN * nbi + (lane_id % 4) * 2;
const int nrow_group = threadIdx.x / 32;
const int row_idx_offset = kBlockM * m_block + lane_id / 4 + nrow_group * 16 /* 2*8 */;
// (2, nrow), 2*8 for each
const int group_stride = kNWarps * 16;
#pragma unroll
for (int nj = 0; nj < size<1, 1>(tensor); ++nj) {
// SM80_16x8x16_F32F16F16F32_TN中的一组中, 一行4个线程处理8个value
const int col_idx_base = col_idx_offset + nj * 8;
for (int j = 0; j < size<1, 0>(tensor); ++j) {
// j用于计算value 1和value 2对应col
// col_idx最终表示当前thread所处理的value的列号
const int col_idx = col_idx_base + j;
// mask掉tCrS_rowcol中(QK后的结果)超出范围的部分
// 列号和行号对比
// Without the "make_coord" we get wrong results
// for nrow(2, MMA_M)
for (int mi = 0; mi < size<0, 0>(tensor); ++mi) {
for (int mj = 0; mj < size<0, 1>(tensor); ++mj) {
const int row_idx = row_idx_offset + mi * 8 + mj * group_stride;
if (col_idx > row_idx) {
tensor(make_coord(mi, mj), make_coord(j, nj)) = -INFINITY;
}
// NOTE: A矩阵已经在寄存器中的gemm封装
template<typename Tensor0, typename Tensor1, typename Tensor2, typename Tensor3,
typename TiledMma, typename TiledCopy, typename ThrCopy>
inline __device__ void gemm_A_in_regs(Tensor0 &tCrC, Tensor1 &tCrA, Tensor2 &tCrB, Tensor3 const& tXsB,
TiledMma mma, TiledCopy s2r_copy_b,
ThrCopy s2r_thr_copy_b) {
// NOTE: 符合M N K描述: A[M, K] @ B[N, K] = C[M, N]
CUTE_STATIC_ASSERT_V(size<1>(tCrA) == size<1>(tCrC)); // MMA_M
CUTE_STATIC_ASSERT_V(size<1>(tCrB) == size<2>(tCrC)); // MMA_N
CUTE_STATIC_ASSERT_V(size<2>(tCrA) == size<2>(tCrB)); // MMA_K
// NOTE: retile 成拷贝需要的大小
Tensor tXrB = s2r_thr_copy_b.retile_D(tCrB);
CUTE_STATIC_ASSERT_V(size<1>(tXsB) == size<1>(tXrB)); // N
cute::copy(s2r_copy_b, tXsB(_, _, _0{}), tXrB(_, _, _0{}));
for (int k_block = 0; k_block < size<2>(tCrA); ++k_block) {
if (k_block < size<2>(tCrA) - 1) {
cute::copy(s2r_copy_b, tXsB(_, _, k_block + 1), tXrB(_, _, k_block + 1));
cute::gemm(mma, tCrA(_, _, k_block), tCrB(_, _, k_block), tCrC);
template<typename Tensor0, typename Tensor1,
typename Tensor2, typename Tensor3, typename Tensor4,
typename TiledMma, typename TiledCopyA, typename TiledCopyB,
typename ThrCopyA, typename ThrCopyB>
inline __device__ void gemm_smem(Tensor0 &tCrC, Tensor1 &tCrA, Tensor2 &tCrB, Tensor3 const& tXsA,
Tensor4 const& tXsB, TiledMma mma,
TiledCopyA s2r_copy_a, TiledCopyB s2r_copy_b,
ThrCopyA s2r_thr_copy_a, ThrCopyB s2r_thr_copy_b) {
Tensor tXrA = s2r_thr_copy_a.retile_D(tCrA);
CUTE_STATIC_ASSERT_V(size<1>(tXsA) == size<1>(tXrA)); // M
// NOTE: s -> reg
cute::copy(s2r_copy_a, tXsA(_, _, _0{}), tXrA(_, _, _0{}));
cute::copy(s2r_copy_a, tXsA(_, _, k_block + 1), tXrA(_, _, k_block + 1));
// Blocks until all but N previous cp.async.commit_group operations have committed.
// This differs from cute::cp_async_wait in that when N = 0 we don't call cp.async.wait_all
// (which is equivalent to commit_group then wait_group 0).
// Instead we just call cp.async.wait_group 0, which is slightly faster.
// https://github.com/NVIDIA/cutlass/blob/master/include/cute/arch/copy_sm80.hpp#L113
template <int N>
CUTE_HOST_DEVICE
void cp_async_wait() {
#if defined(CUTE_ARCH_CP_ASYNC_SM80_ENABLED)
asm volatile("cp.async.wait_group %0;\n" :: "n"(N));
#endif
// copy from S to D with tiled_copy
// TODO: 需要支持causal模式的的跳过拷贝
template <typename TiledCopy, typename Engine0, typename Layout0, typename Engine1, typename Layout1>
inline __device__ void copy(TiledCopy tiled_copy, Tensor<Engine0, Layout0> const &S,
Tensor<Engine1, Layout1> &D) {
CUTE_STATIC_ASSERT_V(rank(S) == Int<3>{});
CUTE_STATIC_ASSERT_V(rank(D) == Int<3>{});
CUTE_STATIC_ASSERT_V(size<0>(S) == size<0>(D)); // MMA
CUTE_STATIC_ASSERT_V(size<1>(S) == size<1>(D)); // MMA_M
CUTE_STATIC_ASSERT_V(size<2>(S) == size<2>(D)); // MMA_K
for (int m = 0; m < size<1>(S); ++m) {
// TODO: 原版处这里identity_MN是用来跳过大块的block的, predicate用于跳过block内的拷贝
// TODO: 添加predicate逻辑, 用于跳过无用拷贝
// if (get<0>(identity_MN(0, m, 0)) < max_MN)
for (int k = 0; k < size<2>(S); ++k) {
cute::copy(tiled_copy, S(_, m, k), D(_, m, k));
// Convert rowcol_layout from (nrow=(2, MMA_M), ncol=(2, MMA_N)) to ((2, 2, 2), MMA_M, MMA_N / 2)
// if using m16n8k16, or to ((2, 2, 1), MMA_M, MMA_N) if using m16n8k8.
template<typename MMA_traits, typename Layout>
inline __device__ auto convert_layout_rowcol_Aregs(Layout rowcol_layout) {
using X = Underscore;
static_assert(decltype(size<0, 0>(rowcol_layout))::value == 2);
static_assert(decltype(size<1, 0>(rowcol_layout))::value == 2);
constexpr int mma_shape_K = get<2>(typename MMA_traits::Shape_MNK{});
static_assert(mma_shape_K == 8 || mma_shape_K == 16);
constexpr int MMA_N_divisor = mma_shape_K == 8 ? 1 : 2;
auto l = logical_divide(rowcol_layout, Shape<X, Shape<X, Int<MMA_N_divisor>>>{}); // ((2, MMA_M), (2, (2, MMA_N / 2)))
// TD [2023-08-13]: Same error as above on Cutlass 3.2
// return make_layout(make_layout(get<1, 0>(l), get<0, 0>(l), get<1, 1, 0>(l)),
// get<0, 1>(l),
// get<1, 1, 1>(l));
return make_layout(make_layout(get<0>(get<1>(l)), get<0>(get<0>(l)), get<0>(get<1>(get<1>(l)))),
get<1>(get<0>(l)),
get<1>(get<1>(get<1>(l))));
};
// TODO: not work
template <typename To_type, typename Engine, typename Layout>
inline __device__ auto convert_type(Tensor<Engine, Layout> const &tensor) {
using From_type = typename Engine::value_type;
constexpr int numel = decltype(size(tensor))::value;
cutlass::NumericArrayConverter<To_type, From_type, numel> convert_op;
// HACK: this requires tensor to be "contiguous"
auto frag = convert_op(*reinterpret_cast<const cutlass::Array<From_type, numel> *>(tensor.data()));
return make_tensor(make_rmem_ptr<To_type>(&frag), tensor.layout());
// TODO:
// https://github.com/NVIDIA/cutlass/issues/802
// TODO: convert出来后数据是否在寄存器?
template <typename Fragment>
inline __device__ auto convert_type_f32_to_f16(Fragment const &acc_fp32) {
Tensor acc_fp16 = make_tensor<cute::half_t>(shape(acc_fp32));
{
Tensor acc_fp32x2 = recast< float2>(acc_fp32);
Tensor acc_fp16x2 = recast<__half2>(acc_fp16);
for (int i = 0; i < size(acc_fp32x2); ++i) { acc_fp16x2(i) = __float22half2_rn(acc_fp32x2(i)); }
return acc_fp16;
// Apply the exp to all the elements.
template <bool Scale_max=true, typename Engine0, typename Layout0, typename Engine1, typename Layout1>
inline __device__ void scale_apply_exp2(Tensor<Engine0, Layout0> &tensor, Tensor<Engine1, Layout1> const &max, const float scale) {
static_assert(Layout0::rank == 2, "Only support 2D Tensor");
static_assert(Layout1::rank == 1, "Only support 1D Tensor");
CUTE_STATIC_ASSERT_V(size<0>(max) == size<0>(tensor));
for (int mi = 0; mi < size<0>(tensor); ++mi) {
// If max is -inf, then all elements must have been -inf (possibly due to masking).
// We don't want (-inf - (-inf)) since that would give NaN.
// If we don't have float around M_LOG2E the multiplication is done in fp64.
const float max_scaled = max(mi) == -INFINITY ? 0.f : max(mi) * (Scale_max ? scale : float(M_LOG2E));
for (int ni = 0; ni < size<1>(tensor); ++ni) {
// Instead of computing exp(x - max), we compute exp2(x * log_2(e) -
// max * log_2(e)) This allows the compiler to use the ffma
// instruction instead of fadd and fmul separately.
tensor(mi, ni) = expf(tensor(mi, ni) * scale - max_scaled);
// Convert acc_layout from (MMA=4, MMA_M, MMA_N) to (nrow=(2, MMA_M), ncol=(2, MMA_N))
// TODO: 搞清楚经过convert_layout_acc_rowcol后(nrow=(2, MMA_M), ncol=(2, MMA_N))的数学含义
// 形象的解释是把
// T1.V0
// T1.V1
// 变为
// 这样符合MMA tile的行列直觉
template<typename Layout>
inline __device__ auto convert_layout_acc_rowcol(Layout acc_layout) {
static_assert(decltype(size<0>(acc_layout))::value == 4);
static_assert(decltype(rank(acc_layout))::value == 3);
auto l = logical_divide(acc_layout, Shape<_2>{}); // ((2, 2), MMA_M, MMA_N)
// TD [2023-08-13]: Idk why but get<0, 1>(l) doesn't work for Cutlass 3.2, I'm getting
// "int_tuple.hpp(74): error: conversion to inaccessible base class"
// return make_layout(make_layout(get<0, 1>(l), get<1>(l)), make_layout(get<0, 0>(l), get<2>(l)));
return make_layout(make_layout(get<1>(get<0>(l)), get<1>(l)), make_layout(get<0>(get<0>(l)), get<2>(l)));
template<bool Is_first, typename Tensor0, typename Tensor1, typename Tensor2>
inline __device__ void softmax_rescale_o(Tensor0 &tCrS_rowcol, Tensor1 &rRowMax, Tensor1 &rRowSum,
Tensor2 &tCrO, float softmax_scale_log2) {
// tCrS_rowcol与tCrS共享存储, 从当前QK分块的分数原地更新为指数权重。
// rRowMax/rRowSum保存每行状态, tCrO保存需要rescale的输出分子。
if (Is_first) {
// NOTE: 优化, 第一次softmax不需要rescale, 只需要记录分子, max, sum
reduce_max</*zero_init=*/true>(tCrS_rowcol, rRowMax);
flash::scale_apply_exp2(tCrS_rowcol, rRowMax, softmax_scale_log2);
reduce_sum(tCrS_rowcol, rRowSum);
} else {
// 记录上一次的max
Tensor rRowMax_prev = make_fragment_like(rRowMax);
cute::copy(rRowMax, rRowMax_prev);
// TODO: reduce的实现学习一下
// NOTE: 计算新max到rRowMax
// reduce_max包含步:
// 1. 求当前thread内max: 遍历
// 2. reduce thread间的max: 使用shift技巧reduce
reduce_max</*zero_init=*/false>(tCrS_rowcol, rRowMax);
// Reshape tCrO from (MMA=4, MMA_M, MMA_K) to (nrow=(2, MMA_M), ncol=(2, MMA_K))
// 将tCrO转换成符合2D直觉的(nrow, ncol)的形状
Tensor tCrO_rowcol = make_tensor(tCrO.data(), flash::convert_layout_acc_rowcol(tCrO.layout()));
for (int mi = 0; mi < size(rRowMax); ++mi) {
// NOTE: 辅助变量: 当前max
float row_max = rRowMax(mi);
// NOTE: 计算旧score的rescale值
// NOTE: 因为QK(影响max)计算时没有考虑softmax_scale, 所以这里要补上
float rescale = expf((rRowMax_prev(mi) - row_max) * softmax_scale_log2);
// NOTE: rescale旧分母部分
rRowSum(mi) *= rescale;
// NOTE: 旧分子部分rescale
// tCrO_rowcol.shape = (nrow, ncol)
for (int ni = 0; ni < size<1>(tCrO_rowcol); ++ni) { tCrO_rowcol(mi, ni) *= rescale; }
// NOTE: 计算新分子部分: 对所有tCrS_rowcol进行rescale
// NOTE: 累加新分母
Tensor rRowSum_cur = make_fragment_like(rRowSum);
// NOTE:利用新分子来累加新分母
// 1. 线程内累加: 遍历
// 2. 线程间累加: 使用shift技巧reduce
reduce_sum(tCrS_rowcol, rRowSum_cur);
// NOTE: 新分母累加到旧分母
for (int mi = 0; mi < size(rRowSum); ++mi) { rRowSum(mi) += rRowSum_cur(mi); }
} // namespace flash
void set_params_fprop(Flash_fwd_params ¶ms,
// device pointers
const torch::Tensor q,
const torch::Tensor k,
const torch::Tensor v,
torch::Tensor out,
void *softmax_lse_d,
float softmax_scale,
bool is_causal) {
memset(¶ms, 0, sizeof(params));
params.bs = q.size(0);
params.head = q.size(1);
params.q_seqlen = q.size(2);
params.dim = q.size(3);
params.k_head = k.size(1);
params.k_seqlen = k.size(2);
params.bs_stride = q.stride(0);
params.head_stride = q.stride(1);
params.seqlen_stride = q.stride(2);
params.dim_stride = q.stride(3);
params.softmax_scale = softmax_scale;
// TODO: 使用log2做scale
params.softmax_scale_log2 = softmax_scale * M_LOG2E;
params.is_causal = is_causal;
params.is_bf16 = q.dtype() == torch::kBFloat16;
// LogSumExp save for backward
params.softmax_lse_ptr = softmax_lse_d;
// TODO: get ptr
params.q_ptr = q.data_ptr();
params.k_ptr = k.data_ptr();
params.v_ptr = v.data_ptr();
params.out_ptr = out.data_ptr();
// Shared Storage with Aligned addresses.
template <class ElementType, class SmemLayoutQ, class SmemLayoutK, class SmemLayoutV>
struct SharedStorage {
// TODO: Aligned的话smem的计算是否有问题
cute::array_aligned<ElementType, cute::cosize_v<SmemLayoutQ>> smem_q;
cute::array_aligned<ElementType, cute::cosize_v<SmemLayoutK>> smem_k;
cute::array_aligned<ElementType, cute::cosize_v<SmemLayoutV>> smem_v;
template <typename Kernel_traits, bool Is_causal=false, typename Params>
__global__ void flash_attention_v2_cutlass_kernel(const Params params) {
// m block index
const int m_block = blockIdx.x;
// bs * head
const int base_id = blockIdx.y;
// TODO: 传入泛型
// NOTE: 小技巧
using Element = typename Kernel_traits::Element;
using ElementAccum = typename Kernel_traits::ElementAccum;
// using TiledMma = typename Kernel_traits::MMA;
using TiledMma = typename Kernel_traits::TiledMma;
using index_t = typename Kernel_traits::index_t;
using SmemLayoutQ = typename Kernel_traits::SmemLayoutQ;
using SmemLayoutK = typename Kernel_traits::SmemLayoutKV;
using SmemLayoutV = typename Kernel_traits::SmemLayoutKV;
using SmemLayoutVt = typename Kernel_traits::SmemLayoutVtransposed;
using SmemLayoutVtNoSwizzle = typename Kernel_traits::SmemLayoutVtransposedNoSwizzle;
constexpr int kNWarps = Kernel_traits::kNWarps;
constexpr int kBlockM = Kernel_traits::kBlockM;
constexpr int kBlockN = Kernel_traits::kBlockN;
constexpr int kHeadDim = Kernel_traits::kHeadDim;
// Shared memory.
extern __shared__ char smem_[];
using SharedStorage = SharedStorage<Element, SmemLayoutQ, SmemLayoutK, SmemLayoutV>;
SharedStorage &shared_storage = *reinterpret_cast<SharedStorage *>(smem_);
const int bs_head_offset = base_id * params.head_stride;
// TODO: base offset for MHA
// NOTE: convert C pointer to Tensor for convenience
Tensor mQ = make_tensor(
make_gmem_ptr(reinterpret_cast<Element *>(params.q_ptr) + bs_head_offset),
make_shape(params.q_seqlen, Int<kHeadDim>{}),
make_stride(Int<kHeadDim>{}, Int<1>{}));
Tensor mK = make_tensor(
make_gmem_ptr(reinterpret_cast<Element *>(params.k_ptr) + bs_head_offset),
make_shape(params.k_seqlen, Int<kHeadDim>{}),
Tensor mV = make_tensor(
make_gmem_ptr(reinterpret_cast<Element *>(params.v_ptr) + bs_head_offset),
Tensor mO = make_tensor(
make_gmem_ptr(reinterpret_cast<Element *>(params.out_ptr) + bs_head_offset),
Tensor mLSE = make_tensor(
make_gmem_ptr(reinterpret_cast<ElementAccum *>(params.softmax_lse_ptr) + base_id * params.q_seqlen),
// Shape<Int<kBlockM>, Stride<_1>{}>{},
make_shape(params.q_seqlen),
make_stride(Int<1>{}));
// 加载Q, K, V分块
// (kBlockM, kHeadDim, num_tile_n)
Tensor gQ = local_tile(mQ, make_tile(Int<kBlockM>{}, Int<kHeadDim>{}), make_coord(m_block, _));
// (kBlockN, kHeadDim, num_tile_n)
// NOTE: loading流水线, 初次加载所需K, V
Tensor gK = local_tile(mK, make_tile(Int<kBlockN>{}, Int<kHeadDim>{}), make_coord(0, _));
Tensor gV = local_tile(mV, make_tile(Int<kBlockN>{}, Int<kHeadDim>{}), make_coord(0, _));
// 获取MMA抽象
TiledMma mma;
auto thr_mma = mma.get_slice(threadIdx.x);
// Construct SMEM tensors.
Tensor sQ = make_tensor(make_smem_ptr(shared_storage.smem_q.data()), SmemLayoutQ{});
Tensor sK = make_tensor(make_smem_ptr(shared_storage.smem_k.data()), SmemLayoutK{});
Tensor sV = make_tensor(make_smem_ptr(shared_storage.smem_v.data()), SmemLayoutV{});
// Tensor for V Transpose; used in GEMM-II.
Tensor sVt = make_tensor(make_smem_ptr(shared_storage.smem_v.data()), SmemLayoutVt{});
Tensor sVtNoSwizzle = make_tensor(make_smem_ptr(shared_storage.smem_v.data()), SmemLayoutVtNoSwizzle{});
// NOTE: copy抽象
// NOTE: QKV gmem -> smem拷贝的抽象
typename Kernel_traits::GmemTiledCopyQKV copy_qkv;
auto thr_copy_qkv = copy_qkv.get_thread_slice(threadIdx.x);
// NOTE: 定义gmem -> smem拷贝的src, dst
Tensor tQgQ = thr_copy_qkv.partition_S(gQ(_, _, 0));
Tensor tQsQ = thr_copy_qkv.partition_D(sQ);
Tensor tKgK = thr_copy_qkv.partition_S(gK(_, _, 0));
Tensor tKsK = thr_copy_qkv.partition_D(sK);
Tensor tVgV = thr_copy_qkv.partition_S(gV(_, _, 0));
Tensor tVsV = thr_copy_qkv.partition_D(sV);
// NOTE: 定义smem -> reg拷贝的dst
// partition_fragment与partition类似, 只是返回的是寄存器表示
Tensor tCrQ = thr_mma.partition_fragment_A(sQ); // (MMA,MMA_M,MMA_K)
Tensor tCrK = thr_mma.partition_fragment_B(sK); // (MMA,MMA_N,MMA_K)
Tensor tCrVt = thr_mma.partition_fragment_B(sVtNoSwizzle); // (MMA, MMA_K,MMA_N)
// Copy Atom retiling
// TODO: 理解这里的atom retiling
// NOTE: 准备从smem加载Q, K, V到寄存器的Copy对象
auto s2r_copy_q = make_tiled_copy_A(typename Kernel_traits::SmemCopyAtom{}, mma);
auto s2r_thr_copy_q = s2r_copy_q.get_thread_slice(threadIdx.x);
Tensor tXsQ = s2r_thr_copy_q.partition_S(sQ);
auto s2r_copy_k = make_tiled_copy_B(typename Kernel_traits::SmemCopyAtom{}, mma);
auto s2r_thr_copy_k = s2r_copy_k.get_thread_slice(threadIdx.x);
Tensor tXsK = s2r_thr_copy_k.partition_S(sK);
// TODO: 拷贝时转置
// NOTE: smem->reg拷贝Vt
auto s2r_copy_v = make_tiled_copy_B(typename Kernel_traits::SmemCopyAtomTransposed{}, mma);
auto s2r_thr_copy_v = s2r_copy_v.get_thread_slice(threadIdx.x);
Tensor tXsVt = s2r_thr_copy_v.partition_S(sVt);
// NOTE: tQ/tK/tV/tC/tX表示线程分工, g/s/r表示gmem/smem/register
// retile只建立寄存器的Copy视图, 真正的数据加载由copy执行
// tKgK表示gmem中的K, 用作gmem->smem的src
// tKsK表示smem中的K, 用作gmem->smem的dst
// tXsK表示smem中的K, 用作smem->reg的src
// NOTE: make_identity_tensor创建只有形状的tensor用于拷贝
// 在copy时用于跳过整块的block
// // TODO: cQ等用在causal模式, 暂时无用
// // Construct identity layout for sQ and sK
// Tensor cQ = make_identity_tensor(make_shape(size<0>(sQ), size<1>(sQ))); // (BLK_M,BLK_K) -> (blk_m,blk_k)
// Tensor cKV = make_identity_tensor(make_shape(size<0>(sK), size<1>(sK))); // (BLK_N,BLK_K) -> (blk_n,blk_k)
// // Repeat the partitioning with identity layouts
// Tensor tQcQ = thr_copy_qkv.partition_S(cQ); // (ACPY,ACPY_M,ACPY_K) -> (blk_m,blk_k)
// Tensor tKVcKV = thr_copy_qkv.partition_S(cKV); // (BCPY,BCPY_N,BCPY_K) -> (blk_n,blk_k)
// 流水线加载初始Q, K
// 加载Q到smem
flash::copy(copy_qkv, tQgQ, tQsQ);
// 加载K到smem
flash::copy(copy_qkv, tKgK, tKsK);
// 开始执行异步拷贝
cute::cp_async_fence();
Tensor tCrO = partition_fragment_C(mma, Shape<Int<kBlockM>, Int<kHeadDim>>{});
// step1: slice-k compute QK block
// Q[BLOCK_M, BLOCK_N] @ K[BLOCK_M, BLOCK_N].T = O[BLOCK_M, BLOCK_M]
// step2:
// advance K, V
// NOTE: K, V分块的数量: 处理的区间
const int n_block_min = 0;
// NOTE: 1. mask between N BLOCKs if is causal mode
int seqlen_start = m_block * kBlockM;
int seqlen_end = (m_block + 1) * kBlockM;
int n_block_max = Is_causal ? cute::ceil_div(seqlen_end, kBlockN) : cute::ceil_div(params.k_seqlen, kBlockN);
// NOTE: 需要记录的max
Tensor rRowMax = make_tensor<ElementAccum>(Shape<Int<2 * size<1>(tCrO)>>{});
// NOTE: 需要记录的denom
Tensor rRowSum = make_fragment_like(rRowMax);
clear(tCrO);
for (int nbi = n_block_min; nbi < n_block_max; nbi++) {
auto tCrS = partition_fragment_C(mma, make_shape(Int<kBlockM>{}, Int<kBlockN>{}));
clear(tCrS);
// 等待Q, K的gmem -> smem拷贝完成, 即Q, K就绪
// wait<0>表示等待还剩0个未完成
flash::cp_async_wait<0>();
__syncthreads();
// gemm的同时异步加载V
gV = local_tile(mV, make_tile(Int<kBlockN>{}, Int<kHeadDim>{}), make_coord(nbi, _));
tVgV = thr_copy_qkv.partition_S(gV(_, _, 0));
// 异步加载V到smem
flash::copy(copy_qkv, tVgV, tVsV);
// 发起异步拷贝
// O = Q@K.T
// NOTE: 加载smem中的数据到reg再做gemm, **加载期间执行retile**
flash::gemm_smem(tCrS, tCrQ, tCrK, tXsQ, tXsK, mma, s2r_copy_q, s2r_copy_k,
s2r_thr_copy_q, s2r_thr_copy_k
);
Tensor tCrS_rowcol = make_tensor(tCrS.data(), flash::convert_layout_acc_rowcol(tCrS.layout()));
// NOTE: 2. mask within N BLOCKs
if (Is_causal == true && nbi * kBlockN >= seqlen_start) {
flash::mask_within_nblock<kBlockM, kBlockN, kNWarps>(tCrS_rowcol, m_block, nbi);
// NOTE: 等待V加载完成, 为下个K加载准备初始状态
// advance K
if (nbi != n_block_max - 1) {
gK = local_tile(mK, make_tile(Int<kBlockN>{}, Int<kHeadDim>{}), make_coord(nbi + 1, _));
tKgK = thr_copy_qkv.partition_S(gK(_, _, 0));
// 计算softmax
// NOTE: tCrO记录softmax后所有的分子
nbi == 0 ? flash::softmax_rescale_o</*Is_first=*/true>(tCrS_rowcol, rRowMax, rRowSum, tCrO, params.softmax_scale) :
flash::softmax_rescale_o</*Is_first=*/false>(tCrS_rowcol, rRowMax, rRowSum, tCrO, params.softmax_scale);
// 实际执行QK @ V
// (score AKA tCrS): QK[M, N] @ V[N, dim]
// NOTE: DABC: F32F16F16F32, convert D type(F32) to A type(F16)
// TODO: convert_type目前写死
Tensor rP = flash::convert_type_f32_to_f16(tCrS);
// NOTE: Convert from layout C to layout A
Tensor tCrP = make_tensor(rP.data(), flash::convert_layout_rowcol_Aregs<TiledMma>(tCrS_rowcol.layout()));
flash::gemm_A_in_regs(tCrO, tCrP, tCrVt, tXsVt, mma, s2r_copy_v, s2r_thr_copy_v);
// Epilogue
// NOTE: 最后统一除上分母部分
// AKA reshape to (nrow, ncol) but with specific MMA layout
// NOTE: 保存lse给bwd
Tensor rLSE = make_fragment_like(rRowSum);
// for row
for (int mi = 0; mi < size<0>(tCrO_rowcol); ++mi) {
float sum = rRowSum(mi);
float inv_sum = (sum == 0.f || sum != sum) ? 1.f : 1.f / sum;
// compute rLSE
// NOTE: here we use max * scale
rLSE(mi) = (sum == 0.f || sum != sum) ? INFINITY : rRowMax(mi) * params.softmax_scale + __logf(sum);
float scale = inv_sum;
// for col
for (int ni = 0; ni < size<1>(tCrO_rowcol); ++ni) {
tCrO_rowcol(mi, ni) *= scale;
// Convert tCrO from fp32 to fp16.
Tensor tCrO_fp16 = flash::convert_type_f32_to_f16(tCrO);
// 复用sQ的smem做sO的拷出
Tensor sO = make_tensor(sQ.data(), typename Kernel_traits::SmemLayoutO{}); // (SMEM_M,SMEM_N)
// Partition sO to match the accumulator partitioning
// TODO: review
auto r2s_copy_o = make_tiled_copy_C(typename Kernel_traits::SmemCopyAtomO{}, mma);
auto r2s_thr_copy_o = r2s_copy_o.get_thread_slice(threadIdx.x);
Tensor tXrO = r2s_thr_copy_o.retile_S(tCrO_fp16); // ((Atom,AtomNum), MMA_M, MMA_N)
Tensor tXsO = r2s_thr_copy_o.partition_D(sO); // ((Atom,AtomNum),PIPE_M,PIPE_N)
// NOTE: 先拷贝到smem
cute::copy(r2s_copy_o, tXrO, tXsO);
Tensor gO = local_tile(mO, make_tile(Int<kBlockM>{}, Int<kHeadDim>{}), make_coord(m_block, _));
// 创建到smem -> gmem的拷贝
typename Kernel_traits::GmemTiledCopyO copy_o;
auto thr_copy_o = copy_o.get_thread_slice(threadIdx.x);
Tensor tOsO = thr_copy_o.partition_S(sO); // ((Atom,AtomNum),ATOM_M,ATOM_N)
Tensor tOgO = thr_copy_o.partition_D(gO(_, _, 0));
// NOTE:: 再拷贝到gmem
// TODO: review, 这里两个copy的作用
Tensor tOrO = make_tensor<Element>(shape(tOgO));
cute::copy(copy_o, tOsO, tOrO);
flash::copy(copy_o, tOrO, tOgO);
// NOTE: 写回lse
Tensor gLSE = local_tile(mLSE, make_tile(Int<kBlockM>{}), make_coord(m_block));
Tensor cO = make_identity_tensor(Shape<Int<kBlockM>, Int<kHeadDim>>{}); // (BLK_M,BLK_K) -> (blk_m,blk_k)
Tensor tCcO = thr_mma.partition_C(cO); // (MMA,MMA_M,MMA_K)
static_assert(decltype(size<0>(tCcO))::value == 4);
// Convert to ((2, 2), MMA_M, MMA_K) then take only the row indices.
// TODO: review this shape
Tensor tCcO_row = logical_divide(tCcO, Shape<_2>{})(make_coord(0, _), _, 0);
CUTE_STATIC_ASSERT_V(size(rLSE) == size(tCcO_row)); // MMA_M
// TODO: 搞清楚这里的逻辑
if (get<1>(tCcO_row(0)) == 0) {
for (int mi = 0; mi < size(rLSE); ++mi) {
const int row = get<0>(tCcO_row(mi));
// if (row < binfo.actual_seqlen_q - m_block * kBlockM) { gLSE(row) = rLSE(mi); }
gLSE(row) = rLSE(mi);
template<typename Kernel_traits, bool Is_causal>
void run_flash_fwd(Flash_fwd_params ¶ms, cudaStream_t stream) {
// TODO: check if works: default stream = 0
const int num_m_block =
(params.q_seqlen + Kernel_traits::kBlockM - 1) / Kernel_traits::kBlockM;
dim3 grid(num_m_block, params.bs * params.head, 1);
dim3 block(Kernel_traits::kNThreads);
int smem_size = int(sizeof(SharedStorage<Element, SmemLayoutQ, SmemLayoutK, SmemLayoutV>));
auto kernel = &flash_attention_v2_cutlass_kernel<Kernel_traits, Is_causal, Flash_fwd_params>;
// NOTE: smem过大时需要设置
if (smem_size >= 48 * 1024) {
CUDA_ERROR_CHECK(cudaFuncSetAttribute(
kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, smem_size));
// TODO: stream
kernel<<<grid, block, smem_size>>>(params);
template<typename T, int Headdim>
void run_flash_fwd_(Flash_fwd_params ¶ms, cudaStream_t stream);
// TODO: 挨个写出特化, 目前使用通用模板
// 如, run_flash_fwd_hdim32用于特化hdim=32
// 这样做可以根据实际情况微调kBlockN和kBlockM的组合, 也可以加速编译
void run_flash_fwd_(Flash_fwd_params ¶ms, cudaStream_t stream) {
BOOL_SWITCH(params.is_causal, Is_causal, [&] {
// run_flash_fwd<Flash_fwd_kernel_traits<Headdim, /*kBlockM_=*/128, /*kBlockN_=*/128, /*kNWarps_=*/4, T>, Is_causal>(params, stream);
// TODO: kBlockM, kBlockN的组合
run_flash_fwd<Flash_fwd_kernel_traits<Headdim, /*kBlockM_=*/64, /*kBlockN_=*/64, /*kNWarps_=*/4, T>, Is_causal>(params, stream);
});
// entry point of flash attention
void run_flash_attn_cutlass(Flash_fwd_params ¶ms, cudaStream_t stream) {
// FP16_SWITCH yield elem_type namespace
FP16_SWITCH(!params.is_bf16, [&] {
// FWD_HEADDIM_SWITCH yield kHeadDim constexpr
FWD_HEADDIM_SWITCH(params.dim, [&] {
run_flash_fwd_<elem_type, kHeadDim>(params, stream);
std::vector<torch::Tensor> flash_attention_v2_cutlass(torch::Tensor q, torch::Tensor k,
torch::Tensor v, bool is_causal = false, float softmax_scale=1) {
CHECK_INPUT(q);
CHECK_INPUT(k);
CHECK_INPUT(v);
// batch size
int bs = q.size(0);
// head number
int head = q.size(1);
// seqlen
int seqlen = q.size(2);
// dim
int dim = q.size(3);
auto out = torch::empty_like(q);
auto opts = q.options();
auto softmax_lse = torch::empty({bs, head, seqlen}, opts.dtype(torch::kFloat32));
Flash_fwd_params params;
set_params_fprop(params, q, k, v, out,
softmax_lse.data_ptr(), softmax_scale, is_causal);
run_flash_attn_cutlass(params, 0);
// Wait until kernel finish.
cudaDeviceSynchronize();
CUDA_ERROR_CHECK(cudaGetLastError());
return {out, softmax_lse};