KernelBench hard · RTX PRO 6000

KDA CUTLASS Kimi K3 (256k)

3.15%geomean peak fraction across shapes

manually audited: clean

Genuine chunked KDA (Kimi Delta Attention) forward, round-2 session after two round-1 attempts failed the large_qkv numeric-stress case. Three-kernel pipeline: K1 (Triton _kda_intra_kernel) does the in-chunk gate cumsum in log2 domain (fp32), factorises decay around the CHUNK-END gate value Gl (e^{G-Gl} / e^{Gl-G}, keeping both factors <= 1-ish and bounded), builds Aqk/Akk via bf16 tl.dot with fp32 accum, then inverts (I - Akk) by exact sequential forward substitution in fp32 (64 static-range row updates, no Neumann shortcut), and emits w, u, qg, kn, el. K2/K3 are hand-written CUDA mma.sync.m16n8k16 + cp.async kernels (load_inline, sm_120a): K2 runs the sequential inter-chunk state scan S = S*el + kn^T @ (u - w @ S) with fp32 register state, K3 forms o = qg @ h + Aqk @ v_new. Real delta-rule math on live inputs every call; no fla / chunk_kda / forbidden ops, no caching, no grader interaction. Low 0.0315 fraction = honest slow-ish kernel on a latency/bandwidth-bound problem graded against a dense-FLOP roofline.

harnesskinetic-claudeagent session1h 58mtotal wall2h 5mcheck7mbenchmark16soutput tokens768,729gpu-lock wait7mgpu-lock held57sregimecompute

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

2×1024×8×128×128×640.124 ms3.5%0.20 TB/s · 11% of 1.8 TB/s HBM · also 17 TFLOPS (3% of compute)
2×2048×8×128×128×640.188 ms4.6%0.27 TB/s · 15% of 1.8 TB/s HBM · also 23 TFLOPS (5% of compute)
1×4096×8×128×128×640.235 ms3.7%0.21 TB/s · 12% of 1.8 TB/s HBM · also 18 TFLOPS (4% of compute)
1×2048×4×128×128×640.126 ms1.7%0.10 TB/s · 6% of 1.8 TB/s HBM · also 9 TFLOPS (2% of compute)

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

geomean(3.5% · 4.6% · 3.7% · 1.7%) = 3.1%

Kernel source (redacted)
"""KDA (Kimi Delta Attention) forward, chunk form — custom Triton kernel pipeline.

Three-kernel chunked implementation built on tl.dot tensor-core GEMMs:

  K1 (intra, per (b,h,chunk)):  in-chunk gate cumsum; decay-factorised
      Aqk = (s*q*e^{G-Gl}) @ (k*e^{Gl-G})^T and Akk = (k*e^{G-Gl}) @ (k*e^{Gl-G})^T
      (Gl = G at chunk end), strict-lower/beta masking, forward-substitution
      UT transform -> Ainv; w = Ainv @ (beta*e^G*k), u = Ainv @ (beta*v).
      Stores Aqk (masked incl. diag, scaled), kn (=k*e^{Gl-G}, doubles as kg),
      w, u, qg (=s*q*e^G), el (=e^{Gl} per channel).
  K2 (scan, per (b,h,v-slice)): sequential over chunks:
      store h_i (chunk-start state), v_new = u - w @ h_i,
      S <- S * el + kn^T @ v_new.
  K3 (out,  per (b,h,chunk)): o = qg @ h_i + (masked Aqk) @ v_new.

All GEMM inputs bf16, accumulation fp32. chunk_size 64 assumed (T % 64 == 0),
K / V arbitrary powers of two (the benchmark uses K=V=128).
"""
from __future__ import annotations

import torch
import torch.nn as nn
import triton
import triton.language as tl
from torch.utils.cpp_extension import load_inline

RCP_LN2 = tl.constexpr(1.4426950408889634)  # 1/ln(2): keep decay in log2 domain, use exp2


