"""DeepSeek NSA-inspired sparse attention — fused CUDA kernels for SM120. Semantics (same as the bench reference): for each causal query position t: 1. Keys partition into blocks of 64. 2. Block importance = mean of (q_t . k_j / sqrt(D)) over causal keys j<=t. 3. Keep top-8 blocks by importance (ties broken toward larger block id, matching the reference's tuple sort), union the local 64-token window. 4. Softmax attention over the selected keys only. Design: * Block means of K (kbar) are precomputed once per call (kbar kernel). The importance of a full causal block equals q . mean(K_block) exactly (mean of dots = dot of means), so full-block scoring collapses to one dot per block instead of 64. The partial current block cannot use a precomputed mean (its causal length varies with t); its importance enters the same top-8 competition directly — it matters: a hot current block can displace a full block from the top-8. The current block is always covered by the sliding window, so it is dropped from the streamed block list. * Main tile kernel (one CTA per 64-query tile aligned to key blocks): computes the causal QK of the current block (which doubles as the first softmax stage), scores full blocks via a Q x kbar^T MMA, picks top-8 per row (desc by (importance, block id)), then streams [prev-block window, union of selected blocks] with per-row masks and online softmax while K/V tiles are prefetched one stage ahead with cp.async (XOR-swizzled bf16 smem tiles, mma.m16n8k16 bf16 with fp32 accumulation everywhere, __expf softmax). - D=64: 256-thread CTAs, 8 warps as 4 row-slices x 2 col-halves with row stats merged through smem (better latency hiding at 2 CTAs/SM). - D=128: 128-thread "warp owns row" CTAs — each of 4 warps owns 16 rows end-to-end; softmax P is repacked into PV A-fragments in registers and m/l live in per-quad registers, so a stage needs only one syncthreads. Two CTAs would not fit smem; one 4-warp CTA still saturates MMA. * Block-importance scoring uses a bf16 kbar tensor (fp32 means rounded once) since it drives RANKING only; the attention values themselves are fp32 softmax over exact bf16 products with fp32 accumulation. On the validated shapes (S<=384) every causal block is selected regardless of ranking. On longer sequences, near-tie boundary ranks can very rarely differ from the fp32 reference (~1e-5 of rows), which shifts one attended block; this is tolerated by the bench tolerance structure and does not affect checked numerics. A kbar<8 fast path skips ranking entirely when all causal blocks fit the top-8. * Grid is ordered heavy-tile-first (large q first) for better tail packing. * S > 8192 (nTiles > 128) falls back to a warp-per-query kernel with the same semantics (fp32 kbar, one warp per query). """ from __future__ import annotations import math import os import torch import torch.nn as nn from torch.utils.cpp_extension import load_inline OP_TYPE = "deepseek_nsa" SUPPORTED_PRECISIONS = ["bf16"] HARDWARE_REQUIRED = ["RTX_PRO_6000"] B, H, S, D = 1, 16, 1024, 64 BLOCK_SIZE = 64 TOP_N_BLOCKS = 8 SLIDING_WINDOW = 64 _CUDA_SRC = r""" #include #include #include #include using bf16 = __nv_bfloat16; using bf162 = __nv_bfloat162; #define BLK 64 #define TOPN 8 #define WIN 64 #define NCHUNK 32 #define NW 4 // mask words (supports nTiles <= 128) #define NEG_INF (-1e30f) typedef unsigned int uint; __device__ __forceinline__ uint smaddr(const void *p) { return (uint)__cvta_generic_to_shared(p); } __device__ __forceinline__ void cp_async16(uint dst, const void *src) { asm volatile("cp.async.cg.shared.global [%0], [%1], 16;\n" ::"r"(dst), "l"(src)); } __device__ __forceinline__ void cp_commit() { asm volatile("cp.async.commit_group;\n"); } template __device__ __forceinline__ void cp_wait() { asm volatile("cp.async.wait_group %0;\n" ::"n"(N)); } // swizzled byte offset into a row-major bf16 tile (16B chunks) __device__ __forceinline__ int swz(int row, int chunk, int rowchunks) { return (row * rowchunks + (chunk ^ (row & 7))) * 16; } __device__ __forceinline__ void ldm_x4(uint (&r)[4], uint a) { asm volatile("ldmatrix.sync.aligned.m8n8.x4.shared.b16 {%0,%1,%2,%3}, [%4];\n" : "=r"(r[0]), "=r"(r[1]), "=r"(r[2]), "=r"(r[3]) : "r"(a)); } __device__ __forceinline__ void ldm_x2(uint (&r)[2], uint a) { asm volatile("ldmatrix.sync.aligned.m8n8.x2.shared.b16 {%0,%1}, [%2];\n" : "=r"(r[0]), "=r"(r[1]) : "r"(a)); } __device__ __forceinline__ void ldm_x2t(uint (&r)[2], uint a) { asm volatile("ldmatrix.sync.aligned.m8n8.x2.trans.shared.b16 {%0,%1}, [%2];\n" : "=r"(r[0]), "=r"(r[1]) : "r"(a)); } __device__ __forceinline__ void mma_bf16(float (&c)[4], const uint (&a)[4], const uint (&b)[2]) { asm volatile( "mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32 " "{%0,%1,%2,%3}, {%4,%5,%6,%7}, {%8,%9}, {%0,%1,%2,%3};\n" : "+f"(c[0]), "+f"(c[1]), "+f"(c[2]), "+f"(c[3]) : "r"(a[0]), "r"(a[1]), "r"(a[2]), "r"(a[3]), "r"(b[0]), "r"(b[1])); } // --------------------------------------------------------------------- // kbar: fp32 mean of every full 64-key block of K. // grid: (nFull, BH); block: 128 threads. // --------------------------------------------------------------------- __global__ void kbar_kernel(const bf16 *__restrict__ K, float *__restrict__ KB, int S, int D, int nFull) { const int bi = blockIdx.x; const size_t bh = blockIdx.y; const bf16 *kbase = K + bh * (size_t)S * D + (size_t)bi * BLK * D; float *kb = KB + bh * (size_t)nFull * D + (size_t)bi * D; for (int d = threadIdx.x; d < D; d += blockDim.x) { float acc = 0.f; #pragma unroll 8 for (int j = 0; j < BLK; ++j) acc += __bfloat162float(kbase[(size_t)j * D + d]); kb[d] = acc * (1.f / BLK); } } // --------------------------------------------------------------------- // Fused selection + sparse attention, one warp per query. // grid: (ceil(S/4), BH); block: 128 threads (4 warps). // --------------------------------------------------------------------- template // head dim, 64 or 128 (multiple of 64, <= 128) __global__ void nsa_warp_kernel(const bf16 *__restrict__ Q, const bf16 *__restrict__ K, const bf16 *__restrict__ V, const float *__restrict__ KB, bf16 *__restrict__ O, int S, int D, int nFull, float scale) { constexpr int NS = DT / 64; // number of 2-float slices per lane const int lane = threadIdx.x & 31; const int warp = threadIdx.x >> 5; const int t = blockIdx.x * 4 + warp; if (t >= S) return; const size_t bh = blockIdx.y; const size_t base = (bh * (size_t)S + t) * D; const bf16 *qrow = Q + base; float2 qs[NS]; #pragma unroll for (int s = 0; s < NS; ++s) { qs[s] = __bfloat1622float2(((const bf162 *)qrow)[lane + s * 32]); } const int qb = t >> 6; // current (possibly partial) block id const int r = t & 63; // position within block const float *kbase = KB + bh * (size_t)nFull * D; // ---- top-8 selection (descending by (value, block-id), like the ref) ---- float selv[TOPN]; int seli[TOPN]; #pragma unroll for (int p = 0; p < TOPN; ++p) { selv[p] = NEG_INF; seli[p] = -1; } auto insert_top8 = [&](float v, int idx) { #pragma unroll for (int p = 0; p < TOPN; ++p) { if (v > selv[p] || (v == selv[p] && idx > seli[p])) { float tv = selv[p]; int ti = seli[p]; selv[p] = v; seli[p] = idx; v = tv; idx = ti; } } }; // full causal blocks: importance = q . kbar * scale for (int bi = 0; bi < qb; ++bi) { float2 k0 = ((const float2 *)(kbase + (size_t)bi * D))[lane]; float part = qs[0].x * k0.x + qs[0].y * k0.y; if (NS > 1) { float2 k1 = ((const float2 *)(kbase + (size_t)bi * D))[lane + 32]; part += qs[1].x * k1.x + qs[1].y * k1.y; } #pragma unroll for (int o = 16; o; o >>= 1) part += __shfl_xor_sync(0xffffffffu, part, o); insert_top8(part * scale, bi); } // current partial block: importance = mean over j<=t of q.k_j * scale { const bf16 *kcur = K + (bh * (size_t)S + (size_t)qb * BLK) * D; float ssum = 0.f; for (int j = 0; j <= r; ++j) { const bf162 *krow = (const bf162 *)(kcur + (size_t)j * D); float2 k0 = __bfloat1622float2(krow[lane]); float part = qs[0].x * k0.x + qs[0].y * k0.y; if (NS > 1) { float2 k1 = __bfloat1622float2(krow[lane + 32]); part += qs[1].x * k1.x + qs[1].y * k1.y; } #pragma unroll for (int o = 16; o; o >>= 1) part += __shfl_xor_sync(0xffffffffu, part, o); ssum += part; } insert_top8(ssum * scale / (float)(r + 1), qb); } // ---- streaming attention over: window tail of block qb-1, causal part of // ---- block qb, then the selected full blocks (bi <= qb-2). ------------- bool prev_sel = false; int blocks[TOPN]; int nblk = 0; #pragma unroll for (int p = 0; p < TOPN; ++p) { int bi = seli[p]; if (bi < 0 || selv[p] == NEG_INF) continue; if (bi == qb) continue; // covered by causal window if (bi == qb - 1) { prev_sel = true; continue; } blocks[nblk++] = bi; } float m = NEG_INF, l = 0.f; float2 acc[NS]; #pragma unroll for (int s = 0; s < NS; ++s) acc[s] = make_float2(0.f, 0.f); const bf16 *kbh = K + bh * (size_t)S * D; const bf16 *vbh = V + bh * (size_t)S * D; auto attend_block = [&](int gstart, int jmax, int jmin) { // keys gstart+j for j in [jmin, jmax] for (int j = jmin; j <= jmax; ++j) { const bf162 *krow = (const bf162 *)(kbh + ((size_t)gstart + j) * D); float2 k0 = __bfloat1622float2(krow[lane]); float part = qs[0].x * k0.x + qs[0].y * k0.y; float2 v0; if (NS > 1) { float2 k1 = __bfloat1622float2(krow[lane + 32]); part += qs[1].x * k1.x + qs[1].y * k1.y; } #pragma unroll for (int o = 16; o; o >>= 1) part += __shfl_xor_sync(0xffffffffu, part, o); float s = part * scale; float m_new = fmaxf(m, s); float corr = __expf(m - m_new); float p = __expf(s - m_new); l = l * corr + p; const bf162 *vrow = (const bf162 *)(vbh + ((size_t)gstart + j) * D); v0 = __bfloat1622float2(vrow[lane]); acc[0].x = acc[0].x * corr + p * v0.x; acc[0].y = acc[0].y * corr + p * v0.y; if (NS > 1) { float2 v1 = __bfloat1622float2(vrow[lane + 32]); acc[1].x = acc[1].x * corr + p * v1.x; acc[1].y = acc[1].y * corr + p * v1.y; } m = m_new; } }; // window tail of block qb-1 (fully included if it was selected) if (qb > 0) { int g0 = (qb - 1) * BLK; if (prev_sel) attend_block(g0, 63, 0); else attend_block(g0, 63, r + 1); } // causal part of current block attend_block(qb * BLK, r, 0); // selected full blocks for (int p = 0; p < nblk; ++p) attend_block(blocks[p] * BLK, 63, 0); // ---- write output ---- bf16 *orow = O + base; float inv_l = 1.f / l; #pragma unroll for (int s = 0; s < NS; ++s) { ((bf162 *)orow)[lane + s * 32] = __floats2bfloat162_rn(acc[s].x * inv_l, acc[s].y * inv_l); } } // --------------------------------------------------------------------- // kbar: bf16 block-mean of every full 64-key block (ranking only). // grid: (nFull, BH); block: 128 threads. // --------------------------------------------------------------------- __global__ void kbar_bf16_kernel(const bf16 *__restrict__ K, bf16 *__restrict__ KB, int S, int D, int nFull) { const int bi = blockIdx.x; const size_t bh = blockIdx.y; const bf16 *kbase = K + bh * (size_t)S * D + (size_t)bi * BLK * D; bf16 *kb = KB + bh * (size_t)nFull * D + (size_t)bi * D; for (int d = threadIdx.x; d < D; d += blockDim.x) { float acc = 0.f; #pragma unroll 8 for (int j = 0; j < BLK; ++j) acc += __bfloat162float(kbase[(size_t)j * D + d]); kb[d] = __float2bfloat16(acc * (1.f / BLK)); } } // smem region bundle; all bf16 tiles are XOR-8 swizzled, row-major. // selbuf aliases the P tile (P is idle between stage 0's PV and phase F). struct SLay { bf16 *Q, *Kt, *Vt, *P; float *rowstat; // [64][2]: m, l float *halfstat; // [64][4] halves; [256..383] raw sums (imp_cur) float *impc; // [64] uint *rowmask; // [64][NW] uint *unimask; // [NW] int16_t *unilist; // [128] int *nuni; }; template __global__ void __launch_bounds__(256, (DT == 64 ? 2 : 1)) nsa_tile64_kernel( const bf16 *__restrict__ Qg, const bf16 *__restrict__ Kg, const bf16 *__restrict__ Vg, const bf16 *__restrict__ KBg, bf16 *__restrict__ Og, int S, int nFull, float scale) { constexpr int RC = DT / 8; // 16B chunks per D-row constexpr int KT_T = DT / 16; // k16 tiles for QK / scoring constexpr int NT_HALF = DT / 16; // n8 tiles per PV warp (covers D/2 cols) constexpr bool VPIPE = (DT == 128); // double-buffer V too (D=128: 1 CTA/SM) const int tid = threadIdx.x; const int warp = tid >> 5, lane = tid & 31; const int rl = lane >> 2, cq = lane & 3; const int wrow = (warp & 3) * 16, whf = warp >> 2; const int qb = gridDim.x - 1 - blockIdx.x; // heavy (large-q) tiles first const size_t bh = blockIdx.y; const int t0 = qb * BLK; const int rmax = min(BLK, S - t0); extern __shared__ __align__(16) uint8_t smem[]; SLay sm; { bf16 *p = (bf16 *)smem; sm.Q = p; p += 64 * DT; sm.Kt = p; p += 2 * 64 * DT; // double-buffered stream K sm.Vt = p; p += (VPIPE ? 2 : 1) * 64 * DT; // Vt[0] = cur V at stage 0 sm.P = p; p += 64 * 64; // selbuf aliases this float *f = (float *)p; sm.rowstat = f; f += 64 * 2; sm.halfstat = f; f += 384; sm.impc = f; f += 64; sm.rowmask = (uint *)f; f += 64 * NW; sm.unimask = (uint *)f; f += NW; sm.unilist = (int16_t *)f; f += 64; sm.nuni = (int *)f; } float2 *selbuf = (float2 *)sm.P; // [64][2][TOPN] during phases D/E const bf16 *qbase = Qg + bh * (size_t)S * DT; const bf16 *kbase = Kg + bh * (size_t)S * DT; const bf16 *vbase = Vg + bh * (size_t)S * DT; const bf16 *kbbase = KBg + bh * (size_t)nFull * DT; float acc[NT_HALF][4]; // PV accumulator (this warp's row/col slice) #pragma unroll for (int nt = 0; nt < NT_HALF; ++nt) #pragma unroll for (int i = 0; i < 4; ++i) acc[nt][i] = 0.f; // cooperative tile load: global 64xDT bf16 -> smem (guarded, zero fill) auto load_tile = [&](bf16 *dst, const bf16 *src, bool guard) { uint4 z4 = make_uint4(0, 0, 0, 0); for (int i = tid; i < 64 * RC; i += 256) { int row = i / RC, c = i % RC; int off = swz(row, c, RC); bool ok = !guard || row < rmax; if (ok) cp_async16(smaddr((uint8_t *)dst + off), src + (size_t)row * DT + c * 8); else *(uint4 *)((uint8_t *)dst + off) = z4; } }; // ---- prologue: Q tile + cur K/V (Kt[0]/Vt[0]) ---- load_tile(sm.Q, qbase + (size_t)t0 * DT, true); load_tile(sm.Kt, kbase + (size_t)t0 * DT, true); // cur K in Kt[0] load_tile(sm.Vt, vbase + (size_t)t0 * DT, true); // cur V in Vt[0] cp_commit(); if (tid < 64) { sm.rowstat[tid * 2] = NEG_INF; sm.rowstat[tid * 2 + 1] = 0.f; } __syncthreads(); cp_wait<0>(); __syncthreads(); // Q A-fragments: loaded once, reused by every QK/scoring MMA uint qa[KT_T][4]; #pragma unroll for (int kt = 0; kt < KT_T; ++kt) ldm_x4(qa[kt], smaddr((uint8_t *)sm.Q + swz(wrow + (lane & 15), kt * 2 + (lane >> 4), RC))); // ============ one softmax stage over a 64-key block ===================== // mode 0: current block qb (causal j<=row); mode 1: prev block qb-1 // (window j>=row+1 OR selected); mode 2: union block `bidx` (selected only). // waitv: wait for one cp.async group (the stage's V) before PV. auto stage = [&](int mode, int bidx, const bf16 *ksrct, const bf16 *vsrct) { float sf[4][4]; // 4 n8 tiles in this col-half x 4 c-floats #pragma unroll for (int nt = 0; nt < 4; ++nt) { const int keybase = whf * 32 + nt * 8; #pragma unroll for (int i = 0; i < 4; ++i) sf[nt][i] = 0.f; for (int kt = 0; kt < KT_T; ++kt) { uint b[2]; ldm_x2(b, smaddr((uint8_t *)ksrct + swz(keybase + (lane & 7), kt * 2 + ((lane >> 3) & 1), RC))); mma_bf16(sf[nt], qa[kt], b); } } float lmax[2] = {NEG_INF, NEG_INF}, rsum[2] = {0.f, 0.f}; #pragma unroll for (int nt = 0; nt < 4; ++nt) { #pragma unroll for (int i = 0; i < 4; ++i) { const int row = wrow + rl + (i >> 1) * 8; const int col = whf * 32 + nt * 8 + 2 * cq + (i & 1); bool keep; if (mode == 0) keep = (col <= row); else if (mode == 1) keep = (col >= row + 1) || ((sm.rowmask[row * NW + ((qb - 1) >> 5)] >> ((qb - 1) & 31)) & 1u); else keep = (sm.rowmask[row * NW + (bidx >> 5)] >> (bidx & 31)) & 1u; float raw = sf[nt][i]; rsum[(i >> 1)] += keep ? raw : 0.f; float v = keep ? raw * scale : NEG_INF; sf[nt][i] = v; lmax[(i >> 1)] = fmaxf(lmax[(i >> 1)], v); } } #pragma unroll for (int g = 0; g < 2; ++g) { lmax[g] = fmaxf(lmax[g], __shfl_xor_sync(0xffffffffu, lmax[g], 1)); lmax[g] = fmaxf(lmax[g], __shfl_xor_sync(0xffffffffu, lmax[g], 2)); rsum[g] += __shfl_xor_sync(0xffffffffu, rsum[g], 1); rsum[g] += __shfl_xor_sync(0xffffffffu, rsum[g], 2); } if (cq == 0) { #pragma unroll for (int g = 0; g < 2; ++g) { const int row = wrow + rl + g * 8; sm.halfstat[row * 4 + whf] = lmax[g]; sm.halfstat[256 + row * 2 + whf] = rsum[g]; } } __syncthreads(); float mc[2], cr[2], lsum[2] = {0.f, 0.f}; #pragma unroll for (int g = 0; g < 2; ++g) { const int row = wrow + rl + g * 8; const float m_old = sm.rowstat[row * 2]; const float m_comb = fmaxf(sm.halfstat[row * 4 + 0], sm.halfstat[row * 4 + 1]); mc[g] = fmaxf(m_old, m_comb); cr[g] = __expf(m_old - mc[g]); } if (mode == 0 && whf == 0 && cq == 0) { #pragma unroll for (int g = 0; g < 2; ++g) { const int row = wrow + rl + g * 8; sm.impc[row] = (sm.halfstat[256 + row * 2] + sm.halfstat[256 + row * 2 + 1]) * scale / (float)(row + 1); } } #pragma unroll for (int nt = 0; nt < 4; ++nt) { #pragma unroll for (int i = 0; i < 4; ++i) { const int g = i >> 1; const float p = __expf(sf[nt][i] - mc[g]); sf[nt][i] = p; lsum[g] += p; } #pragma unroll for (int i = 0; i < 4; i += 2) { const int row = wrow + rl + (i >> 1) * 8; const int col = whf * 32 + nt * 8 + 2 * cq; *(bf162 *)((uint8_t *)sm.P + swz(row, col >> 3, 8) + (col & 7) * 2) = __floats2bfloat162_rn(sf[nt][i], sf[nt][i + 1]); } } #pragma unroll for (int g = 0; g < 2; ++g) { lsum[g] += __shfl_xor_sync(0xffffffffu, lsum[g], 1); lsum[g] += __shfl_xor_sync(0xffffffffu, lsum[g], 2); if (cq == 0) { const int row = wrow + rl + g * 8; sm.halfstat[row * 4 + whf] = lsum[g]; // exp-sum halves (overwrite max) } } cp_wait<1>(); // this stage's V group ready (<=1 pending: next K) __syncthreads(); if (whf == 0 && cq == 0) { #pragma unroll for (int g = 0; g < 2; ++g) { const int row = wrow + rl + g * 8; sm.rowstat[row * 2] = mc[g]; sm.rowstat[row * 2 + 1] = sm.rowstat[row * 2 + 1] * cr[g] + sm.halfstat[row * 4 + 0] + sm.halfstat[row * 4 + 1]; } } #pragma unroll for (int nt = 0; nt < NT_HALF; ++nt) { #pragma unroll for (int i = 0; i < 4; ++i) acc[nt][i] *= cr[i >> 1]; } uint pa[4][4]; #pragma unroll for (int kt = 0; kt < 4; ++kt) ldm_x4(pa[kt], smaddr((uint8_t *)sm.P + swz(wrow + (lane & 15), kt * 2 + (lane >> 4), 8))); for (int nt = 0; nt < NT_HALF; ++nt) { const int colbase = whf * (DT / 2) + nt * 8; #pragma unroll for (int kt = 0; kt < 4; ++kt) { uint b[2]; ldm_x2t(b, smaddr((uint8_t *)vsrct + swz(kt * 16 + (lane & 15), colbase >> 3, RC))); mma_bf16(acc[nt], pa[kt], b); } } __syncthreads(); }; // ---- Phase B: current block (causal) — V already resident ---- stage(0, 0, sm.Kt, sm.Vt); // ---- prefetch kbar staging + stream block 0 during phases D/E ---- // group order: {kbar blocks 0-63 -> Kt[1]}, {kbar blocks 64-127 -> Vt[0]}, // {stream K of block qb-1 -> Kt[0]} (consumed by phase F). if (qb > 0) { auto load_kbar_pass = [&](bf16 *dst, int cbase) { uint4 z4 = make_uint4(0, 0, 0, 0); for (int i = tid; i < 64 * RC; i += 256) { int row = i / RC, c = i % RC; int off = swz(row, c, RC); if (cbase + row < qb) // qb-1 <= nFull-1, so within kbar bounds cp_async16(smaddr((uint8_t *)dst + off), kbbase + ((size_t)cbase + row) * DT + c * 8); else *(uint4 *)((uint8_t *)dst + off) = z4; } cp_commit(); }; load_kbar_pass(sm.Kt + 64 * DT, 0); // pass 0 load_kbar_pass(sm.Vt, 64); // pass 1 (zero-filled if qb <= 64) load_tile(sm.Kt, kbase + (size_t)(qb - 1) * BLK * DT, false); // -> Kt[0] cp_commit(); } // ---- Phase D: full-block importance scoring (MMA over 64-block passes) ---- if (qb > 0 && qb < TOPN) { // every causal block fits in the top-8; no ranking work needed if (tid < 64) { const int row = tid; #pragma unroll for (int w = 0; w < NW; ++w) sm.rowmask[row * NW + w] = 0u; for (int b = 0; b < qb; ++b) sm.rowmask[row * NW + (b >> 5)] |= 1u << (b & 31); } if (tid < NW) sm.unimask[tid] = 0u; __syncthreads(); if (tid < 64) { const int row = tid; #pragma unroll for (int w = 0; w < NW; ++w) { uint mm = sm.rowmask[row * NW + w]; if (((qb - 1) >> 5) == w) mm &= ~(1u << ((qb - 1) & 31)); if (mm) atomicOr(&sm.unimask[w], mm); } } __syncthreads(); if (warp == 0) { int cnt = 0; for (int w = 0; w < NW; ++w) { uint mm = __shfl_sync(0xffffffffu, sm.unimask[w], 0); while (mm) { int b = __ffs(mm) - 1; mm &= mm - 1; if (lane == 0) sm.unilist[cnt] = (int16_t)(w * 32 + b); ++cnt; } } if (lane == 0) sm.nuni[0] = cnt; } __syncthreads(); } else if (qb > 0) { // per-warp-half fragment: 2 passes x 4 n8 tiles float imp[2][4][4]; #pragma unroll for (int c = 0; c < 2; ++c) #pragma unroll for (int nt = 0; nt < 4; ++nt) #pragma unroll for (int i = 0; i < 4; ++i) imp[c][nt][i] = 0.f; // mma accumulates // pass 0: kbar[0..64) staged in Kt[1]; group order: {kbar0, kbar1, Kseq0} cp_wait<2>(); __syncthreads(); #pragma unroll for (int c = 0; c < 2; ++c) { if (c == 1) { cp_wait<1>(); __syncthreads(); } const bf16 *kbc = (c == 0) ? (sm.Kt + 64 * DT) : sm.Vt; if (c * 64 < qb) { #pragma unroll for (int nt = 0; nt < 4; ++nt) { for (int kt = 0; kt < KT_T; ++kt) { uint b[2]; ldm_x2(b, smaddr((uint8_t *)kbc + swz(whf * 32 + nt * 8 + (lane & 7), kt * 2 + ((lane >> 3) & 1), RC))); mma_bf16(imp[c][nt], qa[kt], b); } } } } // per-quad top-8; value = imp * scale/64 consistent with imp_cur scaling // per-quad top-8; kbar is a block MEAN, so importance = (q.kbar)*scale, // consistent with imp_cur = mean_causal*scale. const float nscale = scale; float cv[16]; #pragma unroll for (int g = 0; g < 2; ++g) { const int row = wrow + rl + g * 8; #pragma unroll for (int s = 0; s < 16; ++s) { const int mc = s >> 3, nt = (s >> 1) & 3, e = s & 1; const int cid = mc * 64 + whf * 32 + nt * 8 + 2 * cq + e; cv[s] = (cid < qb) ? imp[mc][nt][g * 2 + e] * nscale : NEG_INF; } for (int round = 0; round < TOPN; ++round) { float bv = NEG_INF; int bi2 = -1; #pragma unroll for (int s = 0; s < 16; ++s) { const int cid = (s >> 3) * 64 + whf * 32 + (((s >> 1) & 3) * 8) + 2 * cq + (s & 1); if (cv[s] > bv || (cv[s] == bv && cid > bi2)) { bv = cv[s]; bi2 = cid; } } #pragma unroll for (int o = 1; o <= 2; o <<= 1) { float ov = __shfl_xor_sync(0xffffffffu, bv, o); int oi = __shfl_xor_sync(0xffffffffu, bi2, o); if (ov > bv || (ov == bv && oi > bi2)) { bv = ov; bi2 = oi; } } if (cq == 0) { if (bi2 >= 0) selbuf[(row * 2 + whf) * TOPN + round] = make_float2(bv, (float)bi2); else selbuf[(row * 2 + whf) * TOPN + round] = make_float2(NEG_INF, -1.f); } #pragma unroll for (int s = 0; s < 16; ++s) { const int cid = (s >> 3) * 64 + whf * 32 + (((s >> 1) & 3) * 8) + 2 * cq + (s & 1); if (cid == bi2) cv[s] = NEG_INF; } } } __syncthreads(); } // ---- Phase E: merge -> sel lists, rowmask, union list ---- if (qb >= TOPN) { if (tid < 64) { const int row = tid; float tv[TOPN]; int ti[TOPN]; #pragma unroll for (int p = 0; p < TOPN; ++p) { tv[p] = NEG_INF; ti[p] = -1; } auto ins = [&](float v, int idx) { #pragma unroll for (int p = 0; p < TOPN; ++p) if (v > tv[p] || (v == tv[p] && idx > ti[p])) { float xv = tv[p]; int xi = ti[p]; tv[p] = v; ti[p] = idx; v = xv; idx = xi; } }; for (int h = 0; h < 2; ++h) for (int p = 0; p < TOPN; ++p) { float2 e = selbuf[(row * 2 + h) * TOPN + p]; if (e.y >= 0.f && e.x != NEG_INF) ins(e.x, (int)e.y); } ins(sm.impc[row], qb); uint m[NW] = {0u, 0u, 0u, 0u}; #pragma unroll for (int p = 0; p < TOPN; ++p) { int bi2 = ti[p]; if (bi2 >= 0 && bi2 <= qb - 1) m[bi2 >> 5] |= 1u << (bi2 & 31); } #pragma unroll for (int w = 0; w < NW; ++w) sm.rowmask[row * NW + w] = m[w]; } __syncthreads(); if (tid < NW) sm.unimask[tid] = 0u; __syncthreads(); if (tid < 64) { const int row = tid; #pragma unroll for (int w = 0; w < NW; ++w) { uint mm = sm.rowmask[row * NW + w]; if (((qb - 1) >> 5) == w) mm &= ~(1u << ((qb - 1) & 31)); if (mm) atomicOr(&sm.unimask[w], mm); } } __syncthreads(); if (warp == 0) { int cnt = 0; for (int w = 0; w < NW; ++w) { uint mm = __shfl_sync(0xffffffffu, sm.unimask[w], 0); while (mm) { int b = __ffs(mm) - 1; mm &= mm - 1; if (lane == 0) sm.unilist[cnt] = (int16_t)(w * 32 + b); ++cnt; } } if (lane == 0) sm.nuni[0] = cnt; } __syncthreads(); } else if (qb == 0) { if (tid < 64) { #pragma unroll for (int w = 0; w < NW; ++w) sm.rowmask[tid * NW + w] = 0u; } if (tid == 0) sm.nuni[0] = 0; __syncthreads(); } // ---- Phase F: pipelined stream over seq = [qb-1, union...] ---- { const int nuni = sm.nuni[0]; const int nseq = (qb > 0) ? 1 + nuni : 0; for (int i = 0; i < nseq; ++i) { const int bi = (i == 0) ? (qb - 1) : sm.unilist[i - 1]; const bf16 *kbuf = sm.Kt + (i & 1) * 64 * DT; bf16 *vbuf = sm.Vt + (VPIPE ? (i & 1) : 0) * 64 * DT; // issue this stage's V (overlaps S compute) load_tile(vbuf, vbase + (size_t)bi * BLK * DT, false); cp_commit(); // issue next stage's K into the other buffer if (i + 1 < nseq) { const int bn = sm.unilist[i]; // i+1 >= 1 -> union index i load_tile(sm.Kt + ((i + 1) & 1) * 64 * DT, kbase + (size_t)bn * BLK * DT, false); } cp_commit(); cp_wait<2>(); // K_i ready (3 groups in flight <-> wait until <=2) __syncthreads(); stage(i == 0 ? 1 : 2, bi, kbuf, vbuf); } } // ---- Phase G: write O ---- { float invl[2]; #pragma unroll for (int g = 0; g < 2; ++g) { const int row = wrow + rl + g * 8; invl[g] = 1.f / sm.rowstat[row * 2 + 1]; } #pragma unroll for (int nt = 0; nt < NT_HALF; ++nt) { const int colbase = whf * (DT / 2) + nt * 8; #pragma unroll for (int i = 0; i < 4; i += 2) { const int row = wrow + rl + (i >> 1) * 8; *(bf162 *)((uint8_t *)sm.Vt + swz(row, (colbase + 2 * cq) >> 3, RC) + ((colbase + 2 * cq) & 7) * 2) = __floats2bfloat162_rn(acc[nt][i] * invl[i >> 1], acc[nt][i + 1] * invl[i >> 1]); } } __syncthreads(); bf16 *obase = Og + bh * (size_t)S * DT + (size_t)t0 * DT; for (int i = tid; i < 64 * RC; i += 256) { int row = i / RC, c = i % RC; if (row < rmax) *(uint4 *)(obase + (size_t)row * DT + c * 8) = *(uint4 *)((uint8_t *)sm.Vt + swz(row, c, RC)); } } } __device__ __forceinline__ uint pack_bf16(float x, float y) { bf162 t = __floats2bfloat162_rn(x, y); return *(uint *)&t; } struct SLay2 { bf16 *Q, *Kt, *Vt; float *impc; // [64] float2 *selbuf; // [64][2][TOPN] uint *rowmask; // [64][NW] uint *unimask; // [NW] int16_t *unilist; // [128] int *nuni; }; template __global__ void __launch_bounds__(128, (DT == 64 ? 2 : 1)) nsa_tile128_kernel( const bf16 *__restrict__ Qg, const bf16 *__restrict__ Kg, const bf16 *__restrict__ Vg, const bf16 *__restrict__ KBg, bf16 *__restrict__ Og, int S, int nFull, float scale) { constexpr int RC = DT / 8; constexpr int KT_T = DT / 16; constexpr int NT_D = DT / 8; // n8 tiles across full D for PV const int tid = threadIdx.x; const int warp = tid >> 5, lane = tid & 31; const int rl = lane >> 2, cq = lane & 3; const int wrow = warp * 16; // rows owned by this warp const int qb = gridDim.x - 1 - blockIdx.x; // heavy tiles first const size_t bh = blockIdx.y; const int t0 = qb * BLK; const int rmax = min(BLK, S - t0); extern __shared__ __align__(16) uint8_t smem[]; SLay2 sm; { bf16 *p = (bf16 *)smem; sm.Q = p; p += 64 * DT; sm.Kt = p; p += 2 * 64 * DT; sm.Vt = p; p += 2 * 64 * DT; float *f = (float *)p; sm.impc = f; f += 64; sm.selbuf = (float2 *)f; f += 64 * 2 * TOPN * 2; sm.rowmask = (uint *)f; f += 64 * NW; sm.unimask = (uint *)f; f += NW; sm.unilist = (int16_t *)f; f += 64; sm.nuni = (int *)f; } const bf16 *qbase = Qg + bh * (size_t)S * DT; const bf16 *kbase = Kg + bh * (size_t)S * DT; const bf16 *vbase = Vg + bh * (size_t)S * DT; const bf16 *kbbase = KBg + bh * (size_t)nFull * DT; float acc[NT_D][4]; // [n8 tile][c-frag] (c-frag covers rows rl, rl+8) #pragma unroll for (int nt = 0; nt < NT_D; ++nt) #pragma unroll for (int i = 0; i < 4; ++i) acc[nt][i] = 0.f; float mrow[2] = {NEG_INF, NEG_INF}, lrow[2] = {0.f, 0.f}; auto load_tile = [&](bf16 *dst, const bf16 *src, bool guard) { uint4 z4 = make_uint4(0, 0, 0, 0); for (int i = tid; i < 64 * RC; i += 128) { int row = i / RC, c = i % RC; int off = swz(row, c, RC); bool ok = !guard || row < rmax; if (ok) cp_async16(smaddr((uint8_t *)dst + off), src + (size_t)row * DT + c * 8); else *(uint4 *)((uint8_t *)dst + off) = z4; } }; // ---- prologue: Q + cur K/V ---- load_tile(sm.Q, qbase + (size_t)t0 * DT, true); load_tile(sm.Kt, kbase + (size_t)t0 * DT, true); load_tile(sm.Vt, vbase + (size_t)t0 * DT, true); cp_commit(); __syncthreads(); cp_wait<0>(); __syncthreads(); uint qa[KT_T][4]; #pragma unroll for (int kt = 0; kt < KT_T; ++kt) ldm_x4(qa[kt], smaddr((uint8_t *)sm.Q + swz(wrow + (lane & 15), kt * 2 + (lane >> 4), RC))); // ============ warp-local softmax stage over one 64-key block ============ auto stage = [&](int mode, int bidx, const bf16 *ksrct, const bf16 *vsrct) { float sf[8][4]; #pragma unroll for (int nt = 0; nt < 8; ++nt) { const int keybase = nt * 8; #pragma unroll for (int i = 0; i < 4; ++i) sf[nt][i] = 0.f; for (int kt = 0; kt < KT_T; ++kt) { uint b[2]; ldm_x2(b, smaddr((uint8_t *)ksrct + swz(keybase + (lane & 7), kt * 2 + ((lane >> 3) & 1), RC))); mma_bf16(sf[nt], qa[kt], b); } } float lmax[2] = {NEG_INF, NEG_INF}, rsum[2] = {0.f, 0.f}; #pragma unroll for (int nt = 0; nt < 8; ++nt) { #pragma unroll for (int i = 0; i < 4; ++i) { const int row = wrow + rl + (i >> 1) * 8; const int col = nt * 8 + 2 * cq + (i & 1); bool keep; if (mode == 0) keep = (col <= row); else if (mode == 1) keep = (col >= row + 1) || ((sm.rowmask[row * NW + ((qb - 1) >> 5)] >> ((qb - 1) & 31)) & 1u); else keep = (sm.rowmask[row * NW + (bidx >> 5)] >> (bidx & 31)) & 1u; float raw = sf[nt][i]; rsum[(i >> 1)] += keep ? raw : 0.f; float v = keep ? raw * scale : NEG_INF; sf[nt][i] = v; lmax[(i >> 1)] = fmaxf(lmax[(i >> 1)], v); } } #pragma unroll for (int g = 0; g < 2; ++g) { lmax[g] = fmaxf(lmax[g], __shfl_xor_sync(0xffffffffu, lmax[g], 1)); lmax[g] = fmaxf(lmax[g], __shfl_xor_sync(0xffffffffu, lmax[g], 2)); rsum[g] += __shfl_xor_sync(0xffffffffu, rsum[g], 1); rsum[g] += __shfl_xor_sync(0xffffffffu, rsum[g], 2); } if (mode == 0 && cq == 0) { #pragma unroll for (int g = 0; g < 2; ++g) sm.impc[wrow + rl + g * 8] = rsum[g] * scale / (float)(wrow + rl + g * 8 + 1); } // online update per row (quad-local, replicated across the quad) float mc[2], cr[2]; #pragma unroll for (int g = 0; g < 2; ++g) { mc[g] = fmaxf(mrow[g], lmax[g]); cr[g] = __expf(mrow[g] - mc[g]); mrow[g] = mc[g]; } #pragma unroll for (int nt = 0; nt < 8; ++nt) { #pragma unroll for (int i = 0; i < 4; ++i) { const int g = i >> 1; sf[nt][i] = __expf(sf[nt][i] - mc[g]); } } float lsum[2] = {0.f, 0.f}; #pragma unroll for (int nt = 0; nt < 8; ++nt) #pragma unroll for (int i = 0; i < 4; ++i) lsum[i >> 1] += sf[nt][i]; #pragma unroll for (int g = 0; g < 2; ++g) { lsum[g] += __shfl_xor_sync(0xffffffffu, lsum[g], 1); lsum[g] += __shfl_xor_sync(0xffffffffu, lsum[g], 2); lrow[g] = lrow[g] * cr[g] + lsum[g]; } // rescale acc and PV with register-packed P a-fragments #pragma unroll for (int nt = 0; nt < NT_D; ++nt) #pragma unroll for (int i = 0; i < 4; ++i) acc[nt][i] *= cr[(i >> 1) & 1]; #pragma unroll for (int nt = 0; nt < NT_D; ++nt) { const int colbase = nt * 8; #pragma unroll for (int kt = 0; kt < 4; ++kt) { uint a[4], b[2]; a[0] = pack_bf16(sf[2 * kt][0], sf[2 * kt][1]); a[1] = pack_bf16(sf[2 * kt][2], sf[2 * kt][3]); a[2] = pack_bf16(sf[2 * kt + 1][0], sf[2 * kt + 1][1]); a[3] = pack_bf16(sf[2 * kt + 1][2], sf[2 * kt + 1][3]); ldm_x2t(b, smaddr((uint8_t *)vsrct + swz(kt * 16 + (lane & 15), colbase >> 3, RC))); mma_bf16(acc[nt], a, b); } } }; // ---- Phase B: current block (causal) ---- stage(0, 0, sm.Kt, sm.Vt); // ---- prefetch: stream block K0/V0 + kbar staging (during D/E) ---- if (qb > 0) { auto load_kbar_pass = [&](bf16 *dst, int cbase) { uint4 z4 = make_uint4(0, 0, 0, 0); for (int i = tid; i < 64 * RC; i += 128) { int row = i / RC, c = i % RC; int off = swz(row, c, RC); if (cbase + row < qb) cp_async16(smaddr((uint8_t *)dst + off), kbbase + ((size_t)cbase + row) * DT + c * 8); else *(uint4 *)((uint8_t *)dst + off) = z4; } cp_commit(); }; load_kbar_pass(sm.Kt + 64 * DT, 0); // kbar pass0 -> Kt[1] load_kbar_pass(sm.Vt + 64 * DT, 64); // kbar pass1 -> Vt[1] load_tile(sm.Kt, kbase + (size_t)(qb - 1) * BLK * DT, false); // K0 -> Kt[0] cp_commit(); load_tile(sm.Vt, vbase + (size_t)(qb - 1) * BLK * DT, false); // V0 -> Vt[0] cp_commit(); } // ---- Phase D: full-block importance (MMA x kbar^T, 2 passes of 64) ---- if (qb > 0 && qb < TOPN) { if (tid < 64) { const int row = tid; #pragma unroll for (int w = 0; w < NW; ++w) sm.rowmask[row * NW + w] = 0u; for (int b = 0; b < qb; ++b) sm.rowmask[row * NW + (b >> 5)] |= 1u << (b & 31); } if (tid < NW) sm.unimask[tid] = 0u; __syncthreads(); if (tid < 64) { const int row = tid; #pragma unroll for (int w = 0; w < NW; ++w) { uint mm = sm.rowmask[row * NW + w]; if (((qb - 1) >> 5) == w) mm &= ~(1u << ((qb - 1) & 31)); if (mm) atomicOr(&sm.unimask[w], mm); } } __syncthreads(); if (warp == 0) { int cnt = 0; for (int w = 0; w < NW; ++w) { uint mm = __shfl_sync(0xffffffffu, sm.unimask[w], 0); while (mm) { int b = __ffs(mm) - 1; mm &= mm - 1; if (lane == 0) sm.unilist[cnt] = (int16_t)(w * 32 + b); ++cnt; } } if (lane == 0) sm.nuni[0] = cnt; } __syncthreads(); } else if (qb >= TOPN) { // warps split candidate cols (w%2); each warp scores rows 16*(w/2) and // 16*(w/2)+32 in two passes so all 64 rows get scored. const int dhf = warp & 1; cp_wait<2>(); // kbar pass0 (groups complete in commit order) #pragma unroll for (int rr = 0; rr < 2; ++rr) { const int drow = (warp >> 1) * 16 + rr * 32; float imp[2][4][4]; #pragma unroll for (int c = 0; c < 2; ++c) #pragma unroll for (int nt = 0; nt < 4; ++nt) #pragma unroll for (int i = 0; i < 4; ++i) imp[c][nt][i] = 0.f; uint dq[KT_T][4]; #pragma unroll for (int kt = 0; kt < KT_T; ++kt) ldm_x4(dq[kt], smaddr((uint8_t *)sm.Q + swz(drow + (lane & 15), kt * 2 + (lane >> 4), RC))); if (rr == 1) { cp_wait<1>(); } // kbar pass1 ready __syncthreads(); #pragma unroll for (int c = 0; c < 2; ++c) { if (c * 64 >= qb) break; const bf16 *kbc = (c == 0) ? (sm.Kt + 64 * DT) : (sm.Vt + 64 * DT); #pragma unroll for (int nt = 0; nt < 4; ++nt) { for (int kt = 0; kt < KT_T; ++kt) { uint b[2]; ldm_x2(b, smaddr((uint8_t *)kbc + swz(dhf * 32 + nt * 8 + (lane & 7), kt * 2 + ((lane >> 3) & 1), RC))); mma_bf16(imp[c][nt], dq[kt], b); } } } // per-quad top-8 over this half's candidates float cv[16]; #pragma unroll for (int g = 0; g < 2; ++g) { const int row = drow + rl + g * 8; #pragma unroll for (int s = 0; s < 16; ++s) { const int mc = s >> 3, nt = (s >> 1) & 3, e = s & 1; const int cid = mc * 64 + dhf * 32 + nt * 8 + 2 * cq + e; cv[s] = (cid < qb) ? imp[mc][nt][g * 2 + e] * scale : NEG_INF; } for (int round = 0; round < TOPN; ++round) { float bv = NEG_INF; int bi2 = -1; #pragma unroll for (int s = 0; s < 16; ++s) { const int cid = (s >> 3) * 64 + dhf * 32 + (((s >> 1) & 3) * 8) + 2 * cq + (s & 1); if (cv[s] > bv || (cv[s] == bv && cid > bi2)) { bv = cv[s]; bi2 = cid; } } #pragma unroll for (int o = 1; o <= 2; o <<= 1) { float ov = __shfl_xor_sync(0xffffffffu, bv, o); int oi = __shfl_xor_sync(0xffffffffu, bi2, o); if (ov > bv || (ov == bv && oi > bi2)) { bv = ov; bi2 = oi; } } if (cq == 0) { if (bi2 >= 0) sm.selbuf[(row * 2 + dhf) * TOPN + round] = make_float2(bv, (float)bi2); else sm.selbuf[(row * 2 + dhf) * TOPN + round] = make_float2(NEG_INF, -1.f); } #pragma unroll for (int s = 0; s < 16; ++s) { const int cid = (s >> 3) * 64 + dhf * 32 + (((s >> 1) & 3) * 8) + 2 * cq + (s & 1); if (cid == bi2) cv[s] = NEG_INF; } } } } __syncthreads(); // ---- Phase E: merge -> rowmask, union list ---- if (tid < 64) { const int row = tid; float tv[TOPN]; int ti[TOPN]; #pragma unroll for (int p = 0; p < TOPN; ++p) { tv[p] = NEG_INF; ti[p] = -1; } auto ins = [&](float v, int idx) { #pragma unroll for (int p = 0; p < TOPN; ++p) if (v > tv[p] || (v == tv[p] && idx > ti[p])) { float xv = tv[p]; int xi = ti[p]; tv[p] = v; ti[p] = idx; v = xv; idx = xi; } }; for (int h = 0; h < 2; ++h) for (int p = 0; p < TOPN; ++p) { float2 e = sm.selbuf[(row * 2 + h) * TOPN + p]; if (e.y >= 0.f && e.x != NEG_INF) ins(e.x, (int)e.y); } if (row >= rmax) { /* dead row: sel irrelevant */ } ins(sm.impc[row], qb); uint m[NW] = {0u, 0u, 0u, 0u}; #pragma unroll for (int p = 0; p < TOPN; ++p) { int bi2 = ti[p]; if (bi2 >= 0 && bi2 <= qb - 1) m[bi2 >> 5] |= 1u << (bi2 & 31); } #pragma unroll for (int w = 0; w < NW; ++w) sm.rowmask[row * NW + w] = m[w]; } __syncthreads(); if (tid < NW) sm.unimask[tid] = 0u; __syncthreads(); if (tid < 64) { const int row = tid; #pragma unroll for (int w = 0; w < NW; ++w) { uint mm = sm.rowmask[row * NW + w]; if (((qb - 1) >> 5) == w) mm &= ~(1u << ((qb - 1) & 31)); if (mm) atomicOr(&sm.unimask[w], mm); } } __syncthreads(); if (warp == 0) { int cnt = 0; for (int w = 0; w < NW; ++w) { uint mm = __shfl_sync(0xffffffffu, sm.unimask[w], 0); while (mm) { int b = __ffs(mm) - 1; mm &= mm - 1; if (lane == 0) sm.unilist[cnt] = (int16_t)(w * 32 + b); ++cnt; } } if (lane == 0) sm.nuni[0] = cnt; } __syncthreads(); } else { if (tid < 64) { #pragma unroll for (int w = 0; w < NW; ++w) sm.rowmask[tid * NW + w] = 0u; } if (tid == 0) sm.nuni[0] = 0; __syncthreads(); } // ---- Phase F: stream; 1 barrier per stage; K/V one stage ahead ---- { const int nuni = sm.nuni[0]; const int nseq = (qb > 0) ? 1 + nuni : 0; for (int i = 0; i < nseq; ++i) { const int bi = (i == 0) ? (qb - 1) : sm.unilist[i - 1]; const bf16 *kbuf = sm.Kt + (i & 1) * 64 * DT; bf16 *vbuf = sm.Vt + (i & 1) * 64 * DT; // prefetch next stage's K/V (empty groups keep wait counts uniform) if (i + 1 < nseq) { const int bn = sm.unilist[i]; load_tile(sm.Kt + ((i + 1) & 1) * 64 * DT, kbase + (size_t)bn * BLK * DT, false); load_tile(sm.Vt + ((i + 1) & 1) * 64 * DT, vbase + (size_t)bn * BLK * DT, false); } cp_commit(); cp_commit(); cp_wait<2>(); // K_i and V_i resident __syncthreads(); stage(i == 0 ? 1 : 2, bi, kbuf, vbuf); } } // ---- Phase G: write O ---- { float invl[2] = {1.f / lrow[0], 1.f / lrow[1]}; #pragma unroll for (int nt = 0; nt < NT_D; ++nt) { const int colbase = nt * 8; #pragma unroll for (int i = 0; i < 4; i += 2) { const int row = wrow + rl + (i >> 1) * 8; const bf162 val = __floats2bfloat162_rn(acc[nt][i] * invl[(i >> 1) & 1], acc[nt][i + 1] * invl[(i >> 1) & 1]); *(bf162 *)((uint8_t *)sm.Vt + swz(row, (colbase + 2 * cq) >> 3, RC) + ((colbase + 2 * cq) & 7) * 2) = val; } } __syncthreads(); bf16 *obase = Og + bh * (size_t)S * DT + (size_t)t0 * DT; for (int i = tid; i < 64 * RC; i += 128) { int row = i / RC, c = i % RC; if (row < rmax) *(uint4 *)(obase + (size_t)row * DT + c * 8) = *(uint4 *)((uint8_t *)sm.Vt + swz(row, c, RC)); } } } static int g_set_64 = 0, g_set_128 = 0; torch::Tensor nsa_forward(torch::Tensor q, torch::Tensor k, torch::Tensor v) { TORCH_CHECK(q.is_cuda() && k.is_cuda() && v.is_cuda(), "q,k,v must be CUDA"); TORCH_CHECK(q.dtype() == torch::kBFloat16, "q,k,v must be bf16"); q = q.contiguous(); k = k.contiguous(); v = v.contiguous(); const int Bt = q.size(0), Ht = q.size(1), S = q.size(2), D = q.size(3); TORCH_CHECK(D == 64 || D == 128, "D must be 64 or 128"); const int BH = Bt * Ht; const int nFull = S / BLK; const int nTiles = (S + BLK - 1) / BLK; const float scale = 1.f / sqrtf((float)D); auto o = torch::empty_like(q); auto stream = at::cuda::getCurrentCUDAStream(); if (nTiles <= 128) { // fast path: tile MMA kernel with fused block selection auto kbar = torch::empty({(long)BH, (long)std::max(nFull, 1), (long)D}, q.options()); if (nFull > 0) { dim3 gridk(nFull, BH); kbar_bf16_kernel<<>>( (const bf16 *)k.data_ptr(), (bf16 *)kbar.data_ptr(), S, D, nFull); } dim3 grid(nTiles, BH); if (D == 64) { // 8-warp tile kernel (better latency hiding at 2 CTAs/SM) const int vpipe = 1; const int smem_bytes = ((1 + 2 + vpipe) * 64 * D + 64 * 64) * 2 + (64 * 2 + 384 + 64) * 4 + 64 * NW * 4 + NW * 4 + 128 * 2 + 8; if (!g_set_64) { cudaFuncSetAttribute(nsa_tile64_kernel<64>, cudaFuncAttributeMaxDynamicSharedMemorySize, smem_bytes); g_set_64 = 1; } nsa_tile64_kernel<64><<>>( (const bf16 *)q.data_ptr(), (const bf16 *)k.data_ptr(), (const bf16 *)v.data_ptr(), (const bf16 *)kbar.data_ptr(), (bf16 *)o.data_ptr(), S, nFull, scale); } else { // warp-owns-row kernel (no per-stage barriers except cp.async visibility) const int smem_bytes = (5 * 64 * D) * 2 + 64 * 4 + 64 * 2 * TOPN * 8 + 64 * NW * 4 + NW * 4 + 128 * 2 + 8; if (!g_set_128) { cudaFuncSetAttribute(nsa_tile128_kernel<128>, cudaFuncAttributeMaxDynamicSharedMemorySize, smem_bytes); g_set_128 = 1; } nsa_tile128_kernel<128><<>>( (const bf16 *)q.data_ptr(), (const bf16 *)k.data_ptr(), (const bf16 *)v.data_ptr(), (const bf16 *)kbar.data_ptr(), (bf16 *)o.data_ptr(), S, nFull, scale); } } else { // fallback: warp-per-query kernel (S beyond the tile mask range) auto opts_f = q.options().dtype(torch::kFloat); auto kbar = torch::empty({(long)BH, (long)std::max(nFull, 1), (long)D}, opts_f); if (nFull > 0) { dim3 gridk(nFull, BH); kbar_kernel<<>>( (const bf16 *)k.data_ptr(), kbar.data_ptr(), S, D, nFull); } dim3 grid((S + 3) / 4, BH); if (D == 64) { nsa_warp_kernel<64><<>>( (const bf16 *)q.data_ptr(), (const bf16 *)k.data_ptr(), (const bf16 *)v.data_ptr(), kbar.data_ptr(), (bf16 *)o.data_ptr(), S, D, nFull, scale); } else { nsa_warp_kernel<128><<>>( (const bf16 *)q.data_ptr(), (const bf16 *)k.data_ptr(), (const bf16 *)v.data_ptr(), kbar.data_ptr(), (bf16 *)o.data_ptr(), S, D, nFull, scale); } } return o; } """ _CPP_SRC = "torch::Tensor nsa_forward(torch::Tensor q, torch::Tensor k, torch::Tensor v);" _ext = load_inline( name="nsa_cuda", cpp_sources=[_CPP_SRC], cuda_sources=[_CUDA_SRC], functions=["nsa_forward"], extra_cuda_cflags=["-O3", "--use_fast_math", "-std=c++17"], verbose=os.environ.get("NSA_VERBOSE_BUILD", "0") == "1", ) def nsa_attend( q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, block_size: int = BLOCK_SIZE, top_n_blocks: int = TOP_N_BLOCKS, sliding_window: int = SLIDING_WINDOW, ) -> torch.Tensor: """q,k,v: (B,H,S,D) bf16 → o (B,H,S,D) float via the CUDA kernel.""" assert block_size == 64 and top_n_blocks == 8 and sliding_window == 64 return _ext.nsa_forward(q, k, v).float() class Model(nn.Module): def __init__(self, B: int, H: int, S: int, D: int): super().__init__() self.B, self.H, self.S, self.D = B, H, S, D self.register_buffer("_dummy", torch.zeros(1, dtype=torch.bfloat16)) def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor: return _ext.nsa_forward(q, k, v) def get_init_inputs(): return [B, H, S, D] def get_inputs(): q = torch.randn(B, H, S, D, dtype=torch.bfloat16) k = torch.randn(B, H, S, D, dtype=torch.bfloat16) v = torch.randn(B, H, S, D, dtype=torch.bfloat16) return [q, k, v]