2026-05-29 04:36:52 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* DSV4 FMHA — TMA async load infrastructure for Blackwell SM100.
|
|
|
|
|
|
*
|
|
|
|
|
|
* ==================================================================
|
|
|
|
|
|
* DESIGN
|
|
|
|
|
|
* ==================================================================
|
|
|
|
|
|
*
|
|
|
|
|
|
* Replaces scalar GMEM reads in the load warp with async TMA bulk
|
2026-05-29 06:34:58 +00:00
|
|
|
|
* copies via cp.async.bulk.tensor.3d. The pipeline:
|
2026-05-29 04:36:52 +00:00
|
|
|
|
*
|
|
|
|
|
|
* Host: CUtensorMap creation for Q, K, V tiles
|
|
|
|
|
|
* Kernel:
|
2026-05-29 06:34:58 +00:00
|
|
|
|
* 1. TMA warp issues cp.async.bulk.tensor.3d → SMEM (row-major)
|
2026-05-29 04:36:52 +00:00
|
|
|
|
* 2. mbarrier wait for TMA completion
|
|
|
|
|
|
* 3. Load warp transposes row-major SMEM → canonical K-major SMEM
|
|
|
|
|
|
* 4. MMA warp runs tcgen05.mma as before
|
|
|
|
|
|
*
|
|
|
|
|
|
* For double-buffered pipeline overlap (future):
|
|
|
|
|
|
* - Two SMEM buffers per tensor (sQ0/sQ1, sK0/sK1)
|
|
|
|
|
|
* - TMA load of K-tile (kt+1) overlaps with MMA on K-tile (kt)
|
|
|
|
|
|
* - Pipeline stages managed via mbarrier arrive/wait
|
|
|
|
|
|
*
|
|
|
|
|
|
* ==================================================================
|
|
|
|
|
|
* TMA DESCRIPTOR LAYOUT
|
|
|
|
|
|
* ==================================================================
|
|
|
|
|
|
*
|
|
|
|
|
|
* We create 2D CUtensorMap descriptors for each tile the kernel needs:
|
|
|
|
|
|
*
|
|
|
|
|
|
* Q tile: (T, HD) — one tile for the full Q
|
|
|
|
|
|
* K tile: (s_k, HD) — one tile for the full K (or (128, 16) per K-sub-tile)
|
|
|
|
|
|
* V tile: (HD, s_k) — transposed, one tile for the full V
|
|
|
|
|
|
*
|
|
|
|
|
|
* TMA copies data from GMEM to SMEM in row-major order. After TMA
|
|
|
|
|
|
* completion, the load warp transposes from row-major to the
|
|
|
|
|
|
* canonical K-major core-matrix layout that tcgen05.mma expects.
|
|
|
|
|
|
*
|
|
|
|
|
|
* For the multirow kernel, Q is (T, HD) and K is (s_k, HD).
|
|
|
|
|
|
* Since TMA operates on 2D tiles and our SMEM is (128, 16) per
|
|
|
|
|
|
* MMA K-tile, we have two choices:
|
|
|
|
|
|
*
|
|
|
|
|
|
* Option A: TMA load full (T, HD) → row-major SMEM → transpose
|
|
|
|
|
|
* - One TMA descriptor for Q, one for K
|
|
|
|
|
|
* - Larger SMEM footprint (need row-major + canonical)
|
|
|
|
|
|
* - Simpler descriptor management
|
|
|
|
|
|
*
|
|
|
|
|
|
* Option B: TMA load per (128, 16) K-sub-tile
|
|
|
|
|
|
* - One TMA descriptor, multiple TMA issues with different coords
|
|
|
|
|
|
* - Same SMEM as current (no double buffer needed for single-stage)
|
|
|
|
|
|
* - Matches the existing K-tiling loop structure
|
|
|
|
|
|
*
|
|
|
|
|
|
* We choose Option B: TMA per (128, 16) K-sub-tile. This:
|
|
|
|
|
|
* - Reuses the exact same SMEM layout as the current kernel
|
|
|
|
|
|
* - Fits the existing QK loop structure (kt = 0..NKT_QK-1)
|
|
|
|
|
|
* - Enables future pipeline overlap with minimal changes
|
|
|
|
|
|
* - The TMA descriptor covers the full (T, HD) or (s_k, HD) tensor,
|
|
|
|
|
|
* and we issue TMA loads for specific (col, row) coordinates
|
|
|
|
|
|
* targeting each 128×16 tile
|
|
|
|
|
|
*
|
|
|
|
|
|
* ==================================================================
|
|
|
|
|
|
* MBARRIER PROTOCOL
|
|
|
|
|
|
* ==================================================================
|
|
|
|
|
|
*
|
|
|
|
|
|
* TMA async copies use mbarrier for completion signaling:
|
|
|
|
|
|
*
|
|
|
|
|
|
* 1. Init mbarrier with expected transaction count = 1
|
2026-05-29 06:34:58 +00:00
|
|
|
|
* 2. Issue cp.async.bulk.tensor.3d with the mbarrier
|
2026-05-29 04:36:52 +00:00
|
|
|
|
* 3. Wait on mbarrier parity (spin or yield)
|
|
|
|
|
|
* 4. After wait returns, SMEM data is ready
|
|
|
|
|
|
*
|
|
|
|
|
|
* The mbarrier lives in SMEM. One mbarrier per outstanding TMA
|
|
|
|
|
|
* operation. For single-stage (no overlap), we use one mbarrier
|
|
|
|
|
|
* and wait immediately after issue.
|
|
|
|
|
|
*
|
|
|
|
|
|
* ==================================================================
|
|
|
|
|
|
* SWIZZLE CONSIDERATIONS
|
|
|
|
|
|
* ==================================================================
|
|
|
|
|
|
*
|
|
|
|
|
|
* TMA descriptors support SWIZZLE_NONE, SWIZZLE_32B, SWIZZLE_64B,
|
|
|
|
|
|
* SWIZZLE_128B. The swizzle pattern in SMEM matches what UMMA
|
|
|
|
|
|
* descriptors expect when using make_umma_desc_kmajor_sw128.
|
|
|
|
|
|
*
|
|
|
|
|
|
* Current kernel uses SWIZZLE_NONE (make_umma_desc_kmajor_none).
|
|
|
|
|
|
* With TMA, we have two paths:
|
|
|
|
|
|
*
|
|
|
|
|
|
* Path 1: TMA with SWIZZLE_NONE → SMEM is row-major → transpose to canonical
|
|
|
|
|
|
* Path 2: TMA with SWIZZLE_128B → SMEM is swizzled → UMMA reads directly
|
|
|
|
|
|
*
|
|
|
|
|
|
* Path 2 is the production target: no transpose needed, TMA writes
|
|
|
|
|
|
* in the exact layout MMA reads. But getting the swizzle right is
|
|
|
|
|
|
* tricky and needs careful verification.
|
|
|
|
|
|
*
|
|
|
|
|
|
* We start with Path 1 (SWIZZLE_NONE + transpose) to get TMA working,
|
|
|
|
|
|
* then upgrade to Path 2 (SWIZZLE_128B, zero-copy) for performance.
|
|
|
|
|
|
* ==================================================================
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
|
|
#include "fmha_common.cuh"
|
|
|
|
|
|
#include <cstdint>
|
|
|
|
|
|
|
|
|
|
|
|
namespace dsv4::kernels::attention {
|
|
|
|
|
|
|
|
|
|
|
|
// ==================================================================
|
|
|
|
|
|
// TMA descriptor helpers (host-side)
|
|
|
|
|
|
// ==================================================================
|
|
|
|
|
|
// These are called from host code to create CUtensorMap objects
|
|
|
|
|
|
// that the kernel uses for TMA async copies.
|
|
|
|
|
|
// ==================================================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Create a 2D TMA descriptor for a BF16 tensor of shape (rows, cols).
|
|
|
|
|
|
* The tensor is row-major in GMEM with stride = cols.
|
|
|
|
|
|
* TMA tile dimensions are (tile_rows, tile_cols).
|
|
|
|
|
|
*
|
|
|
|
|
|
* The descriptor is written to `out` (host memory).
|
|
|
|
|
|
* Must be copied to device memory before kernel launch.
|
|
|
|
|
|
*/
|
|
|
|
|
|
inline bool create_tma_desc_2d_bf16(
|
|
|
|
|
|
CUtensorMap* out,
|
|
|
|
|
|
const void* gmem_ptr, // device pointer to the BF16 tensor
|
|
|
|
|
|
uint64_t rows, // global dimension 0 (number of rows)
|
|
|
|
|
|
uint64_t cols, // global dimension 1 (number of columns)
|
|
|
|
|
|
uint32_t tile_rows, // TMA tile dimension 0
|
|
|
|
|
|
uint32_t tile_cols, // TMA tile dimension 1
|
|
|
|
|
|
CUtensorMapSwizzle swizzle = CU_TENSOR_MAP_SWIZZLE_NONE
|
|
|
|
|
|
) {
|
2026-05-29 06:34:58 +00:00
|
|
|
|
// CUDA 13: globalStrides are in BYTES, not elements!
|
|
|
|
|
|
// globalStrides[0] = globalDim[0] * elementSizeInBytes = cols * 2 (BF16)
|
|
|
|
|
|
// globalStrides[1] = globalStrides[0] * globalDim[1] = cols * 2 * rows
|
|
|
|
|
|
// But for rank=2, only 1 stride is needed (rank-1)
|
|
|
|
|
|
//
|
|
|
|
|
|
// For rank=3 (recommended for CUDA 13), 2 strides are needed.
|
|
|
|
|
|
// We use rank=3 with degenerate 3rd dimension = 1.
|
|
|
|
|
|
// This avoids rank=2 edge cases and matches CUTLASS convention.
|
|
|
|
|
|
//
|
|
|
|
|
|
// 3D: (cols, rows, 1) innermost-first
|
|
|
|
|
|
uint64_t global_dim[] = {cols, rows, 1};
|
|
|
|
|
|
uint64_t global_str[] = {cols * 2, cols * 2 * rows}; // byte strides
|
|
|
|
|
|
// Tile: (tile_cols, tile_rows, 1) innermost-first
|
|
|
|
|
|
uint32_t tile_dim[] = {tile_cols, tile_rows, 1};
|
|
|
|
|
|
// Element strides within tile: 1 for each dim
|
|
|
|
|
|
uint32_t tile_str[] = {1, 1, 1};
|
2026-05-29 04:36:52 +00:00
|
|
|
|
|
|
|
|
|
|
CUresult res = cuTensorMapEncodeTiled(
|
|
|
|
|
|
out,
|
2026-05-29 06:34:58 +00:00
|
|
|
|
CU_TENSOR_MAP_DATA_TYPE_BFLOAT16, // Proper BF16 type
|
|
|
|
|
|
3, // 3D tensor (degenerate 3rd dim)
|
2026-05-29 04:36:52 +00:00
|
|
|
|
const_cast<void*>(gmem_ptr),
|
|
|
|
|
|
global_dim, global_str, tile_dim, tile_str,
|
|
|
|
|
|
CU_TENSOR_MAP_INTERLEAVE_NONE,
|
|
|
|
|
|
swizzle,
|
|
|
|
|
|
CU_TENSOR_MAP_L2_PROMOTION_NONE,
|
|
|
|
|
|
CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE
|
|
|
|
|
|
);
|
2026-05-29 04:38:57 +00:00
|
|
|
|
if (res != CUDA_SUCCESS) {
|
2026-05-29 06:34:58 +00:00
|
|
|
|
fprintf(stderr, "cuTensorMapEncodeTiled failed: error=%d, gdim=[%lu,%lu,%lu], gstr=[%lu,%lu], tdim=[%u,%u,%u], tstr=[%u,%u,%u]\n",
|
|
|
|
|
|
(int)res, global_dim[0], global_dim[1], global_dim[2], global_str[0], global_str[1],
|
|
|
|
|
|
tile_dim[0], tile_dim[1], tile_dim[2], tile_str[0], tile_str[1], tile_str[2]);
|
2026-05-29 04:38:57 +00:00
|
|
|
|
}
|
2026-05-29 04:36:52 +00:00
|
|
|
|
return res == CUDA_SUCCESS;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================================================================
|
|
|
|
|
|
// TMA kernel-side operations
|
|
|
|
|
|
// ==================================================================
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-05-29 06:34:58 +00:00
|
|
|
|
* Initialize an mbarrier in SMEM with expected byte count.
|
2026-05-29 04:36:52 +00:00
|
|
|
|
* Only one thread should call this.
|
2026-05-29 06:34:58 +00:00
|
|
|
|
* For TMA with complete_tx::bytes, the expected count is the number
|
|
|
|
|
|
* of bytes that will be transferred.
|
2026-05-29 04:36:52 +00:00
|
|
|
|
*/
|
2026-05-29 06:34:58 +00:00
|
|
|
|
__device__ __forceinline__ void tma_mbarrier_init(uint32_t smem_mbar, uint32_t expected_bytes) {
|
2026-05-29 04:36:52 +00:00
|
|
|
|
asm volatile("mbarrier.init.shared.b64 [%0], %1;"
|
2026-05-29 06:34:58 +00:00
|
|
|
|
:: "r"(smem_mbar), "r"(expected_bytes));
|
2026-05-29 04:36:52 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2026-05-29 06:34:58 +00:00
|
|
|
|
* Issue a 3D TMA async copy from GMEM to SMEM.
|
2026-05-29 04:36:52 +00:00
|
|
|
|
*
|
|
|
|
|
|
* The TMA descriptor must be in device memory (GMEM).
|
|
|
|
|
|
* Only ONE thread per CTA should issue the TMA copy.
|
|
|
|
|
|
*
|
|
|
|
|
|
* After issue, the data will be written to SMEM asynchronously.
|
|
|
|
|
|
* Use tma_mbarrier_wait to wait for completion.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param smem_dst SMEM destination address (via __cvta_generic_to_shared)
|
|
|
|
|
|
* @param tma_desc Pointer to CUtensorMap in device memory (uint64_t cast)
|
|
|
|
|
|
* @param smem_mbar SMEM mbarrier address (via __cvta_generic_to_shared)
|
2026-05-29 06:34:58 +00:00
|
|
|
|
* @param coord_x Innermost dimension coordinate (columns)
|
|
|
|
|
|
* @param coord_y Middle dimension coordinate (rows)
|
|
|
|
|
|
* @param coord_z Outermost dimension coordinate (degenerate = 0)
|
2026-05-29 04:36:52 +00:00
|
|
|
|
*/
|
2026-05-29 06:34:58 +00:00
|
|
|
|
__device__ __forceinline__ void tma_load_3d(
|
2026-05-29 04:36:52 +00:00
|
|
|
|
uint32_t smem_dst,
|
|
|
|
|
|
uint64_t tma_desc,
|
|
|
|
|
|
uint32_t smem_mbar,
|
|
|
|
|
|
int coord_x,
|
2026-05-29 06:34:58 +00:00
|
|
|
|
int coord_y,
|
|
|
|
|
|
int coord_z = 0
|
2026-05-29 04:36:52 +00:00
|
|
|
|
) {
|
|
|
|
|
|
asm volatile(
|
2026-05-29 06:34:58 +00:00
|
|
|
|
"cp.async.bulk.tensor.3d.shared::cluster.global.mbarrier::complete_tx::bytes "
|
|
|
|
|
|
"[%0], [%1, {%3, %4, %5}], [%2];"
|
2026-05-29 04:36:52 +00:00
|
|
|
|
:: "r"(smem_dst),
|
|
|
|
|
|
"l"(tma_desc),
|
|
|
|
|
|
"r"(smem_mbar),
|
|
|
|
|
|
"r"(coord_x),
|
2026-05-29 06:34:58 +00:00
|
|
|
|
"r"(coord_y),
|
|
|
|
|
|
"r"(coord_z)
|
2026-05-29 04:36:52 +00:00
|
|
|
|
: "memory"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Wait for mbarrier completion (spin-wait).
|
|
|
|
|
|
* Only ONE thread should wait (or all threads, but typically just the
|
|
|
|
|
|
* thread that issued the TMA copy).
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param smem_mbar SMEM mbarrier address (via __cvta_generic_to_shared)
|
|
|
|
|
|
*/
|
|
|
|
|
|
__device__ __forceinline__ void tma_mbarrier_wait(uint32_t smem_mbar) {
|
|
|
|
|
|
int phase = 0;
|
|
|
|
|
|
asm volatile(
|
|
|
|
|
|
"{\n\t"
|
|
|
|
|
|
".reg .pred p;\n\t"
|
|
|
|
|
|
"LOOP:\n\t"
|
|
|
|
|
|
"mbarrier.try_wait.parity.shared.b64 p, [%0], %1;\n\t"
|
|
|
|
|
|
"@p bra DONE;\n\t"
|
|
|
|
|
|
"bra LOOP;\n\t"
|
|
|
|
|
|
"DONE:\n\t"
|
|
|
|
|
|
"}"
|
|
|
|
|
|
:: "r"(smem_mbar), "r"(phase)
|
|
|
|
|
|
: "memory"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Invalidate L2 prefetch to ensure TMA sees fresh data.
|
|
|
|
|
|
* Call before issuing TMA loads if the data was recently written.
|
|
|
|
|
|
*/
|
|
|
|
|
|
__device__ __forceinline__ void tma_cp_commit() {
|
|
|
|
|
|
asm volatile("cp.async.commit_group;" ::: "memory");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================================================================
|
|
|
|
|
|
// TMA parameter structure
|
|
|
|
|
|
// ==================================================================
|
|
|
|
|
|
|
|
|
|
|
|
struct FmhaTmaDescriptors {
|
|
|
|
|
|
CUtensorMap* __restrict__ tma_q; // Q descriptor: (T, HD) row-major
|
|
|
|
|
|
CUtensorMap* __restrict__ tma_k; // K descriptor: (s_k, HD) row-major
|
|
|
|
|
|
CUtensorMap* __restrict__ tma_v; // V descriptor: (HD, s_k) row-major
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace dsv4::kernels::attention
|