# ===========================================================================
# CUDA kernels (hand-written mma.sync m16n8k16 + cp.async pipeline)
# ===========================================================================
CUDA_SRC = r"""
#include <torch/extension.h>
#include <cuda_bf16.h>

using bf16 = __nv_bfloat16;
#define DINLINE __device__ __forceinline__

DINLINE unsigned smem_u32(const void* p) {
    return static_cast<unsigned>(__cvta_generic_to_shared(p));
}
DINLINE void ldmatrix_x4(unsigned& r0, unsigned& r1, unsigned& r2, unsigned& r3, unsigned addr) {
    asm volatile("ldmatrix.sync.aligned.m8n8.x4.shared.b16 {%0,%1,%2,%3}, [%4];\n"
                 : "=r"(r0), "=r"(r1), "=r"(r2), "=r"(r3) : "r"(addr));
}
DINLINE void ldmatrix_x2_trans(unsigned& r0, unsigned& r1, unsigned addr) {
    asm volatile("ldmatrix.sync.aligned.m8n8.x2.trans.shared.b16 {%0,%1}, [%2];\n"
                 : "=r"(r0), "=r"(r1) : "r"(addr));
}
DINLINE void ldmatrix_x4_trans(unsigned& r0, unsigned& r1, unsigned& r2, unsigned& r3, unsigned addr) {
    asm volatile("ldmatrix.sync.aligned.m8n8.x4.trans.shared.b16 {%0,%1,%2,%3}, [%4];\n"
                 : "=r"(r0), "=r"(r1), "=r"(r2), "=r"(r3) : "r"(addr));
}
DINLINE void stmatrix_x4(unsigned addr, unsigned r0, unsigned r1, unsigned r2, unsigned r3) {
    asm volatile("stmatrix.sync.aligned.m8n8.x4.shared.b16 [%0], {%1,%2,%3,%4};\n"
                 :: "r"(addr), "r"(r0), "r"(r1), "r"(r2), "r"(r3));
}
DINLINE void mma_bf16(float& c0, float& c1, float& c2, float& c3,
                      unsigned a0, unsigned a1, unsigned a2, unsigned a3,
                      unsigned b0, unsigned b1) {
    asm volatile(
        "mma.sync.aligned.m16n8k16.row.col.f32.bf16.bf16.f32 "
        "{%0,%1,%2,%3}, {%4,%5,%6,%7}, {%8,%9}, {%0,%1,%2,%3};\n"
        : "+f"(c0), "+f"(c1), "+f"(c2), "+f"(c3)
        : "r"(a0), "r"(a1), "r"(a2), "r"(a3), "r"(b0), "r"(b1));
}
DINLINE void cp_async_16(unsigned smem, const void* gmem) {
    asm volatile("cp.async.cg.shared.global [%0], [%1], 16;\n" :: "r"(smem), "l"(gmem));
}
DINLINE void cp_commit() { asm volatile("cp.async.commit_group;\n"); }
template <int N>
DINLINE void cp_wait() { asm volatile("cp.async.wait_group %0;\n" :: "n"(N)); }
DINLINE unsigned pack_bf16(float a, float b) {
    unsigned r;
    asm volatile("cvt.rn.bf16x2.f32 %0, %1, %2;\n" : "=r"(r) : "f"(b), "f"(a));
    return r;
}

// ---------------------------------------------------------------------------
// Scan kernel: one block per (bh, v-slice of 32). 8 warps.
//   for each chunk i: h[i] = S; vn = u - w @ S; S = S * el + kn^T @ vn
// ---------------------------------------------------------------------------
#define BV 32
#define BT 64
#define DK 128
#define PADW 8
#define PADH 8

struct ScanParams {
    const bf16* kn; const bf16* w; const bf16* u; const float* el;
    bf16* h; bf16* vnew;
    int T, NT, H, K, V;
};

__global__ void __launch_bounds__(256, 1) scan_kernel(ScanParams p) {
    extern __shared__ char smem_raw[];
    bf16 (*s_w)[DK + PADW]   = reinterpret_cast<bf16(*)[DK + PADW]>(smem_raw);
    bf16 (*s_kn)[DK + PADW]  = s_w + 2 * BT;
    bf16 (*s_u)[BV + PADW]   = reinterpret_cast<bf16(*)[BV + PADW]>(s_kn + 2 * BT);
    bf16 (*s_vn)[BV + PADW]  = s_u + 2 * BT;
    bf16 (*s_h)[BV + PADH]   = reinterpret_cast<bf16(*)[BV + PADH]>(s_vn + BT);
    float* s_el              = reinterpret_cast<float*>(s_h + DK);

    const int i_bh = blockIdx.x;
    const int i_v = blockIdx.y;
    const int i_b = i_bh / p.H;
    const int i_h = i_bh % p.H;
    const int tid = threadIdx.x;
    const int warp = tid / 32;
    const int lane = tid % 32;
    const int NT = p.NT, H = p.H, K = p.K, V = p.V;

    const long base_qk = (long)(i_b * p.T) * H * K + (long)i_h * K;
    const long base_v = (long)(i_b * p.T) * H * V + (long)i_h * V + i_v * BV;
    bf16* base_h = p.h + ((long)i_bh * NT) * K * V + i_v * BV;

    auto prefetch = [&](int it) {
        if (it >= NT) return;
        const bf16* gw = p.w + base_qk + (long)it * BT * H * K;
        const bf16* gkn = p.kn + base_qk + (long)it * BT * H * K;
        const bf16* gu = p.u + base_v + (long)it * BT * H * V;
        const float* gel = p.el + ((long)i_bh * NT + it) * K;
        bf16* dw = (bf16*)(s_w + (it & 1) * BT);
        bf16* dkn = (bf16*)(s_kn + (it & 1) * BT);
        bf16* du = (bf16*)(s_u + (it & 1) * BT);
        float* del = s_el + (it & 1) * DK;
        for (int i = tid; i < BT * DK / 8; i += 256) {
            int r = (i * 8) / DK, c = (i * 8) % DK;
            cp_async_16(smem_u32(dw + r * (DK + PADW) + c), gw + (long)r * H * K + c);
            cp_async_16(smem_u32(dkn + r * (DK + PADW) + c), gkn + (long)r * H * K + c);
        }
        for (int i = tid; i < BT * BV / 8; i += 256) {
            int r = (i * 8) / BV, c = (i * 8) % BV;
            cp_async_16(smem_u32(du + r * (BV + PADW) + c), gu + (long)r * H * V + c);
        }
        for (int i = tid; i < DK / 4; i += 256)
            cp_async_16(smem_u32(del + i * 4), gel + i * 4);
    };

    prefetch(0);
    cp_commit();

    float S[4][4];
#pragma unroll
    for (int i = 0; i < 4; i++)
        for (int j = 0; j < 4; j++) S[i][j] = 0.f;

    // h[0] = 0
    {
        int r = warp * 16 + lane / 4;
#pragma unroll
        for (int nf = 0; nf < 4; nf++) {
            int c = nf * 8 + (lane % 4) * 2;
            *reinterpret_cast<unsigned*>(base_h + (long)r * V + c) = 0u;
            *reinterpret_cast<unsigned*>(base_h + (long)(r + 8) * V + c) = 0u;
        }
    }

    for (int it = 0; it < NT; it++) {
        bf16* cw = (bf16*)(s_w + (it & 1) * BT);
        bf16* ckn = (bf16*)(s_kn + (it & 1) * BT);
        bf16* cu = (bf16*)(s_u + (it & 1) * BT);
        float* cel = s_el + (it & 1) * DK;

        cp_wait<0>();
        __syncthreads();
        prefetch(it + 1);
        cp_commit();

        // ---- dot1: acc = -u + w @ h (skip mma at it==0, S==0) ----
        int wm1 = warp % 4, wn1 = warp / 4;
        float vn[2][4];
#pragma unroll
        for (int nf = 0; nf < 2; nf++) {
            int r = wm1 * 16 + lane / 4;
            int c = wn1 * 16 + nf * 8 + (lane % 4) * 2;
            unsigned u01 = *reinterpret_cast<unsigned*>(&cu[r * (BV + PADW) + c]);
            unsigned u23 = *reinterpret_cast<unsigned*>(&cu[(r + 8) * (BV + PADW) + c]);
            float2 f01 = __bfloat1622float2(*reinterpret_cast<__nv_bfloat162*>(&u01));
            float2 f23 = __bfloat1622float2(*reinterpret_cast<__nv_bfloat162*>(&u23));
            vn[nf][0] = -f01.x; vn[nf][1] = -f01.y;
            vn[nf][2] = -f23.x; vn[nf][3] = -f23.y;
        }
#pragma unroll
        for (int ks = 0; ks < DK / 16 && it > 0; ks++) {
            unsigned a0, a1, a2, a3;
            {
                int r = wm1 * 16 + (lane & 15);
                int c = ks * 16 + (lane >= 16 ? 8 : 0);
                ldmatrix_x4(a0, a1, a2, a3, smem_u32(cw + r * (DK + PADW) + c));
            }
#pragma unroll
            for (int nf = 0; nf < 2; nf++) {
                unsigned b0, b1;
                int rr = ks * 16 + (lane & 15);
                int cc = wn1 * 16 + nf * 8;
                ldmatrix_x2_trans(b0, b1, smem_u32(&s_h[rr][cc]));
                mma_bf16(vn[nf][0], vn[nf][1], vn[nf][2], vn[nf][3], a0, a1, a2, a3, b0, b1);
            }
        }
        // vn = -( -u + w@h ) = u - w@h
        long base_vn = base_v + (long)it * BT * H * V;
        unsigned packed[2][2];
#pragma unroll
        for (int nf = 0; nf < 2; nf++) {
            int r = wm1 * 16 + lane / 4;
            int c = wn1 * 16 + nf * 8 + (lane % 4) * 2;
            packed[nf][0] = pack_bf16(-vn[nf][0], -vn[nf][1]);
            packed[nf][1] = pack_bf16(-vn[nf][2], -vn[nf][3]);
            *reinterpret_cast<unsigned*>(p.vnew + base_vn + (long)r * H * V + c) = packed[nf][0];
            *reinterpret_cast<unsigned*>(p.vnew + base_vn + (long)(r + 8) * H * V + c) = packed[nf][1];
        }
        {
            int r = wm1 * 16 + (lane & 7) + ((lane / 8) & 1) * 8;
            int c = wn1 * 16 + (lane / 16) * 8;
            stmatrix_x4(smem_u32(&s_vn[r][c]), packed[0][0], packed[0][1], packed[1][0], packed[1][1]);
        }
        __syncthreads();

        // ---- S *= el; dot2: S += kn^T @ vn ----
        float el0 = cel[warp * 16 + lane / 4];
        float el1 = cel[warp * 16 + lane / 4 + 8];
#pragma unroll
        for (int nf = 0; nf < 4; nf++) {
            S[nf][0] *= el0; S[nf][1] *= el0;
            S[nf][2] *= el1; S[nf][3] *= el1;
        }
#pragma unroll
        for (int ks = 0; ks < BT / 16; ks++) {
            unsigned a0, a1, a2, a3;
            {
                int t = ks * 16 + (lane & 7) + ((lane / 16) * 8);
                int ch = warp * 16 + ((lane / 8) & 1) * 8;
                ldmatrix_x4_trans(a0, a1, a2, a3, smem_u32(ckn + t * (DK + PADW) + ch));
            }
#pragma unroll
            for (int nf = 0; nf < 4; nf++) {
                unsigned b0, b1;
                int rr = ks * 16 + (lane & 15);
                ldmatrix_x2_trans(b0, b1, smem_u32(&s_vn[rr][nf * 8]));
                mma_bf16(S[nf][0], S[nf][1], S[nf][2], S[nf][3], a0, a1, a2, a3, b0, b1);
            }
        }
        // pack S -> s_h + global h[it+1]
        {
            int r = warp * 16 + lane / 4;
            unsigned ph[4][2];
#pragma unroll
            for (int nf = 0; nf < 4; nf++) {
                ph[nf][0] = pack_bf16(S[nf][0], S[nf][1]);
                ph[nf][1] = pack_bf16(S[nf][2], S[nf][3]);
                int c = nf * 8 + (lane % 4) * 2;
                if (it + 1 < NT) {
                    *reinterpret_cast<unsigned*>(base_h + (long)(it + 1) * K * V + (long)r * V + c) = ph[nf][0];
                    *reinterpret_cast<unsigned*>(base_h + (long)(it + 1) * K * V + (long)(r + 8) * V + c) = ph[nf][1];
                }
            }
#pragma unroll
            for (int nfp = 0; nfp < 2; nfp++) {
                int rr = warp * 16 + (lane & 7) + ((lane / 8) & 1) * 8;
                int cc = nfp * 16 + (lane / 16) * 8;
                stmatrix_x4(smem_u32(&s_h[rr][cc]), ph[2 * nfp][0], ph[2 * nfp][1],
                            ph[2 * nfp + 1][0], ph[2 * nfp + 1][1]);
            }
        }
        __syncthreads();
    }
}

// ---------------------------------------------------------------------------
// Output kernel: o = qg @ h + Aqk @ v_new, per (bh, chunk). 8 warps.
// ---------------------------------------------------------------------------
struct OutParams {
    const bf16* qg; const bf16* h; const bf16* Aqk; const bf16* vnew; bf16* o;
    int T, NT, H, K, V;
};

__global__ void __launch_bounds__(256, 1) out_kernel(OutParams p) {
    extern __shared__ char smem_raw[];
    bf16 (*s_qg)[DK + PADW]  = reinterpret_cast<bf16(*)[DK + PADW]>(smem_raw);   // [64][136]
    bf16 (*s_h)[DK + PADW]   = s_qg + BT;                                       // [128][136]
    bf16 (*s_A)[BT + PADW]   = reinterpret_cast<bf16(*)[BT + PADW]>(s_h + DK);  // [64][72]
    bf16 (*s_vn)[DK + PADW]  = reinterpret_cast<bf16(*)[DK + PADW]>(s_A + BT);  // [64][136]

    const int i_c = blockIdx.x;
    const int i_bh = blockIdx.y;
    const int i_b = i_bh / p.H;
    const int i_h = i_bh % p.H;
    const int tid = threadIdx.x;
    const int warp = tid / 32;
    const int lane = tid % 32;
    const int H = p.H, K = p.K, V = p.V, NT = p.NT;

    const long row_qk = ((long)(i_b * NT + i_c) * BT) * H * K + (long)i_h * K;
    const long row_v = ((long)(i_b * NT + i_c) * BT) * H * V + (long)i_h * V;
    const long row_A = ((long)(i_b * NT + i_c) * BT) * H * BT + (long)i_h * BT;
    const bf16* gh = p.h + ((long)i_bh * p.NT + i_c) * K * V;

    // stage everything
    for (int i = tid; i < BT * DK / 8; i += 256) {
        int r = (i * 8) / DK, c = (i * 8) % DK;
        cp_async_16(smem_u32(&s_qg[r][c]), p.qg + row_qk + (long)r * H * K + c);
        cp_async_16(smem_u32(&s_vn[r][c]), p.vnew + row_v + (long)r * H * V + c);
    }
    for (int i = tid; i < DK * DK / 8; i += 256) {
        int r = (i * 8) / DK, c = (i * 8) % DK;
        cp_async_16(smem_u32(&s_h[r][c]), gh + (long)r * V + c);
    }
    for (int i = tid; i < BT * BT / 8; i += 256) {
        int r = (i * 8) / BT, c = (i * 8) % BT;
        cp_async_16(smem_u32(&s_A[r][c]), p.Aqk + row_A + (long)r * H * BT + c);
    }
    cp_commit();
    cp_wait<0>();
    __syncthreads();

    int wm = warp % 4, wn = warp / 4;   // warp covers 16m x 64n
    float acc[8][4];
#pragma unroll
    for (int i = 0; i < 8; i++)
        for (int j = 0; j < 4; j++) acc[i][j] = 0.f;

    // dot1: qg (64x128) @ h (128x128)
#pragma unroll
    for (int ks = 0; ks < DK / 16; ks++) {
        unsigned a0, a1, a2, a3;
        {
            int r = wm * 16 + (lane & 15);
            int c = ks * 16 + (lane >= 16 ? 8 : 0);
            ldmatrix_x4(a0, a1, a2, a3, smem_u32(&s_qg[r][c]));
        }
#pragma unroll
        for (int nf = 0; nf < 8; nf++) {
            unsigned b0, b1;
            int rr = ks * 16 + (lane & 15);
            int cc = wn * 64 + nf * 8;
            ldmatrix_x2_trans(b0, b1, smem_u32(&s_h[rr][cc]));
            mma_bf16(acc[nf][0], acc[nf][1], acc[nf][2], acc[nf][3], a0, a1, a2, a3, b0, b1);
        }
    }
    // dot2: Aqk (64x64) @ vn (64x128)
#pragma unroll
    for (int ks = 0; ks < BT / 16; ks++) {
        unsigned a0, a1, a2, a3;
        {
            int r = wm * 16 + (lane & 15);
            int c = ks * 16 + (lane >= 16 ? 8 : 0);
            ldmatrix_x4(a0, a1, a2, a3, smem_u32(&s_A[r][c]));
        }
#pragma unroll
        for (int nf = 0; nf < 8; nf++) {
            unsigned b0, b1;
            int rr = ks * 16 + (lane & 15);
            int cc = wn * 64 + nf * 8;
            ldmatrix_x2_trans(b0, b1, smem_u32(&s_vn[rr][cc]));
            mma_bf16(acc[nf][0], acc[nf][1], acc[nf][2], acc[nf][3], a0, a1, a2, a3, b0, b1);
        }
    }
    // store o
    bf16* go = p.o + row_v;
#pragma unroll
    for (int nf = 0; nf < 8; nf++) {
        int r = wm * 16 + lane / 4;
        int c = wn * 64 + nf * 8 + (lane % 4) * 2;
        *reinterpret_cast<unsigned*>(go + (long)r * H * V + c) = pack_bf16(acc[nf][0], acc[nf][1]);
        *reinterpret_cast<unsigned*>(go + (long)(r + 8) * H * V + c) = pack_bf16(acc[nf][2], acc[nf][3]);
    }
}

torch::Tensor out_forward(torch::Tensor qg, torch::Tensor h, torch::Tensor Aqk,
                          torch::Tensor vnew, torch::Tensor o, long T, long NT, long H) {
    OutParams p;
    p.qg = reinterpret_cast<const bf16*>(qg.data_ptr());
    p.h = reinterpret_cast<const bf16*>(h.data_ptr());
    p.Aqk = reinterpret_cast<const bf16*>(Aqk.data_ptr());
    p.vnew = reinterpret_cast<const bf16*>(vnew.data_ptr());
    p.o = reinterpret_cast<bf16*>(o.data_ptr());
    p.T = T; p.NT = NT; p.H = H; p.K = 128; p.V = 128;
    dim3 grid(NT, qg.size(0) * H);
    size_t smem = 64 * (DK + PADW) * 2 + 128 * (DK + PADW) * 2 + 64 * (BT + PADW) * 2 + 64 * (DK + PADW) * 2;
    static bool init = false;
    if (!init) {
        cudaFuncSetAttribute(out_kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, (int)smem);
        init = true;
    }
    out_kernel<<<grid, 256, smem>>>(p);
    return o;
}

torch::Tensor scan_forward(torch::Tensor kn, torch::Tensor w, torch::Tensor u,
                           torch::Tensor el, torch::Tensor h, torch::Tensor vnew,
                           long T, long NT, long H) {    ScanParams p;
    p.kn = reinterpret_cast<const bf16*>(kn.data_ptr());
    p.w = reinterpret_cast<const bf16*>(w.data_ptr());
    p.u = reinterpret_cast<const bf16*>(u.data_ptr());
    p.el = el.data_ptr<float>();
    p.h = reinterpret_cast<bf16*>(h.data_ptr());
    p.vnew = reinterpret_cast<bf16*>(vnew.data_ptr());
    p.T = T; p.NT = NT; p.H = H; p.K = 128; p.V = 128;
    long BH = kn.size(0) * H;
    dim3 grid(BH, 128 / BV);
    size_t smem = 2 * 64 * (DK + PADW) * 2 * 2 + 2 * 64 * (BV + PADW) * 2 + 64 * (BV + PADW) * 2 + 128 * (BV + PADH) * 2 + 2 * DK * 4;
    static bool init = false;
    if (!init) {
        cudaFuncSetAttribute(scan_kernel, cudaFuncAttributeMaxDynamicSharedMemorySize, (int)smem);
        init = true;
    }
    scan_kernel<<<grid, 256, smem>>>(p);
    return h;
}
"""

