KernelBench hard · RTX PRO 6000
W4A16 GEMM Claude Fable 5
manually audited: clean
Genuine fused W4A16 dequant+GEMM with three per-shape custom paths, all fusing int4 unpack + dequant + GEMM in one pass over the packed weight stream (no bf16 weight matrix ever materialized): M=1 decode is a custom CUDA GEMV over 32-column repacked slabs with fp16 magic-number nibble dequant (LOP3 into the 0x6400 mantissa) and an algebraic per-group zero-point correction; M=16/32 is a hand-rolled mma.sync.m16n8k16 fp16 kernel with cp.async ring buffers and warp-exclusive K-slices; other M (256 prefill) is a Triton tl.dot kernel on even/odd K planes. Per- (data_ptr, M) CUDA graphs cache the launch sequence only - the empirical test proves every replay recomputes from the live x/scales/zeros buffers. No forbidden ops, no reference import, no grader tampering. Rebench geomean 0.107 (original in-run 0.0991, RESULT: LOW under wave concurrency; the clean sequential re-grade is the published number).
Kernel source (redacted)
"""Fused W4A16 (int4 weight, bf16 activation) GEMM for B200 (SM100).
Scheme (AWQ/GPTQ-style asymmetric int4, group=128 along K):
w_bf[k, n] = (unpack(w_q)[k, n] - zeros[k // 128, n]) * scales[k // 128, n]
y = x @ w_bf
All paths fuse unpack+dequant+GEMM in one pass over the packed int4 stream
(no bf16 weight matrix ever hits HBM). Nibbles are dequantized with the fp16
magic-number trick: OR the nibble into the mantissa of 0x6400 to get
fp16(1024 + q) with one LOP3, then (q1024 - (1024 + z)) * s = (q - z) * s
exactly (z is an integer 0..15, so 1024+z is exact in fp16).
Per-shape kernels (K = 4096, group 128):
* M == 1 (decode): custom CUDA GEMV. Weights repacked once per Model into
32-column contiguous slabs; each of the N/32 CTAs streams its 64KB slab
with perfectly coalesced 16B-per-thread loads (unit u = j*256+t holds
packed row kh = 8t+j, so thread t owns k in [16t, 16t+16), one quant
group). Column-paired HFMA2 accumulation; the zero-point is applied
algebraically per group as y_c = s_c * (qx_c - z_c * sum_k x_k) so the
inner loop has no per-element subtract. Padded smem transpose reduction.
* M == 16 / 32: custom CUDA mma.sync.m16n8k16 fp16 kernel. Weights repacked
into "bricks" so each thread's 8/16-byte cp.async is exactly its
B-fragment bytes (byte = (k, k+1) nibbles of one column = one B register
after the magic dequant). x is pre-arranged into A-fragment order by a
tiny in-graph prep kernel, so A fragments are single 16B loads. NWARP
warps split K (each warp owns an exclusive slice; the cp.async ring in
dynamic smem is barrier-free because every thread reads back only its own
bytes), then a smem atomicAdd epilogue reduces the k-slices.
* other M (incl. 256, prefill): Triton tl.dot kernel. The packed byte tile
is dequantized with the same fp16 magic trick (bitcast, no int->float
conversions) and fed to two tl.dot calls against even/odd K planes of x
(pre-split to fp16 by an in-graph prep kernel) - the accumulator stays in
tcgen05 tensor memory for the whole K loop.
Per-input-pointer CUDA graphs remove CPU launch overhead: the graph caches
the *launch sequence*, not results - every replay recomputes from the live
x / w_q / scales / zeros buffers (in-place input mutation changes the
output). Weight repacks are derived once from the loaded state_dict.
"""
from __future__ import annotations
import os
os.environ.setdefault("CUDA_HOME", "/usr/local/cuda-12.8")
import torch
import torch.nn as nn
import triton
import triton.language as tl
GROUP_SIZE = 128
# ===========================================================================
# Triton fused dequant + GEMM (fallback / M == 256 path)
# ===========================================================================
def _gemm_configs():
return [
triton.Config({"BLOCK_M": 16, "BLOCK_N": 64, "BLOCK_KH": 64}, num_warps=4, num_stages=5),
triton.Config({"BLOCK_M": 16, "BLOCK_N": 128, "BLOCK_KH": 64}, num_warps=8, num_stages=4),
triton.Config({"BLOCK_M": 32, "BLOCK_N": 64, "BLOCK_KH": 64}, num_warps=4, num_stages=5),
triton.Config({"BLOCK_M": 32, "BLOCK_N": 128, "BLOCK_KH": 64}, num_warps=8, num_stages=4),
triton.Config({"BLOCK_M": 64, "BLOCK_N": 128, "BLOCK_KH": 64}, num_warps=8, num_stages=4),
triton.Config({"BLOCK_M": 128, "BLOCK_N": 128, "BLOCK_KH": 32}, num_warps=8, num_stages=4),
triton.Config({"BLOCK_M": 128, "BLOCK_N": 128, "BLOCK_KH": 64}, num_warps=8, num_stages=3),
triton.Config({"BLOCK_M": 256, "BLOCK_N": 128, "BLOCK_KH": 64}, num_warps=8, num_stages=2),
]
@triton.autotune(configs=_gemm_configs(), key=["M", "N", "K"])
@triton.jit
def _w4a16_gemm_kernel(
xe_ptr, xo_ptr, wq_ptr, s_ptr, z_ptr, y_ptr,
M, N, K,
BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_KH: tl.constexpr,
):
# xe/xo: (M, K//2) fp16 even/odd K planes. BLOCK_KH divides 64.
pid = tl.program_id(0)
pid_m = pid % tl.cdiv(M, BLOCK_M)
pid_n = pid // tl.cdiv(M, BLOCK_M)
offs_m = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
offs_n = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
offs_kh = tl.arange(0, BLOCK_KH)
m_mask = offs_m < M
KH2 = K // 2
acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
for kh0 in range(0, KH2, BLOCK_KH):
g = kh0 // 64
s = tl.load(s_ptr + g * N + offs_n).to(tl.float16)
z16 = ((tl.load(z_ptr + g * N + offs_n).to(tl.uint16)) | 0x6400).to(tl.float16, bitcast=True)
b = tl.load(wq_ptr + (kh0 + offs_kh)[:, None] * N + offs_n[None, :])
qlo = ((b & 0xF).to(tl.uint16) | 0x6400).to(tl.float16, bitcast=True)
qhi = ((b >> 4).to(tl.uint16) | 0x6400).to(tl.float16, bitcast=True)
wlo = (qlo - z16[None, :]) * s[None, :]
whi = (qhi - z16[None, :]) * s[None, :]
xe = tl.load(xe_ptr + offs_m[:, None] * KH2 + (kh0 + offs_kh)[None, :],
mask=m_mask[:, None], other=0.0)
xo = tl.load(xo_ptr + offs_m[:, None] * KH2 + (kh0 + offs_kh)[None, :],
mask=m_mask[:, None], other=0.0)
acc = tl.dot(xe, wlo, acc)
acc = tl.dot(xo, whi, acc)
tl.store(y_ptr + offs_m[:, None] * N + offs_n[None, :], acc.to(tl.bfloat16),
mask=m_mask[:, None])
# ===========================================================================
# CUDA kernels: decode GEMV, mma.sync GEMM, prep kernels
# ===========================================================================
_CUDA_SRC = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <cuda_fp16.h>
#include <cuda_bf16.h>
#include <cstdint>
#define EXP_BITS 0x64006400u
// ---------------------------------------------------------------------------
// Decode GEMV (M == 1): 32-column slabs.
// unit(j, t, h) = 16B half h of packed row kh = 8t + j, stored at
// ((j*256 + t)*2 + h)*16B within the slab. Thread t owns k in [16t, 16t+16)
// (quant group t >> 3).
// ---------------------------------------------------------------------------
__global__ void __launch_bounds__(256)
w4a16_gemv_kernel(const uint4* __restrict__ wp,
const __nv_bfloat16* __restrict__ x,
const __nv_bfloat16* __restrict__ scales,
const __nv_bfloat16* __restrict__ zeros,
__nv_bfloat16* __restrict__ y,
const int N)
{
const int slab = blockIdx.x;
const int t = threadIdx.x;
const int col0 = slab * 32;
// stage scales+zeros: 32 groups x 32 cols bf16 each (512 uint32 each)
__shared__ uint32_t sz_sm[2 * 32 * 16];
#pragma unroll
for (int i = 0; i < 2; i++) {
const int idx = t + 256 * i;
const int row = idx >> 4, off = idx & 15;
sz_sm[idx] = reinterpret_cast<const uint32_t*>(scales + (size_t)row * N + col0)[off];
sz_sm[512 + idx] = reinterpret_cast<const uint32_t*>(zeros + (size_t)row * N + col0)[off];
}
half2 Xe[8], Xo[8];
float sx = 0.f;
{
const uint4* xp = reinterpret_cast<const uint4*>(x) + 2 * t;
uint4 a = xp[0], b = xp[1];
const __nv_bfloat16* xa = reinterpret_cast<const __nv_bfloat16*>(&a);
const __nv_bfloat16* xb = reinterpret_cast<const __nv_bfloat16*>(&b);
#pragma unroll
for (int i = 0; i < 4; i++) {
float e = __bfloat162float(xa[2 * i]) * 0.015625f;
float o = __bfloat162float(xa[2 * i + 1]) * 0.015625f;
Xe[i] = __float2half2_rn(e);
Xo[i] = __float2half2_rn(o);
sx += e + o;
}
#pragma unroll
for (int i = 0; i < 4; i++) {
float e = __bfloat162float(xb[2 * i]) * 0.015625f;
float o = __bfloat162float(xb[2 * i + 1]) * 0.015625f;
Xe[4 + i] = __float2half2_rn(e);
Xo[4 + i] = __float2half2_rn(o);
sx += e + o;
}
}
__syncthreads();
const uint4* base = wp + (size_t)slab * 4096;
const half2 K1024 = __float2half2_rn(1024.f);
const half2 INV16 = __float2half2_rn(0.0625f);
const half2 NEG64 = __float2half2_rn(-64.f);
const uint32_t LO_MASK = 0x000F000Fu;
const uint32_t HI_MASK = 0x00F000F0u;
// acc[qi][0] = cols (4qi, 4qi+2), acc[qi][1] = cols (4qi+1, 4qi+3)
half2 acc[8][2];
#pragma unroll
for (int p = 0; p < 8; p++) {
acc[p][0] = __float2half2_rn(0.f);
acc[p][1] = __float2half2_rn(0.f);
}
#pragma unroll
for (int j = 0; j < 8; j++) {
const uint4 u0v = base[(j * 256 + t) * 2];
const uint4 u1v = base[(j * 256 + t) * 2 + 1];
const uint32_t vs[8] = {u0v.x, u0v.y, u0v.z, u0v.w, u1v.x, u1v.y, u1v.z, u1v.w};
const half2 xe = Xe[j], xo = Xo[j];
#pragma unroll
for (int qi = 0; qi < 8; qi++) {
const uint32_t v = vs[qi];
const uint32_t v8 = v >> 8;
uint32_t w0 = (v & LO_MASK) | EXP_BITS;
uint32_t w1 = (v & HI_MASK) | EXP_BITS;
uint32_t w2 = (v8 & LO_MASK) | EXP_BITS;
uint32_t w3 = (v8 & HI_MASK) | EXP_BITS;
half2 wlo02 = __hsub2(*reinterpret_cast<half2*>(&w0), K1024);
half2 whi02 = __hfma2(*reinterpret_cast<half2*>(&w1), INV16, NEG64);
half2 wlo13 = __hsub2(*reinterpret_cast<half2*>(&w2), K1024);
half2 whi13 = __hfma2(*reinterpret_cast<half2*>(&w3), INV16, NEG64);
acc[qi][0] = __hfma2(wlo02, xe, acc[qi][0]);
acc[qi][0] = __hfma2(whi02, xo, acc[qi][0]);
acc[qi][1] = __hfma2(wlo13, xe, acc[qi][1]);
acc[qi][1] = __hfma2(whi13, xo, acc[qi][1]);
}
}
const int g = t >> 3;
const __nv_bfloat16* s_sm = reinterpret_cast<const __nv_bfloat16*>(sz_sm);
const __nv_bfloat16* z_sm = reinterpret_cast<const __nv_bfloat16*>(sz_sm + 512);
float r[32];
#pragma unroll
for (int qi = 0; qi < 8; qi++) {
r[4 * qi + 0] = __low2float(acc[qi][0]);
r[4 * qi + 1] = __low2float(acc[qi][1]);
r[4 * qi + 2] = __high2float(acc[qi][0]);
r[4 * qi + 3] = __high2float(acc[qi][1]);
}
#pragma unroll
for (int c = 0; c < 32; c++) {
const float s = __bfloat162float(s_sm[g * 32 + c]) * 64.f;
const float z = __bfloat162float(z_sm[g * 32 + c]);
r[c] = s * (r[c] - z * sx);
}
// smem transpose reduction: rows padded to 33 floats (conflict-free)
__shared__ float red[256 * 33];
#pragma unroll
for (int c = 0; c < 32; c++) red[t * 33 + c] = r[c];
__syncthreads();
{
const int c = t & 31, slice = t >> 5;
float v = 0.f;
#pragma unroll
for (int i = 0; i < 32; i++)
v += red[(slice * 32 + i) * 33 + c];
__syncthreads();
red[slice * 33 + c] = v;
}
__syncthreads();
if (t < 32) {
float v = 0.f;
#pragma unroll
for (int w = 0; w < 8; w++) v += red[w * 33 + t];
y[col0 + t] = __float2bfloat16(v);
}
}
void w4a16_gemv(torch::Tensor wp, torch::Tensor x, torch::Tensor scales,
torch::Tensor zeros, torch::Tensor y, int64_t N)
{
auto stream = at::cuda::getCurrentCUDAStream();
w4a16_gemv_kernel<<<(int)(N / 32), 256, 0, stream>>>(
reinterpret_cast<const uint4*>(wp.data_ptr<uint8_t>()),
reinterpret_cast<const __nv_bfloat16*>(x.data_ptr()),
reinterpret_cast<const __nv_bfloat16*>(scales.data_ptr()),
reinterpret_cast<const __nv_bfloat16*>(zeros.data_ptr()),
reinterpret_cast<__nv_bfloat16*>(y.data_ptr()),
(int)N);
}
// ---------------------------------------------------------------------------
// mma.sync.m16n8k16 fp16 W4A16 GEMM (M = 16 / 32), K = 4096.
// ---------------------------------------------------------------------------
__device__ __forceinline__ half2 dq(uint32_t byte, half2 z1024, half2 s2)
{
uint32_t v = (byte & 0xFu) | ((byte & 0xF0u) << 12) | EXP_BITS;
return __hmul2(__hsub2(*reinterpret_cast<half2*>(&v), z1024), s2);
}
__device__ __forceinline__ void cp16(uint32_t dst_smem, const void* src)
{
asm volatile("cp.async.cg.shared.global [%0], [%1], 16;\n"
:: "r"(dst_smem), "l"(src));
}
__device__ __forceinline__ void cp8(uint32_t dst_smem, const void* src)
{
asm volatile("cp.async.ca.shared.global [%0], [%1], 8;\n"
:: "r"(dst_smem), "l"(src));
}
template<int WM, int NWARP, int S, int STAGES, int NT>
__global__ void __launch_bounds__(NWARP * 32)
w4a16_mma_kernel(const uint8_t* __restrict__ wp,
const uint4* __restrict__ xf,
const __nv_bfloat16* __restrict__ scales,
const __nv_bfloat16* __restrict__ zeros,
__nv_bfloat16* __restrict__ y,
const int N)
{
constexpr int CTA_COLS = NT * 8;
constexpr int BB = NT * 2;
constexpr int BU = (32 * BB) / 16;
constexpr int SLOT = BU + 32 * WM;
extern __shared__ uint4 smem[];
const int c = blockIdx.x;
const int w = threadIdx.x >> 5;
const int t = threadIdx.x & 31;
const int col0 = c * CTA_COLS;
__shared__ float red[WM * 16][CTA_COLS + 2];
for (int idx = threadIdx.x; idx < WM * 16 * CTA_COLS; idx += NWARP * 32)
red[idx / CTA_COLS][idx % CTA_COLS] = 0.f;
float acc[WM][NT][4];
#pragma unroll
for (int mt = 0; mt < WM; mt++)
#pragma unroll
for (int nt = 0; nt < NT; nt++)
#pragma unroll
for (int i = 0; i < 4; i++) acc[mt][nt][i] = 0.f;
const uint8_t* bp = wp + ((size_t)(c * NWARP + w) * S) * 32 * BB + t * BB;
const uint4* ap = xf + ((size_t)w * S * WM) * 32 + t;
uint4* ring = smem + (size_t)w * STAGES * SLOT;
const uint32_t ring_base = __cvta_generic_to_shared(ring);
#pragma unroll
for (int p = 0; p < STAGES - 1; p++) {
if (p < S) {
const uint32_t bslot = ring_base + (uint32_t)(p * SLOT) * 16 + t * BB;
if (NT == 8) cp16(bslot, bp + (size_t)p * 32 * BB);
else cp8(bslot, bp + (size_t)p * 32 * BB);
#pragma unroll
for (int mt = 0; mt < WM; mt++)
cp16(ring_base + (uint32_t)(p * SLOT + BU + mt * 32 + t) * 16,
ap + ((size_t)p * WM + mt) * 32);
}
asm volatile("cp.async.commit_group;\n");
}
half2 z2[NT], s2[NT];
#pragma unroll
for (int j = 0; j < S; j++) {
if ((j & 7) == 0) {
const int g = (w * S + j) >> 3;
#pragma unroll
for (int nt = 0; nt < NT; nt++) {
const int col = col0 + nt * 8 + (t >> 2);
float sv = __bfloat162float(scales[(size_t)g * N + col]);
float zv = __bfloat162float(zeros[(size_t)g * N + col]);
s2[nt] = __float2half2_rn(sv);
z2[nt] = __float2half2_rn(1024.f + zv);
}
}
asm volatile("cp.async.wait_group %0;\n" :: "n"(STAGES - 2));
const int slot = j & (STAGES - 1);
uint32_t vs[BB / 4];
{
const char* bsrc = reinterpret_cast<const char*>(ring) + (size_t)(slot * SLOT) * 16 + t * BB;
if (NT == 8) {
const uint4 cur = *reinterpret_cast<const uint4*>(bsrc);
vs[0] = cur.x; vs[1] = cur.y; vs[2] = cur.z; vs[3] = cur.w;
} else {
const uint2 cur = *reinterpret_cast<const uint2*>(bsrc);
vs[0] = cur.x; vs[1] = cur.y;
}
}
uint4 av[WM];
#pragma unroll
for (int mt = 0; mt < WM; mt++)
av[mt] = ring[slot * SLOT + BU + mt * 32 + t];
{
const int p = j + STAGES - 1;
if (p < S) {
const int ps = p & (STAGES - 1);
const uint32_t bslot = ring_base + (uint32_t)(ps * SLOT) * 16 + t * BB;
if (NT == 8) cp16(bslot, bp + (size_t)p * 32 * BB);
else cp8(bslot, bp + (size_t)p * 32 * BB);
#pragma unroll
for (int mt = 0; mt < WM; mt++)
cp16(ring_base + (uint32_t)(ps * SLOT + BU + mt * 32 + t) * 16,
ap + ((size_t)p * WM + mt) * 32);
}
asm volatile("cp.async.commit_group;\n");
}
uint32_t b[NT][2];
#pragma unroll
for (int q = 0; q < BB / 4; q++) {
const uint32_t v = vs[q];
const int nt0 = q * 2;
half2 r0 = dq(v & 0xFFu, z2[nt0], s2[nt0]);
half2 r1 = dq((v >> 8) & 0xFFu, z2[nt0], s2[nt0]);
half2 r2 = dq((v >> 16) & 0xFFu, z2[nt0 + 1], s2[nt0 + 1]);
half2 r3 = dq(v >> 24, z2[nt0 + 1], s2[nt0 + 1]);
b[nt0][0] = *reinterpret_cast<uint32_t*>(&r0);
b[nt0][1] = *reinterpret_cast<uint32_t*>(&r1);
b[nt0 + 1][0] = *reinterpret_cast<uint32_t*>(&r2);
b[nt0 + 1][1] = *reinterpret_cast<uint32_t*>(&r3);
}
#pragma unroll
for (int mt = 0; mt < WM; mt++) {
#pragma unroll
for (int nt = 0; nt < NT; nt++) {
asm volatile(
"mma.sync.aligned.m16n8k16.row.col.f32.f16.f16.f32 "
"{%0,%1,%2,%3}, {%4,%5,%6,%7}, {%8,%9}, {%0,%1,%2,%3};\n"
: "+f"(acc[mt][nt][0]), "+f"(acc[mt][nt][1]),
"+f"(acc[mt][nt][2]), "+f"(acc[mt][nt][3])
: "r"(av[mt].x), "r"(av[mt].y), "r"(av[mt].z), "r"(av[mt].w),
"r"(b[nt][0]), "r"(b[nt][1]));
}
}
}
__syncthreads();
const int m_lo = t >> 2;
#pragma unroll
for (int mt = 0; mt < WM; mt++) {
#pragma unroll
for (int nt = 0; nt < NT; nt++) {
const int n0 = nt * 8 + (t & 3) * 2;
atomicAdd(&red[mt * 16 + m_lo][n0], acc[mt][nt][0]);
atomicAdd(&red[mt * 16 + m_lo][n0 + 1], acc[mt][nt][1]);
atomicAdd(&red[mt * 16 + m_lo + 8][n0], acc[mt][nt][2]);
atomicAdd(&red[mt * 16 + m_lo + 8][n0 + 1], acc[mt][nt][3]);
}
}
__syncthreads();
for (int idx = threadIdx.x; idx < WM * 16 * CTA_COLS; idx += NWARP * 32) {
const int m = idx / CTA_COLS, n = idx % CTA_COLS;
y[(size_t)m * N + col0 + n] = __float2bfloat16(red[m][n]);
}
}
template<int WM, int NWARP, int S, int STAGES, int NT>
void launch_mma(const uint8_t* wpp, const uint4* xp, const __nv_bfloat16* sp,
const __nv_bfloat16* zp, __nv_bfloat16* yp, int N,
cudaStream_t stream)
{
constexpr int BU = (32 * NT * 2) / 16;
constexpr int SLOT = BU + 32 * WM;
const int smem_bytes = NWARP * STAGES * SLOT * 16;
static bool configured = false;
if (!configured) {
cudaFuncSetAttribute(w4a16_mma_kernel<WM, NWARP, S, STAGES, NT>,
cudaFuncAttributeMaxDynamicSharedMemorySize, smem_bytes);
configured = true;
}
w4a16_mma_kernel<WM, NWARP, S, STAGES, NT>
<<<N / (NT * 8), NWARP * 32, smem_bytes, stream>>>(wpp, xp, sp, zp, yp, N);
}
void w4a16_mma(torch::Tensor wp, torch::Tensor xf, torch::Tensor scales,
torch::Tensor zeros, torch::Tensor y,
int64_t M, int64_t N, int64_t K)
{
auto stream = at::cuda::getCurrentCUDAStream();
TORCH_CHECK(K == 4096, "K must be 4096");
const uint8_t* wpp = wp.data_ptr<uint8_t>();
const uint4* xp = reinterpret_cast<const uint4*>(xf.data_ptr());
const __nv_bfloat16* sp = reinterpret_cast<const __nv_bfloat16*>(scales.data_ptr());
const __nv_bfloat16* zp = reinterpret_cast<const __nv_bfloat16*>(zeros.data_ptr());
__nv_bfloat16* yp = reinterpret_cast<__nv_bfloat16*>(y.data_ptr());
if (M == 16) // NT=8, NWARP=8, S=32
launch_mma<1, 8, 32, 8, 8>(wpp, xp, sp, zp, yp, (int)N, stream);
else if (M == 32) // NT=4, NWARP=4, S=64
launch_mma<2, 4, 64, 8, 4>(wpp, xp, sp, zp, yp, (int)N, stream);
else
TORCH_CHECK(false, "unsupported M");
}
// ---------------------------------------------------------------------------
// Prep kernels (run inside the same CUDA graph as the main kernel)
// ---------------------------------------------------------------------------
// A-fragment prep: bf16 (M, K) -> fp16 units in A-fragment order.
// unit u = ((w*S + j)*WM + mt)*32 + t; element e (0..7):
// m = mt*16 + t/4 + 8*((e>>1)&1), k = (w*S+j)*16 + (t%4)*2 + (e&1) + 8*(e>>2)
__global__ void afrag_prep_kernel(const __nv_bfloat16* __restrict__ x,
uint4* __restrict__ xf,
const int K, const int WM, const int total)
{
const int u = blockIdx.x * blockDim.x + threadIdx.x;
if (u >= total) return;
const int t = u & 31;
int r = u >> 5;
const int mt = r % WM;
const int step = r / WM; // = w*S + j (slices*S total)
const int kbase = step * 16 + (t & 3) * 2;
const int m0 = mt * 16 + (t >> 2);
__half out[8];
#pragma unroll
for (int half_k = 0; half_k < 2; half_k++) {
#pragma unroll
for (int mrow = 0; mrow < 2; mrow++) {
const int m = m0 + 8 * mrow;
const int k = kbase + 8 * half_k;
const int e = half_k * 4 + mrow * 2;
out[e] = __float2half(__bfloat162float(x[(size_t)m * K + k]));
out[e + 1] = __float2half(__bfloat162float(x[(size_t)m * K + k + 1]));
}
}
xf[u] = *reinterpret_cast<uint4*>(out);
}
void afrag_prep(torch::Tensor x, torch::Tensor xf, int64_t K, int64_t WM)
{
auto stream = at::cuda::getCurrentCUDAStream();
const int total = (int)(xf.numel() / 16); // uint8 numel -> uint4 count
afrag_prep_kernel<<<(total + 255) / 256, 256, 0, stream>>>(
reinterpret_cast<const __nv_bfloat16*>(x.data_ptr()),
reinterpret_cast<uint4*>(xf.data_ptr()),
(int)K, (int)WM, total);
}
// even/odd K split + fp16 convert: x (M, K) bf16 -> xe, xo (M, K/2) fp16
__global__ void xsplit_prep_kernel(const uint4* __restrict__ x,
uint4* __restrict__ xe,
uint4* __restrict__ xo,
const int total) // output uint4 count
{
const int i = blockIdx.x * blockDim.x + threadIdx.x;
if (i >= total) return;
const uint4 a = x[2 * i];
const uint4 b = x[2 * i + 1];
const __nv_bfloat16* pa = reinterpret_cast<const __nv_bfloat16*>(&a);
const __nv_bfloat16* pb = reinterpret_cast<const __nv_bfloat16*>(&b);
__half e[8], o[8];
#pragma unroll
for (int p = 0; p < 4; p++) {
e[p] = __float2half(__bfloat162float(pa[2 * p]));
o[p] = __float2half(__bfloat162float(pa[2 * p + 1]));
e[4 + p] = __float2half(__bfloat162float(pb[2 * p]));
o[4 + p] = __float2half(__bfloat162float(pb[2 * p + 1]));
}
xe[i] = *reinterpret_cast<uint4*>(e);
xo[i] = *reinterpret_cast<uint4*>(o);
}
void xsplit_prep(torch::Tensor x, torch::Tensor xe, torch::Tensor xo)
{
auto stream = at::cuda::getCurrentCUDAStream();
const int total = (int)(xe.numel() / 8);
xsplit_prep_kernel<<<(total + 255) / 256, 256, 0, stream>>>(
reinterpret_cast<const uint4*>(x.data_ptr()),
reinterpret_cast<uint4*>(xe.data_ptr()),
reinterpret_cast<uint4*>(xo.data_ptr()),
total);
}
"""
_CPP_SRC = r"""
#include <torch/extension.h>
void w4a16_gemv(torch::Tensor wp, torch::Tensor x, torch::Tensor scales,
torch::Tensor zeros, torch::Tensor y, int64_t N);
void w4a16_mma(torch::Tensor wp, torch::Tensor xf, torch::Tensor scales,
torch::Tensor zeros, torch::Tensor y,
int64_t M, int64_t N, int64_t K);
void afrag_prep(torch::Tensor x, torch::Tensor xf, int64_t K, int64_t WM);
void xsplit_prep(torch::Tensor x, torch::Tensor xe, torch::Tensor xo);
"""
_EXT = None
_EXT_FAILED = False
def _get_ext():
global _EXT, _EXT_FAILED
if _EXT is None and not _EXT_FAILED:
try:
from torch.utils.cpp_extension import load_inline
_EXT = load_inline(
name="w4a16_sol",
cpp_sources=[_CPP_SRC],
cuda_sources=[_CUDA_SRC],
functions=["w4a16_gemv", "w4a16_mma", "afrag_prep", "xsplit_prep"],
extra_cuda_cflags=[
"-O3", "--use_fast_math",
"-gencode=arch=compute_100,code=sm_100",
],
verbose=False,
)
except Exception:
_EXT_FAILED = True
return _EXT
# ===========================================================================
# Weight repacks (derived from the live state_dict once per Model)
# ===========================================================================
def _repack_slabs32(w_q: torch.Tensor) -> torch.Tensor:
"""(2048, N) packed uint8 -> 32-col slabs for the decode GEMV."""
Kh, N = w_q.shape
u = torch.arange(Kh, device=w_q.device)
kh_of_u = 8 * (u % 256) + (u // 256)
slabs = w_q.view(Kh, N // 32, 2, 16).permute(1, 0, 2, 3) # (nslab, kh, half, 16)
return slabs[:, kh_of_u, :, :].contiguous().view(-1)
def _repack_bricks(w_q: torch.Tensor, slices: int, ntiles: int) -> torch.Tensor:
"""(K/2, N) uint8 -> mma bricks: addr = (((c*SL+w)*S+j)*32+t)*BB + i."""
Kh, N = w_q.shape
K = Kh * 2
S = K // (16 * slices)
bb = ntiles * 2
cta_cols = ntiles * 8
n_cta = N // cta_cols
dev = w_q.device
c = torch.arange(n_cta, device=dev).view(-1, 1, 1, 1, 1)
w = torch.arange(slices, device=dev).view(1, -1, 1, 1, 1)
j = torch.arange(S, device=dev).view(1, 1, -1, 1, 1)
t = torch.arange(32, device=dev).view(1, 1, 1, -1, 1)
i = torch.arange(bb, device=dev).view(1, 1, 1, 1, -1)
kh = (w * S + j) * 8 + (t % 4) + 4 * (i % 2)
col = c * cta_cols + (i // 2) * 8 + t // 4
kh, col = torch.broadcast_tensors(kh, col)
return w_q[kh.reshape(-1), col.reshape(-1)].reshape(-1).contiguous()
# ===========================================================================
# Model
# ===========================================================================
class Model(nn.Module):
def __init__(self, M: int, N: int, K: int, group_size: int = GROUP_SIZE):
super().__init__()
assert K % group_size == 0 and K % 2 == 0
self.M, self.N, self.K = M, N, K
self.group_size = group_size
n_groups = K // group_size
self.register_buffer("w_q", torch.empty(K // 2, N, dtype=torch.uint8))
self.register_buffer("scales", torch.empty(n_groups, N, dtype=torch.bfloat16))
self.register_buffer("zeros", torch.empty(n_groups, N, dtype=torch.bfloat16))
self._graphs: dict = {}
self._wp: dict = {} # repacked weights per path
self._last = None # (key, graph, y) single-entry fast path
# ---- path selection -------------------------------------------------
def _path(self, M: int) -> str:
std = self.K == 4096 and self.group_size == 128 and _get_ext() is not None
if M == 1 and std and self.N % 32 == 0:
return "gemv"
if M == 16 and std and self.N % 64 == 0:
return "mma"
if M == 32 and std and self.N % 32 == 0:
return "mma"
if M == 1:
return "triton_pad" # generic path handles M=1 via masking
return "triton"
def _get_wp(self, path: str, M: int) -> torch.Tensor:
key = (path, M if path == "mma" else 0)
wp = self._wp.get(key)
if wp is None:
if path == "gemv":
wp = _repack_slabs32(self.w_q)
else: # mma
slices = 8 if M == 16 else 4
nt = 8 if M == 16 else 4
wp = _repack_bricks(self.w_q, slices, nt)
self._wp[key] = wp
return wp
# ---- kernel invocation (captured into the graph) ---------------------
def _make_call(self, x: torch.Tensor, path: str):
M, N, K = x.shape[0], self.N, self.K
y = torch.empty(M, N, device=x.device, dtype=torch.bfloat16)
ext = _get_ext()
if path == "gemv":
wp = self._get_wp(path, M)
def call():
ext.w4a16_gemv(wp, x.view(-1), self.scales, self.zeros, y.view(-1), N)
elif path == "mma":
wp = self._get_wp(path, M)
wm = 1 if M == 16 else 2
xf = torch.empty(M * K * 2, device=x.device, dtype=torch.uint8)
def call():
ext.afrag_prep(x, xf, K, wm)
ext.w4a16_mma(wp, xf, self.scales, self.zeros, y, M, N, K)
else:
kh2 = K // 2
xe = torch.empty(M, kh2, device=x.device, dtype=torch.float16)
xo = torch.empty(M, kh2, device=x.device, dtype=torch.float16)
grid = lambda meta: (
triton.cdiv(M, meta["BLOCK_M"]) * triton.cdiv(N, meta["BLOCK_N"]),
)
if ext is not None and K % 16 == 0:
def split():
ext.xsplit_prep(x, xe, xo)
else:
def split():
x16 = x.to(torch.float16).view(M, kh2, 2)
xe.copy_(x16[:, :, 0])
xo.copy_(x16[:, :, 1])
def call():
split()
_w4a16_gemm_kernel[grid](
xe, xo, self.w_q, self.scales, self.zeros, y, M, N, K
)
return call, y
def forward(self, x: torch.Tensor) -> torch.Tensor:
if not x.is_contiguous():
x = x.contiguous()
key = (x.data_ptr(), x.shape[0])
last = self._last
if last is not None and last[0] == key:
last[1].replay()
return last[2]
M = x.shape[0]
entry = self._graphs.get(key)
if entry is not None:
g, y, _ = entry
self._last = (key, g, y)
g.replay()
return y
path = self._path(M)
call, y = self._make_call(x, path)
# Warm twice (Triton autotune / ext load happen here) outside capture.
call()
call()
torch.cuda.synchronize()
g = torch.cuda.CUDAGraph()
with torch.cuda.graph(g):
call()
if len(self._graphs) >= 32:
self._graphs.clear()
self._last = None
self._graphs[key] = (g, y, x) # hold x: the graph reads its buffer
self._last = (key, g, y)
g.replay()
return y
# Skip nn.Module hook machinery on the hot path.
__call__ = forward
20260719_065412_or-fable_anthropic_claude-fable-5_07_w4a16_gemm