kernelbench.com

KernelBench hard · H100

KDA CUTLASS DeepSeek V4 Flash (0731)

faileddid not score

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.

harnessor-fableagent session2h 20mtotal wall4h 20mcheck13sbenchmark3soutput tokensgpu-lock wait5mgpu-lock held4mregimecompute

Per-shape vs governing ceilingeach shape graded against whichever binds — bf16 compute or HBM bandwidth

2×1024×8×128×128×640.081 ms3.5%0.31 TB/s · 15% of 2.0 TB/s HBM · also 26 TFLOPS (3% of compute)
2×2048×8×128×128×640.127 ms4.5%0.40 TB/s · 20% of 2.0 TB/s HBM · also 34 TFLOPS (4% of compute)
1×4096×8×128×128×640.164 ms3.5%0.31 TB/s · 15% of 2.0 TB/s HBM · also 26 TFLOPS (3% of compute)
1×2048×4×128×128×640.093 ms1.5%0.14 TB/s · 7% of 2.0 TB/s HBM · also 12 TFLOPS (2% of compute)

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