KernelBench hard · H100
KDA CUTLASS DeepSeek V4 Flash (0731)
manually audited: clean
Genuine from-scratch two-kernel Triton implementation of KDA chunked forward. kda_intra_kernel (one program per (batch, chunk, head)): in-chunk cumsum of the log-decay g, decay-weighted kg/kd/qg in bf16, A_kk = (k e^g)(k e^-g)^T via tl.dot, then (I - L)^{-1} for the strictly-lower- triangular UT transform via the exact nilpotent power expansion (I+L)(I+L^2)(I+L^4), producing w/u plus the masked Aqk and the inter-chunk kprime/D. kda_scan_o_kernel (one program per (batch, head, V-slice of 32)): sequential inter-chunk recurrence with the state S in registers — vnew = u - w S; o = qg S + Aqk vnew; S = S*D + kprime^T vnew — matching the reference chunk recurrence. No fla.* import; zero textual hits of every problem.yaml forbidden entry, and zero hits of data_ptr / CUDAGraph / os.environ / getenv in solution.py — no caching, graph replay, or env sniffing at all (pre-allocated buffers in Model.__init__ are pure scratch/ output storage recomputed every forward). template_mutated=false. The official in-session check never ran: a sibling fp8_gemm session held the bench GPU lock ~4h and check.py timed out waiting (check.contended.log is 0 bytes; failure_reason=check_timeout is a lock-starvation artifact, infra not model fault). The agent's own flywheel was healthy before starvation (31 transcript lines containing PASS from in-session check runs). The sequential isolated re-grade proved it: check.log PASS (numeric stress on), benchmark geomean 0.0302, RESULT: LOW — honest low bf16-tl.dot score, no shortcut path.
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.5% · 4.5% · 3.5% · 1.5%) = 3.0%
Kernel source (redacted)
"""Kimi Delta Attention (KDA) forward, chunk form -- custom Triton kernels.
Two kernels:
1. kda_intra_kernel -- one program per (batch, chunk, head); computes the
intra-chunk quantities (decay-corrected write basis w, u, the q-k
interaction Aqk, and the inter-chunk keys kprime/decay D) in a fully
parallel fashion.
2. kda_scan_o_kernel -- one program per (batch, head, V-slice); runs the
inter-chunk linear-attention recurrence with the state held in registers
and produces the output o on the fly.
The intra-chunk inverse (I - L)^{-1} uses the nilpotent power expansion
(I+L)(I+L^2)(I+L^4), which is exact to ~2e-3 (well within the 5e-2 tolerance)
because L is strictly lower triangular.
This is a from-scratch implementation of the chunk-parallel KDA forward
(no calls into the FLA library).
"""
from __future__ import annotations
import torch
import torch.nn as nn
import triton
import triton.language as tl
# ---------------------------------------------------------------------------
# Kernel 1: intra-chunk quantities. Grid = (B * NT * H,).
# ---------------------------------------------------------------------------
@triton.jit
def kda_intra_kernel(
q, k, v, g, beta,
w_ptr, u_ptr, qg_ptr, Aqk_ptr, kprime_ptr, D_ptr,
scale,
T,
H: tl.constexpr,
NT: tl.constexpr,
K: tl.constexpr,
V: tl.constexpr,
BT: tl.constexpr,
):
i_c = tl.program_id(0)
i_b = i_c // (NT * H)
i_rem = i_c % (NT * H)
i_n = i_rem // H
i_h = i_rem % H
t0 = i_n * BT
o_c = tl.arange(0, BT)
o_k = tl.arange(0, K)
o_v = tl.arange(0, V)
pq = q + (i_b * T + t0 + o_c)[:, None] * (H * K) + i_h * K + o_k[None, :]
pk = k + (i_b * T + t0 + o_c)[:, None] * (H * K) + i_h * K + o_k[None, :]
pg = g + (i_b * T + t0 + o_c)[:, None] * (H * K) + i_h * K + o_k[None, :]
pv = v + (i_b * T + t0 + o_c)[:, None] * (H * V) + i_h * V + o_v[None, :]
pbeta = beta + (i_b * T + t0 + o_c) * H + i_h
b_k = tl.load(pk)
b_q = tl.load(pq)
b_v = tl.load(pv)
b_g = tl.load(pg).to(tl.float32)
b_beta = tl.load(pbeta).to(tl.float32)
# in-chunk cumsum of the log-decay
g_c = tl.cumsum(b_g, axis=0)
eg = tl.exp(g_c)
kf = b_k.to(tl.float32)
qf = b_q.to(tl.float32)
vf = b_v.to(tl.float32)
kg = (kf * eg).to(tl.bfloat16)
kd = (kf * tl.exp(-g_c)).to(tl.bfloat16)
qg = (qf * scale * eg).to(tl.bfloat16)
ch = ((i_b * NT + i_n) * H + i_h)
# store qg right away (frees registers early)
tl.store(qg_ptr + ch * (BT * K) + o_c[:, None] * K + o_k[None, :], qg)
# A_kk = (k*exp(g)) @ (k*exp(-g))^T (all c,i; masked/solved below)
A_raw = tl.dot(kg, tl.trans(kd))
# B = (I - L)^{-1} where L[c,i] = -beta_c * A_raw[c,i] (c>i), via the
# nilpotent power expansion. L is strictly lower triangular, so
# (I+L)(I+L^2)(I+L^4) reproduces the inverse to ~2e-3.
m_low = o_c[:, None] > o_c[None, :]
L = tl.where(m_low, -A_raw * b_beta[:, None], 0.0).to(tl.bfloat16)
I = tl.where(o_c[:, None] == o_c[None, :], 1.0, 0.0).to(tl.bfloat16)
# interleaved so fewer power tiles are live at once
Bm = I + L
L2 = tl.dot(L, L).to(tl.bfloat16)
Bm = tl.dot(Bm, I + L2).to(tl.bfloat16)
L4 = tl.dot(L2, L2).to(tl.bfloat16)
Bm = tl.dot(Bm, I + L4).to(tl.bfloat16)
# u = A @ v (A = B @ diag(beta)); store immediately
bv = (b_beta[:, None] * vf).to(tl.bfloat16)
u = tl.dot(Bm, bv)
tl.store(u_ptr + ch * (BT * V) + o_c[:, None] * V + o_v[None, :], u.to(tl.bfloat16))
# w = A @ (g.exp()*k)
bk = (b_beta[:, None] * kf * eg).to(tl.bfloat16)
w = tl.dot(Bm, bk)
tl.store(w_ptr + ch * (BT * K) + o_c[:, None] * K + o_k[None, :], w.to(tl.bfloat16))
# Aqk = (q*scale*exp(g)) @ (k*exp(-g))^T, lower-incl-diag mask
Aqk = tl.dot(qg, tl.trans(kd))
Aqk = tl.where(o_c[:, None] >= o_c[None, :], Aqk, 0.0)
tl.store(Aqk_ptr + ch * (BT * BT) + o_c[:, None] * BT + o_c[None, :], Aqk.to(tl.bfloat16))
# k' = k * exp(g_last - g) and D = exp(g_last) for the inter-chunk update
g_last = tl.gather(g_c, tl.full([1, K], BT - 1, tl.int32), axis=0)
kprime = (kf * tl.exp(g_last - g_c)).to(tl.bfloat16)
tl.store(kprime_ptr + ch * (BT * K) + o_c[:, None] * K + o_k[None, :], kprime)
tl.store(D_ptr + ch * K + o_k[None, :], tl.exp(g_last))
# ---------------------------------------------------------------------------
# Kernel 2: inter-chunk recurrence + output. Grid = (B * H * (V//BV),).
# v_i = u_i - w_i @ S_i
# o_i = qg_i @ S_i + Aqk_i @ v_i
# S_{i+1} = S_i * D_i + kprime_i^T @ v_i
# ---------------------------------------------------------------------------
@triton.jit
def kda_scan_o_kernel(
w_ptr, u_ptr, qg_ptr, Aqk_ptr, kprime_ptr, D_ptr, o_ptr,
T, H,
NT: tl.constexpr,
K: tl.constexpr,
V: tl.constexpr,
BT: tl.constexpr,
BV: tl.constexpr,
):
NV: tl.constexpr = V // BV
pid = tl.program_id(0)
i_bh = pid // NV
i_vs = pid % NV
i_b = i_bh // H
i_h = i_bh % H
v0 = i_vs * BV
o_c = tl.arange(0, BT)
o_k = tl.arange(0, K)
o_v = v0 + tl.arange(0, BV)
S = tl.zeros([BV, K], dtype=tl.float32)
for i_n in range(NT):
t0 = i_n * BT
ch = ((i_b * NT + i_n) * H + i_h)
b_w = tl.load(w_ptr + ch * (BT * K) + o_c[:, None] * K + o_k[None, :])
b_qg = tl.load(qg_ptr + ch * (BT * K) + o_c[:, None] * K + o_k[None, :])
b_kp = tl.load(kprime_ptr + ch * (BT * K) + o_c[:, None] * K + o_k[None, :])
b_A = tl.load(Aqk_ptr + ch * (BT * BT) + o_c[:, None] * BT + o_c[None, :])
b_u = tl.load(u_ptr + ch * (BT * V) + o_c[:, None] * V + o_v[None, :])
b_D = tl.load(D_ptr + ch * K + o_k).to(tl.float32)
Sb = S.to(tl.bfloat16)
b_v = b_u.to(tl.float32) - tl.dot(b_w, tl.trans(Sb))
o_tile = tl.dot(b_qg, tl.trans(Sb)) + tl.dot(b_A, b_v.to(tl.bfloat16))
po = o_ptr + (i_b * T + t0 + o_c)[:, None] * (H * V) + i_h * V + o_v[None, :]
tl.store(po, o_tile.to(tl.bfloat16))
S = S * b_D[None, :] + tl.trans(tl.dot(tl.trans(b_kp), b_v.to(tl.bfloat16)))
# ---------------------------------------------------------------------------
# Driver
# ---------------------------------------------------------------------------
def kda_forward(q, k, v, g, beta, scale, chunk_size=64,
w=None, u=None, qg=None, Aqk=None, kprime=None, D=None, o=None):
B, T, H, K = q.shape
V = v.shape[-1]
BT = chunk_size
NT = T // BT
device = q.device
if w is None:
w = torch.empty(B, NT, H, BT, K, device=device, dtype=torch.bfloat16)
u = torch.empty(B, NT, H, BT, V, device=device, dtype=torch.bfloat16)
qg = torch.empty(B, NT, H, BT, K, device=device, dtype=torch.bfloat16)
Aqk = torch.empty(B, NT, H, BT, BT, device=device, dtype=torch.bfloat16)
kprime = torch.empty(B, NT, H, BT, K, device=device, dtype=torch.bfloat16)
D = torch.empty(B, NT, H, K, device=device, dtype=torch.float32)
o = torch.empty(B, T, H, V, device=device, dtype=torch.bfloat16)
grid1 = (B * NT * H,)
kda_intra_kernel[grid1](
q, k, v, g, beta,
w, u, qg, Aqk, kprime, D,
scale, T,
H=H, NT=NT, K=K, V=V, BT=BT, num_warps=8, num_stages=1,
)
BV = 32
NV = V // BV
grid2 = (B * H * NV,)
kda_scan_o_kernel[grid2](
w, u, qg, Aqk, kprime, D, o,
T, H,
NT=NT, K=K, V=V, BT=BT, BV=BV, num_warps=4, num_stages=3,
)
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)
# Pre-allocate the intermediate buffers once (shapes are fixed per
# instance). Non-persistent so they don't appear in state_dict.
NT = T // chunk_size
for name, shape, dtype in [
("w", (B, NT, H, chunk_size, K), torch.bfloat16),
("u", (B, NT, H, chunk_size, V), torch.bfloat16),
("qg", (B, NT, H, chunk_size, K), torch.bfloat16),
("Aqk", (B, NT, H, chunk_size, chunk_size), torch.bfloat16),
("kprime", (B, NT, H, chunk_size, K), torch.bfloat16),
("D", (B, NT, H, K), torch.float32),
("_o", (B, T, H, V), torch.bfloat16),
]:
self.register_buffer(name, torch.zeros(shape, dtype=dtype), persistent=False)
def forward(
self,
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
g: torch.Tensor,
beta: torch.Tensor,
) -> torch.Tensor:
return kda_forward(
q, k, v, g, beta,
scale=self.scale, chunk_size=self.chunk_size,
w=self.w, u=self.u, qg=self.qg, Aqk=self.Aqk,
kprime=self.kprime, D=self.D, o=self._o,
)
# 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]
20260801_190840_or-fable_deepseek_deepseek-v4-flash-0731_02_kda_cutlass