CPP_SRC = """
torch::Tensor scan_forward(torch::Tensor kn, torch::Tensor w, torch::Tensor u, torch::Tensor el, torch::Tensor h, torch::Tensor vnew, long T, long NT, long H);
torch::Tensor out_forward(torch::Tensor qg, torch::Tensor h, torch::Tensor Aqk, torch::Tensor vnew, torch::Tensor o, long T, long NT, long H);
"""

_cuda_mod = None


def _get_cuda():
    global _cuda_mod
    if _cuda_mod is None:
        _cuda_mod = load_inline(
            name="kda_cuda_v2",
            cpp_sources=CPP_SRC,
            cuda_sources=CUDA_SRC,
            functions=["scan_forward", "out_forward"],
            extra_cuda_cflags=["-O3", "-gencode=arch=compute_120a,code=sm_120a"],
            verbose=False,
        )
    return _cuda_mod


# ---------------------------------------------------------------------------
# K1: intra-chunk kernel
# ---------------------------------------------------------------------------
@triton.jit
def _subst16(M):
    """Forward substitution (reference loop) on one strictly-lower 16x16 tile.
    Returns M_loop + I16, i.e. (I16 - M)^{-1}."""
    o = tl.arange(0, 16)
    for i in tl.static_range(1, 16):
        row = tl.sum(tl.where((o == i)[:, None], M, 0.0), axis=0)
        upd = tl.sum(row[:, None] * M, axis=0)
        row = row + upd
        M = tl.where((o == i)[:, None], row[None, :], M)
    return M + tl.where(o[:, None] == o[None, :], 1.0, 0.0)


