kernelbench.com

KernelBench hard · H100

W4A16 GEMM GLM-5.2

8.33%geomean peak fraction across shapes
harnesszai-claudeagent session45mtotal wall55mcheck5mbenchmark4moutput tokensgpu-lock wait9mgpu-lock held43sregimememory

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

1×12288×40960.116 ms11.3%0.23 TB/s · 11% of 2.0 TB/s HBM · also 1 TFLOPS (0% of compute)
32×12288×40960.163 ms8.3%0.17 TB/s · 8% of 2.0 TB/s HBM · also 20 TFLOPS (3% of compute)
256×12288×40960.234 ms7.3%110 TFLOPS · 15% of 756 TF bf16 peak · also 0.15 TB/s (7% of HBM)
1×4096×40960.102 ms4.3%0.09 TB/s · 4% of 2.0 TB/s HBM · also 0 TFLOPS (0% of compute)
16×14336×40960.116 ms13.4%0.27 TB/s · 13% of 2.0 TB/s HBM · also 16 TFLOPS (2% of compute)

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

geomean(11.3% · 8.3% · 7.3% · 4.3% · 13.4%) = 8.3%

Kernel source (redacted)
"""W4A16 weight-only quantized GEMM for H100 PCIe (SM90 Hopper).

Fuses int4 unpack + per-group dequant + GEMM in a single Triton kernel, modeled
on the group-quant mixed-input matmul pattern (see torchao hqq _mixed_mm_kernel):
  * packed uint8 weights (low nibble = even-K, high nibble = odd-K)
  * dequant w = (q - z) * s with per-group (group=128) bf16 scales/zeros
  * a single tl.dot per K-iteration with fp32 accumulation
  * group-M L2 swizzle, autotuned over IO-bound (small M) and compute-bound tiles
"""
from __future__ import annotations

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

GROUP_SIZE = 128


def _io_configs():
    cfgs = []
    for num_stages in (4, 5):
        for bn in (64, 128, 256):
            for bk in (64, 128):
                warps = 4 if bn <= 128 else 8
                cfgs.append(Config({"BLOCK_M": 16, "BLOCK_N": bn, "BLOCK_K": bk,
                                    "GROUP_M": 8}, num_stages=num_stages, num_warps=warps))
    for num_stages in (4, 5):
        cfgs.append(Config({"BLOCK_M": 32, "BLOCK_N": 128, "BLOCK_K": 128,
                            "GROUP_M": 8}, num_stages=num_stages, num_warps=4))
        cfgs.append(Config({"BLOCK_M": 32, "BLOCK_N": 256, "BLOCK_K": 128,
                            "GROUP_M": 8}, num_stages=num_stages, num_warps=8))
    return cfgs


def _compute_configs():
    cfgs = []
    for bm, bn, bk, st, w in (
        (64, 128, 128, 4, 4),
        (64, 256, 128, 4, 4),
        (128, 128, 128, 4, 4),
        (128, 256, 128, 3, 8),
        (128, 128, 64, 4, 4),
        (256, 128, 128, 3, 8),
        (64, 128, 64, 5, 4),
        (128, 256, 64, 3, 8),
    ):
        cfgs.append(Config({"BLOCK_M": bm, "BLOCK_N": bn, "BLOCK_K": bk,
                            "GROUP_M": 8}, num_stages=st, num_warps=w))
    return cfgs


