KernelBench hard · H100
FP8 GEMM GLM-5.2
7.84%geomean peak fraction across shapes
harnesszai-claudeagent session45mtotal wall48mcheck52sbenchmark2moutput tokens144,156gpu-lock wait3mgpu-lock held18sregimecompute
Per-shape vs governing ceilingeach shape graded against whichever binds — fp8 compute or HBM bandwidth
4096×4096×40960.226 ms40.2%608 TFLOPS · 40% of 1,513 TF fp8 peak · also 0.30 TB/s (15% of HBM)
4096×4096×412713.454 ms0.7%10 TFLOPS · 1% of 1,513 TF fp8 peak · also 0.01 TB/s (0% of HBM)
32×8192×81920.102 ms2.8%0.67 TB/s · 33% of 2.0 TB/s HBM · also 42 TFLOPS (3% of compute)
4096×14336×40960.639 ms49.8%753 TFLOPS · 50% of 1,513 TF fp8 peak · also 0.30 TB/s (15% of HBM)
compute-bound memory-bound · bar + right column = official fraction of the ceiling (the geomean input)
geomean(40.2% · 0.7% · 2.8% · 49.8%) = 7.8%
Kernel source (redacted)
"""FP8 e4m3 x e4m3 GEMM for H100 PCIe (SM90 Hopper).
y = (x @ w.T) * weight_scale (output bf16)
x: fp8_e4m3 (M, K)
w: fp8_e4m3 (N, K) [already quantized into the e4m3 range]
weight_scale: fp32 (N,) [per-output-channel dequant scale]
The MMA runs as fp8 x fp8 -> fp32 on Hopper tensor cores; the per-channel
scale is folded into the epilogue. This is mathematically equivalent to the
bf16-matmul reference (fp8->bf16 is exact) up to accumulation-order rounding,
well within the 0.2 atol/rtol tolerance.
"""
import torch
import torch.nn as nn
import triton
import triton.language as tl
@triton.jit
def _fp8_gemm_kernel(
A, B, C, S,
M, N, K,
stride_am, stride_ak,
stride_bn, stride_bk,
stride_cm, stride_cn,
BLOCK_M: tl.constexpr,
BLOCK_N: tl.constexpr,
BLOCK_K: tl.constexpr,
GROUP_M: tl.constexpr,
):
pid = tl.program_id(axis=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 % num_pid_in_group) % group_size_m)
pid_n = (pid % num_pid_in_group) // group_size_m
offs_am = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
offs_bn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
offs_k = tl.arange(0, BLOCK_K)
# A (M,K) row-major: A[m,k]; B (N,K) row-major, used transposed -> B[k,n]=w[n,k]
A_ptrs = A + (offs_am[:, None] * stride_am + offs_k[None, :] * stride_ak)
B_ptrs = B + (offs_k[:, None] * stride_bk + offs_bn[None, :] * stride_bn)
accumulator = tl.zeros((BLOCK_M, BLOCK_N), dtype=tl.float32)
for k in range(0, tl.cdiv(K, BLOCK_K)):
k_rem = K - k * BLOCK_K
a = tl.load(A_ptrs, mask=offs_k[None, :] < k_rem, other=0.0)
b = tl.load(B_ptrs, mask=offs_k[:, None] < k_rem, other=0.0)
accumulator = tl.dot(a, b, accumulator)
A_ptrs += BLOCK_K * stride_ak
B_ptrs += BLOCK_K * stride_bk
offs_cm = pid_m * BLOCK_M + tl.arange(0, BLOCK_M)
offs_cn = pid_n * BLOCK_N + tl.arange(0, BLOCK_N)
C_ptrs = C + (offs_cm[:, None] * stride_cm + offs_cn[None, :] * stride_cn)
c_mask = (offs_cm[:, None] < M) & (offs_cn[None, :] < N)
scale = tl.load(S + offs_cn, mask=offs_cn < N, other=0.0)
acc = accumulator * scale[None, :]
tl.store(C_ptrs, acc.to(tl.bfloat16), mask=c_mask)
def _select_config(M: int, N: int, K: int) -> dict:
"""Hand-picked Hopper fp8 tile configs (refined via benchmarking)."""
if M <= 64:
# Skinny / decode: memory-bound; keep M tile small, N tile moderate.
return dict(BLOCK_M=32, BLOCK_N=128, BLOCK_K=128, GROUP_M=8,
num_warps=4, num_stages=3)
# Big-M compute-bound shapes.
return dict(BLOCK_M=128, BLOCK_N=256, BLOCK_K=128, GROUP_M=8,
num_warps=8, num_stages=3)
class Model(nn.Module):
def __init__(self, M: int, N: int, K: int):
super().__init__()
self.M, self.N, self.K = M, N, K
# Must match reference exactly (strict load_state_dict): weight (N,K) fp8,
# weight_scale (N,) fp32.
self.register_buffer("weight", torch.empty(N, K, dtype=torch.float8_e4m3fn))
self.register_buffer("weight_scale", torch.empty(N, dtype=torch.float32))
def forward(self, x: torch.Tensor) -> torch.Tensor:
M = x.shape[0]
N, K = self.N, self.K
assert x.dtype == torch.float8_e4m3fn and x.is_contiguous()
w = self.weight
assert w.dtype == torch.float8_e4m3fn and w.is_contiguous()
y = torch.empty(M, N, dtype=torch.bfloat16, device=x.device)
cfg = _select_config(M, N, K)
BM, BN, BK = cfg["BLOCK_M"], cfg["BLOCK_N"], cfg["BLOCK_K"]
grid = (triton.cdiv(M, BM) * triton.cdiv(N, BN),)
_fp8_gemm_kernel[grid](
x, w, y, self.weight_scale,
M, N, K,
x.stride(0), x.stride(1),
w.stride(0), w.stride(1),
y.stride(0), y.stride(1),
BLOCK_M=BM, BLOCK_N=BN, BLOCK_K=BK, GROUP_M=cfg["GROUP_M"],
num_warps=cfg["num_warps"], num_stages=cfg["num_stages"],
)
return y
20260618_170819_zai-claude_glm-5.2_01_fp8_gemm