KernelBench hard · B200

W4A16 GEMM Kimi K3 (1M)

2.64%geomean peak fraction across shapes

manually audited: clean

Genuine fused W4A16 Triton kernel with grouped-launch swizzle: single kernel walks K in 128-row quant groups (64 packed uint8 rows per tile), unpacks nibbles in registers, dequants ((nibble - zero) * scale) in bf16, and accumulates via two tl.dot calls per group on even/odd-K activation slices split out of one contiguous load (tl.reshape + tl.split). GROUP_M=8 program swizzle for L2 reuse, M-dependent block-config pick, pipelined tl.range(num_stages). Stateless forward, no caching, no data_ptr, no inspect/stack sniffing, no forbidden ops. The known kinetic failure mode - reading prior kinetic runs' annotation YAMLs - is absent here: zero foreign run ids and zero results/annotations references in the entire transcript. Long honest session (4.1 h, $107) dominated by CUTLASS SM100 mixed-input and hand-CUDA experiments that dead-ended; final answer fell back to the tuned Triton kernel.

harnesskinetic-claudeagent session4h 3mtotal wall4h 9mcheck6mbenchmark17soutput tokens226,813cost$106.85gpu-lock wait1h 18mgpu-lock held19mregimememory

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

1×12288×40960.092 ms3.6%0.29 TB/s · 4% of 8.0 TB/s HBM · also 1 TFLOPS (0% of compute)
32×12288×40960.099 ms3.5%0.28 TB/s · 4% of 8.0 TB/s HBM · also 33 TFLOPS (1% of compute)
256×12288×40960.229 ms1.9%112 TFLOPS · 5% of 2,250 TF bf16 peak · also 0.15 TB/s (2% of HBM)
1×4096×40960.090 ms1.3%0.10 TB/s · 1% of 8.0 TB/s HBM · also 0 TFLOPS (0% of compute)
16×14336×40960.095 ms4.2%0.34 TB/s · 4% of 8.0 TB/s HBM · also 20 TFLOPS (1% of compute)

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

geomean(3.6% · 3.5% · 1.9% · 1.3% · 4.2%) = 2.6%

Kernel source (redacted)
"""W4A16 weight-only quantized GEMM (AWQ/GPTQ-style, group_size=128, bf16).

Fused unpack + dequant + GEMM. V1: Triton kernel for all shapes.

Layout trick: instead of interleaving nibbles along K in registers, load the
packed (K//2, N) uint8 tile once and do TWO dots per group:
    acc += x_even @ dequant(lo_nibbles) + x_odd @ dequant(hi_nibbles)
Each tile spans exactly one 128-row group (64 packed rows), so scales/zeros
are scalar-per-column within the tile and broadcast cleanly.
"""
from __future__ import annotations

import torch
import torch.nn as nn
import triton
import triton.language as tl

OP_TYPE = "gemm_w4a16"
SUPPORTED_PRECISIONS = ["int4_bf16"]
HARDWARE_REQUIRED = ["RTX_PRO_6000", "H100", "B200"]

GROUP_SIZE = 128


@triton.jit
def _w4a16_kernel(
    X, WQ, SC, ZO, OUT,
    M, N, K,
    BM: tl.constexpr, BN: tl.constexpr,
    GROUP_M: tl.constexpr, NSTAGE: tl.constexpr,
):
    # Each k-tile covers KP packed rows = 2*KP k values = one 128 group (KP=64).
    KP: tl.constexpr = 64
    pid = tl.program_id(0)
    grid_m = tl.cdiv(M, BM)
    grid_n = tl.cdiv(N, BN)
    width = GROUP_M * grid_n
    group_id = pid // width
    group_size = min(grid_m - group_id * GROUP_M, GROUP_M)
    pid_m = group_id * GROUP_M + (pid % group_size)
    pid_n = (pid % width) // group_size

    rm = pid_m * BM + tl.arange(0, BM)
    rn = pid_n * BN + tl.arange(0, BN)
    rk = tl.arange(0, KP)
    rk2 = tl.arange(0, 2 * KP)
    m_mask = rm < M

    KH = K // 2
    x_ptr = X + rm[:, None] * K + rk2[None, :]
    w_ptr = WQ + rk[:, None] * N + rn[None, :]
    g_ptr = SC + rn  # + group*N as we advance
    z_ptr = ZO + rn

    acc = tl.zeros((BM, BN), dtype=tl.float32)
    for g in tl.range(0, KH, KP, num_stages=NSTAGE):
        # x tile: (BM, 2*KP) contiguous k, split even/odd k -> two dots
        a = tl.load(x_ptr, mask=m_mask[:, None], other=0.0)
        xe, xo = tl.split(tl.reshape(a, (BM, KP, 2)))
        w = tl.load(w_ptr)
        s = tl.load(g_ptr)
        z = tl.load(z_ptr)
        lo = (w & 0xF).to(tl.bfloat16)
        hi = ((w >> 4) & 0xF).to(tl.bfloat16)
        b_e = (lo - z) * s
        b_o = (hi - z) * s
        acc = tl.dot(xe, b_e, acc)
        acc = tl.dot(xo, b_o, acc)
        x_ptr += 2 * KP
        w_ptr += KP * N
        g_ptr += N
        z_ptr += N

    o_ptr = OUT + rm[:, None] * N + rn[None, :]
    tl.store(o_ptr, acc.to(tl.bfloat16), mask=m_mask[:, None])


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
        assert K % 2 == 0
        self.M, self.N, self.K = M, N, K
        self.group_size = group_size
        n_groups = K // group_size
        Kh = K // 2
        self.register_buffer("w_q", torch.zeros(Kh, N, dtype=torch.uint8))
        self.register_buffer("scales", torch.zeros(n_groups, N, dtype=torch.bfloat16))
        self.register_buffer("zeros", torch.zeros(n_groups, N, dtype=torch.bfloat16))

        # config pick (tuned later)
        if M <= 16:
            self.BM, self.BN, self.nw, self.ns = 16, 128, 4, 4
        elif M <= 32:
            self.BM, self.BN, self.nw, self.ns = 32, 128, 4, 4
        else:
            self.BM, self.BN, self.nw, self.ns = 64, 128, 4, 3

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        M = x.shape[0]
        out = torch.empty((M, self.N), dtype=torch.bfloat16, device=x.device)
        grid = (triton.cdiv(M, self.BM) * triton.cdiv(self.N, self.BN),)
        _w4a16_kernel[grid](
            x, self.w_q, self.scales, self.zeros, out,
            M, self.N, self.K,
            BM=self.BM, BN=self.BN, GROUP_M=8, NSTAGE=self.ns,
            num_warps=self.nw,
        )
        return out


M = 1
N = 12288
K = 4096


def get_inputs():
    x = torch.randn(M, K, dtype=torch.bfloat16)
    return [x]


def get_init_inputs():
    return [M, N, K]

20260719_030522_kinetic-claude_kinetic-0715_1m__07_w4a16_gemm