@triton.jit
def _split_blocks(x):
    """(64,64) -> 16 tiles (16,16) in row-major block order (bi,bj)."""
    xr = tl.reshape(x, (4, 16, 4, 16))        # (bi, ri, bj, ci)
    xr = tl.permute(xr, (1, 3, 0, 2))         # (ri, ci, bi, bj)
    xr = tl.reshape(xr, (16, 16, 2, 2, 2, 2))  # (ri, ci, bih, bil, bjh, bjl)
    a0, a1 = tl.split(xr)
    b00, b01 = tl.split(a0)
    b10, b11 = tl.split(a1)
    c000, c001 = tl.split(b00)
    c010, c011 = tl.split(b01)
    c100, c101 = tl.split(b10)
    c110, c111 = tl.split(b11)
    d00, d01 = tl.split(c000)   # d_i0/d_i1: bi = i; bj fixed by c-level
    d10, d11 = tl.split(c001)
    d20, d21 = tl.split(c010)
    d30, d31 = tl.split(c011)
    d40, d41 = tl.split(c100)
    d50, d51 = tl.split(c101)
    d60, d61 = tl.split(c110)
    d70, d71 = tl.split(c111)
    return (d00, d40, d20, d60,
            d10, d50, d30, d70,
            d01, d41, d21, d61,
            d11, d51, d31, d71)


