KernelBench hard · H100
KDA CUTLASS MiniMax-M3
wrongdid not score
harnessminimax-claudeagent session45mtotal wall47mcheck2mbenchmark—output tokens3,944gpu-lock wait2mgpu-lock held8sregimecompute
Per-shape vs governing ceilingeach shape graded against whichever binds — bf16 compute or HBM bandwidth
No per-shape benchmark data archived for this run.
Kernel source (redacted)
"""Custom Triton kernel for Kimi Delta Attention (KDA) chunk forward.
Implements the chunk-parallel KDA forward from fla (Songlin Yang et al.) using
hand-written Triton kernels for the H100 PCIe (SM90 Hopper, HBM2e, ~2.0 TB/s).
No FLA / fla.ops imports -- this is a from-scratch implementation.
Algorithm (H == HV in this problem, so no GVA expansion is needed):
1. In-chunk cumsum of g (per channel) -- torch op.
2. For each chunk c, compute intra-chunk (in fp32, with numerical centering
around the middle of the chunk to keep exp values bounded):
Aqk[c] = lower-tri(q[i] * exp(g[i] - g[j]), k[j]) * scale [BT, BT]
Akk[c] = lower-tri(k[i] * exp(g[i] - g[j]), k[j]) * beta[i] [BT, BT]
then solve Akk_inv by forward substitution (Woodbury-style; in fp32
for stability), and compute:
w[c] = Akk_inv @ (exp(g) * k) [BT, K]
u[c] = Akk_inv @ v [BT, V]
3. Inter-chunk recurrence (one program per (B, H, V-tile)):
v_new[c] = u[c] - w[c] @ h_prev
h_{c+1} = h_c * exp(g[last]) + (k[c] * (exp(g[last]) - exp(g[c]))) @ v_new[c]
Stores h in (B, NT, H, K, V) and v_new in (B, T, H, V).
4. Output:
o[c] = (q * exp(g)) @ h_{c} + Aqk @ v_new[c]
"""
from __future__ import annotations
import torch
import torch.nn as nn
import triton
import triton.language as tl
# ---------------------------------------------------------------------------
# Step 1: in-chunk cumsum of g (PyTorch -- small op)
# ---------------------------------------------------------------------------
def _chunk_local_cumsum_g(g: torch.Tensor, chunk_size: int) -> torch.Tensor:
"""g: (B, T, H, K) fp32. Returns in-chunk cumsum (per chunk, per channel)."""
B, T, H, K = g.shape
NT = T // chunk_size
g4 = g.view(B, NT, chunk_size, H, K)
return g4.cumsum(dim=2).view(B, T, H, K)
# ---------------------------------------------------------------------------
# Step 2: intra-chunk compute -- Aqk [BT, BT] + Akk_inv [BT, BT] (fp32)
# + w [BT, K] + u [BT, V]
# ---------------------------------------------------------------------------
@triton.jit
def _kda_intra_wu_kernel(
q_ptr, k_ptr, v_ptr, g_ptr, beta_ptr,
A_ptr, w_ptr, u_ptr,
scale,
NT, T,
H: tl.constexpr, HV: tl.constexpr,
K: tl.constexpr, V: tl.constexpr,
BT: tl.constexpr,
CENTER: tl.constexpr,
):
"""One program per (NT, B*H) tile. Computes Aqk, Akk, solves Akk_inv, w, u.
Layout: tensors are stored as (B, T, H, ...) row-major contiguous.
"""
i_t = tl.program_id(0)
i_bh = tl.program_id(1)
i_b = i_bh // HV
i_hv = i_bh % HV
bos = i_b * T
q = q_ptr + (bos * H + i_hv) * K
k = k_ptr + (bos * H + i_hv) * K
v = v_ptr + (bos * HV + i_hv) * V
g = g_ptr + (bos * HV + i_hv) * K
beta_p = beta_ptr + bos * HV + i_hv
A = A_ptr + (bos * HV + i_hv) * BT
w_o = w_ptr + (bos * HV + i_hv) * K
u_o = u_ptr + (bos * HV + i_hv) * V
o_t = i_t * BT + tl.arange(0, BT)
o_k = tl.arange(0, K)
o_v = tl.arange(0, V)
o_bt = tl.arange(0, BT)
m_t = o_t < T
# ---- Load g for whole chunk (in fp32) and pick a "center" row to subtract
g_offs = o_t[:, None] * (HV * K) + o_k[None, :]
b_g = tl.load(g + g_offs, mask=m_t[:, None], other=0.0).to(tl.float32) # [BT, K]
# Center on row CENTER of the chunk: a single vector g_c [K]
b_gc = tl.load(g + (i_t * BT + CENTER) * (HV * K) + o_k).to(tl.float32) # [K]
b_gm = b_g - b_gc[None, :] # [BT, K], centered
# ---- Load q, k, v ----
q_offs = o_t[:, None] * (H * K) + o_k[None, :]
b_q = tl.load(q + q_offs, mask=m_t[:, None], other=0.0).to(tl.float32) # [BT, K]
k_offs = o_t[:, None] * (H * K) + o_k[None, :]
b_k = tl.load(k + k_offs, mask=m_t[:, None], other=0.0).to(tl.float32) # [BT, K]
v_offs = o_t[:, None] * (HV * V) + o_v[None, :]
b_v = tl.load(v + v_offs, mask=m_t[:, None], other=0.0).to(tl.float32) # [BT, V]
b_beta = tl.load(beta_p + o_t * HV, mask=m_t, other=0.0).to(tl.float32) # [BT]
# ---- Build Aqk [BT, BT] and Akk [BT, BT] ----
# For each pair (i, j), we need exp(g[i] - g[j]). We center on g_c (same for all),
# so exp(g[i] - g[j]) = exp((g[i] - g_c) - (g[j] - g_c)) = exp(gm[i]) * exp(-gm[j])
# where gm = b_gm. We do the matmul directly:
# A[i, j] = <q[i, :], k[j, :] * exp(g[i, :] - g[j, :])> * beta[i or 1]
# = <q[i, :] * exp(gm[i, :]), k[j, :] * exp(-gm[j, :])> * beta[i or 1]
exp_gi = tl.exp(b_gm) # [BT, K]
exp_neg_gj = tl.exp(-b_gm) # [BT, K]
b_qg = b_q * exp_gi # [BT, K]
b_kg = b_k * exp_neg_gj # [BT, K]
b_kb_g = (b_k * b_beta[:, None]) * exp_neg_gj # [BT, K]
# Single matmul for Aqk and Akk
b_Aqk = tl.dot(b_qg.to(tl.bfloat16), tl.trans(b_kg).to(tl.bfloat16), out_dtype=tl.float32)
b_Akk = tl.dot(b_kb_g.to(tl.bfloat16), tl.trans(b_kg).to(tl.bfloat16), out_dtype=tl.float32)
# Mask strictly lower triangular (set diag and above to 0)
m_strict = o_bt[:, None] > o_bt[None, :]
b_Aqk = tl.where(m_strict, b_Aqk, 0.0)
b_Akk = tl.where(m_strict, b_Akk, 0.0)
# Forward substitution in fp32 to compute (I + L + L^2 + ... + L^63) where L is
# the lower-strict matrix above. b_Akk already includes the beta[i] factor.
m_diag = o_bt[:, None] == o_bt[None, :]
b_inv = b_Akk
cur = b_Akk
for _ in range(1, BT):
cur = tl.dot(cur, b_Akk, out_dtype=tl.float32, input_precision='ieee')
b_inv = b_inv + cur
b_inv = b_inv + m_diag.to(tl.float32) # add I
# Multiply by beta[j] (column factor)
b_inv = b_inv * b_beta[None, :]
# ---- Compute w = b_inv @ (exp(g) * k) and u = b_inv @ v ----
b_kg2 = b_k * tl.exp(b_g) # [BT, K]
b_w = tl.dot(b_inv.to(tl.bfloat16), b_kg2.to(tl.bfloat16), out_dtype=tl.float32)
b_u = tl.dot(b_inv.to(tl.bfloat16), b_v.to(tl.bfloat16), out_dtype=tl.float32)
# ---- Store outputs ----
# w [BT, K]
w_offs = o_t[:, None] * (HV * K) + o_k[None, :]
tl.store(w_o + w_offs, b_w.to(tl.bfloat16), mask=m_t[:, None])
# u [BT, V]
u_offs = o_t[:, None] * (HV * V) + o_v[None, :]
tl.store(u_o + u_offs, b_u.to(tl.bfloat16), mask=m_t[:, None])
# Aqk [BT, BT] (per chunk) - store the lower-strict part scaled.
A_offs = o_t[:, None] * (HV * BT) + o_bt[None, :]
A_mask = m_t[:, None]
b_Aqk_scaled = b_Aqk * scale
tl.store(A + A_offs, b_Aqk_scaled.to(tl.bfloat16), mask=A_mask)
# ---------------------------------------------------------------------------
# Step 3: inter-chunk recurrence (one program per (B, H, V-tile))
# ---------------------------------------------------------------------------
@triton.jit
def _kda_inter_h_kernel(
g_ptr, k_ptr, w_ptr, u_ptr, v_new_ptr, h_ptr,
NT, T,
H: tl.constexpr, HV: tl.constexpr,
K: tl.constexpr, V: tl.constexpr,
BT: tl.constexpr, BK: tl.constexpr, BV: tl.constexpr,
):
"""Inter-chunk recurrent state update, stores h in a (B, NT, HV, K, V) buffer.
Grid: (cdiv(V, BV), B * HV).
"""
i_v = tl.program_id(0)
i_bh = tl.program_id(1)
i_b = i_bh // HV
i_hv = i_bh % HV
bos = i_b * T
g = g_ptr + (bos * HV + i_hv) * K
k = k_ptr + (bos * H + i_hv) * K
w_p = w_ptr + (bos * HV + i_hv) * K
u_p = u_ptr + (bos * HV + i_hv) * V
v_new = v_new_ptr + (bos * HV + i_hv) * V
h = h_ptr + ((i_b * NT) * HV + i_hv) * K * V
# h state: [BK, BV] in registers, in fp32.
b_h = tl.zeros([BK, BV], dtype=tl.float32)
o_v = i_v * BV + tl.arange(0, BV)
o_k = tl.arange(0, BK)
o_t = tl.arange(0, BT)
for i_t in range(NT):
# Save h state
h_offs = i_t * HV * K * V + o_k[:, None] * V + o_v[None, :]
tl.store(h + h_offs, b_h.to(tl.bfloat16))
# Load w[c]: [BT, K]
w_offs = (i_t * BT + o_t[:, None]) * (HV * K) + o_k[None, :]
b_w = tl.load(w_p + w_offs).to(tl.float32)
# v_new = u - w @ h
u_offs = (i_t * BT + o_t[:, None]) * (HV * V) + o_v[None, :]
b_u = tl.load(u_p + u_offs).to(tl.float32)
# w [BT, K] @ h [K, BV] -> [BT, BV]
b_wh = tl.dot(b_w.to(tl.bfloat16), b_h.to(tl.bfloat16), out_dtype=tl.float32)
b_v_new = b_u - b_wh
tl.store(v_new + u_offs, b_v_new.to(tl.bfloat16))
# Update h: h = h * exp(g[last, :]) + (k * exp(g_last - g)) @ v_new
g_offs = (i_t * BT + o_t[:, None]) * (HV * K) + o_k[None, :]
b_g = tl.load(g + g_offs).to(tl.float32)
last_t = i_t * BT + BT - 1
g_last_offs = last_t * (HV * K) + o_k
b_g_last = tl.load(g + g_last_offs).to(tl.float32)
b_g_last_exp = tl.exp(b_g_last) # [K]
k_offs = (i_t * BT + o_t[:, None]) * (H * K) + o_k[None, :]
b_k = tl.load(k + k_offs).to(tl.float32)
b_kg = b_k * tl.exp(b_g_last[None, :] - b_g) # [BT, K]
b_h = b_h * b_g_last_exp[:, None]
b_kg_v = tl.dot(b_kg.to(tl.bfloat16), b_v_new.to(tl.bfloat16), out_dtype=tl.float32)
b_h = b_h + b_kg_v
# ---------------------------------------------------------------------------
# Step 4: output o = (q * exp(g)) @ h + Aqk @ v_new
# ---------------------------------------------------------------------------
@triton.jit
def _kda_output_kernel(
q_ptr, g_ptr, A_ptr, v_new_ptr, h_ptr, o_ptr,
scale,
NT, T,
H: tl.constexpr, HV: tl.constexpr,
K: tl.constexpr, V: tl.constexpr,
BT: tl.constexpr, BK: tl.constexpr, BV: tl.constexpr,
):
"""Output: o[c] = (q * exp(g)) @ h[c] + Aqk @ v_new[c].
Grid: (cdiv(V, BV), NT, B * HV).
"""
i_v = tl.program_id(0)
i_t = tl.program_id(1)
i_bh = tl.program_id(2)
i_b = i_bh // HV
i_hv = i_bh % HV
bos = i_b * T
o_v = i_v * BV + tl.arange(0, BV)
o_t = i_t * BT + tl.arange(0, BT)
m_t = o_t < T
o_k = tl.arange(0, K)
o_bt = tl.arange(0, BT)
q = q_ptr + (bos * H + i_hv) * K
g = g_ptr + (bos * HV + i_hv) * K
A = A_ptr + (bos * HV + i_hv) * BT
v_new = v_new_ptr + (bos * HV + i_hv) * V
o = o_ptr + (bos * HV + i_hv) * V
h = h_ptr + ((i_b * NT + i_t) * HV + i_hv) * K * V
b_o = tl.zeros([BT, BV], dtype=tl.float32)
# 1) o += Aqk @ v_new (intra-chunk part)
A_offs = o_t[:, None] * (HV * BT) + o_bt[None, :]
b_A = tl.load(A + A_offs, mask=m_t[:, None], other=0.0).to(tl.float32)
v_offs = o_t[:, None] * (HV * V) + o_v[None, :]
b_v = tl.load(v_new + v_offs, mask=m_t[:, None], other=0.0).to(tl.float32)
m_ltri = o_bt[:, None] >= o_bt[None, :]
b_A = tl.where(m_ltri, b_A, 0.0)
b_o = b_o + tl.dot(b_A.to(tl.bfloat16), b_v.to(tl.bfloat16), out_dtype=tl.float32)
# 2) o += (q * exp(g)) @ h
g_offs = o_t[:, None] * (HV * K) + o_k[None, :]
b_g = tl.load(g + g_offs, mask=m_t[:, None], other=0.0).to(tl.float32)
q_offs = o_t[:, None] * (H * K) + o_k[None, :]
b_q = tl.load(q + q_offs, mask=m_t[:, None], other=0.0).to(tl.float32)
b_qg = b_q * tl.exp(b_g)
h_offs = o_k[:, None] * V + o_v[None, :]
b_h = tl.load(h + h_offs).to(tl.float32)
b_o = b_o + tl.dot(b_qg.to(tl.bfloat16), b_h.to(tl.bfloat16), out_dtype=tl.float32)
o_offs = o_t[:, None] * (HV * V) + o_v[None, :]
tl.store(o + o_offs, b_o.to(tl.bfloat16), mask=m_t[:, None])
# ---------------------------------------------------------------------------
# Top-level wrapper
# ---------------------------------------------------------------------------
def kda_forward(
q: torch.Tensor, # (B, T, H, K) bf16
k: torch.Tensor, # (B, T, H, K) bf16
v: torch.Tensor, # (B, T, H, V) bf16
g: torch.Tensor, # (B, T, H, K) fp32
beta: torch.Tensor, # (B, T, H) bf16
scale: float,
chunk_size: int = 64,
) -> torch.Tensor:
B, T, H, K = q.shape
V = v.shape[-1]
BT = chunk_size
NT = T // BT
assert T % BT == 0
assert H == v.shape[2]
# Step 1: cumsum g within chunks
g_cum = _chunk_local_cumsum_g(g.contiguous(), chunk_size).contiguous() # (B, T, H, K)
# Step 2: per-chunk Aqk + solve + w, u
A = torch.empty((B, T, H, BT), dtype=torch.bfloat16, device=q.device)
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)
CENTER = (BT // 2) - 1 # middle row of the chunk
grid_intra = (NT, B * H)
_kda_intra_wu_kernel[grid_intra](
q, k, v, g_cum, beta,
A, w, u,
scale,
NT, T,
H=H, HV=H,
K=K, V=V,
BT=BT, CENTER=CENTER,
num_warps=4, num_stages=2,
)
# Step 3: inter-chunk recurrence; produces v_new and h
v_new = torch.empty_like(u)
h_buf = torch.empty((B, NT, H, K, V), dtype=torch.bfloat16, device=q.device)
BV = 64 if V >= 64 else 32
BK = min(64, K)
grid_inter = (triton.cdiv(V, BV), B * H)
_kda_inter_h_kernel[grid_inter](
g_cum, k, w, u, v_new, h_buf,
NT, T,
H=H, HV=H,
K=K, V=V,
BT=BT, BK=BK, BV=BV,
num_warps=4, num_stages=2,
)
# Step 4: output
o = torch.empty((B, T, H, V), dtype=torch.bfloat16, device=q.device)
grid_out = (triton.cdiv(V, BV), NT, B * H)
_kda_output_kernel[grid_out](
q, g_cum, A, v_new, h_buf, o,
scale,
NT, T,
H=H, HV=H,
K=K, V=V,
BT=BT, BK=BK, BV=BV,
num_warps=4, num_stages=2,
)
return o
# ---------------------------------------------------------------------------
# Module shim
# ---------------------------------------------------------------------------
OP_TYPE = "linear_attention"
SUPPORTED_PRECISIONS = ["bf16"]
HARDWARE_REQUIRED = ["RTX_PRO_6000", "H100", "B200"]
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: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
g: torch.Tensor,
beta: torch.Tensor,
) -> torch.Tensor:
return kda_forward(q, k, v, g, beta, self.scale, self.chunk_size)
# Module-level shape shims
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]
20260618_064813_minimax-claude_MiniMax-M3_02_kda_cutlass