KernelBench hard · B200

W4A16 GEMM Claude Fable 5

10.7%geomean peak fraction across shapes

manually audited: clean

harnessor-fableagent session1h 58mtotal wall1h 58mcheck7sbenchmark7soutput tokensgpu-lock wait25mgpu-lock held6mregimememory

Per-shape vs governing ceilingeach shape graded against whichever binds — bf16 compute or HBM bandwidth

1×12288×40960.017 ms20.3%1.62 TB/s · 20% of 8.0 TB/s HBM · also 6 TFLOPS (0% of compute)
32×12288×40960.030 ms11.6%0.93 TB/s · 12% of 8.0 TB/s HBM · also 107 TFLOPS (5% of compute)
256×12288×40960.093 ms4.7%278 TFLOPS · 12% of 2,250 TF bf16 peak · also 0.38 TB/s (5% of HBM)
1×4096×40960.012 ms9.4%0.75 TB/s · 9% of 8.0 TB/s HBM · also 3 TFLOPS (0% of compute)
16×14336×40960.030 ms13.5%1.08 TB/s · 13% of 8.0 TB/s HBM · also 64 TFLOPS (3% of compute)

compute-bound memory-bound · bar + right column = official fraction of the ceiling (the geomean input)

geomean(20.3% · 11.6% · 4.7% · 9.4% · 13.5%) = 10.7%

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