@triton.jit
def _hcat(a, b):
    """(M,N),(M,N) -> (M,2N) column concat."""
    return tl.reshape(tl.permute(tl.join(a, b), (0, 2, 1)), (a.shape[0], 2 * a.shape[1]))


@triton.jit
def _vcat(a, b):
    """(M,N),(M,N) -> (2M,N) row concat."""
    return tl.reshape(tl.permute(tl.join(a, b), (2, 0, 1)), (2 * a.shape[0], a.shape[1]))


@triton.jit
def _blocked_inv(b_A):
    """(64,64) strictly-lower fp32 (already negated+masked) -> Ainv (64,64) fp32
    with unit diagonal. Exact block forward substitution in 16x16 tiles."""
    A00, A01, A02, A03, A10, A11, A12, A13, A20, A21, A22, A23, A30, A31, A32, A33 = _split_blocks(b_A)
    D0 = _subst16(A00)
    D1 = _subst16(A11)
    D2 = _subst16(A22)
    D3 = _subst16(A33)
    X10 = tl.dot(D1, tl.dot(A10, D0, input_precision="tf32"), input_precision="tf32")
    X20 = tl.dot(D2, tl.dot(A20, D0, input_precision="tf32") + tl.dot(A21, X10, input_precision="tf32"), input_precision="tf32")
    X21 = tl.dot(D2, tl.dot(A21, D1, input_precision="tf32"), input_precision="tf32")
    X30 = tl.dot(D3, tl.dot(A30, D0, input_precision="tf32") + tl.dot(A31, X10, input_precision="tf32") + tl.dot(A32, X20, input_precision="tf32"), input_precision="tf32")
    X31 = tl.dot(D3, tl.dot(A31, D1, input_precision="tf32") + tl.dot(A32, X21, input_precision="tf32"), input_precision="tf32")
    X32 = tl.dot(D3, tl.dot(A32, D2, input_precision="tf32"), input_precision="tf32")
    Z = tl.zeros((16, 16), dtype=tl.float32)
    r0 = _hcat(_hcat(D0, Z), _hcat(Z, Z))
    r1 = _hcat(_hcat(X10, D1), _hcat(Z, Z))
    r2 = _hcat(_hcat(X20, X21), _hcat(D2, Z))
    r3 = _hcat(_hcat(X30, X31), _hcat(X32, D3))
    return _vcat(_vcat(r0, r1), _vcat(r2, r3))


