"""FP8 e4m3 GEMM: y = (x @ w.T) * weight_scale, bf16 out. sm_120 (RTX PRO 6000). Custom CUDA kernel, Blackwell-consumer tensor-core path: - fp8 tensor-core MMA (mma.sync.m16n8k32.e4m3, fp32 accumulate) - warp-specialized persistent pipeline: one elected producer thread issues TMA (cp.async.bulk.tensor.2d, SWIZZLE_128B) stage copies; consumer warps run mma off ldmatrix-composed fragments; stages tracked by mbarriers (no block-wide barriers in the hot loop) - per-output-channel dequant scale folded into the epilogue - skinny-M split-K variant with fp32 atomic workspace + finalize - misaligned-K (K % 16 != 0) repacks operands to zero-padded rows (K->K16) """ import os os.environ.setdefault("TORCH_CUDA_ARCH_LIST", "12.0a") import torch import torch.nn as nn from torch.utils.cpp_extension import load_inline _CUDA_SRC = r""" #include #include #include #include #include #include #include #include #include #define DEVINL __device__ __forceinline__ DEVINL uint32_t smem_u32(const void* p) { return static_cast(__cvta_generic_to_shared(p)); } DEVINL void mbar_init(uint32_t bar, uint32_t count) { asm volatile("mbarrier.init.shared.b64 [%0], %1;\n" ::"r"(bar), "r"(count)); } DEVINL void cp16(uint32_t dst, const void* src, bool pred) { int sz = pred ? 16 : 0; asm volatile("cp.async.cg.shared.global [%0], [%1], 16, %2;\n" ::"r"(dst), "l"(src), "r"(sz)); } DEVINL void cp_async_mbar_arrive(uint32_t bar) { asm volatile("cp.async.mbarrier.arrive.noinc.shared.b64 [%0];\n" ::"r"(bar)); } DEVINL void mbar_arrive(uint32_t bar) { asm volatile("mbarrier.arrive.shared.b64 _, [%0];\n" ::"r"(bar)); } DEVINL void mbar_arrive_expect_tx(uint32_t bar, uint32_t tx) { asm volatile("mbarrier.arrive.expect_tx.shared::cta.b64 _, [%0], %1;\n" ::"r"(bar), "r"(tx)); } DEVINL void mbar_wait(uint32_t bar, uint32_t phase) { // bounded spin with watchdog: a pipeline bug traps instead of wedging the GPU for (long it = 0;; ++it) { uint32_t done; asm volatile( "{\n\t.reg .pred P;\n\t" "mbarrier.try_wait.parity.shared.b64 P, [%1], %2;\n\t" "selp.u32 %0, 1, 0, P;\n\t}\n" : "=r"(done) : "r"(bar), "r"(phase)); if (done) return; if (it > (1L << 29)) __trap(); } } DEVINL void fence_async_shared() { asm volatile("fence.proxy.async.shared::cta;\n"); } DEVINL void tma_2d(uint32_t dst, const CUtensorMap* tmap, int x0, int y0, uint32_t bar) { asm volatile( "cp.async.bulk.tensor.2d.shared::cluster.global.mbarrier::complete_tx::bytes [%0], [%1, {%2, %3}], [%4];\n" ::"r"(dst), "l"(tmap), "r"(x0), "r"(y0), "r"(bar)); } DEVINL void ldsm_x4(uint32_t& r0, uint32_t& r1, uint32_t& r2, uint32_t& r3, uint32_t addr) { asm volatile("ldmatrix.sync.aligned.m8n8.x4.shared.b16 {%0,%1,%2,%3}, [%4];\n" : "=r"(r0), "=r"(r1), "=r"(r2), "=r"(r3) : "r"(addr)); } DEVINL void mma_fp8(float& d0, float& d1, float& d2, float& d3, uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t b0, uint32_t b1) { asm volatile( "mma.sync.aligned.m16n8k32.row.col.f32.e4m3.e4m3.f32 " "{%0,%1,%2,%3}, {%4,%5,%6,%7}, {%8,%9}, {%0,%1,%2,%3};\n" : "+f"(d0), "+f"(d1), "+f"(d2), "+f"(d3) : "r"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(b0), "r"(b1)); } // TMA SWIZZLE_128B layout (verified identical to TMA's swizzle atom): // 16B-chunk g of row r lands at r*128 + (g ^ (r&7))*16 DEVINL int swz128(int r, int g) { return r * 128 + ((g ^ (r & 7)) * 16); } // --------------------------------------------------------------------------- // Warp-specialized persistent fp8 GEMM with TMA producer. // grid.x over tiles (persistent-capped for the big path), grid.y for split-K. // --------------------------------------------------------------------------- template __global__ void __launch_bounds__(NCONS * 32 + 32, 1) fp8_ws_kernel( const __grid_constant__ CUtensorMap tmapA, const __grid_constant__ CUtensorMap tmapB, const float* __restrict__ scale, void* __restrict__ y, int M, int N, int K) { static_assert(BK == 128, "BK=128 assumed"); constexpr int WARPS_N = BN / WN; static_assert((BM / WM) * (BN / WN) == NCONS, "consumer warp grid mismatch"); extern __shared__ uint8_t smem[]; uint8_t* smA = smem; uint8_t* smB = smem + STAGES * BM * BK; uint64_t* bars = reinterpret_cast(smB + STAGES * BN * BK); const int tid = threadIdx.x; const int warp = tid >> 5; const int lane = tid & 31; const int num_m = (M + BM - 1) / BM; const int num_n = (N + BN - 1) / BN; const int kt_total = (K + BK - 1) / BK; int kt_begin = 0, kt_end = kt_total; if (SPLITK) { int kt_per = (kt_total + gridDim.y - 1) / gridDim.y; kt_begin = blockIdx.y * kt_per; kt_end = min(kt_total, kt_begin + kt_per); if (kt_begin >= kt_end) return; // this split owns no work } const int num_kt = kt_end - kt_begin; const int ntiles = num_m * num_n; const int gsz = gridDim.x; auto tile_coords = [&](int t, int& tm, int& tn) { int in_group = GROUP_M * num_n; int gid = t / in_group; int first_m = gid * GROUP_M; int size_m = min(num_m - first_m, GROUP_M); tm = first_m + (t % size_m); tn = (t % in_group) / size_m; }; if (tid == 0) { for (int s = 0; s < STAGES; ++s) { mbar_init(smem_u32(&bars[s]), 1); mbar_init(smem_u32(&bars[STAGES + s]), 32 * NCONS); } asm volatile("fence.mbarrier_init.release.cluster;\n"); } fence_async_shared(); __syncthreads(); if (warp >= NCONS) { // ---------------- producer: single elected TMA thread ---------------- if (tid == NCONS * 32) { uint32_t bar_full_base = smem_u32(&bars[0]); uint32_t bar_empty_base = smem_u32(&bars[STAGES]); int tL = blockIdx.x, ktL = kt_begin; int lm0, ln0; { int tm, tn; tile_coords(tL, tm, tn); lm0 = tm * BM; ln0 = tn * BN; } long it_load = 0; while (tL < ntiles) { if (it_load >= STAGES) mbar_wait(bar_empty_base + (it_load % STAGES) * 8, (uint32_t)(((it_load / STAGES) - 1) & 1)); int stage = (int)(it_load % STAGES); uint32_t fb = bar_full_base + stage * 8; mbar_arrive_expect_tx(fb, (BM + BN) * BK); tma_2d(smem_u32(smA + stage * BM * BK), &tmapA, ktL * BK, lm0, fb); tma_2d(smem_u32(smB + stage * BN * BK), &tmapB, ktL * BK, ln0, fb); ++it_load; if (++ktL == kt_end) { ktL = kt_begin; tL += gsz; if (tL < ntiles) { int tm, tn; tile_coords(tL, tm, tn); lm0 = tm * BM; ln0 = tn * BN; } } } } } else { // ---------------- consumer ---------------- const int warp_m = (warp / WARPS_N) * WM; const int warp_n = (warp % WARPS_N) * WN; uint32_t bar_full_base = smem_u32(&bars[0]); uint32_t bar_empty_base = smem_u32(&bars[STAGES]); uint32_t afrag[2][WM / 16][4]; uint32_t bfrag[2][WN / 8][2]; auto load_frags = [&](int st, int kstep, int buf) { const uint8_t* sA = smA + st * BM * BK; #pragma unroll for (int mi = 0; mi < WM / 16; ++mi) { int lrow = warp_m + mi * 16 + (lane & 7) + ((lane >> 3) & 1) * 8; int g = kstep * 2 + (lane >> 4); ldsm_x4(afrag[buf][mi][0], afrag[buf][mi][1], afrag[buf][mi][2], afrag[buf][mi][3], smem_u32(sA + swz128(lrow, g))); } const uint8_t* sB = smB + st * BN * BK; static_assert((WN / 8) % 4 == 0, "ldsm.x4 groups 4 n8 tiles"); #pragma unroll for (int nib = 0; nib < WN / 8 / 4; ++nib) { int j = lane >> 3; int row = warp_n + (nib * 4 + j) * 8 + (lane & 7); uint32_t a0 = smem_u32(sB + swz128(row, kstep * 2)); uint32_t a1 = smem_u32(sB + swz128(row, kstep * 2 + 1)); uint32_t r0, r1, r2, r3, s0, s1, s2, s3; ldsm_x4(r0, r1, r2, r3, a0); ldsm_x4(s0, s1, s2, s3, a1); bfrag[buf][nib * 4 + 0][0] = r0; bfrag[buf][nib * 4 + 0][1] = s0; bfrag[buf][nib * 4 + 1][0] = r1; bfrag[buf][nib * 4 + 1][1] = s1; bfrag[buf][nib * 4 + 2][0] = r2; bfrag[buf][nib * 4 + 2][1] = s2; bfrag[buf][nib * 4 + 3][0] = r3; bfrag[buf][nib * 4 + 3][1] = s3; } }; float acc[WM / 16][WN / 8][4]; long it_comp = 0; for (int tC = blockIdx.x; tC < ntiles; tC += gsz) { int tm, tn; tile_coords(tC, tm, tn); const int m0 = tm * BM; const int n0 = tn * BN; #pragma unroll for (int mi = 0; mi < WM / 16; ++mi) #pragma unroll for (int ni = 0; ni < WN / 8; ++ni) #pragma unroll for (int r = 0; r < 4; ++r) acc[mi][ni][r] = 0.f; for (int kt = 0; kt < num_kt; ++kt) { int stage = (int)(it_comp % STAGES); mbar_wait(bar_full_base + stage * 8, (uint32_t)((it_comp / STAGES) & 1)); load_frags(stage, 0, 0); #pragma unroll for (int ks = 0; ks < 4; ++ks) { if (ks < 3) load_frags(stage, ks + 1, (ks + 1) & 1); int cur = ks & 1; #pragma unroll for (int mi = 0; mi < WM / 16; ++mi) #pragma unroll for (int ni = 0; ni < WN / 8; ++ni) mma_fp8(acc[mi][ni][0], acc[mi][ni][1], acc[mi][ni][2], acc[mi][ni][3], afrag[cur][mi][0], afrag[cur][mi][1], afrag[cur][mi][2], afrag[cur][mi][3], bfrag[cur][ni][0], bfrag[cur][ni][1]); } mbar_arrive(bar_empty_base + stage * 8); ++it_comp; } if (SPLITK) { float* yw = reinterpret_cast(y); #pragma unroll for (int mi = 0; mi < WM / 16; ++mi) { #pragma unroll for (int ni = 0; ni < WN / 8; ++ni) { int r = m0 + warp_m + mi * 16 + (lane >> 2); int c = n0 + warp_n + ni * 8 + (lane & 3) * 2; if (c < N) { if (r < M) { atomicAdd(yw + (int64_t)r * N + c, acc[mi][ni][0]); } if (r + 8 < M) { atomicAdd(yw + (int64_t)(r + 8) * N + c, acc[mi][ni][2]); } } if (c + 1 < N) { if (r < M) { atomicAdd(yw + (int64_t)r * N + c + 1, acc[mi][ni][1]); } if (r + 8 < M) { atomicAdd(yw + (int64_t)(r + 8) * N + c + 1, acc[mi][ni][3]); } } } } } else { __nv_bfloat16* yb = reinterpret_cast<__nv_bfloat16*>(y); #pragma unroll for (int mi = 0; mi < WM / 16; ++mi) { #pragma unroll for (int ni = 0; ni < WN / 8; ++ni) { int r = m0 + warp_m + mi * 16 + (lane >> 2); int c = n0 + warp_n + ni * 8 + (lane & 3) * 2; float s0 = 0.f, s1 = 0.f; if (c + 1 < N) { s0 = scale[c]; s1 = scale[c + 1]; } __nv_bfloat162 plo(__float2bfloat16(acc[mi][ni][0] * s0), __float2bfloat16(acc[mi][ni][1] * s1)); __nv_bfloat162 phi(__float2bfloat16(acc[mi][ni][2] * s0), __float2bfloat16(acc[mi][ni][3] * s1)); if (r < M && c + 1 < N) *reinterpret_cast<__nv_bfloat162*>(yb + (int64_t)r * N + c) = plo; if (r + 8 < M && c + 1 < N) *reinterpret_cast<__nv_bfloat162*>(yb + (int64_t)(r + 8) * N + c) = phi; } } } } } } // Skinny-M split-K GEMM with LDGSTS producers (best DRAM streaming pattern). // Tile (32 x BN), 4 consumer warps, 4 producer warps, stages x (32+BN)*128B smem. template __global__ void __launch_bounds__(NCONS * 32 + 128, 1) fp8_skinny_kernel( const uint8_t* __restrict__ x, const uint8_t* __restrict__ w, const float* __restrict__ scale, float* __restrict__ acc_ws, int M, int N, int K) { constexpr int BM = 32; constexpr int WARPS_N = BN / WN; static_assert(BK == 128, "BK=128"); static_assert((BM / WM) * (BN / WN) == NCONS, "warp grid mismatch"); extern __shared__ uint8_t smem[]; uint8_t* smA = smem; uint8_t* smB = smem + STAGES * BM * BK; uint64_t* bars = reinterpret_cast(smB + STAGES * BN * BK); const int tid = threadIdx.x; const int warp = tid >> 5; const int lane = tid & 31; const int num_n = (N + BN - 1) / BN; const int n0 = blockIdx.x * BN; const int kt_total = (K + BK - 1) / BK; const int kt_per = (kt_total + gridDim.y - 1) / gridDim.y; const int kt_begin = blockIdx.y * kt_per; const int kt_end = min(kt_total, kt_begin + kt_per); if (kt_begin >= kt_end) return; if (tid == 0) { for (int s = 0; s < STAGES; ++s) { mbar_init(smem_u32(&bars[s]), 128); mbar_init(smem_u32(&bars[STAGES + s]), 32 * NCONS); } asm volatile("fence.mbarrier_init.release.cluster;\n"); } fence_async_shared(); __syncthreads(); if (warp >= NCONS) { // ---------------- producer: 128 lanes of cp.async ---------------- const int pl = tid - NCONS * 32; const int cp_r0 = pl / 8; const int cp_g0 = pl % 8; uint32_t bar_full_base = smem_u32(&bars[0]); uint32_t bar_empty_base = smem_u32(&bars[STAGES]); int stage = 0; for (int kt = kt_begin; kt < kt_end; ++kt) { long it = kt - kt_begin; (void)it; if (kt - kt_begin >= STAGES) mbar_wait(bar_empty_base + stage * 8, (uint32_t)((((kt - kt_begin) / STAGES) - 1) & 1)); int kb = kt * BK; uint8_t* dstA = smA + stage * BM * BK; #pragma unroll for (int c = 0; c < (BM * 8) / 128; ++c) { int r = cp_r0 + c * 16; int grow = r; int goff = kb + cp_g0 * 16; bool ok = (grow < M) && (goff < K); cp16(smem_u32(dstA + swz128(r, cp_g0)), x + (ok ? (int64_t)grow * K + goff : 0), ok); } uint8_t* dstB = smB + stage * BN * BK; #pragma unroll for (int c = 0; c < (BN * 8) / 128; ++c) { int r = cp_r0 + c * 16; int grow = n0 + r; int goff = kb + cp_g0 * 16; bool ok = (grow < N) && (goff < K); cp16(smem_u32(dstB + swz128(r, cp_g0)), w + (ok ? (int64_t)grow * K + goff : 0), ok); } cp_async_mbar_arrive(bar_full_base + stage * 8); stage = stage + 1 == STAGES ? 0 : stage + 1; } } else { // ---------------- consumer ---------------- const int warp_m = (warp / WARPS_N) * WM; const int warp_n = (warp % WARPS_N) * WN; uint32_t bar_full_base = smem_u32(&bars[0]); uint32_t bar_empty_base = smem_u32(&bars[STAGES]); uint32_t afrag[2][WM / 16][4]; uint32_t bfrag[2][WN / 8][2]; auto load_frags = [&](int st, int kstep, int buf) { const uint8_t* sA = smA + st * BM * BK; #pragma unroll for (int mi = 0; mi < WM / 16; ++mi) { int lrow = warp_m + mi * 16 + (lane & 7) + ((lane >> 3) & 1) * 8; int g = kstep * 2 + (lane >> 4); ldsm_x4(afrag[buf][mi][0], afrag[buf][mi][1], afrag[buf][mi][2], afrag[buf][mi][3], smem_u32(sA + swz128(lrow, g))); } const uint8_t* sB = smB + st * BN * BK; static_assert((WN / 8) % 4 == 0, "ldsm.x4 groups 4 n8 tiles"); #pragma unroll for (int nib = 0; nib < WN / 8 / 4; ++nib) { int j = lane >> 3; int row = warp_n + (nib * 4 + j) * 8 + (lane & 7); uint32_t a0 = smem_u32(sB + swz128(row, kstep * 2)); uint32_t a1 = smem_u32(sB + swz128(row, kstep * 2 + 1)); uint32_t r0, r1, r2, r3, s0, s1, s2, s3; ldsm_x4(r0, r1, r2, r3, a0); ldsm_x4(s0, s1, s2, s3, a1); bfrag[buf][nib * 4 + 0][0] = r0; bfrag[buf][nib * 4 + 0][1] = s0; bfrag[buf][nib * 4 + 1][0] = r1; bfrag[buf][nib * 4 + 1][1] = s1; bfrag[buf][nib * 4 + 2][0] = r2; bfrag[buf][nib * 4 + 2][1] = s2; bfrag[buf][nib * 4 + 3][0] = r3; bfrag[buf][nib * 4 + 3][1] = s3; } }; float acc[WM / 16][WN / 8][4]; #pragma unroll for (int mi = 0; mi < WM / 16; ++mi) #pragma unroll for (int ni = 0; ni < WN / 8; ++ni) #pragma unroll for (int r = 0; r < 4; ++r) acc[mi][ni][r] = 0.f; int stage = 0; for (int kt = kt_begin; kt < kt_end; ++kt) { long it_comp = kt - kt_begin; mbar_wait(bar_full_base + stage * 8, (uint32_t)((it_comp / STAGES) & 1)); load_frags(stage, 0, 0); #pragma unroll for (int ks = 0; ks < 4; ++ks) { if (ks < 3) load_frags(stage, ks + 1, (ks + 1) & 1); int cur = ks & 1; #pragma unroll for (int mi = 0; mi < WM / 16; ++mi) #pragma unroll for (int ni = 0; ni < WN / 8; ++ni) mma_fp8(acc[mi][ni][0], acc[mi][ni][1], acc[mi][ni][2], acc[mi][ni][3], afrag[cur][mi][0], afrag[cur][mi][1], afrag[cur][mi][2], afrag[cur][mi][3], bfrag[cur][ni][0], bfrag[cur][ni][1]); } mbar_arrive(bar_empty_base + stage * 8); stage = stage + 1 == STAGES ? 0 : stage + 1; } float* yw = acc_ws; #pragma unroll for (int mi = 0; mi < WM / 16; ++mi) { #pragma unroll for (int ni = 0; ni < WN / 8; ++ni) { int r = warp_m + mi * 16 + (lane >> 2); int c = n0 + warp_n + ni * 8 + (lane & 3) * 2; if (c < N) { if (r < M) { atomicAdd(yw + (int64_t)r * N + c, acc[mi][ni][0]); } if (r + 8 < M) { atomicAdd(yw + (int64_t)(r + 8) * N + c, acc[mi][ni][2]); } } if (c + 1 < N) { if (r < M) { atomicAdd(yw + (int64_t)r * N + c + 1, acc[mi][ni][1]); } if (r + 8 < M) { atomicAdd(yw + (int64_t)(r + 8) * N + c + 1, acc[mi][ni][3]); } } } } } } // finalize for split-K: y = acc * scale -> bf16 __global__ void finalize_kernel(const float* __restrict__ acc, const float* __restrict__ scale, __nv_bfloat16* __restrict__ y, int64_t total, int N) { int64_t i = (int64_t)blockIdx.x * blockDim.x + threadIdx.x; if (i < total) y[i] = __float2bfloat16(acc[i] * scale[i % N]); } // pad fp8 rows K -> Kpad (Kpad % 16 == 0), zero-filling the pad region __global__ void pad_kernel(const uint8_t* __restrict__ in, uint8_t* __restrict__ out, int nrows, int K, int Kpad) { int64_t cpr = Kpad / 16; int64_t nchunk = (int64_t)nrows * cpr; for (int64_t idx = blockIdx.x * (int64_t)blockDim.x + threadIdx.x; idx < nchunk; idx += gridDim.x * (int64_t)blockDim.x) { int r = (int)(idx / cpr); int o = (int)(idx % cpr) * 16; uint4 v = {0, 0, 0, 0}; if (o + 16 <= K) { const uint8_t* s = in + (int64_t)r * K + o; uintptr_t sa = (uintptr_t)s; uintptr_t al = sa & ~(uintptr_t)15; int sh = (int)(sa - al); uint32_t b[8]; const uint32_t* w32 = reinterpret_cast(al); uintptr_t end = (uintptr_t)in + (int64_t)nrows * K; #pragma unroll for (int i = 0; i < 8; ++i) { const uint32_t* p = w32 + i; uintptr_t pa = (uintptr_t)p; b[i] = (pa + 4 <= end) ? *p : 0; } uint8_t* vb = reinterpret_cast(&v); const uint8_t* bb = reinterpret_cast(b); #pragma unroll for (int i = 0; i < 16; ++i) vb[i] = bb[sh + i]; } else if (o < K) { const uint8_t* s = in + (int64_t)r * K + o; uint8_t* vb = reinterpret_cast(&v); for (int i = 0; i < K - o; ++i) vb[i] = s[i]; } reinterpret_cast(out + (int64_t)r * Kpad + o)[0] = v; } } // --------------------------------------------------------------------------- // host // --------------------------------------------------------------------------- static int g_sms = -1; static int num_sms() { if (g_sms < 0) g_sms = at::cuda::getCurrentDeviceProperties()->multiProcessorCount; return g_sms; } #include #include struct TmapKey { uint64_t base; uint64_t rows; uint64_t cols; uint64_t box; bool operator<(const TmapKey& o) const { return std::tie(base, rows, cols, box) < std::tie(o.base, o.rows, o.cols, o.box); } }; static std::map g_tmap_cache; static CUtensorMap encode_map(const void* base, int rows, int cols, int boxrows) { // tensor maps encode only (base, rows, cols, box) - no content dependence TmapKey key{(uint64_t)base, (uint64_t)rows, (uint64_t)cols, (uint64_t)boxrows}; auto it = g_tmap_cache.find(key); if (it != g_tmap_cache.end()) return it->second; CUtensorMap t; uint64_t gdims[2] = {(uint64_t)cols, (uint64_t)rows}; uint64_t gstr[1] = {(uint64_t)cols}; uint32_t box[2] = {128, (uint32_t)boxrows}; uint32_t estr[2] = {1, 1}; CUresult r = cuTensorMapEncodeTiled(&t, CU_TENSOR_MAP_DATA_TYPE_UINT8, 2, const_cast(base), gdims, gstr, box, estr, CU_TENSOR_MAP_INTERLEAVE_NONE, CU_TENSOR_MAP_SWIZZLE_128B, CU_TENSOR_MAP_L2_PROMOTION_L2_128B, CU_TENSOR_MAP_FLOAT_OOB_FILL_NONE); TORCH_CHECK(r == CUDA_SUCCESS, "cuTensorMapEncodeTiled failed: ", (int)r); if (g_tmap_cache.size() > 64) g_tmap_cache.clear(); g_tmap_cache.emplace(key, t); return t; } template void launch_ws(const void* x, const void* w, const float* s, void* y, int M, int N, int K, int sk, cudaStream_t stream, bool grid_cap) { auto kfn = fp8_ws_kernel; constexpr int smem = STAGES * (BM + BN) * BK + 128; static bool set = false; if (!set) { cudaFuncSetAttribute(kfn, cudaFuncAttributeMaxDynamicSharedMemorySize, smem); set = true; } long ntiles = (long)((M + BM - 1) / BM) * ((N + BN - 1) / BN); int gs = (!SPLITK && grid_cap) ? (int)std::min(ntiles, num_sms()) : (int)ntiles; dim3 grid(gs, sk); CUtensorMap tmapA = encode_map(x, M, K, BM); CUtensorMap tmapB = encode_map(w, N, K, BN); kfn<<>>(tmapA, tmapB, s, y, M, N, K); } torch::Tensor gemm_big(torch::Tensor x, torch::Tensor w, torch::Tensor scale) { int M = x.size(0), N = w.size(0), K = x.size(1); torch::Tensor y = at::empty({M, N}, x.options().dtype(at::kBFloat16)); if (N >= 8192) { launch_ws<128, 128, 32, 64, 128, 3, 4, 8, false>( (const void*)x.data_ptr(), (const void*)w.data_ptr(), scale.data_ptr(), y.data_ptr(), M, N, K, 1, at::cuda::getCurrentCUDAStream(), true); } else { launch_ws<128, 128, 32, 64, 128, 3, 8, 8, false>( (const void*)x.data_ptr(), (const void*)w.data_ptr(), scale.data_ptr(), y.data_ptr(), M, N, K, 1, at::cuda::getCurrentCUDAStream(), true); } return y; } torch::Tensor gemm_skinny(torch::Tensor x, torch::Tensor w, torch::Tensor scale, torch::Tensor acc, int64_t sk64) { int M = x.size(0), N = w.size(0), K = x.size(1); torch::Tensor y = at::empty({M, N}, x.options().dtype(at::kBFloat16)); cudaStream_t stream = at::cuda::getCurrentCUDAStream(); cudaMemsetAsync(acc.data_ptr(), 0, (int64_t)M * N * 4, stream); { auto kfn = fp8_skinny_kernel<256, 32, 64, 128, 2, 4>; constexpr int smem = 2 * (32 + 256) * 128 + 128; static bool set = false; if (!set) { cudaFuncSetAttribute(kfn, cudaFuncAttributeMaxDynamicSharedMemorySize, smem); set = true; } dim3 grid((N + 255) / 256, (unsigned)sk64); kfn<<>>( (const uint8_t*)x.data_ptr(), (const uint8_t*)w.data_ptr(), scale.data_ptr(), acc.data_ptr(), M, N, K); } int64_t total = (int64_t)M * N; int threads = 256; int blocks = (int)((total + threads - 1) / threads); finalize_kernel<<>>( acc.data_ptr(), scale.data_ptr(), reinterpret_cast<__nv_bfloat16*>(y.data_ptr()), total, N); return y; } void pad_fp8(torch::Tensor inp, torch::Tensor out, int64_t K, int64_t Kpad) { int nrows = out.size(0); size_t total = (size_t)nrows * (Kpad / 16); int threads = 256; int blocks = (int)std::min((total + threads - 1) / threads, 8 * 188); pad_kernel<<>>( (const uint8_t*)inp.data_ptr(), (uint8_t*)out.data_ptr(), nrows, (int)K, (int)Kpad); } """ _CPP_SRC = r""" torch::Tensor gemm_big(torch::Tensor x, torch::Tensor w, torch::Tensor scale); torch::Tensor gemm_skinny(torch::Tensor x, torch::Tensor w, torch::Tensor scale, torch::Tensor acc, int64_t sk); void pad_fp8(torch::Tensor inp, torch::Tensor out, int64_t K, int64_t Kpad); """ _ext = load_inline( name="fp8_gemm_ext_v2", cpp_sources=[_CPP_SRC], cuda_sources=[_CUDA_SRC], functions=["gemm_big", "gemm_skinny", "pad_fp8"], extra_cuda_cflags=[ "-arch=sm_120a", "-O3", "--expt-relaxed-constexpr", "-std=c++17", ], extra_ldflags=["-lcuda"], verbose=False, ) _SMS = None def _pick_sk(M: int, N: int) -> int: global _SMS if _SMS is None: _SMS = torch.cuda.get_device_properties(0).multi_processor_count num_n = (N + 255) // 256 tiles = ((M + 31) // 32) * num_n want = max(1, (2 * _SMS + tiles - 1) // tiles) return max(1, min(3, want)) class Model(nn.Module): """y = ((x @ w.T) * weight_scale).to(bf16). weight: fp8_e4m3 (N, K); weight_scale: (N,) fp32 per-output-channel scale. """ def __init__(self, M: int, N: int, K: int): super().__init__() self.M, self.N, self.K = M, N, K self.register_buffer("weight", torch.empty(N, K, dtype=torch.float8_e4m3fn)) self.register_buffer("weight_scale", torch.empty(N, dtype=torch.float32)) self._pad_bufs = None self._acc_buf = None self._sk = None self._pad_x_src = None def forward(self, x: torch.Tensor) -> torch.Tensor: M, K = x.shape N = self.weight.shape[0] w = self.weight K2 = K if K % 16 != 0: K16 = (K + 127) // 128 * 128 if self._pad_bufs is None or self._pad_bufs[0] != (M, N, K16, x.device): self._pad_bufs = ((M, N, K16, x.device), torch.empty((M, K16), device=x.device, dtype=torch.int8), torch.empty((N, K16), device=x.device, dtype=torch.int8), None, None) _, xpi, wpi, wp_state, xp_state = self._pad_bufs # repack operands only when storage pointer or version counter changes; # keep a strong ref to the source x so its pointer cannot be recycled # by the caching allocator while its pad entry is live xkey = (x.data_ptr(), x._version) if xp_state != xkey: _ext.pad_fp8(x, xpi, K, K16) xp_state = xkey self._pad_x_src = x wkey = (w.data_ptr(), w._version) if wp_state != wkey: _ext.pad_fp8(w, wpi, K, K16) wp_state = wkey self._pad_bufs = (self._pad_bufs[0], xpi, wpi, wp_state, xp_state) x2, w2 = xpi, wpi K2 = K16 else: x2, w2 = x, w if M <= 96: if self._acc_buf is None: self._acc_buf = torch.zeros((M, N), device=x.device, dtype=torch.float32) self._sk = _pick_sk(M, N) return _ext.gemm_skinny(x2, w2, self.weight_scale, self._acc_buf, self._sk) else: return _ext.gemm_big(x2, w2, self.weight_scale) M = 4096 N = 4096 K = 4096 def get_inputs(): x = (torch.rand(M, K) * 8 - 4).to(torch.float8_e4m3fn) return [x] def get_init_inputs(): return [M, N, K]