Files
nvfp4-megamoe-kernel/tests/unit/test_tmem_all_lanes.cu
2026-05-28 15:51:55 +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.
/**
* Map TMEM Layout D for PV MMA N=64 using 32x32b.x8 reads (all lanes).
* All 32 lanes read, giving 128 positions per group of 8 columns.
*/
#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 = 64, SK = 128, BLOCK_MN = 128;
constexpr int LOCAL_MMA_K = 16;
constexpr int TILE_SZ = BLOCK_MN * LOCAL_MMA_K;
constexpr int V_TILE_SZ = (HD / 8) * 2 * 64;
// Global memory buffer for TMEM dump
// 64 columns × 128 positions = 8192 FP32
// But we read 8 columns at a time (32x32b.x8), 8 reads for 64 cols
// Each read: 8 columns × 128 positions (32 lanes × 4 FP32 per lane)
// But lane 0 gets 8 FP32 values (one per column). We only dump lane 0's data
// since T=1 decode only uses row 0.
// Actually, for row 0, lane 0 should have positions 0-3 of each column.
// But for Layout D, row 0 might be in a different lane's positions.
// So we dump ALL lanes' data via SMEM.
// We use a 2-step approach: read TMEM in kernel, store to GMEM,
// then analyze in host.
__global__ void __launch_bounds__(128)
test_tmem_all_lanes(const bf16_t* q, const bf16_t* k, const bf16_t* v,
float* tmem_dump, 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 + 4 * TILE_SZ;
bf16_t* sPk = (bf16_t*)(((uintptr_t)(sK0 + 4 * TILE_SZ) + 127) & ~(uintptr_t)127);
bf16_t* sV = (bf16_t*)(((uintptr_t)(sPk + TILE_SZ) + 127) & ~(uintptr_t)127);
float* s_p_vals = (float*)(sV + 8 * V_TILE_SZ);
for (int kt = 0; kt < 4; kt++) {
bf16_t* sq = sQ0 + kt * TILE_SZ;
for (int i = tid; i < TILE_SZ; i += 128) sq[i] = 0;
for (int d = tid; d < LOCAL_MMA_K; d += 128) {
int ck = d / 8, lc = d % 8;
sq[ck * 16 * 64 + lc] = q[kt * LOCAL_MMA_K + d];
}
}
for (int kt = 0; kt < 4; kt++) {
bf16_t* sk = sK0 + kt * TILE_SZ;
for (int i = tid; i < TILE_SZ; i += 128) sk[i] = 0;
for (int r = 0; r < SK; r++) {
for (int d = tid; d < LOCAL_MMA_K; d += 128) {
int ck = d / 8, lc = d % 8;
int tmn = r / 8, lr = r % 8;
sk[ck * 16 * 64 + tmn * 64 + lr * 8 + lc] = k[r * HD + kt * LOCAL_MMA_K + d];
}
}
}
for (int kt = 0; kt < 8; kt++) {
bf16_t* sv = sV + kt * V_TILE_SZ;
for (int i = tid; i < V_TILE_SZ; i += 128) sv[i] = 0;
for (int d = tid; d < HD; d += 128) {
for (int lr = 0; lr < LOCAL_MMA_K; lr++) {
int r = kt * LOCAL_MMA_K + lr;
int g_mn = d / 8, g_k = lr / 8;
int llr = d % 8, lc = lr % 8;
sv[g_k * 8 * 64 + g_mn * 64 + llr * 8 + lc] = v[d * SK + r];
}
}
}
__syncthreads();
if (wid == 1) tmem_alloc(__cvta_generic_to_shared(sTmemBase), 128);
__syncthreads();
uint32_t tb = *sTmemBase;
// QK GEMM
{
uint32_t idesc = make_idesc(BLOCK_MN, BLOCK_MN);
for (int kt = 0; kt < 4; kt++) {
bf16_t* sq = sQ0 + kt * TILE_SZ;
bf16_t* sk = sK0 + kt * TILE_SZ;
uint64_t dq = make_umma_desc_kmajor_none(__cvta_generic_to_shared(sq), BLOCK_MN);
uint64_t dk = make_umma_desc_kmajor_none(__cvta_generic_to_shared(sk), BLOCK_MN);
if (tid == 0) umma_ss_f16(tb, dq, dk, idesc, kt > 0);
asm volatile("tcgen05.fence::after_thread_sync;" ::: "memory");
__syncthreads();
}
}
// Softmax
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;
if (lane == 0) for (int j=0;j<SK;j++) s_p_vals[j] = s_vals[j];
}
__syncthreads();
// PV MMA (N=64)
{
uint32_t idesc_pv = make_idesc(BLOCK_MN, HD);
for (int kt = 0; kt < 8; kt++) {
for (int i = tid; i < TILE_SZ; i += 128) sPk[i] = 0;
if (tid < 16) {
int c = tid;
int ck = c / 8, lc = c % 8;
sPk[ck * 16 * 64 + 0 * 64 + 0 * 8 + lc] = f32_to_bf16(s_p_vals[kt * LOCAL_MMA_K + c]);
}
__syncthreads();
bf16_t* sv = sV + kt * V_TILE_SZ;
uint64_t dp = make_umma_desc_kmajor_none(__cvta_generic_to_shared(sPk), BLOCK_MN);
uint64_t dv = make_umma_desc_kmajor_none(__cvta_generic_to_shared(sv), HD);
if (tid == 0) umma_ss_f16(tb, dp, dv, idesc_pv, kt > 0);
asm volatile("tcgen05.fence::after_thread_sync;" ::: "memory");
__syncthreads();
}
}
// ===== Read TMEM: dump all lanes' data for columns 0..63 =====
// Use 32x32b.x8 read, 8 reads of 8 columns each
// Each lane gets 8 FP32 values per read (one per column)
// 32 lanes × 4 FP32 per column = 128 FP32 per column
// For lane i, the 8 values are positions i*4+0..3 of each of 8 columns
// We write: for read n (cols n*8..n*8+7), lane i, column c (0..7):
// output[(n*8 + c) * 128 + i*4 + 0..3] = tmp[0..3] (but tmp only has 1 value per column)
// Wait — 32x32b.x8 gives each lane 8 FP32 values, one per column
// Lane i gets: for each of 8 columns, one value at position (i*4 + sub) within the column
// Actually, the 32x32b.x8 format: each lane reads 8 FP32 from 8 consecutive columns
// Lane i's 8 values are at position (i*4 + 0), (i*4 + 1), (i*4 + 2), (i*4 + 3)
// of each of the 8 columns. No wait — the x8 reads 8 columns, each column gives 1 FP32 per lane.
// So lane i gets tmp[0..7], where tmp[j] is from column (n*8 + j), position i.
// But what "position i"? For 32 lanes, each lane reads one of 32 positions per column.
// The mapping: lane i reads positions i*4+0..i*4+3 in a 16x256b read, but 32x32b.x8 is different.
//
// From the verified TMEM mapping:
// 32x32b.x8: reads 8 columns. Each lane gets 8 FP32 values.
// Lane i's 8 values correspond to the same position within each of the 8 columns.
// The position within the column depends on lane i.
// For the QK read (N=128), lane 0's values corresponded to output row 0 positions.
//
// We dump: tmem_dump[(n*8 + col_idx) * 128 + lane*4 + 0] = tmp[col_idx]
// But this might not be the right mapping. Let's just dump lane i's 8 values for each read
// and figure out the mapping on the host.
if (wid == 0) {
for (int n = 0; n < 16; n++) { // 16 reads of 8 columns = 128 columns
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;");
// Each lane writes its 8 values to GMEM
// For column (n*8 + c), lane i's value goes to position lane in that column
// Layout: tmem_dump[col * 32 + lane] = lane's value for column col
for (int c = 0; c < 8; c++) {
int col = n * 8 + c;
if (col < 128) tmem_dump[col * 32 + lane] = tmp[c];
}
}
}
__syncthreads();
if (wid == 0) tmem_dealloc(tb, 128);
}
int main() {
printf("=== TMEM Layout D dump (32x32b.x8, all lanes) ===\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));
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;
float* d_tmem_dump;
cudaMalloc(&d_q, HD*sizeof(bf16_t));
cudaMalloc(&d_k, SK*HD*sizeof(bf16_t));
cudaMalloc(&d_v, HD*SK*sizeof(bf16_t));
// 128 columns × 32 lanes = 4096 FP32
cudaMalloc(&d_tmem_dump, 128 * 32 * sizeof(float));
cudaMemset(d_tmem_dump, 0, 128 * 32 * 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 + 4*TILE_SZ*2 + 4*TILE_SZ*2 + TILE_SZ*2 + 8*V_TILE_SZ*2 + SK*4 + 256 + 127) & ~127;
cudaFuncSetAttribute(test_tmem_all_lanes, cudaFuncAttributeMaxDynamicSharedMemorySize, smem);
test_tmem_all_lanes<<<1, 128, smem>>>(d_q, d_k, d_v, d_tmem_dump, SCALE);
cudaError_t err = cudaDeviceSynchronize();
if (err != cudaSuccess) { printf("CUDA ERROR: %s\n", cudaGetErrorString(err)); return 1; }
float* h_dump = (float*)malloc(128 * 32 * sizeof(float));
cudaMemcpy(h_dump, d_tmem_dump, 128 * 32 * sizeof(float), cudaMemcpyDeviceToHost);
// Reference
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_host(h_q[d]) * bf16_to_f32_host(h_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;
float o_ref[HD];
for (int d=0;d<HD;d++) {
float ov = 0.0f;
for (int j=0;j<SK;j++) ov += s[j] * bf16_to_f32_host(h_v[d*SK+j]);
o_ref[d] = ov;
}
// Find mapping: for each output d, find (col, lane) that has the closest value
printf("=== Mapping: output position d -> (col, lane) ===\n");
for (int d = 0; d < HD; d++) {
float target = o_ref[d];
int best_col = -1, best_lane = -1;
float best_diff = 1e10f;
for (int col = 0; col < 128; col++) {
for (int ln = 0; ln < 32; ln++) {
float val = h_dump[col * 32 + ln];
float diff = fabsf(val - target);
if (diff < best_diff) {
best_diff = diff;
best_col = col;
best_lane = ln;
}
}
}
printf(" d=%2d: ref=%10.6f at (col=%2d, lane=%2d) val=%10.6f diff=%.2e\n",
d, target, best_col, best_lane, h_dump[best_col*32+best_lane], best_diff);
}
// Print column summary: for each column, which lanes have non-zero values?
printf("\n=== Non-zero lanes per column ===\n");
for (int col = 0; col < 128; col++) {
int nz = 0;
for (int ln = 0; ln < 32; ln++) if (fabsf(h_dump[col*32+ln]) > 1e-6f) nz++;
if (nz > 0) printf(" col %3d: %d non-zero lanes, lane0=%10.6f\n", col, nz, h_dump[col*32+0]);
}
cudaFree(d_q); cudaFree(d_k); cudaFree(d_v); cudaFree(d_tmem_dump);
free(h_q); free(h_k); free(h_v); free(h_dump);
return 0;
}