@triton.jit
def _kda_intra_kernel(
    q, k, v, g, beta,
    kn_o, w_o, u_o, qg_o, Aqk_o, el_o,
    scale,
    NT,
    H: tl.constexpr, K: tl.constexpr, V: tl.constexpr,
    BT: tl.constexpr,
):
    i_c = tl.program_id(0)
    i_bh = tl.program_id(1)
    i_b = i_bh // H
    i_h = i_bh % H

    o_t = tl.arange(0, BT)
    o_k = tl.arange(0, K)
    o_v = tl.arange(0, V)

    row_qk = (i_b * NT + i_c) * BT * H * K + i_h * K
    row_v = (i_b * NT + i_c) * BT * H * V + i_h * V

    p_g = g + row_qk + o_t[:, None] * (H * K) + o_k[None, :]
    p_k = k + row_qk + o_t[:, None] * (H * K) + o_k[None, :]
    p_q = q + row_qk + o_t[:, None] * (H * K) + o_k[None, :]
    p_v = v + row_v + o_t[:, None] * (H * V) + o_v[None, :]
    p_beta = beta + (i_b * NT + i_c) * BT * H + i_h + o_t * H

    b_g = tl.load(p_g)                             # fp32 (BT,K)
    b_beta = tl.load(p_beta).to(tl.float32)        # (BT,)
    b_k = tl.load(p_k).to(tl.float32)              # (BT,K)

    # in-chunk cumsum in log2 domain
    b_gl2 = tl.cumsum(b_g, axis=0) * RCP_LN2       # (BT,K)
    b_glast = tl.sum(b_g, axis=0) * RCP_LN2        # (K,)
    b_dg = b_gl2 - b_glast[None, :]                # G - Gl (log2)

    b_ep = tl.math.exp2(b_dg)                      # e^{G-Gl}
    b_en = tl.math.exp2(-b_dg)                     # e^{Gl-G}
    b_el = tl.math.exp2(b_glast)                   # e^{Gl} (K,)

    b_kp = b_k * b_ep                              # fp32
    b_kn16 = (b_k * b_en).to(tl.bfloat16)
    b_kp16 = b_kp.to(tl.bfloat16)

    # store kn (KSDA "kg": used by K2 state update) and el
    tl.store(kn_o + row_qk + o_t[:, None] * (H * K) + o_k[None, :], b_kn16)
    tl.store(el_o + (i_bh * NT + i_c) * K + o_k, b_el)

    b_q = tl.load(p_q).to(tl.float32)
    b_qe = b_q * (b_ep * scale)
    b_qs16 = b_qe.to(tl.bfloat16)                  # s*q*e^{G-Gl}
    tl.store(qg_o + row_qk + o_t[:, None] * (H * K) + o_k[None, :],
             (b_qe * b_el[None, :]).to(tl.bfloat16))  # s*q*e^{G}

    # --- Aqk: keep s <= t (incl. diag) ---
    b_Aqk = tl.dot(b_qs16, tl.trans(b_kn16))
    m_le = o_t[:, None] >= o_t[None, :]
    b_Aqk = tl.where(m_le, b_Aqk, 0.0)
    tl.store(Aqk_o + (i_b * NT + i_c) * BT * H * BT + i_h * BT
             + o_t[:, None] * (H * BT) + o_t[None, :], b_Aqk.to(tl.bfloat16))

    # --- Akk raw: strictly lower, negated, beta on rows ---
    b_Akk = tl.dot(b_kp16, tl.trans(b_kn16))
    m_lt = o_t[:, None] > o_t[None, :]
    b_A = tl.where(m_lt, -b_beta[:, None] * b_Akk, 0.0)

    # ---- forward substitution (sequential rows) -> Ainv (unit lower) ----
    for i in tl.static_range(1, BT):
        row = tl.sum(tl.where((o_t == i)[:, None], b_A, 0.0), axis=0)
        upd = tl.sum(row[:, None] * b_A, axis=0)
        b_A = tl.where((o_t == i)[:, None], (row + upd)[None, :], b_A)
    m_I = o_t[:, None] == o_t[None, :]
    b_A = b_A + tl.where(m_I, 1.0, 0.0)
    b_A16 = b_A.to(tl.bfloat16)

    # w = Ainv @ (beta * e^{G} * k); u = Ainv @ (beta * v)
    b_kb = (b_kp * b_el[None, :] * b_beta[:, None]).to(tl.bfloat16)
    b_w = tl.dot(b_A16, b_kb)
    tl.store(w_o + row_qk + o_t[:, None] * (H * K) + o_k[None, :], b_w.to(tl.bfloat16))

    b_vb = (tl.load(p_v).to(tl.float32) * b_beta[:, None]).to(tl.bfloat16)
    b_u = tl.dot(b_A16, b_vb)
    tl.store(u_o + row_v + o_t[:, None] * (H * V) + o_v[None, :], b_u.to(tl.bfloat16))


