Files
nvfp4-megamoe-kernel/tests/unit/test_sw128_qk.cu
2026-05-28 16:36:53 +00:00

173 lines
6.4 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 SW128 UMMA descriptor for QK GEMM.
* If this works, we can switch from NONE to SW128 and then add TMA loads.
*
* SW128 swizzle: TMA produces data in a format that MMA can read directly.
* The SMEM layout is different from the NONE (canonical) layout.
*
* Key difference:
* NONE: SMEM is written manually in canonical core-matrix order
* SW128: SMEM is written by TMA in 128-byte swizzled order
*/
#include <cuda_runtime.h>
#include <cstdio>
#include <cstring>
#include <cmath>
#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 NKT_QK = HD / MMA_K_BF16;
constexpr int TILE_SZ = BLOCK_MN * MMA_K_BF16;
// The SW128 SMEM layout is what TMA produces when loading a (128, 16) BF16 tile.
// For a 128×16 BF16 matrix with 128-byte swizzle:
// The swizzle permutes the 128-byte sectors to avoid bank conflicts.
// We need to write data in the SW128 layout for the MMA to read correctly.
// From CUTLASS and the PTX spec, the SW128 layout for a (128, 16) BF16 tile:
// Each 128-byte row is stored at address: row * 16 * 2 = row * 32 bytes
// With SW128, the row address is XOR-swizzled with bits from the column index.
// The exact formula depends on the swizzle pattern.
// For now, let's test: use the NONE (canonical) layout we know works,
// but with the SW128 descriptor. If the results are wrong, we know the
// SMEM layout must change for SW128.
__global__ void __launch_bounds__(128)
test_sw128_qk(const bf16_t* q, const bf16_t* k,
float* o_scalar, float scale)
{
const int tid = threadIdx.x, wid = tid / 32, lane = tid % 32;
extern __shared__ char sbuf[];
uint32_t* sTmemBase = (uint32_t*)sbuf;
bf16_t* sQ0 = (bf16_t*)(((uintptr_t)(sbuf + 4) + 15) & ~(uintptr_t)15);
bf16_t* sK0 = sQ0 + TILE_SZ;
// Load Q and K in CANONICAL layout (same as working NONE path)
for (int i = tid; i < TILE_SZ; i += 128) sQ0[i] = 0;
for (int d = tid; d < MMA_K_BF16; d += 128) {
int ck = d / 8, lc = d % 8;
sQ0[ck * 16 * 64 + lc] = q[d];
}
for (int i = tid; i < TILE_SZ; i += 128) sK0[i] = 0;
for (int r = 0; r < SK; r++) {
for (int d = tid; d < MMA_K_BF16; d += 128) {
int ck = d / 8, lc = d % 8;
int tmn = r / 8, lr = r % 8;
sK0[ck * 16 * 64 + tmn * 64 + lr * 8 + lc] = k[r * HD + d];
}
}
__syncthreads();
// TMEM alloc
if (wid == 1) tmem_alloc(__cvta_generic_to_shared(sTmemBase), 128);
__syncthreads();
uint32_t tb = *sTmemBase;
// QK GEMM with SW128 descriptor
{
// Try SW128 descriptor for Q
uint64_t dq_sw128 = make_umma_desc_kmajor_sw128(__cvta_generic_to_shared(sQ0), BLOCK_MN);
uint64_t dk_sw128 = make_umma_desc_kmajor_sw128(__cvta_generic_to_shared(sK0), BLOCK_MN);
uint32_t idesc = make_idesc(BLOCK_MN, BLOCK_MN);
if (tid == 0) umma_ss_f16(tb, dq_sw128, dk_sw128, idesc, false);
asm volatile("tcgen05.fence::after_thread_sync;" ::: "memory");
__syncthreads();
}
// Read S from TMEM (row 0 only)
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;
// Compare with reference
if (tid == 0) {
printf("SW128 QK result (first 8 attention weights): ");
for (int j=0;j<8;j++) printf("%.6f ", s_vals[j]);
printf("\n");
}
}
// 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 += bf16_to_f32(q[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;
printf("Reference (first 8 attention weights): ");
for (int j=0;j<8;j++) printf("%.6f ", s[j]);
printf("\n");
float cs=0,na=0,nb=0;
// Can't compare directly since we need s_vals from another thread
// Just print both and let human verify
}
if (wid == 0) tmem_dealloc(tb, 128);
}
int main() {
printf("=== SW128 UMMA descriptor test (HD=16) ===\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));
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);
bf16_t *d_q,*d_k;
float* d_o_scalar;
cudaMalloc(&d_q, HD*sizeof(bf16_t));
cudaMalloc(&d_k, SK*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);
int smem = (4+16 + TILE_SZ*2 + TILE_SZ*2 + 256 + 127) & ~127;
test_sw128_qk<<<1, 128, smem>>>(d_q, d_k, d_o_scalar, SCALE);
cudaError_t err = cudaDeviceSynchronize();
if (err != cudaSuccess) { printf("CUDA ERROR: %s\n", cudaGetErrorString(err)); return 1; }
cudaFree(d_q); cudaFree(d_k); cudaFree(d_o_scalar);
free(h_q); free(h_k);
return 0;
}