@triton.jit
def _w4a16_kernel(
    A, B, scales_ptr, zeros_ptr, C,
    M, N, K,
    stride_am, stride_ak,
    stride_bk, stride_bn,
    stride_cm, stride_cn,
    stride_sg, stride_sn,
    BLOCK_M: tl.constexpr,
    BLOCK_N: tl.constexpr,
    BLOCK_K: tl.constexpr,
    GROUP_M: tl.constexpr,
    QGROUP: tl.constexpr,
):
    pid = tl.program_id(0)
    grid_m = tl.cdiv(M, BLOCK_M)
    grid_n = tl.cdiv(N, BLOCK_N)
    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

    ram = tl.max_contiguous(tl.multiple_of(pid_m * BLOCK_M + tl.arange(0, BLOCK_M), BLOCK_M), BLOCK_M)
    rbn = tl.max_contiguous(tl.multiple_of(pid_n * BLOCK_N + tl.arange(0, BLOCK_N), BLOCK_N), BLOCK_N)

    rk_full = tl.arange(0, BLOCK_K)
    rk_half = tl.arange(0, BLOCK_K // 2)

    A += ram[:, None] * stride_am + rk_full[None, :] * stride_ak
    B += rk_half[:, None] * stride_bk + rbn[None, :] * stride_bn

    offs_scale_n = pid_n * BLOCK_N * stride_sn + tl.arange(0, BLOCK_N) * stride_sn

    acc = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)

    for k in range(0, tl.cdiv(K, BLOCK_K)):
        a = tl.load(A)
        qb = tl.load(B)

        g = (k * BLOCK_K) // QGROUP
        scales = tl.load(scales_ptr + offs_scale_n + g * stride_sg)
        zeros = tl.load(zeros_ptr + offs_scale_n + g * stride_sg)

        # unsigned int4 unpack (values 0..15)
        qb_lo = qb & 0xF
        qb_hi = qb >> 4
        dq_b = tl.join(
            qb_lo.to(tl.float16).to(tl.bfloat16),
            qb_hi.to(tl.float16).to(tl.bfloat16),
        ).permute(0, 2, 1).reshape(BLOCK_K, BLOCK_N)

        dq_b = (dq_b - zeros[None, :]) * scales[None, :]
        acc += tl.dot(a, dq_b, out_dtype=tl.float32, input_precision="ieee")

        A += BLOCK_K * stride_ak
        B += (BLOCK_K // 2) * stride_bk

    acc = acc.to(tl.bfloat16)
    offs_cm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
    offs_cn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
    C = C + stride_cm * offs_cm[:, None] + stride_cn * offs_cn[None, :]
    mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
    tl.store(C, acc, mask=mask)


_w4a16_io = triton.autotune(_io_configs(), key=["M", "N", "K"])(_w4a16_kernel)
_w4a16_compute = triton.autotune(_compute_configs(), key=["M", "N", "K"])(_w4a16_kernel)


def _launch(kernel, x, wq, scales, zeros, out, M, N, K):
    grid = lambda meta: (triton.cdiv(M, meta["BLOCK_M"]) * triton.cdiv(N, meta["BLOCK_N"]),)
    kernel[grid](
        x, wq, scales, zeros, out,
        M, N, K,
        x.stride(0), x.stride(1),
        wq.stride(0), wq.stride(1),
        out.stride(0), out.stride(1),
        scales.stride(0), scales.stride(1),
        QGROUP=GROUP_SIZE,
    )


def w4a16_gemm(x, wq, scales, zeros, M, N, K):
    out = torch.empty((M, N), dtype=torch.bfloat16, device=x.device)
    # Dispatch: tiny-M is IO-bound; large-M is compute-bound.
    if M <= 64:
        _launch(_w4a16_io, x, wq, scales, zeros, out, M, N, K)
    else:
        _launch(_w4a16_compute, x, wq, scales, zeros, out, M, N, K)
    return out


class Model(nn.Module):
    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
        self.register_buffer("w_q", torch.zeros((K // 2, N), dtype=torch.uint8))
        self.register_buffer("scales", torch.zeros((K // 128, N), dtype=torch.bfloat16))
        self.register_buffer("zeros", torch.zeros((K // 128, N), dtype=torch.bfloat16))

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = x.contiguous()
        return w4a16_gemm(x, self.w_q, self.scales, self.zeros, self.M, self.N, self.K)


M = 1
N = 12288
K = 4096


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


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

20260618_170934_zai-claude_glm-5.2_07_w4a16_gemm