Files
nvfp4-megamoe-kernel/tests/unit/archive/test_fmha_tma_driver_api.cu
biondizzle a40c05f3f2 archive: TMA driver-API files + CUDA 13 TMA discovery notes
Key findings documented in docs/cuda13_tma_notes.md:
- CUDA 13 globalStrides are in BYTES not elements (root cause of desc creation failures)
- BFLOAT16 data type available in CUDA 13
- Driver API descriptors create OK but cp.async.bulk.tensor hangs on driver 13.0 + toolkit 13.2
- CuTeDSL tma_partition works (production path)

Archived (not deleted):
- fmha_tma_driver_api.cuh, fmha_6warp_tma_driver_api.cuh, test_fmha_tma_driver_api.cu
- These will work once driver matches toolkit version
2026-05-29 06:52:39 +00:00

284 lines
12 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Test TMA async FMHA kernel (6-warp, multi-row, TMA loads).
* Compile with -DHD_VAL=64 etc.
*
* TMA descriptor layout:
* Q: (T, HD), tile = (16, 128) — loads (128, 16) sub-tiles, coord = (kt*16, 0)
* K: (s_k, HD), tile = (16, 128) — loads (128, 16) sub-tiles, coord = (kt*16, 0)
* V: (HD, s_k), tile = (128, 16) — loads (16, 128) sub-tiles, coord = (col_start, d_base)
*
* Note: TMA innermost dimension = columns. A tile of (16, 128) means
* 16 columns × 128 rows. The coord is (col_offset, row_offset).
*/
#include <cuda_runtime.h>
#include <cuda.h>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#ifndef HD_VAL
#define HD_VAL 64
#endif
#include "dsv4/kernels/attention/fmha_common.cuh"
#include "dsv4/kernels/attention/fmha_umma_desc.cuh"
#include "dsv4/kernels/attention/fmha_tma.cuh"
using namespace dsv4::kernels::attention;
static bf16_t f32_to_bf16_host(float f) { uint32_t u; memcpy(&u,&f,4); return (uint16_t)(u>>16); }
static float bf16_to_f32_host(bf16_t h) { uint32_t u=(uint32_t)h<<16; float f; memcpy(&f,&u,4); return f; }
constexpr int HD = HD_VAL;
constexpr int SK = 128;
constexpr int MAX_T = 128;
#include "dsv4/kernels/attention/fmha_6warp_tma.cuh"
static int compute_smem_tma() {
size_t off = 0;
off += 4; // sTmemBase
off = (off + 127) & ~(size_t)127;
off += 8; // sMbar
off += MAX_T * sizeof(float); // sRowMax
off += MAX_T * sizeof(float); // sRowSum
off = (off + 127) & ~(size_t)127;
off += 128 * MMA_K_BF16 * sizeof(bf16_t); // sQ_tma (128×16)
off = (off + 127) & ~(size_t)127;
off += 128 * MMA_K_BF16 * sizeof(bf16_t); // sK_tma (128×16)
off = (off + 127) & ~(size_t)127;
off += 128 * MMA_K_BF16 * sizeof(bf16_t); // sQ canonical
off = (off + 127) & ~(size_t)127;
off += 128 * MMA_K_BF16 * sizeof(bf16_t); // sK canonical
off = (off + 127) & ~(size_t)127;
off += 128 * MMA_K_BF16 * sizeof(bf16_t); // sPk canonical
off = (off + 127) & ~(size_t)127;
off += 16 * 128 * sizeof(bf16_t); // sV_tma (16×128)
off = (off + 127) & ~(size_t)127;
off += 128 * MMA_K_BF16 * sizeof(bf16_t); // sV canonical
return (int)off;
}
static void reference_attention_multirow(
const bf16_t* q, const bf16_t* k, const bf16_t* v,
float* o_ref, float* lse_ref,
int hd, int T, int s_k, float scale
) {
for (int t = 0; t < T; t++) {
float s[512];
for (int j = 0; j < s_k; j++) {
float dot = 0.0f;
for (int d = 0; d < hd; d++)
dot += bf16_to_f32_host(q[t * hd + d]) * bf16_to_f32_host(k[j * hd + d]);
s[j] = dot * scale;
}
float mx = -INFINITY;
for (int j = 0; j < s_k; j++) mx = fmaxf(mx, s[j]);
float sm = 0.0f;
for (int j = 0; j < s_k; j++) { s[j] = expf(s[j] - mx); sm += s[j]; }
for (int j = 0; j < s_k; j++) s[j] /= sm;
for (int d = 0; d < hd; d++) {
float ov = 0.0f;
for (int j = 0; j < s_k; j++) ov += s[j] * bf16_to_f32_host(v[d * s_k + j]);
o_ref[t * hd + d] = ov;
}
if (lse_ref) lse_ref[t] = logf(sm) + mx;
}
}
struct TmaDescSet {
CUtensorMap tma_q, tma_k, tma_v;
CUtensorMap *d_tma_q, *d_tma_k, *d_tma_v;
bool create(bf16_t* d_q, bf16_t* d_k, bf16_t* d_v,
int T, int hd, int s_k) {
// Q: (T, HD) row-major. TMA tile = (16, 128) → innermost 16 cols, 128 rows.
// We load sub-tiles of Q at column offsets kt*16.
// For T < 128: TMA will read OOB rows beyond T, but the data is garbage.
// The kernel masks via the canonical transpose which zeros rows ≥ T.
// Actually, we should set the global dims to the ACTUAL tensor size (T, HD).
// TMA handles OOB by returning zeros (with FLOAT_OOB_FILL_NONE).
// But TMA requires tile ≤ global dims. So for T < 128, the tile (16, 128)
// has 128 rows > T rows. TMA REQUIRES tile ≤ global dims.
// Solution: pad the GMEM allocation to at least (128, HD) per head.
// OR: use a tile of (16, T) when T < 128. But tile dims must be power of 2.
// Simplest: always pad to (128, HD) in GMEM allocation.
// Since this is a test, we'll allocate (128, HD) for Q and (128, HD) for K,
// and (HD, 128) for V — all padded.
// Actually, let's just make the global dimensions match the tile dimensions.
// Q global dim = (HD, 128) innermost-first, which means (128 rows, HD cols).
// But the actual data is (T, HD). For T < 128, the extra rows are undefined.
// The kernel handles this via the canonical transpose zeroing.
//
// Hmm, but cuTensorMapEncodeTiled validates that global dims >= tile dims.
// If global_dim[1] = T < 128 = tile_dim[1], it fails.
//
// The real fix: allocate padded buffers. Let me do that in the test.
// Q: global (HD, 128) → 128 rows, HD cols. Tile (16, 128).
if (!create_tma_desc_2d_bf16(&tma_q, d_q, 128, (uint64_t)hd, 128, 16)) {
printf(" Failed to create Q TMA desc: rows=128, cols=%d, tile=128x16\n", hd);
return false;
}
// K: (128, HD). Tile (16, 128).
if (!create_tma_desc_2d_bf16(&tma_k, d_k, (uint64_t)s_k, (uint64_t)hd, 128, 16)) {
printf(" Failed to create K TMA desc\n");
return false;
}
// V: (HD, s_k). Tile (128, 16).
if (!create_tma_desc_2d_bf16(&tma_v, d_v, (uint64_t)hd, (uint64_t)s_k, 128, 16)) {
printf(" Failed to create V TMA desc\n");
return false;
}
cudaMalloc(&d_tma_q, sizeof(CUtensorMap));
cudaMalloc(&d_tma_k, sizeof(CUtensorMap));
cudaMalloc(&d_tma_v, sizeof(CUtensorMap));
cudaMemcpy(d_tma_q, &tma_q, sizeof(CUtensorMap), cudaMemcpyHostToDevice);
cudaMemcpy(d_tma_k, &tma_k, sizeof(CUtensorMap), cudaMemcpyHostToDevice);
cudaMemcpy(d_tma_v, &tma_v, sizeof(CUtensorMap), cudaMemcpyHostToDevice);
return true;
}
void destroy() {
if (d_tma_q) { cudaFree(d_tma_q); d_tma_q = nullptr; }
if (d_tma_k) { cudaFree(d_tma_k); d_tma_k = nullptr; }
if (d_tma_v) { cudaFree(d_tma_v); d_tma_v = nullptr; }
}
};
static int test_single(int T, int n_h = 1, int batch = 1) {
printf("\n=== TMA T=%d, n_h=%d, batch=%d, HD=%d ===\n", T, n_h, batch, HD);
const float SCALE = 1.0f / sqrtf((float)HD);
int total_heads = batch * n_h;
// Allocate PADDED buffers: Q is (128, HD) per head, K is (s_k, HD), V is (HD, s_k)
// Q needs padding because TMA reads (128, 16) tiles — must have 128 rows in GMEM
constexpr int Q_PAD_ROWS = 128;
bf16_t* h_q = (bf16_t*)calloc(total_heads * Q_PAD_ROWS * HD, sizeof(bf16_t));
bf16_t* h_k = (bf16_t*)malloc(total_heads * SK * HD * sizeof(bf16_t));
bf16_t* h_v = (bf16_t*)malloc(total_heads * HD * SK * sizeof(bf16_t));
bf16_t* h_o = (bf16_t*)calloc(total_heads * MAX_T * HD, sizeof(bf16_t));
float* h_lse = (float*)calloc(total_heads * MAX_T, sizeof(float));
srand(42 + T);
// Fill Q data only for T rows
for (int i = 0; i < total_heads * T * HD; i++) h_q[i] = f32_to_bf16_host((float)(rand()%100)/100.0f - 0.5f);
for (int i = 0; i < total_heads * SK * HD; i++) h_k[i] = f32_to_bf16_host((float)(rand()%100)/100.0f - 0.5f);
for (int i = 0; i < total_heads * HD * SK; i++) h_v[i] = f32_to_bf16_host((float)(rand()%100)/100.0f - 0.5f);
// Device allocations — padded for TMA
bf16_t *d_q, *d_k, *d_v, *d_o; float *d_lse;
cudaMalloc(&d_q, total_heads * Q_PAD_ROWS * HD * sizeof(bf16_t)); // padded
cudaMalloc(&d_k, total_heads * SK * HD * sizeof(bf16_t));
cudaMalloc(&d_v, total_heads * HD * SK * sizeof(bf16_t));
cudaMalloc(&d_o, total_heads * MAX_T * HD * sizeof(bf16_t));
cudaMalloc(&d_lse, total_heads * MAX_T * sizeof(float));
cudaMemcpy(d_q, h_q, total_heads * Q_PAD_ROWS * HD * sizeof(bf16_t), cudaMemcpyHostToDevice);
cudaMemcpy(d_k, h_k, total_heads * SK * HD * sizeof(bf16_t), cudaMemcpyHostToDevice);
cudaMemcpy(d_v, h_v, total_heads * HD * SK * sizeof(bf16_t), cudaMemcpyHostToDevice);
int failed = 0;
float min_cos = 1.0f;
for (int b = 0; b < batch; b++) {
for (int h = 0; h < n_h; h++) {
int idx = b * n_h + h;
TmaDescSet tma;
bf16_t* d_q_h = d_q + idx * Q_PAD_ROWS * HD; // padded stride
bf16_t* d_k_h = d_k + idx * SK * HD;
bf16_t* d_v_h = d_v + idx * HD * SK;
if (!tma.create(d_q_h, d_k_h, d_v_h, T, HD, SK)) {
printf(" TMA desc creation failed for head %d batch %d\n", h, b);
failed++; continue;
}
FmhaMultiRowTmaParams params;
params.q = d_q_h; params.k = d_k_h; params.v = d_v_h;
params.o = d_o + idx * MAX_T * HD; params.lse = d_lse + idx * MAX_T;
params.s_k = SK; params.T = T; params.scale = SCALE; params.head_dim = HD;
// Strides must match PADDED allocation
params.q_head_stride = Q_PAD_ROWS * HD; params.q_batch_stride = n_h * Q_PAD_ROWS * HD;
params.k_head_stride = SK * HD; params.k_batch_stride = n_h * SK * HD;
params.v_head_stride = HD * SK; params.v_batch_stride = n_h * HD * SK;
params.o_head_stride = MAX_T * HD; params.o_batch_stride = n_h * MAX_T * HD;
params.lse_head_stride = MAX_T; params.lse_batch_stride = n_h * MAX_T;
params.tma_q = tma.d_tma_q;
params.tma_k = tma.d_tma_k;
params.tma_v = tma.d_tma_v;
int smem = compute_smem_tma();
if (smem > 48 * 1024)
cudaFuncSetAttribute(fmha_6warp_tma_kernel<HD>, cudaFuncAttributeMaxDynamicSharedMemorySize, smem);
dim3 grid(1, 1, 1);
fmha_6warp_tma_kernel<HD><<<grid, 192, smem>>>(params);
cudaError_t err = cudaDeviceSynchronize();
if (err != cudaSuccess) {
printf(" CUDA ERROR b=%d h=%d: %s\n", b, h, cudaGetErrorString(err));
failed++; tma.destroy(); continue;
}
// Verify against reference using UN-padded Q data
bf16_t* h_o_head = (bf16_t*)malloc(T * HD * sizeof(bf16_t));
float* h_lse_head = (float*)malloc(T * sizeof(float));
cudaMemcpy(h_o_head, d_o + idx * MAX_T * HD, T * HD * sizeof(bf16_t), cudaMemcpyDeviceToHost);
cudaMemcpy(h_lse_head, d_lse + idx * MAX_T, T * sizeof(float), cudaMemcpyDeviceToHost);
float o_ref[MAX_T * 512]; float lse_ref[MAX_T];
reference_attention_multirow(
h_q + idx * Q_PAD_ROWS * HD, // padded but first T rows have data
h_k + idx * SK * HD, h_v + idx * HD * SK,
o_ref, lse_ref, HD, T, SK, SCALE);
for (int t = 0; t < T; t++) {
float cs=0,na=0,nb=0;
for (int d=0;d<HD;d++) {
float a=bf16_to_f32_host(h_o_head[t*HD+d]), b2=o_ref[t*HD+d];
if(fabsf(b2)>1e-4f){cs+=a*b2;na+=a*a;nb+=b2*b2;}
}
cs /= (sqrtf(na)*sqrtf(nb)+1e-10f);
if(cs<min_cos) min_cos=cs;
if(cs<0.999f) { printf(" FAIL b=%d h=%d t=%d cos=%.6f\n",b,h,t,cs); failed++; }
float lse_err = fabsf(h_lse_head[t] - lse_ref[t]);
if(lse_err > 0.01f) { printf(" FAIL LSE b=%d h=%d t=%d kernel=%.6f ref=%.6f err=%.6f\n",b,h,t,h_lse_head[t],lse_ref[t],lse_err); failed++; }
}
free(h_o_head); free(h_lse_head);
tma.destroy();
}
}
printf(" min_cos=%.8f %s\n", min_cos, failed==0?"PASSED":"FAILED");
cudaFree(d_q); cudaFree(d_k); cudaFree(d_v); cudaFree(d_o); cudaFree(d_lse);
free(h_q); free(h_k); free(h_v); free(h_o); free(h_lse);
return failed == 0;
}
int main() {
printf("TMA Async FMHA test (HD=%d)\n", HD);
int ok = 1;
printf("\n--- Single KV tile tests (TMA) ---\n");
ok &= test_single(128); // Test T=128 first (simplest — no padding issues)
ok &= test_single(64);
ok &= test_single(32);
ok &= test_single(16);
ok &= test_single(1);
printf("\n--- Multi-head tests (TMA) ---\n");
ok &= test_single(4, 4, 1);
printf("\n%s\n", ok ? "ALL PASSED" : "SOME FAILED");
return ok ? 0 : 1;
}