KernelBench hard · B200

KDA CUTLASS GPT-5.6 Sol

0.68%geomean peak fraction across shapes

manually audited: clean

harnesscodexagent session20mtotal wall20mcheck10sbenchmark4soutput tokens37,563gpu-lock wait0sgpu-lock held14sregimecompute

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

2×1024×8×128×128×640.122 ms0.8%0.21 TB/s · 3% of 8.0 TB/s HBM · also 18 TFLOPS (1% of compute)
2×2048×8×128×128×640.195 ms1.0%0.26 TB/s · 3% of 8.0 TB/s HBM · also 22 TFLOPS (1% of compute)
1×4096×8×128×128×640.253 ms0.8%0.20 TB/s · 2% of 8.0 TB/s HBM · also 17 TFLOPS (1% of compute)
1×2048×4×128×128×640.130 ms0.4%0.10 TB/s · 1% of 8.0 TB/s HBM · also 8 TFLOPS (0% of compute)

compute-bound memory-bound · bar + right column = official fraction of the ceiling (the geomean input)

geomean(0.8% · 1.0% · 0.8% · 0.4%) = 0.7%

Kernel source (redacted)
"""Fused Kimi Delta Attention forward for fixed K=V=128.

The chunk formulation in the reference is an algebraic blocking of the token
recurrence.  This implementation fuses that recurrence and keeps each slice of
the 128x128 state in registers for the complete sequence.
"""
from __future__ import annotations

import torch
import torch.nn as nn
import triton
import triton.language as tl


@triton.jit
def _kda_recurrent_fwd(
    q_ptr,
    k_ptr,
    v_ptr,
    g_ptr,
    beta_ptr,
    o_ptr,
    T: tl.constexpr,
    H: tl.constexpr,
    scale: tl.constexpr,
    BLOCK_V: tl.constexpr,
    BLOCK_K: tl.constexpr,
):
    bh = tl.program_id(0)
    vb = tl.program_id(1)
    b = bh // H
    h = bh - b * H

    rk = tl.arange(0, BLOCK_K)
    rv = vb * BLOCK_V + tl.arange(0, BLOCK_V)
    state = tl.zeros((BLOCK_K, BLOCK_V), dtype=tl.float32)

    q_bh = q_ptr + b * T * H * BLOCK_K + h * BLOCK_K
    k_bh = k_ptr + b * T * H * BLOCK_K + h * BLOCK_K
    g_bh = g_ptr + b * T * H * BLOCK_K + h * BLOCK_K
    v_bh = v_ptr + b * T * H * BLOCK_K + h * BLOCK_K
    o_bh = o_ptr + b * T * H * BLOCK_K + h * BLOCK_K
    beta_bh = beta_ptr + b * T * H + h

    for t in range(T):
        q = tl.load(q_bh + t * H * BLOCK_K + rk).to(tl.float32)
        k = tl.load(k_bh + t * H * BLOCK_K + rk).to(tl.float32)
        decay = tl.exp(tl.load(g_bh + t * H * BLOCK_K + rk))
        value = tl.load(v_bh + t * H * BLOCK_K + rv).to(tl.float32)
        beta = tl.load(beta_bh + t * H).to(tl.float32)

        state *= decay[:, None]
        residual = beta * (value - tl.sum(k[:, None] * state, axis=0))
        state += k[:, None] * residual[None, :]
        out = scale * tl.sum(q[:, None] * state, axis=0)
        tl.store(o_bh + t * H * BLOCK_K + rv, out)


@triton.jit
def _local_gate_scan(
    g_ptr,
    gc_ptr,
    T,
    H: tl.constexpr,
    BLOCK_K: tl.constexpr,
):
    """64-wide local prefix sum of the per-token log gates."""
    chunk = tl.program_id(0)
    bh = tl.program_id(1)
    kb = tl.program_id(2)
    b = bh // H
    h = bh - b * H
    rt = tl.arange(0, 64)[:, None]
    rk = kb * BLOCK_K + tl.arange(0, BLOCK_K)[None, :]
    t = chunk * 64 + rt
    offsets = ((b * T + t) * H + h) * 128 + rk
    x = tl.load(g_ptr + offsets)
    x = tl.cumsum(x, axis=0)
    tl.store(gc_ptr + offsets, x)