# ---------------------------------------------------------------------------
# K2: sequential state scan
# ---------------------------------------------------------------------------
@triton.jit
def _kda_scan_kernel(
    kn, w, u, el, h_o, vnew_o,
    T, NT,
    H: tl.constexpr, K: tl.constexpr, V: tl.constexpr,
    BT: tl.constexpr, BV: tl.constexpr,
):
    i_bh = tl.program_id(0)
    i_v = tl.program_id(1)
    i_b = i_bh // H
    i_h = i_bh % H

    o_t = tl.arange(0, BT)
    o_k = tl.arange(0, K)
    o_v = i_v * BV + tl.arange(0, BV)

    b_h = tl.zeros((K, BV), dtype=tl.float32)

    base_qk = i_b * T * H * K + i_h * K
    base_v = i_b * T * H * V + i_h * V
    p_h = h_o + i_bh * NT * K * V + o_k[:, None] * V + o_v[None, :]

    for i_t in range(NT):
        # store chunk-start state
        tl.store(p_h + i_t * K * V, b_h.to(tl.bfloat16))

        row_qk = base_qk + i_t * BT * H * K
        row_v = base_v + i_t * BT * H * V

        b_w = tl.load(w + row_qk + o_t[:, None] * (H * K) + o_k[None, :])
        b_u = tl.load(u + row_v + o_t[:, None] * (H * V) + o_v[None, :]).to(tl.float32)
        b_vn = b_u - tl.dot(b_w, b_h.to(tl.bfloat16))
        b_vn16 = b_vn.to(tl.bfloat16)
        tl.store(vnew_o + row_v + o_t[:, None] * (H * V) + o_v[None, :], b_vn16)

        b_kn = tl.load(kn + row_qk + o_t[:, None] * (H * K) + o_k[None, :])
        b_el = tl.load(el + (i_bh * NT + i_t) * K + o_k)
        b_h = b_h * b_el[:, None] + tl.dot(tl.trans(b_kn), b_vn16)


