KernelBench hard · H100
KDA CUTLASS Kimi K3 (256k)
manually audited: clean
Genuine chunk-parallel KDA (Kimi Delta Attention) forward, pure Triton, 3-kernel pipeline. K1 (_kda_kk_kernel) does the per-chunk gate cumsum via a triangular-ones tf32 matmul (log2 units), builds the decayed Aqk/Akk chunk matrices (fp16 MMA, fp32 acc) and the qg/kg/ke decayed projections plus the chunk-final decay e^{gc_last}. K2 (_kda_solve_wu_kernel) inverts (I - Akk) in-register with a Neumann product-doubling scheme (exact for the nilpotent strictly-lower Akk) and forms w = Ainv @ (beta*ke), u = Ainv @ (beta*v). K3 (_kda_scan_out_kernel) runs the exact sequential inter-chunk recurrence with a K x BV fp32 register state: o = qg @ S + Aqk @ (u - w @ S), S = S * 2^{gc_last} + kg^T @ (u - w @ S). This is the real delta-rule math: no fla / chunk_kda / forbidden ops, no cached or constant output path, no grader interaction. Host side caches only shape-keyed workspace buffers and compiled-kernel handles (direct .run launches) to cut launch overhead.
Per-shape vs governing ceilingeach shape graded against whichever binds — bf16 compute or HBM bandwidth
compute-bound memory-bound · bar + right column = official fraction of the ceiling (the geomean input)
geomean(3.0% · 3.5% · 2.8% · 1.5%) = 2.5%
Kernel source (redacted)
"""KDA forward (chunk form) — Triton kernels for SM90.
3-kernel pipeline:
K1 "kk": per (chunk, b*h): g cumsum in-register (tril-ones tf32 dot, log2
units), decayed qk/kk dots (fp16 MMA, fp32 acc). Emits Aqk (masked,
scale folded), qg, kg (transposed layout), ke (beta folded), Akk
(negated strictly-lower), e^{gc_last}.
K2 "solve_wu": per (chunk, b*h): load Akk, invert (I-A) in-register
(Neumann doubling, fp16 MMA), w = Ainv @ ke, u = Ainv @ (v*beta).
K3 "scan_out" (fused): per (b*h, V-block): sequential over chunks,
S = K x BV fp32 state in registers:
o = qg @ S + Aqk @ (u - w @ S) (written out directly)
S = S * 2^gc_last + kgT @ (u - w @ S)
Host-side: per-shape cached workspace + cached CompiledKernel handles with
direct `.run` launches to keep per-call overhead ~10 us.
"""
from __future__ import annotations
import torch
import torch.nn as nn
import triton
import triton.language as tl
LOG2E = tl.constexpr(1.4426950408889634)
@triton.jit
def _kda_kk_kernel(
q, k, g, beta,
Aqk, Akk, qg, kg, ke, gcl,
scale,
T,
H: tl.constexpr, K: tl.constexpr,
BT: tl.constexpr, BK: tl.constexpr, NK: tl.constexpr,
):
i_t, i_bh = tl.program_id(0), tl.program_id(1)
i_b = i_bh // H
i_h = i_bh % H
t0 = i_t * BT
o_t = t0 + tl.arange(0, BT)
o_r = tl.arange(0, BT)
# lower-tri ones for matrix cumsum (tf32)
m_qk = o_r[:, None] >= o_r[None, :]
m_low = o_r[:, None] > o_r[None, :]
b_L = tl.where(m_qk, 1.0, 0.0)
b_beta = tl.load(beta + (i_b * T + o_t) * H + i_h).to(tl.float32)
b_Aqk = tl.zeros([BT, BT], dtype=tl.float32)
b_A = tl.zeros([BT, BT], dtype=tl.float32)
for i_k in tl.static_range(NK):
o_k = i_k * BK + tl.arange(0, BK)
pg = g + ((i_b * T + o_t[:, None]) * H + i_h) * K + o_k[None, :]
b_g = tl.load(pg)
# cumsum along time via triangular-ones matmul (tf32 MMA)
b_gc = tl.dot(b_L, b_g, input_precision="tf32") * LOG2E
pq = q + ((i_b * T + o_t[:, None]) * H + i_h) * K + o_k[None, :]
pk = k + ((i_b * T + o_t[:, None]) * H + i_h) * K + o_k[None, :]
b_q = tl.load(pq).to(tl.float32)
b_k = tl.load(pk).to(tl.float32)
b_e = tl.exp2(b_gc)
b_ei = tl.exp2(-b_gc)
b_qe = (b_q * b_e * scale).to(tl.float16)
b_ke = (b_k * b_e).to(tl.float16)
b_kei = (b_k * b_ei).to(tl.float16)
b_Aqk += tl.dot(b_qe, tl.trans(b_kei))
b_A += tl.dot(b_ke, tl.trans(b_kei))
pqg = qg + ((i_b * T + o_t[:, None]) * H + i_h) * K + o_k[None, :]
tl.store(pqg, b_qe)
pke = ke + ((i_b * T + o_t[:, None]) * H + i_h) * K + o_k[None, :]
tl.store(pke, (b_ke.to(tl.float32) * b_beta[:, None]).to(tl.float16))
b_gcl = tl.exp2(tl.sum(tl.where((o_r == BT - 1)[:, None], b_gc, 0.0), axis=0)) # (BK,)
NTdim = tl.num_programs(0)
pgcl = gcl + ((i_b * NTdim + i_t) * H + i_h) * K + o_k
tl.store(pgcl, b_gcl)
b_kg = (b_kei.to(tl.float32) * b_gcl[None, :]).to(tl.float16)
pkg = kg + (((i_b * NTdim + i_t) * H + i_h) * K + o_k[:, None]) * BT + o_r[None, :]
tl.store(pkg, tl.trans(b_kg))
b_Aqk = tl.where(m_qk, b_Aqk, 0.0)
pAqk = Aqk + ((i_b * T + o_t[:, None]) * H + i_h) * BT + o_r[None, :]
tl.store(pAqk, b_Aqk.to(tl.float16))
b_A = -tl.where(m_low, b_A * b_beta[:, None], 0.0)
pAkk = Akk + ((i_b * T + o_t[:, None]) * H + i_h) * BT + o_r[None, :]
tl.store(pAkk, b_A.to(tl.float16))
@triton.jit
def _kda_solve_wu_kernel(
Akk, ke, v, beta,
w, u,
T,
H: tl.constexpr, K: tl.constexpr, V: tl.constexpr,
BT: tl.constexpr,
):
i_t, i_bh = tl.program_id(0), tl.program_id(1)
i_b = i_bh // H
i_h = i_bh % H
t0 = i_t * BT
o_t = t0 + tl.arange(0, BT)
o_r = tl.arange(0, BT)
pAkk = Akk + ((i_b * T + o_t[:, None]) * H + i_h) * BT + o_r[None, :]
b_A16 = tl.load(pAkk) # fp16
# inverse of (I - A): Neumann product doubling with fp16 MMA
m_I = (o_r[:, None] == o_r[None, :]).to(tl.float16)
b_Y = b_A16 + m_I
b_P = b_A16
for _ in tl.static_range(4):
b_P = tl.dot(b_P, b_P).to(tl.float16)
b_Y = b_Y + tl.dot(b_Y, b_P).to(tl.float16)
b_Ai = b_Y + tl.dot(b_Y, tl.dot(b_P, b_P).to(tl.float16)).to(tl.float16)
o_k = tl.arange(0, K)
pke = ke + ((i_b * T + o_t[:, None]) * H + i_h) * K + o_k[None, :]
b_ke = tl.load(pke)
b_w = tl.dot(b_Ai, b_ke)
pw = w + ((i_b * T + o_t[:, None]) * H + i_h) * K + o_k[None, :]
tl.store(pw, b_w.to(tl.float16))
b_beta = tl.load(beta + (i_b * T + o_t) * H + i_h).to(tl.float32)
o_v = tl.arange(0, V)
pv = v + ((i_b * T + o_t[:, None]) * H + i_h) * V + o_v[None, :]
b_v = tl.load(pv).to(tl.float32)
b_vb = (b_v * b_beta[:, None]).to(tl.float16)
b_u = tl.dot(b_Ai, b_vb)
pu = u + ((i_b * T + o_t[:, None]) * H + i_h) * V + o_v[None, :]
tl.store(pu, b_u.to(tl.float16))
@triton.jit
def _kda_scan_out_kernel(
w, u, qg, kg, Aqk, gcl, o,
T, NT,
H: tl.constexpr, K: tl.constexpr, V: tl.constexpr,
BT: tl.constexpr, BV: tl.constexpr,
):
i_v, i_bh = tl.program_id(0), tl.program_id(1)
i_b = i_bh // H
i_h = i_bh % H
o_k = tl.arange(0, K)
o_v = i_v * BV + tl.arange(0, BV)
o_t = tl.arange(0, BT)
o_r = tl.arange(0, BT)
b_h = tl.zeros([K, BV], dtype=tl.float32)
w_base = w + (i_b * T * H + i_h) * K
u_base = u + (i_b * T * H + i_h) * V
qg_base = qg + (i_b * T * H + i_h) * K
kg_base = kg + (i_b * NT * H + i_h).to(tl.int64) * K * BT
Aqk_base = Aqk + (i_b * T * H + i_h) * BT
gcl_base = gcl + (i_b * NT * H + i_h) * K
o_base = o + (i_b * T * H + i_h) * V
for c in range(NT):
t0 = c * BT
# ---- recurrence-critical path first ----
pw = w_base + (t0 + o_t[:, None]) * H * K + o_k[None, :]
b_w = tl.load(pw)
b_h16 = b_h.to(tl.float16)
b_vn = tl.dot(b_w, b_h16) # (BT, BV)
pqg = qg_base + (t0 + o_t[:, None]) * H * K + o_k[None, :]
b_qg = tl.load(pqg)
b_o = tl.dot(b_qg, b_h16)
pu = u_base + (t0 + o_t[:, None]) * H * V + o_v[None, :]
b_u = tl.load(pu).to(tl.float32)
b_vn = b_u - b_vn
b_vn16 = b_vn.to(tl.float16)
pgcl = gcl_base + c * H * K + o_k
b_gcl = tl.load(pgcl)
pkg = kg_base + (c * H * K + o_k[:, None]) * BT + o_r[None, :]
b_kg = tl.load(pkg)
b_h = b_h * b_gcl[:, None]
b_h = b_h + tl.dot(b_kg, b_vn16) # state update completes next-iter dependency
# ---- output path (off the recurrence chain) ----
pA = Aqk_base + (t0 + o_t[:, None]) * H * BT + o_r[None, :]
b_A = tl.load(pA)
b_o += tl.dot(b_A, b_vn16)
po = o_base + (t0 + o_t[:, None]) * H * V + o_v[None, :]
tl.store(po, b_o.to(tl.bfloat16))
# ---------------------------------------------------------------------------
# host-side fast path
# ---------------------------------------------------------------------------
def _cfg(B, T, H, K, V):
# (kk: (BK, warps, stages), solve: (warps, stages), scan: (BV, warps, stages))
if B * H <= 8:
return (128, 4, 2), (4, 3), (16, 4, 3)
return (128, 4, 2), (4, 3), (32, 4, 3)
class _ShapePlan:
__slots__ = ("grid_kk", "grid_scan", "cfg", "kk", "solve", "scan", "ws")
def __init__(self):
self.ws = None
self.kk = None
self.solve = None
self.scan = None
_PLANS: dict = {}
def _get_stream(device_index):
return torch.cuda.current_stream(device_index).cuda_stream
def _t(dtype, shape, dev):
return torch.empty(shape, dtype=dtype, device=dev)
def kda_chunk_fwd(q, k, v, g, beta, scale, chunk_size=64):
B, T, H, K = q.shape
V = v.shape[-1]
BT = chunk_size
NT = T // BT
dev = q.device
f16 = torch.float16
f32 = torch.float32
q = q.contiguous(); k = k.contiguous(); v = v.contiguous()
g = g.contiguous(); beta = beta.contiguous()
key = (B, T, H, K, V, dev.index)
plan = _PLANS.get(key)
if plan is None:
plan = _ShapePlan()
plan.cfg = _cfg(B, T, H, K, V)
plan.grid_kk = (NT, B * H)
(kk_bk, kk_w, kk_s), (sv_w, sv_s), (sc_bv, sc_w, sc_s) = plan.cfg
plan.grid_scan = (V // sc_bv, B * H)
Aqk = _t(f16, (B, T, H, BT), dev)
Akk = _t(f16, (B, T, H, BT), dev)
qg = _t(f16, (B, T, H, K), dev)
kg = _t(f16, (B, T, H, K, ), dev)
ke = _t(f16, (B, T, H, K), dev)
gcl = _t(f32, (B, NT, H, K), dev)
w = _t(f16, (B, T, H, K), dev)
u = _t(f16, (B, T, H, V), dev)
o = _t(torch.bfloat16, (B, T, H, V), dev)
plan.ws = (Aqk, Akk, qg, kg, ke, gcl, w, u)
plan.kk = _kda_kk_kernel[plan.grid_kk](
q, k, g, beta, Aqk, Akk, qg, kg, ke, gcl, scale, T,
H=H, K=K, BT=BT, BK=kk_bk, NK=K // kk_bk,
num_warps=kk_w, num_stages=kk_s)
plan.solve = _kda_solve_wu_kernel[plan.grid_kk](
Akk, ke, v, beta, w, u, T,
H=H, K=K, V=V, BT=BT, num_warps=sv_w, num_stages=sv_s)
plan.scan = _kda_scan_out_kernel[plan.grid_scan](
w, u, qg, kg, Aqk, gcl, o, T, NT,
H=H, K=K, V=V, BT=BT, BV=sc_bv,
num_warps=sc_w, num_stages=sc_s)
plan.kk._init_handles()
plan.solve._init_handles()
plan.scan._init_handles()
_PLANS[key] = plan
# first call returns o via the JIT launches above
return o
Aqk, Akk, qg, kg, ke, gcl, w, u = plan.ws
o = _t(torch.bfloat16, (B, T, H, V), dev)
stream = _get_stream(dev.index)
(kk_bk, kk_w, kk_s), (sv_w, sv_s), (sc_bv, sc_w, sc_s) = plan.cfg
ck = plan.kk
ck.run(plan.grid_kk[0], plan.grid_kk[1], 1, stream, ck.function,
ck.packed_metadata, None, None, None,
q, k, g, beta, Aqk, Akk, qg, kg, ke, gcl, scale, T,
H, K, BT, kk_bk, K // kk_bk)
ck = plan.solve
ck.run(plan.grid_kk[0], plan.grid_kk[1], 1, stream, ck.function,
ck.packed_metadata, None, None, None,
Akk, ke, v, beta, w, u, T, H, K, V, BT)
ck = plan.scan
ck.run(plan.grid_scan[0], plan.grid_scan[1], 1, stream, ck.function,
ck.packed_metadata, None, None, None,
w, u, qg, kg, Aqk, gcl, o, T, NT, H, K, V, BT, sc_bv)
return o
class Model(nn.Module):
"""KDA forward (chunk form). No learned parameters."""
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 kda_chunk_fwd(q, k, v, g, beta, scale=self.scale, chunk_size=self.chunk_size)
# Module-level shape shims (overridden by check.py / benchmark.py per shape).
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]
20260715_203941_kinetic-claude_kinetic-0715_02_kda_cutlass