@triton.jit
def _build_chunk_matrices(
    q_ptr,
    k_ptr,
    gc_ptr,
    beta_ptr,
    a_ptr,
    aq_ptr,
    T,
    H: tl.constexpr,
    scale: tl.constexpr,
):
    """Build the two causal 64x64 products and the WY inverse."""
    chunk = tl.program_id(0)
    bh = tl.program_id(1)
    b = bh // H
    h = bh - b * H
    rc = tl.arange(0, 64)
    rk = tl.arange(0, 128)
    t = chunk * 64 + rc
    offs = ((b * T + t[:, None]) * H + h) * 128 + rk[None, :]
    q = tl.load(q_ptr + offs)
    k = tl.load(k_ptr + offs)
    gc = tl.load(gc_ptr + offs)
    ep = tl.exp(gc)
    kp = (k * ep).to(tl.bfloat16)
    kn = (k / ep).to(tl.bfloat16)
    qp = (q * ep).to(tl.bfloat16)

    kk = tl.dot(kp, tl.trans(kn))
    qk = tl.dot(qp, tl.trans(kn)) * scale
    row = rc[:, None]
    col = rc[None, :]
    beta = tl.load(beta_ptr + (b * T + t) * H + h).to(tl.float32)
    inv = -tl.where(row > col, kk * beta[:, None], 0.0)

    # Forward substitution for inv(I + tril(beta * K K^T, -1)).
    for i in range(1, 64):
        r = tl.sum(tl.where(row == i, inv, 0.0), axis=0)
        r = tl.where(rc < i, r, 0.0)
        r += tl.sum(r[:, None] * inv, axis=0)
        inv = tl.where(row == i, r[None, :], inv)
    inv = (inv + (row == col)) * beta[None, :]
    qk = tl.where(row >= col, qk, 0.0)

    base = (bh * (T // 64) + chunk) * 64 * 64
    m_offs = rc[:, None] * 64 + rc[None, :]
    tl.store(a_ptr + base + m_offs, inv.to(tl.bfloat16))
    tl.store(aq_ptr + base + m_offs, qk.to(tl.bfloat16))


@triton.jit
def _solve_diagonal_tiles(
    q_ptr,
    k_ptr,
    gc_ptr,
    beta_ptr,
    diag_ptr,
    aq_ptr,
    T,
    H: tl.constexpr,
    scale: tl.constexpr,
):
    chunk = tl.program_id(0)
    tile = tl.program_id(1)
    bh = tl.program_id(2)
    b = bh // H
    h = bh - b * H
    r = tl.arange(0, 16)
    d = tl.arange(0, 128)
    t = chunk * 64 + tile * 16 + r
    offs = ((b * T + t[:, None]) * H + h) * 128 + d[None, :]
    q = tl.load(q_ptr + offs)
    k = tl.load(k_ptr + offs)
    gc = tl.load(gc_ptr + offs)
    ep = tl.exp(gc)
    kp = (k * ep).to(tl.bfloat16)
    kn = (k / ep).to(tl.bfloat16)
    qp = (q * ep).to(tl.bfloat16)
    kk = tl.dot(kp, tl.trans(kn))
    qk = tl.dot(qp, tl.trans(kn)) * scale
    beta = tl.load(beta_ptr + (b * T + t) * H + h).to(tl.float32)
    row, col = r[:, None], r[None, :]
    inv = -tl.where(row > col, kk * beta[:, None], 0.0)
    for i in range(1, 16):
        current = tl.sum(tl.where(row == i, inv, 0.0), axis=0)
        current = tl.where(r < i, current, 0.0)
        current += tl.sum(current[:, None] * inv, axis=0)
        inv = tl.where(row == i, current[None, :], inv)
    inv += row == col

    db = ((bh * (T // 64) + chunk) * 4 + tile) * 16 * 16
    tl.store(diag_ptr + db + row * 16 + col, inv)
    ab = (bh * (T // 64) + chunk) * 64 * 64
    aoffs = (tile * 16 + row) * 64 + tile * 16 + col
    tl.store(aq_ptr + ab + aoffs, tl.where(row >= col, qk, 0.0).to(tl.bfloat16))


@triton.jit
def _compose_chunk_inverse(
    q_ptr,
    k_ptr,
    gc_ptr,
    beta_ptr,
    diag_ptr,
    a_ptr,
    aq_ptr,
    T,
    H: tl.constexpr,
    scale: tl.constexpr,
):
    chunk = tl.program_id(0)
    bh = tl.program_id(1)
    b = bh // H
    h = bh - b * H
    r = tl.arange(0, 16)
    d = tl.arange(0, 64)

    m10 = tl.zeros((16, 16), tl.float32)
    m20 = tl.zeros((16, 16), tl.float32)
    m21 = tl.zeros((16, 16), tl.float32)
    m30 = tl.zeros((16, 16), tl.float32)
    m31 = tl.zeros((16, 16), tl.float32)
    m32 = tl.zeros((16, 16), tl.float32)
    q10 = tl.zeros((16, 16), tl.float32)
    q20 = tl.zeros((16, 16), tl.float32)
    q21 = tl.zeros((16, 16), tl.float32)
    q30 = tl.zeros((16, 16), tl.float32)
    q31 = tl.zeros((16, 16), tl.float32)
    q32 = tl.zeros((16, 16), tl.float32)

    for kb in range(2):
        ds = kb * 64 + d
        t0 = chunk * 64 + r
        t1 = t0 + 16
        t2 = t0 + 32
        t3 = t0 + 48
        o0 = ((b * T + t0[:, None]) * H + h) * 128 + ds[None, :]
        o1 = ((b * T + t1[:, None]) * H + h) * 128 + ds[None, :]
        o2 = ((b * T + t2[:, None]) * H + h) * 128 + ds[None, :]
        o3 = ((b * T + t3[:, None]) * H + h) * 128 + ds[None, :]
        k0, k1 = tl.load(k_ptr + o0), tl.load(k_ptr + o1)
        k2, k3 = tl.load(k_ptr + o2), tl.load(k_ptr + o3)
        q1, q2 = tl.load(q_ptr + o1), tl.load(q_ptr + o2)
        q3 = tl.load(q_ptr + o3)
        g0, g1 = tl.load(gc_ptr + o0), tl.load(gc_ptr + o1)
        g2, g3 = tl.load(gc_ptr + o2), tl.load(gc_ptr + o3)
        p0 = (k0 * tl.exp(g0)).to(tl.bfloat16)
        p1 = (k1 * tl.exp(g1)).to(tl.bfloat16)
        p2 = (k2 * tl.exp(g2)).to(tl.bfloat16)
        p3 = (k3 * tl.exp(g3)).to(tl.bfloat16)
        n0 = (k0 * tl.exp(-g0)).to(tl.bfloat16)
        n1 = (k1 * tl.exp(-g1)).to(tl.bfloat16)
        n2 = (k2 * tl.exp(-g2)).to(tl.bfloat16)
        qp1 = (q1 * tl.exp(g1)).to(tl.bfloat16)
        qp2 = (q2 * tl.exp(g2)).to(tl.bfloat16)
        qp3 = (q3 * tl.exp(g3)).to(tl.bfloat16)
        n0t, n1t, n2t = tl.trans(n0), tl.trans(n1), tl.trans(n2)
        m10 += tl.dot(p1, n0t)
        m20 += tl.dot(p2, n0t)
        m21 += tl.dot(p2, n1t)
        m30 += tl.dot(p3, n0t)
        m31 += tl.dot(p3, n1t)
        m32 += tl.dot(p3, n2t)
        q10 += tl.dot(qp1, n0t)
        q20 += tl.dot(qp2, n0t)
        q21 += tl.dot(qp2, n1t)
        q30 += tl.dot(qp3, n0t)
        q31 += tl.dot(qp3, n1t)
        q32 += tl.dot(qp3, n2t)

    db = (bh * (T // 64) + chunk) * 4 * 16 * 16
    matoffs = r[:, None] * 16 + r[None, :]
    d00 = tl.load(diag_ptr + db + 0 * 256 + matoffs)
    d11 = tl.load(diag_ptr + db + 1 * 256 + matoffs)
    d22 = tl.load(diag_ptr + db + 2 * 256 + matoffs)
    d33 = tl.load(diag_ptr + db + 3 * 256 + matoffs)
    base_t = b * T + chunk * 64
    b0 = tl.load(beta_ptr + (base_t + r) * H + h).to(tl.float32)
    b1 = tl.load(beta_ptr + (base_t + 16 + r) * H + h).to(tl.float32)
    b2 = tl.load(beta_ptr + (base_t + 32 + r) * H + h).to(tl.float32)
    b3 = tl.load(beta_ptr + (base_t + 48 + r) * H + h).to(tl.float32)
    c10, c20, c21 = m10 * b1[:, None], m20 * b2[:, None], m21 * b2[:, None]
    c30, c31, c32 = m30 * b3[:, None], m31 * b3[:, None], m32 * b3[:, None]
    d10 = -tl.dot(tl.dot(d11, c10), d00)
    d21 = -tl.dot(tl.dot(d22, c21), d11)
    d20 = -tl.dot(d22, tl.dot(c20, d00) + tl.dot(c21, d10))
    d32 = -tl.dot(tl.dot(d33, c32), d22)
    d31 = -tl.dot(d33, tl.dot(c31, d11) + tl.dot(c32, d21))
    d30 = -tl.dot(d33, tl.dot(c30, d00) + tl.dot(c31, d10) + tl.dot(c32, d20))

    ab = (bh * (T // 64) + chunk) * 64 * 64
    # The full WY matrix is D @ diag(beta).  Only its lower blocks are consumed.
    tl.store(a_ptr + ab + (0 * 16 + r[:, None]) * 64 + 0 * 16 + r[None, :], (d00 * b0[None, :]).to(tl.bfloat16))
    tl.store(a_ptr + ab + (1 * 16 + r[:, None]) * 64 + 0 * 16 + r[None, :], (d10 * b0[None, :]).to(tl.bfloat16))
    tl.store(a_ptr + ab + (1 * 16 + r[:, None]) * 64 + 1 * 16 + r[None, :], (d11 * b1[None, :]).to(tl.bfloat16))
    tl.store(a_ptr + ab + (2 * 16 + r[:, None]) * 64 + 0 * 16 + r[None, :], (d20 * b0[None, :]).to(tl.bfloat16))
    tl.store(a_ptr + ab + (2 * 16 + r[:, None]) * 64 + 1 * 16 + r[None, :], (d21 * b1[None, :]).to(tl.bfloat16))
    tl.store(a_ptr + ab + (2 * 16 + r[:, None]) * 64 + 2 * 16 + r[None, :], (d22 * b2[None, :]).to(tl.bfloat16))
    tl.store(a_ptr + ab + (3 * 16 + r[:, None]) * 64 + 0 * 16 + r[None, :], (d30 * b0[None, :]).to(tl.bfloat16))
    tl.store(a_ptr + ab + (3 * 16 + r[:, None]) * 64 + 1 * 16 + r[None, :], (d31 * b1[None, :]).to(tl.bfloat16))
    tl.store(a_ptr + ab + (3 * 16 + r[:, None]) * 64 + 2 * 16 + r[None, :], (d32 * b2[None, :]).to(tl.bfloat16))
    tl.store(a_ptr + ab + (3 * 16 + r[:, None]) * 64 + 3 * 16 + r[None, :], (d33 * b3[None, :]).to(tl.bfloat16))

    tl.store(aq_ptr + ab + (1 * 16 + r[:, None]) * 64 + 0 * 16 + r[None, :], (q10 * scale).to(tl.bfloat16))
    tl.store(aq_ptr + ab + (2 * 16 + r[:, None]) * 64 + 0 * 16 + r[None, :], (q20 * scale).to(tl.bfloat16))
    tl.store(aq_ptr + ab + (2 * 16 + r[:, None]) * 64 + 1 * 16 + r[None, :], (q21 * scale).to(tl.bfloat16))
    tl.store(aq_ptr + ab + (3 * 16 + r[:, None]) * 64 + 0 * 16 + r[None, :], (q30 * scale).to(tl.bfloat16))
    tl.store(aq_ptr + ab + (3 * 16 + r[:, None]) * 64 + 1 * 16 + r[None, :], (q31 * scale).to(tl.bfloat16))
    tl.store(aq_ptr + ab + (3 * 16 + r[:, None]) * 64 + 2 * 16 + r[None, :], (q32 * scale).to(tl.bfloat16))


@triton.jit
def _form_w_u(
    k_ptr,
    v_ptr,
    gc_ptr,
    a_ptr,
    w_ptr,
    u_ptr,
    kg_ptr,
    T,
    H: tl.constexpr,
):
    chunk = tl.program_id(0)
    bh = tl.program_id(1)
    b = bh // H
    h = bh - b * H
    rc = tl.arange(0, 64)
    rd = tl.arange(0, 128)
    t = chunk * 64 + rc
    offs = ((b * T + t[:, None]) * H + h) * 128 + rd[None, :]
    k = tl.load(k_ptr + offs)
    v = tl.load(v_ptr + offs)
    gc = tl.load(gc_ptr + offs)
    ke = (k * tl.exp(gc)).to(tl.bfloat16)
    glast = tl.sum(tl.where(rc[:, None] == 63, gc, 0.0), axis=0)
    kg = (k * tl.exp(glast[None, :] - gc)).to(tl.bfloat16)
    base = (bh * (T // 64) + chunk) * 64 * 64
    a = tl.load(a_ptr + base + rc[:, None] * 64 + rc[None, :])
    a = tl.where(rc[:, None] >= rc[None, :], a, 0.0)
    w = tl.dot(a, ke)
    u = tl.dot(a, v)
    tl.store(w_ptr + offs, w.to(tl.bfloat16))
    tl.store(u_ptr + offs, u.to(tl.bfloat16))
    tl.store(kg_ptr + offs, kg)


@triton.jit
def _chunk_state_output(
    q_ptr,
    k_ptr,
    gc_ptr,
    w_ptr,
    u_ptr,
    aq_ptr,
    o_ptr,
    T,
    NT,
    H: tl.constexpr,
    BLOCK_V: tl.constexpr,
    scale: tl.constexpr,
):
    bh = tl.program_id(0)
    vb = tl.program_id(1)
    b = bh // H
    h = bh - b * H
    rc = tl.arange(0, 64)
    rk = tl.arange(0, 128)
    rv = vb * BLOCK_V + tl.arange(0, BLOCK_V)
    state = tl.zeros((128, BLOCK_V), tl.float32)

    for chunk in range(NT):
        t = chunk * 64 + rc
        qk_offs = ((b * T + t[:, None]) * H + h) * 128 + rk[None, :]
        v_offs = ((b * T + t[:, None]) * H + h) * 128 + rv[None, :]
        q = tl.load(q_ptr + qk_offs)
        k = tl.load(k_ptr + qk_offs)
        gc = tl.load(gc_ptr + qk_offs)
        qg = (q * tl.exp(gc) * scale).to(tl.bfloat16)
        kg = (k * tl.exp(-gc)).to(tl.bfloat16)
        w = tl.load(w_ptr + qk_offs)
        u = tl.load(u_ptr + v_offs)
        state_bf = state.to(tl.bfloat16)
        vn = u.to(tl.float32) - tl.dot(w, state_bf)
        out = tl.dot(qg, state_bf)

        base = (bh * NT + chunk) * 64 * 64
        aq = tl.load(aq_ptr + base + rc[:, None] * 64 + rc[None, :])
        vn_bf = vn.to(tl.bfloat16)
        out += tl.dot(aq, vn_bf)
        tl.store(o_ptr + v_offs, out.to(tl.bfloat16))

        state += tl.dot(tl.trans(kg), vn_bf)
        g_last = tl.sum(tl.where(rc[:, None] == 63, gc, 0.0), axis=0)
        state *= tl.exp(g_last)[:, None]


@triton.jit
def _chunk_state_scan(
    kg_ptr,
    gc_ptr,
    w_ptr,
    u_ptr,
    h_ptr,
    vn_ptr,
    T,
    NT,
    H: tl.constexpr,
    BLOCK_V: tl.constexpr,
):
    """The only sequential portion: one short scan over sequence chunks."""
    bh = tl.program_id(0)
    vb = tl.program_id(1)
    b = bh // H
    hidx = bh - b * H
    rc = tl.arange(0, 64)
    rk = tl.arange(0, 64)
    rv = vb * BLOCK_V + tl.arange(0, BLOCK_V)
    state0 = tl.zeros((64, BLOCK_V), tl.float32)
    state1 = tl.zeros((64, BLOCK_V), tl.float32)

    for chunk in range(NT):
        hbase = (bh * NT + chunk) * 128 * 128
        tl.store(h_ptr + hbase + rk[:, None] * 128 + rv[None, :], state0)
        tl.store(h_ptr + hbase + (rk[:, None] + 64) * 128 + rv[None, :], state1)

        t = chunk * 64 + rc
        off0 = ((b * T + t[:, None]) * H + hidx) * 128 + rk[None, :]
        off1 = off0 + 64
        voff = ((b * T + t[:, None]) * H + hidx) * 128 + rv[None, :]
        w0 = tl.load(w_ptr + off0)
        w1 = tl.load(w_ptr + off1)
        pred = tl.dot(w0, state0.to(tl.bfloat16))
        pred += tl.dot(w1, state1.to(tl.bfloat16))
        vn = tl.load(u_ptr + voff).to(tl.float32) - pred
        vn_bf = vn.to(tl.bfloat16)
        tl.store(vn_ptr + voff, vn_bf)

        kg0 = tl.load(kg_ptr + off0)
        kg1 = tl.load(kg_ptr + off1)
        last0 = tl.load(
            gc_ptr + ((b * T + chunk * 64 + 63) * H + hidx) * 128 + rk
        )
        last1 = tl.load(
            gc_ptr + ((b * T + chunk * 64 + 63) * H + hidx) * 128 + 64 + rk
        )
        state0 *= tl.exp(last0)[:, None]
        state1 *= tl.exp(last1)[:, None]
        state0 += tl.dot(tl.trans(kg0), vn_bf)
        state1 += tl.dot(tl.trans(kg1), vn_bf)


@triton.jit
def _parallel_chunk_output(
    q_ptr,
    gc_ptr,
    vn_ptr,
    h_ptr,
    aq_ptr,
    o_ptr,
    T,
    NT,
    H: tl.constexpr,
    BLOCK_V: tl.constexpr,
    scale: tl.constexpr,
):
    """All chunk reads run in parallel after the state scan."""
    chunk = tl.program_id(0)
    bh = tl.program_id(1)
    vb = tl.program_id(2)
    b = bh // H
    hidx = bh - b * H
    rc = tl.arange(0, 64)
    rk = tl.arange(0, 64)
    rv = vb * BLOCK_V + tl.arange(0, BLOCK_V)
    t = chunk * 64 + rc
    off0 = ((b * T + t[:, None]) * H + hidx) * 128 + rk[None, :]
    off1 = off0 + 64
    voff = ((b * T + t[:, None]) * H + hidx) * 128 + rv[None, :]

    q0 = tl.load(q_ptr + off0)
    q1 = tl.load(q_ptr + off1)
    g0 = tl.load(gc_ptr + off0)
    g1 = tl.load(gc_ptr + off1)
    qg0 = (q0 * tl.exp(g0)).to(tl.bfloat16)
    qg1 = (q1 * tl.exp(g1)).to(tl.bfloat16)
    hbase = (bh * NT + chunk) * 128 * 128
    hs0 = tl.load(h_ptr + hbase + rk[:, None] * 128 + rv[None, :])
    hs1 = tl.load(h_ptr + hbase + (rk[:, None] + 64) * 128 + rv[None, :])
    out = (tl.dot(qg0, hs0) + tl.dot(qg1, hs1)) * scale

    abase = (bh * NT + chunk) * 64 * 64
    aq = tl.load(aq_ptr + abase + rc[:, None] * 64 + rc[None, :])
    aq = tl.where(rc[:, None] >= rc[None, :], aq, 0.0)
    vn = tl.load(vn_ptr + voff)
    out += tl.dot(aq, vn)
    tl.store(o_ptr + voff, out.to(tl.bfloat16))


class Model(nn.Module):
    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:
        assert self.K == 128 and self.V == 128
        nt = self.T // 64
        gc = torch.empty_like(g)
        a = torch.empty(
            self.B * self.H, nt, 64, 64, dtype=torch.bfloat16, device=q.device
        )
        aq = torch.empty_like(a)
        diag = torch.empty(
            self.B * self.H, nt, 4, 16, 16, dtype=torch.float32, device=q.device
        )
        w = torch.empty_like(k)
        u = torch.empty_like(v)
        kg = torch.empty_like(k)
        h = torch.empty(
            self.B * self.H, nt, 128, 128, dtype=torch.bfloat16, device=q.device
        )
        vn = torch.empty_like(v)
        o = torch.empty_like(v)

        _local_gate_scan[(nt, self.B * self.H, 4)](
            g, gc, self.T, H=self.H, BLOCK_K=32, num_warps=4
        )
        _solve_diagonal_tiles[(nt, 4, self.B * self.H)](
            q,
            k,
            gc,
            beta,
            diag,
            aq,
            self.T,
            H=self.H,
            scale=self.scale,
            num_warps=2,
            num_stages=2,
        )
        _compose_chunk_inverse[(nt, self.B * self.H)](
            q,
            k,
            gc,
            beta,
            diag,
            a,
            aq,
            self.T,
            H=self.H,
            scale=self.scale,
            num_warps=2 if self.B * self.H == 4 else 1,
            num_stages=2,
        )
        _form_w_u[(nt, self.B * self.H)](
            k, v, gc, a, w, u, kg, self.T, H=self.H, num_warps=8, num_stages=2
        )
        state_bv = 16
        _chunk_state_scan[(self.B * self.H, self.V // state_bv)](
            kg,
            gc,
            w,
            u,
            h,
            vn,
            self.T,
            nt,
            H=self.H,
            BLOCK_V=state_bv,
            num_warps=2,
            num_stages=2,
        )
        out_bv = 128
        _parallel_chunk_output[(nt, self.B * self.H, self.V // out_bv)](
            q,
            gc,
            vn,
            h,
            aq,
            o,
            self.T,
            nt,
            H=self.H,
            BLOCK_V=out_bv,
            scale=self.scale,
            num_warps=8,
            num_stages=2,
        )
        return o


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]

20260721_182112_codex_gpt-5.6-sol_02_kda_cutlass