Files
nvfp4-megamoe-kernel/tests/unit/test_fmha_ts_hd16.cu

239 lines
10 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.
/**
* Full UMMA FMHA — HD=16, SK=128, with PV GEMM via tcgen05.mma TS
*
* Pipeline: Q×K^T (SS) → softmax → P×V (TS) → epilogue
* TMEM: columns 0-127 = P, columns 128-159 = O
*
* PV GEMM: 8 K-tiles, each A=(128,16) from TMEM, B=(16,16) from SMEM
* Accumulate O in TMEM at columns 128-143
*/
#include <cuda_runtime.h>
#include <cstdio>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include "dsv4/kernels/attention/fmha_common.cuh"
#include "dsv4/kernels/attention/fmha_umma_desc.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 = 16, SK = 128, BLOCK_MN = 128;
constexpr int VKT = SK / MMA_K_BF16; // 8 PV K-tiles
constexpr int V_TILE_SZ = MMA_K_BF16 * HD; // 256 BF16 per V K-tile
constexpr int TMEM_P = 128; // P: 128 columns
constexpr int TMEM_O = 32; // O: 16 cols, round to 32 (min, power of 2)
constexpr int TMEM_N = 256; // total: 128 + 32 = 160 → round to 256
__global__ void __launch_bounds__(128)
test_fmha_ts(const bf16_t* q, const bf16_t* k, const bf16_t* v,
bf16_t* o_out, float* o_scalar, float scale)
{
const int tid = threadIdx.x, wid = tid / 32, lane = tid % 32;
// SMEM: tmem_base + sQ(128,16)+pad + sK(128,16) + V tiles (8 × 16×16)
extern __shared__ char sbuf[];
uint32_t* sTmemBase = (uint32_t*)sbuf;
bf16_t* sQ = (bf16_t*)(((uintptr_t)(sbuf + 4) + 15) & ~(uintptr_t)15);
bf16_t* sK = sQ + 128 * 16 + 4096;
// Fresh aligned V buffer (ensure 128-byte alignment for descriptor)
bf16_t* sV = (bf16_t*)(((uintptr_t)(sK + 128 * 16) + 127) & ~(uintptr_t)127);
float* sQ_row = (float*)(sV + 16 * 16);
for (int d = tid; d < HD; d += 128) sQ_row[d] = bf16_to_f32(q[d]);
// TMEM alloc — 256 columns
if (wid == 1) tmem_alloc(__cvta_generic_to_shared(sTmemBase), TMEM_N);
__syncthreads();
uint32_t tb = *sTmemBase;
uint32_t tb_o = tb + TMEM_P; // O at column 128
// Load Q, K
write_q_to_smem<HD>(sQ, q);
write_k_to_smem<SK, HD>(sK, k);
bf16_t* sQ_pad = sQ + 128 * 16;
for (int i = tid; i < 4096; i += 128) sQ_pad[i] = 0;
// Load V = all 1.0 into (16, 16) canonical (same as isolated test)
for (int i = tid; i < 16 * 16; i += 128) sV[i] = 0;
__syncthreads();
for (int i = tid; i < 16 * 16; i += 128) {
int r = i / 16, c = i % 16;
int ck = c / 8, lc = c % 8;
int tmn = r / 8, lr = r % 8;
sV[ck * 2 * 64 + tmn * 64 + lr * 8 + lc] = f32_to_bf16(1.0f);
}
__syncthreads();
// DEBUG: Skip QK and softmax, write P directly to TMEM
// Write P = all 1.0 (128, 128) — only row 0 has 1.0, rest 0
if (wid == 0) {
for (int n = 0; n < SK / 8; n++) {
// Lane 0 writes 1.0 for row 0, other lanes write 0
float p0=(lane==0)?0.0078125f:0, p1=(lane==0)?0.0078125f:0;
float p2=(lane==0)?0.0078125f:0, p3=(lane==0)?0.0078125f:0;
float p4=(lane==0)?0.0078125f:0, p5=(lane==0)?0.0078125f:0;
float p6=(lane==0)?0.0078125f:0, p7=(lane==0)?0.0078125f:0;
asm volatile("tcgen05.st.sync.aligned.32x32b.x8.b32 [%0],{%1,%2,%3,%4,%5,%6,%7,%8};" :: "r"(tb+n*8),"f"(p0),"f"(p1),"f"(p2),"f"(p3),"f"(p4),"f"(p5),"f"(p6),"f"(p7));
}
tmem_fence_store();
}
__syncthreads();
/*
// STEP 1: QK GEMM — DISABLED for debug
uint64_t desc_q = make_umma_desc_kmajor_none(__cvta_generic_to_shared(sQ), BLOCK_MN);
uint64_t desc_k = make_umma_desc_kmajor_none(__cvta_generic_to_shared(sK), BLOCK_MN);
uint32_t idesc_qk = make_idesc(BLOCK_MN, BLOCK_MN);
if (lane == 0) umma_ss_f16(tb, desc_q, desc_k, idesc_qk, false);
asm volatile("tcgen05.fence::after_thread_sync;" ::: "memory");
__syncthreads();
// STEP 2: Softmax — DISABLED for debug
if (wid == 0) {
float s_vals[SK], row_max = -INFINITY;
for (int n = 0; n < SK / 8; n++) {
float tmp[8];
asm volatile("tcgen05.ld.sync.aligned.32x32b.x8.b32 {%0,%1,%2,%3,%4,%5,%6,%7},[%8];" : "=f"(tmp[0]),"=f"(tmp[1]),"=f"(tmp[2]),"=f"(tmp[3]),"=f"(tmp[4]),"=f"(tmp[5]),"=f"(tmp[6]),"=f"(tmp[7]) : "r"(tb + n*8));
asm volatile("tcgen05.wait::ld.sync.aligned;");
if (lane == 0) for (int c=0;c<8;c++) { s_vals[n*8+c] = tmp[c]*scale; row_max = fmaxf(row_max, tmp[c]*scale); }
}
row_max = wmax(row_max);
float row_sum = 0.0f;
if (lane == 0) for (int j=0;j<SK;j++) { s_vals[j] = expf(s_vals[j]-row_max); row_sum += s_vals[j]; }
row_sum = wsum(row_sum);
if (lane == 0) for (int j=0;j<SK;j++) s_vals[j] /= row_sum;
for (int n = 0; n < SK / 8; n++) {
float p0=(lane==0)?s_vals[n*8+0]:0, p1=(lane==0)?s_vals[n*8+1]:0;
float p2=(lane==0)?s_vals[n*8+2]:0, p3=(lane==0)?s_vals[n*8+3]:0;
float p4=(lane==0)?s_vals[n*8+4]:0, p5=(lane==0)?s_vals[n*8+5]:0;
float p6=(lane==0)?s_vals[n*8+6]:0, p7=(lane==0)?s_vals[n*8+7]:0;
asm volatile("tcgen05.st.sync.aligned.32x32b.x8.b32 [%0],{%1,%2,%3,%4,%5,%6,%7,%8};" :: "r"(tb+n*8),"f"(p0),"f"(p1),"f"(p2),"f"(p3),"f"(p4),"f"(p5),"f"(p6),"f"(p7));
}
tmem_fence_store();
}
__syncthreads();
*/
// Zero O TMEM region at tb (overwriting P — same as isolated test)
if (wid == 0) {
for (int n = 0; n < 2; n++) { // 16 cols / 8 = 2 iterations
float z0=0,z1=0,z2=0,z3=0,z4=0,z5=0,z6=0,z7=0;
asm volatile("tcgen05.st.sync.aligned.32x32b.x8.b32 [%0],{%1,%2,%3,%4,%5,%6,%7,%8};" :: "r"(tb+n*8),"f"(z0),"f"(z1),"f"(z2),"f"(z3),"f"(z4),"f"(z5),"f"(z6),"f"(z7));
}
tmem_fence_store();
}
__syncthreads();
// STEP 3: PV GEMM via tcgen05.mma TS
// A = P (TMEM, 128 rows × SK cols), B = V (SMEM K-tiles)
// C = O (TMEM at tb_o, 128 rows × HD cols)
// MMA: M=128, N=16. K=16 per MMA call, 8 K-tiles total.
uint32_t idesc_pv = make_idesc(BLOCK_MN, HD);
for (int kt = 0; kt < 1; kt++) { // DEBUG: only first PV K-tile
uint64_t dv = make_umma_desc_kmajor_none(__cvta_generic_to_shared(sV), 16);
uint32_t tmem_a = tb; // P's first 16 columns
if (tid == 0) umma_ts_f16(tb, tmem_a, dv, idesc_pv, false); // Write C to tb (overwriting P)
asm volatile("tcgen05.fence::after_thread_sync;" ::: "memory");
__syncthreads();
}
asm volatile("tcgen05.fence::after_thread_sync;" ::: "memory");
__syncthreads();
// STEP 4: Epilogue — read O from TMEM, apply MMA scale (0.5), write to GMEM
if (wid == 0) {
float o_vals[HD];
for (int n = 0; n < HD / 8; n++) {
float tmp[8];
asm volatile("tcgen05.ld.sync.aligned.32x32b.x8.b32 {%0,%1,%2,%3,%4,%5,%6,%7},[%8];" : "=f"(tmp[0]),"=f"(tmp[1]),"=f"(tmp[2]),"=f"(tmp[3]),"=f"(tmp[4]),"=f"(tmp[5]),"=f"(tmp[6]),"=f"(tmp[7]) : "r"(tb + n*8));
asm volatile("tcgen05.wait::ld.sync.aligned;");
if (lane == 0) for (int c=0;c<8;c++) o_vals[n*8+c] = tmp[c] * 2.0f; // Undo MMA 0.5 scale
}
if (lane == 0) for (int d=0;d<HD;d++) o_out[d] = f32_to_bf16(o_vals[d]);
}
__syncthreads();
// Scalar reference
if (tid == 0) {
float s[SK];
for (int j=0;j<SK;j++) {
float dot = 0.0f;
for (int d=0;d<HD;d++) dot += sQ_row[d] * bf16_to_f32(k[j*HD+d]);
s[j] = dot * scale;
}
float mx = -INFINITY;
for (int j=0;j<SK;j++) mx = fmaxf(mx, s[j]);
float sm = 0.0f;
for (int j=0;j<SK;j++) { s[j] = expf(s[j]-mx); sm += s[j]; }
for (int j=0;j<SK;j++) s[j] /= sm;
for (int d=0;d<HD;d++) {
float ov = 0.0f;
for (int j=0;j<SK;j++) ov += s[j] * bf16_to_f32(v[d*SK+j]);
o_scalar[d] = ov;
}
}
if (wid == 0) tmem_dealloc(tb, TMEM_N);
}
int main() {
printf("=== Full UMMA FMHA HD=16 with PV GEMM (TS) ===\n");
const float SCALE = 1.0f / sqrtf((float)HD);
bf16_t* h_q = (bf16_t*)malloc(HD*sizeof(bf16_t));
bf16_t* h_k = (bf16_t*)malloc(SK*HD*sizeof(bf16_t));
bf16_t* h_v = (bf16_t*)malloc(HD*SK*sizeof(bf16_t));
bf16_t* h_o = (bf16_t*)calloc(HD, sizeof(bf16_t));
float* h_o_scalar = (float*)calloc(HD, sizeof(float));
srand(42);
for (int d=0;d<HD;d++) h_q[d] = f32_to_bf16_host((float)(rand()%100)/100.0f-0.5f);
for (int i=0;i<SK*HD;i++) h_k[i] = f32_to_bf16_host((float)(rand()%100)/100.0f-0.5f);
for (int i=0;i<HD*SK;i++) h_v[i] = f32_to_bf16_host((float)(rand()%100)/100.0f-0.5f);
bf16_t *d_q,*d_k,*d_v,*d_o; float *d_o_scalar;
cudaMalloc(&d_q, HD*sizeof(bf16_t));
cudaMalloc(&d_k, SK*HD*sizeof(bf16_t));
cudaMalloc(&d_v, HD*SK*sizeof(bf16_t));
cudaMalloc(&d_o, HD*sizeof(bf16_t));
cudaMalloc(&d_o_scalar, HD*sizeof(float));
cudaMemcpy(d_q, h_q, HD*sizeof(bf16_t), cudaMemcpyHostToDevice);
cudaMemcpy(d_k, h_k, SK*HD*sizeof(bf16_t), cudaMemcpyHostToDevice);
cudaMemcpy(d_v, h_v, HD*SK*sizeof(bf16_t), cudaMemcpyHostToDevice);
int smem = (4+16 + 128*16*2+4096 + 128*16*2 + VKT*V_TILE_SZ*2 + 16*4 + 256 + 127) & ~127;
test_fmha_ts<<<1, 128, smem>>>(d_q, d_k, d_v, d_o, d_o_scalar, SCALE);
cudaError_t err = cudaDeviceSynchronize();
if (err != cudaSuccess) { printf("CUDA ERROR: %s\n", cudaGetErrorString(err)); return 1; }
cudaMemcpy(h_o, d_o, HD*sizeof(bf16_t), cudaMemcpyDeviceToHost);
cudaMemcpy(h_o_scalar, d_o_scalar, HD*sizeof(float), cudaMemcpyDeviceToHost);
printf("O[0..15] MMA: "); for(int d=0;d<HD;d++) printf("%.6f ",bf16_to_f32_host(h_o[d])); printf("\n");
printf("O[0..15] ref: "); for(int d=0;d<HD;d++) printf("%.6f ",h_o_scalar[d]); printf("\n");
float max_diff=0, max_val=0;
for (int d=0;d<HD;d++) {
max_diff = fmaxf(max_diff, fabsf(bf16_to_f32_host(h_o[d]) - h_o_scalar[d]));
max_val = fmaxf(max_val, fabsf(h_o_scalar[d]));
}
float rel_err = max_val>0 ? max_diff/max_val : max_diff;
float cos_sim=0,na=0,nb=0;
for (int d=0;d<HD;d++) { float a=bf16_to_f32_host(h_o[d]),b=h_o_scalar[d]; cos_sim+=a*b; na+=a*a; nb+=b*b; }
cos_sim /= (sqrtf(na)*sqrtf(nb)+1e-10f);
printf("Max rel err: %.8f | cosine: %.8f\n", rel_err, cos_sim);
printf("Test %s\n", cos_sim > 0.999f ? "PASSED" : "FAILED");
cudaFree(d_q); cudaFree(d_k); cudaFree(d_v); cudaFree(d_o); cudaFree(d_o_scalar);
free(h_q); free(h_k); free(h_v); free(h_o); free(h_o_scalar);
return cos_sim > 0.999f ? 0 : 1;
}