KernelBench hard · B200
KDA CUTLASS GLM-5.2
passdid not score
harnesszai-claudeagent session1h 40mtotal wall1h 40mcheck13sbenchmark2soutput tokens—gpu-lock wait2sgpu-lock held13sregimecompute
Per-shape vs governing ceilingeach shape graded against whichever binds — bf16 compute or HBM bandwidth
2×1024×8×128×128×640.073 ms1.3%0.35 TB/s · 4% of 8.0 TB/s HBM · also 29 TFLOPS (1% of compute)
2×2048×8×128×128×640.112 ms1.7%0.45 TB/s · 6% of 8.0 TB/s HBM · also 38 TFLOPS (2% of compute)
1×4096×8×128×128×640.153 ms1.3%0.33 TB/s · 4% of 8.0 TB/s HBM · also 28 TFLOPS (1% of compute)
1×2048×4×128×128×640.091 ms0.5%0.14 TB/s · 2% of 8.0 TB/s HBM · also 12 TFLOPS (1% of compute)
compute-bound memory-bound · bar + right column = official fraction of the ceiling (the geomean input)
geomean(1.3% · 1.7% · 1.3% · 0.5%) = 1.1%
Kernel source (redacted)
"""Custom Triton kernel for Kimi Delta Attention (KDA) forward, chunk form, on B200 (SM100).
Two kernels, mirroring the FLA production decomposition but written from scratch
(no fla.ops imports):
1. INTRA (grid: NT x B*H) -- per chunk, fully parallel:
* in-chunk cumulative gate g_cum = cumsum(g)
* gated K-K gram G[c,i] = sum_d k[c,d] exp(g[c,d]-g[i,d]) k[i,d]
* T = (I - M)^{-1}, M = strictly_lower(-beta * G)
computed exactly via the nilpotent product form
T = prod_{i=0}^{5} (I + M^{2^i}) (BT=64, strictly-lower => M^64 = 0)
* Aqk = lower_tri( (q*exp(g)) @ (k*exp(-g))^T ) (q pre-scaled by `scale`)
* w = T @ (beta * exp(g) * k) , u = T @ (beta * v)
* qg = q*scale*exp(g) , kg = k*exp(g_last - g) , g_last (per-channel decay)
2. FWD_H_FUSED (grid: B*H x V/BV) -- sequential inter-chunk recurrence, also writes o:
state S held as a single (K, BV) tile so the K-contracting matmuls
(w @ S and kg^T @ v_new) are single tl.dot calls, halving the per-step
critical-path depth versus splitting K. The smaller BV also raises program
count (B*H * V/BV) for better SM occupancy. per chunk i:
v_new = u - w @ S
o_i = qg @ S + Aqk @ v_new
S = diag(exp(g_last)) * S + kg^T @ v_new
The delta-rule transition is the full K x K matrix M = diag(d) - kg^T w, so -- like
the reference FLA implementation -- the inter-chunk pass is sequential (a parallel
scan would need K x K x K combines, far costlier than the recurrence). exp2 (with g
pre-scaled by 1/ln2) is used for the fast PTX ex2 path; GEMMs use bf16 operands with
fp32 accum.
"""
from __future__ import annotations
import torch
import torch.nn as nn
import triton
import triton.language as tl
OP_TYPE = "linear_attention"
SUPPORTED_PRECISIONS = ["bf16"]
HARDWARE_REQUIRED = ["RTX_PRO_6000", "H100", "B200"]
_RCP_LN2 = 1.4426950408889634 # 1 / ln(2)
@triton.jit
def _product_solve(M, BT: tl.constexpr, BF16: tl.constexpr):
"""T = (I - M)^{-1} for strictly-lower-triangular nilpotent M (M^BT = 0).
Exact closed form: T = prod_{i=0}^{log2(BT)-1} (I + M^{2^i})
= I + M + M^2 + ... + M^{BT-1} = (I - M)^{-1} (since M^BT = 0).
Implemented for BT = 64 (six factors, M^64 = 0).
BF16: dot inputs are recast to bf16 (2x MMA throughput on the latency-bound
small tiles) while accumulation stays in fp32.
"""
idx = tl.arange(0, BT)
eye = idx[:, None] == idx[None, :]
I = tl.where(eye, 1.0, 0.0) # fp32
T = I + M
if BF16:
P = tl.dot(M.to(tl.bfloat16), M.to(tl.bfloat16))
T = tl.dot(T.to(tl.bfloat16), (I + P).to(tl.bfloat16))
P = tl.dot(P.to(tl.bfloat16), P.to(tl.bfloat16)); T = tl.dot(T.to(tl.bfloat16), (I + P).to(tl.bfloat16))
P = tl.dot(P.to(tl.bfloat16), P.to(tl.bfloat16)); T = tl.dot(T.to(tl.bfloat16), (I + P).to(tl.bfloat16))
P = tl.dot(P.to(tl.bfloat16), P.to(tl.bfloat16)); T = tl.dot(T.to(tl.bfloat16), (I + P).to(tl.bfloat16))
P = tl.dot(P.to(tl.bfloat16), P.to(tl.bfloat16)); T = tl.dot(T.to(tl.bfloat16), (I + P).to(tl.bfloat16))
else:
P = tl.dot(M, M); T = tl.dot(T, I + P)
P = tl.dot(P, P); T = tl.dot(T, I + P)
P = tl.dot(P, P); T = tl.dot(T, I + P)
P = tl.dot(P, P); T = tl.dot(T, I + P)
P = tl.dot(P, P); T = tl.dot(T, I + P)
return T
@triton.jit
def _kda_intra_kernel(
Q, Kp, Vp, G, Beta,
W, U, QG, KG, AQK, GLAST,
scale,
T_total,
H: tl.constexpr,
KDIM: tl.constexpr,
VDIM: tl.constexpr,
BT: tl.constexpr,
BK: tl.constexpr,
BV: tl.constexpr,
):
i_t = tl.program_id(0)
i_bh = tl.program_id(1)
i_b = i_bh // H
i_h = i_bh % H
NT = T_total // BT
NK: tl.constexpr = KDIM // BK
NV: tl.constexpr = VDIM // BV
rcp = 1.4426950408889634
# bases at the (b, h) origin; block offsets carry the chunk position i_t*BT
base_qk = i_b * (T_total * H * KDIM) + i_h * KDIM
base_v = i_b * (T_total * H * VDIM) + i_h * VDIM
base_beta = i_b * (T_total * H) + i_h
b_beta = tl.load(
tl.make_block_ptr(Beta + base_beta, (T_total,), (H,), (i_t * BT,), (BT,), (0,)),
boundary_check=(0,)).to(tl.float32)
# ---- Pass 1: accumulate grams G, Aqk; store qg, kg, g_last ----
b_G = tl.zeros([BT, BT], dtype=tl.float32)
b_Aqk = tl.zeros([BT, BT], dtype=tl.float32)
sk = tl.arange(0, BK)
glast_base = (i_bh * NT + i_t) * KDIM
for i_k in range(NK):
p_off = i_k * BK
b_g = tl.load(
tl.make_block_ptr(G + base_qk, (T_total, KDIM), (H * KDIM, 1),
(i_t * BT, p_off), (BT, BK), (1, 0)),
boundary_check=(0, 1)).to(tl.float32)
gc = tl.cumsum(b_g, axis=0)
eg = tl.math.exp2(gc * rcp)
eng = tl.math.exp2(-gc * rcp)
b_k = tl.load(
tl.make_block_ptr(Kp + base_qk, (T_total, KDIM), (H * KDIM, 1),
(i_t * BT, p_off), (BT, BK), (1, 0)),
boundary_check=(0, 1)).to(tl.float32)
b_q = tl.load(
tl.make_block_ptr(Q + base_qk, (T_total, KDIM), (H * KDIM, 1),
(i_t * BT, p_off), (BT, BK), (1, 0)),
boundary_check=(0, 1)).to(tl.float32) * scale
b_G += tl.dot((b_k * eg).to(tl.bfloat16), tl.trans((b_k * eng).to(tl.bfloat16)))
b_Aqk += tl.dot((b_q * eg).to(tl.bfloat16), tl.trans((b_k * eng).to(tl.bfloat16)))
g_last_cum = tl.sum(b_g, axis=0) # = inclusive-cumsum last row for this K block
kg_blk = b_k * tl.math.exp2((g_last_cum[None, :] - gc) * rcp)
tl.store(
tl.make_block_ptr(QG + base_qk, (T_total, KDIM), (H * KDIM, 1),
(i_t * BT, p_off), (BT, BK), (1, 0)),
(b_q * eg).to(tl.bfloat16), boundary_check=(0, 1))
tl.store(
tl.make_block_ptr(KG + base_qk, (T_total, KDIM), (H * KDIM, 1),
(i_t * BT, p_off), (BT, BK), (1, 0)),
kg_blk.to(tl.bfloat16), boundary_check=(0, 1))
tl.store(GLAST + glast_base + p_off + sk, g_last_cum)
# ---- T = (I - M)^{-1}, M = strictly_lower(-beta * G) ----
idx = tl.arange(0, BT)
b_M = tl.where(idx[:, None] > idx[None, :], -b_beta[:, None] * b_G, 0.0)
b_T = _product_solve(b_M, BT, 1)
loweq = idx[:, None] >= idx[None, :]
b_Aqk = tl.where(loweq, b_Aqk, 0.0)
tl.store(
tl.make_block_ptr(AQK + (i_bh * NT + i_t) * BT * BT,
(BT, BT), (BT, 1), (0, 0), (BT, BT), (1, 0)),
b_Aqk.to(tl.bfloat16))
# ---- Pass 2: w = T @ (beta * exp(g) * k) ----
for i_k in range(NK):
p_off = i_k * BK
b_g = tl.load(
tl.make_block_ptr(G + base_qk, (T_total, KDIM), (H * KDIM, 1),
(i_t * BT, p_off), (BT, BK), (1, 0)),
boundary_check=(0, 1)).to(tl.float32)
eg = tl.math.exp2(tl.cumsum(b_g, axis=0) * rcp)
b_k = tl.load(
tl.make_block_ptr(Kp + base_qk, (T_total, KDIM), (H * KDIM, 1),
(i_t * BT, p_off), (BT, BK), (1, 0)),
boundary_check=(0, 1)).to(tl.float32)
b_w = tl.dot(b_T.to(tl.bfloat16), (b_beta[:, None] * eg * b_k).to(tl.bfloat16))
tl.store(
tl.make_block_ptr(W + base_qk, (T_total, KDIM), (H * KDIM, 1),
(i_t * BT, p_off), (BT, BK), (1, 0)),
b_w.to(tl.bfloat16), boundary_check=(0, 1))
# ---- Pass 3: u = T @ (beta * v) ----
for i_v in range(NV):
p_off = i_v * BV
b_v = tl.load(
tl.make_block_ptr(Vp + base_v, (T_total, VDIM), (H * VDIM, 1),
(i_t * BT, p_off), (BT, BV), (1, 0)),
boundary_check=(0, 1)).to(tl.float32)
b_u = tl.dot(b_T.to(tl.bfloat16), (b_beta[:, None] * b_v).to(tl.bfloat16))
tl.store(
tl.make_block_ptr(U + base_v, (T_total, VDIM), (H * VDIM, 1),
(i_t * BT, p_off), (BT, BV), (1, 0)),
b_u.to(tl.bfloat16), boundary_check=(0, 1))
@triton.jit
def _kda_fwd_h_fused_kernel(
U, W, QG, KG, AQK, GLAST, O,
T_total,
H: tl.constexpr,
KDIM: tl.constexpr,
VDIM: tl.constexpr,
BT: tl.constexpr,
BV: tl.constexpr,
):
"""Sequential inter-chunk recurrence; fuses the output (o) write.
State S held as a single (KDIM, BV) tile so the K-contracting matmuls
(w @ S and kg^T @ v_new) are single tl.dot calls -- this halves the
per-step critical-path depth versus splitting K, and the smaller BV raises
program count (B*H * V/BV) for better SM occupancy.
"""
i_bh = tl.program_id(0)
i_v = tl.program_id(1)
i_b = i_bh // H
i_h = i_bh % H
NT = T_total // BT
RCP: tl.constexpr = 1.4426950408889634
base_qk = i_b * (T_total * H * KDIM) + i_h * KDIM
base_v = i_b * (T_total * H * VDIM) + i_h * VDIM
s = tl.zeros([KDIM, BV], dtype=tl.float32) # full-K recurrent state
sk = tl.arange(0, KDIM)
for i_t in range(NT):
b_u = tl.load(
tl.make_block_ptr(U + base_v, (T_total, VDIM), (H * VDIM, 1),
(i_t * BT, i_v * BV), (BT, BV), (1, 0)),
boundary_check=(0, 1)).to(tl.float32)
# w @ S -> (BT, BV), single dot over full K
b_w = tl.load(
tl.make_block_ptr(W + base_qk, (T_total, KDIM), (H * KDIM, 1),
(i_t * BT, 0), (BT, KDIM), (1, 0)),
boundary_check=(0, 1))
b_vnew = b_u - tl.dot(b_w, s.to(tl.bfloat16))
# o = qg @ S + Aqk @ v_new
b_qg = tl.load(
tl.make_block_ptr(QG + base_qk, (T_total, KDIM), (H * KDIM, 1),
(i_t * BT, 0), (BT, KDIM), (1, 0)),
boundary_check=(0, 1))
b_Aqk = tl.load(
tl.make_block_ptr(AQK + (i_bh * NT + i_t) * BT * BT,
(BT, BT), (BT, 1), (0, 0), (BT, BT), (1, 0)))
b_o = tl.dot(b_qg, s.to(tl.bfloat16)) + tl.dot(b_Aqk, b_vnew.to(tl.bfloat16))
tl.store(
tl.make_block_ptr(O + base_v, (T_total, VDIM), (H * VDIM, 1),
(i_t * BT, i_v * BV), (BT, BV), (1, 0)),
b_o.to(tl.bfloat16), boundary_check=(0, 1))
# S = diag(exp(g_last)) * S + kg^T @ v_new
d = tl.math.exp2(tl.load(GLAST + (i_bh * NT + i_t) * KDIM + sk) * RCP)
b_kg = tl.load(
tl.make_block_ptr(KG + base_qk, (T_total, KDIM), (H * KDIM, 1),
(i_t * BT, 0), (BT, KDIM), (1, 0)),
boundary_check=(0, 1))
s = d[:, None] * s + tl.dot(tl.trans(b_kg), b_vnew.to(tl.bfloat16))
def _run_kda(q, k, v, g, beta, scale, BT=64, BK=64, BV=16,
intra_warps=4, intra_stages=1, fwd_warps=4, fwd_stages=4):
B, T, H, K = q.shape
V = v.shape[-1]
NT = T // BT
# Thin (b,h) grids underfill the GPU; give the intra kernel more warps.
if intra_warps is None:
intra_warps = 8 if B * H <= 4 else 4
q = q.contiguous(); k = k.contiguous(); v = v.contiguous()
g = g.contiguous(); beta = beta.contiguous()
w = torch.empty(B, T, H, K, dtype=torch.bfloat16, device=q.device)
u = torch.empty(B, T, H, V, dtype=torch.bfloat16, device=q.device)
qg = torch.empty(B, T, H, K, dtype=torch.bfloat16, device=q.device)
kg = torch.empty(B, T, H, K, dtype=torch.bfloat16, device=q.device)
Aqk = torch.empty(B * H * NT, BT, BT, dtype=torch.bfloat16, device=q.device)
glast = torch.empty(B * H, NT, K, dtype=torch.float32, device=q.device)
_kda_intra_kernel[(NT, B * H)](
q, k, v, g, beta, w, u, qg, kg, Aqk, glast,
scale, T, H, K, V, BT, BK, V,
num_stages=intra_stages, num_warps=intra_warps)
o = torch.empty(B, T, H, V, dtype=torch.bfloat16, device=q.device)
_kda_fwd_h_fused_kernel[(B * H, V // BV)](
u, w, qg, kg, Aqk, glast, o,
T, H, K, V, BT, BV,
num_stages=fwd_stages, num_warps=fwd_warps)
return o
class Model(nn.Module):
"""KDA forward (chunk form). No learned parameters; all inputs are activations."""
def __init__(self, B: int, T: int, H: int, K: int, V: int, chunk_size: int = 64):
super().__init__()
self.B, self.T, self.H, self.K, self.V = B, T, H, K, V
self.chunk_size = chunk_size
self.scale = float(K) ** -0.5
self.register_buffer("_dummy", torch.zeros(1), persistent=False)
def forward(self, q, k, v, g, beta):
return _run_kda(q, k, v, g, beta, scale=self.scale, BT=self.chunk_size)
# Module-level shape shims (overridden by check.py / benchmark.py per shape via reference).
B = 2
T = 1024
H = 8
K = 128
V = 128
CHUNK_SIZE = 64
def get_inputs():
torch.manual_seed(0)
q = torch.randn(B, T, H, K, dtype=torch.bfloat16) * 0.1
k = torch.randn(B, T, H, K, dtype=torch.bfloat16) * 0.1
v = torch.randn(B, T, H, V, dtype=torch.bfloat16) * 0.1
g = (torch.randn(B, T, H, K, dtype=torch.float32) * 0.1 - 0.05)
beta = torch.sigmoid(torch.randn(B, T, H, dtype=torch.bfloat16))
return [q, k, v, g, beta]
def get_init_inputs():
return [B, T, H, K, V, CHUNK_SIZE]
20260620_111931_zai-claude_glm-5.2_02_kda_cutlass