KernelBench hard · B200
W4A16 GEMM GLM-5.2
passdid not score
harnesszai-claudeagent session1h 40mtotal wall1h 45mcheck3mbenchmark2moutput tokens226,029gpu-lock wait4mgpu-lock held89sregimememory
Per-shape vs governing ceilingeach shape graded against whichever binds — bf16 compute or HBM bandwidth
1×12288×40960.048 ms6.9%0.56 TB/s · 7% of 8.0 TB/s HBM · also 2 TFLOPS (0% of compute)
32×12288×40960.055 ms6.4%0.51 TB/s · 6% of 8.0 TB/s HBM · also 59 TFLOPS (3% of compute)
256×12288×40960.169 ms2.6%152 TFLOPS · 7% of 2,250 TF bf16 peak · also 0.21 TB/s (3% of HBM)
1×4096×40960.040 ms2.8%0.22 TB/s · 3% of 8.0 TB/s HBM · also 1 TFLOPS (0% of compute)
16×14336×40960.048 ms8.2%0.66 TB/s · 8% of 8.0 TB/s HBM · also 39 TFLOPS (2% of compute)
compute-bound memory-bound · bar + right column = official fraction of the ceiling (the geomean input)
geomean(6.9% · 6.4% · 2.6% · 2.8% · 8.2%) = 4.8%
Kernel source (redacted)
"""W4A16 weight-only int4 quantized GEMM for B200 (SM100, Blackwell).
Fused int4 unpack + per-group dequant + GEMM in Triton. AWQ/GPTQ-style asymmetric
int4 with explicit bf16 zero-points and per-group bf16 scales, group size 128.
Weight packing: w_q is (K//2, N) uint8; two int4 per byte, low nibble = even-K
row, high nibble = odd-K row.
Dequant (cvt-free via bf16 bitcast magic): zero-points are integers 0..15, so
raw = bitcast_bf16(0x4300 | nibble) = 128 + nibble (exact)
z_adj = bitcast_bf16(0x4300 | z) = 128 + z (exact)
w_bf = (raw - z_adj) * scale (exact integer diff)
The integer subtraction at magnitude ~135 is exact in bf16, so no fp cvt is
needed for the unpack -- only free bitcasts.
"""
from __future__ import annotations
import torch
import torch.nn as nn
import triton
import triton.language as tl
GROUP_SIZE = 128
def _configs():
cfgs = []
for bm in (16, 32, 64, 128):
for bn in (64, 128, 256):
for nw in (4, 8):
for ns in (3, 4):
cfgs.append(
triton.Config({"BLOCK_M": bm, "BLOCK_N": bn, "BLOCK_K": 128},
num_warps=nw, num_stages=ns)
)
return cfgs
@triton.autotune(configs=_configs(), key=["M", "N", "K"])
@triton.jit
def w4a16_gemm_kernel(
x_ptr, wq_ptr, scales_ptr, zeros_ptr, out_ptr,
M, N, K,
stride_xm, stride_xk,
stride_wkh, stride_wn,
stride_sg, stride_sn,
stride_om, stride_on,
BLOCK_M: tl.constexpr, BLOCK_N: tl.constexpr, BLOCK_K: tl.constexpr,
GROUP_SIZE_K: tl.constexpr,
GROUP_M: tl.constexpr = 8,
):
pid = tl.program_id(0)
num_pid_m = tl.cdiv(M, BLOCK_M)
num_pid_n = tl.cdiv(N, BLOCK_N)
num_pid_in_group = GROUP_M * num_pid_n
group_id = pid // num_pid_in_group
first_pid_m = group_id * GROUP_M
group_size_m = min(num_pid_m - first_pid_m, GROUP_M)
pid_m = first_pid_m + (pid % group_size_m)
pid_n = (pid % num_pid_in_group) // group_size_m
rm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
rn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
rk = tl.arange(0, BLOCK_K)
BK_HALF: tl.constexpr = BLOCK_K // 2
rkh = tl.arange(0, BK_HALF)
m_mask = rm < M
n_mask = rn < N
acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
x_rm = x_ptr + rm[:, None] * stride_xm
w_rn = wq_ptr + rn[None, :] * stride_wn
for ki in range(0, K, BLOCK_K):
g_idx = ki // GROUP_SIZE_K
x = tl.load(x_rm + (ki + rk)[None, :] * stride_xk, mask=m_mask[:, None], other=0.0)
x2 = tl.reshape(x, (BLOCK_M, BK_HALF, 2))
x_even, x_odd = tl.split(x2)
kh = (ki // 2) + rkh
w_packed = tl.load(w_rn + kh[:, None] * stride_wkh, mask=n_mask[None, :], other=0)
raw_lo = tl.cast((w_packed & 0xF).to(tl.uint16) | 0x4300, tl.bfloat16, bitcast=True)
raw_hi = tl.cast(((w_packed >> 4) & 0xF).to(tl.uint16) | 0x4300, tl.bfloat16, bitcast=True)
z = tl.load(zeros_ptr + g_idx * stride_sg + rn * stride_sn, mask=n_mask, other=0.0)
s = tl.load(scales_ptr + g_idx * stride_sg + rn * stride_sn, mask=n_mask, other=0.0)
z_adj = tl.cast(z.to(tl.uint16) | 0x4300, tl.bfloat16, bitcast=True)
w_lo = (raw_lo - z_adj[None, :]) * s[None, :]
w_hi = (raw_hi - z_adj[None, :]) * s[None, :]
acc += tl.dot(x_even, w_lo)
acc += tl.dot(x_odd, w_hi)
out_ptrs = out_ptr + rm[:, None] * stride_om + rn[None, :] * stride_on
tl.store(out_ptrs, acc.to(tl.bfloat16), mask=m_mask[:, None] & n_mask[None, :])
class Model(nn.Module):
"""W4A16 GEMM: y = x @ dequant(w_q, scales, zeros)."""
def __init__(self, M: int, N: int, K: int, group_size: int = GROUP_SIZE):
super().__init__()
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.zeros(K // 2, 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))
def forward(self, x: torch.Tensor) -> torch.Tensor:
x = x.contiguous()
M = x.shape[0]
N, K = self.N, self.K
out = torch.empty((M, N), dtype=torch.bfloat16, device=x.device)
grid = lambda meta: (triton.cdiv(M, meta["BLOCK_M"]) * triton.cdiv(N, meta["BLOCK_N"]),)
w4a16_gemm_kernel[grid](
x, self.w_q, self.scales, self.zeros, out,
M, N, K,
x.stride(0), x.stride(1),
self.w_q.stride(0), self.w_q.stride(1),
self.scales.stride(0), self.scales.stride(1),
out.stride(0), out.stride(1),
GROUP_SIZE_K=self.group_size,
)
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]
20260620_130026_zai-claude_glm-5.2_07_w4a16_gemm