# ---------------------------------------------------------------------------
# K3: output kernel
# ---------------------------------------------------------------------------
@triton.jit
def _kda_out_kernel(
    qg, h, Aqk, vnew, o,
    T, NT,
    H: tl.constexpr, K: tl.constexpr, V: tl.constexpr,
    BT: tl.constexpr,
):
    i_c = tl.program_id(0)
    i_bh = tl.program_id(1)
    i_b = i_bh // H
    i_h = i_bh % H

    o_t = tl.arange(0, BT)
    o_k = tl.arange(0, K)
    o_v = tl.arange(0, V)

    row_qk = (i_b * NT + i_c) * BT * H * K + i_h * K
    row_v = (i_b * NT + i_c) * BT * H * V + i_h * V

    b_qg = tl.load(qg + row_qk + o_t[:, None] * (H * K) + o_k[None, :])
    b_h = tl.load(h + i_bh * NT * K * V + i_c * K * V + o_k[:, None] * V + o_v[None, :])
    b_o = tl.dot(b_qg, b_h)

    b_A = tl.load(Aqk + (i_b * NT + i_c) * BT * H * BT + i_h * BT
                  + o_t[:, None] * (H * BT) + o_t[None, :])
    b_vn = tl.load(vnew + row_v + o_t[:, None] * (H * V) + o_v[None, :])
    b_o += tl.dot(b_A, b_vn)

    tl.store(o + row_v + o_t[:, None] * (H * V) + o_v[None, :], b_o.to(tl.bfloat16))


# ---------------------------------------------------------------------------
# Host-side wrapper
# ---------------------------------------------------------------------------
def kda_chunk_forward(
    q: torch.Tensor, k: torch.Tensor, v: torch.Tensor,
    g: torch.Tensor, beta: torch.Tensor, 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

    q = q.contiguous(); k = k.contiguous(); v = v.contiguous()
    g = g.contiguous(); beta = beta.contiguous()

    dev = q.device
    bf = torch.bfloat16
    BH = B * H

    kn = torch.empty(B, T, H, K, dtype=bf, device=dev)
    w = torch.empty(B, T, H, K, dtype=bf, device=dev)
    u = torch.empty(B, T, H, V, dtype=bf, device=dev)
    qg = torch.empty(B, T, H, K, dtype=bf, device=dev)
    Aqk = torch.empty(B, T, H, BT, dtype=bf, device=dev)
    el = torch.empty(B, H, NT, K, dtype=torch.float32, device=dev)
    h = torch.empty(B, H, NT, K, V, dtype=bf, device=dev)
    v_new = torch.empty(B, T, H, V, dtype=bf, device=dev)
    o = torch.empty(B, T, H, V, dtype=bf, device=dev)

    _kda_intra_kernel[(NT, BH)](
        q, k, v, g, beta, kn, w, u, qg, Aqk, el, scale,
        NT, H=H, K=K, V=V, BT=BT, num_warps=4,
    )
    _get_cuda().scan_forward(kn, w, u, el, h, v_new, T, NT, H)
    _get_cuda().out_forward(qg, h, Aqk, v_new, o, T, NT, H)
    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)

    def forward(
        self,
        q: torch.Tensor,
        k: torch.Tensor,
        v: torch.Tensor,
        g: torch.Tensor,
        beta: torch.Tensor,
    ) -> torch.Tensor:
        return kda_chunk_forward(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]

20260716_091211_kinetic-claude_kinetic-0715_02_kda_cutlass