kernelbench.com

KernelBench hard · H100

Paged Attention Kimi K3 (256k)

51.4%geomean peak fraction across shapes

manually audited: clean

Genuine hand-written fused CUDA paged-attention decode kernel, built via load_inline from inline PTX/MMA source. One kernel streams KV pages from the live pool with cp.async into a multi-stage smem pipeline, does QK^T and P.V on bf16 tensor cores (m16n8k16 / m16n8k8 MMA) with online softmax in fragment layout, cross-warp LSE merge in smem, and an optional split-K path whose last-arriving CTA does the final log-sum-exp merge in-kernel. Output is a fresh at::empty allocation on every call; all pointers come from the live input tensors each invocation. No forbidden op (no flashinfer / vllm / SDPA anywhere in solution.py), no output caching, no cross-run contamination, no grader or tolerance tampering, and numeric stress ran as shipped.

harnesskinetic-claudeagent session20h 9mtotal wall20h 11mcheck3mbenchmark7soutput tokens405,129cost$838.71gpu-lock wait2mgpu-lock held70sregimememory

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

8×32×8×128×1024×160.037 ms44.5%0.91 TB/s · 44% of 2.0 TB/s HBM · also 4 TFLOPS (0% of compute)
32×32×8×128×2048×160.170 ms77.7%1.58 TB/s · 78% of 2.0 TB/s HBM · also 6 TFLOPS (1% of compute)
4×64×8×128×4096×160.058 ms56.5%1.15 TB/s · 57% of 2.0 TB/s HBM · also 9 TFLOPS (1% of compute)
16×32×8×128×1535×160.076 ms65.5%1.33 TB/s · 65% of 2.0 TB/s HBM · also 5 TFLOPS (1% of compute)
8×16×4×64×2000×160.029 ms28.1%0.57 TB/s · 28% of 2.0 TB/s HBM · also 2 TFLOPS (0% of compute)

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

geomean(44.5% · 77.7% · 56.5% · 65.5% · 28.1%) = 51.4%

Kernel source (redacted)
"""Paged attention decode kernel for H100 PCIe (SM90, HBM2e ~2.0 TB/s).

Single-query (decode) attention over a paged KV cache. One fused CUDA kernel:

- Grid (splits, B*Hkv): each CTA owns one (batch, kv-head, token-chunk) triple and
  streams its KV pages straight from the pool with 16B cp.async into an NSTAGES-deep
  smem pipeline (each stage = 32 tokens / two pages, smem rows padded +16B so
  ldmatrix reads are bank-conflict free).
- Attention on tensor cores: warp handles 8 tokens/stage for the whole GQA group
  (pad heads to the m16 tile). QK^T via m16n8k16 bf16 MMA, online softmax in the
  MMA fragment layout (row max/sum via 2 shfl in the 4-lane row groups), p
  re-packed in registers to an m16k8 A-fragment feeding the m16n8k8 P·V MMA.
- Warps keep independent (m, l, acc) online-softmax states, merged in smem (fp32)
  once at the end of the chunk.
- Split-K for occupancy on small grids: per-CTA partial (acc, m, l) goes to a
  scratch workspace; the last-arriving CTA of each (b, kvh) group does the final
  log-sum-exp merge in-kernel (arrival ticket; every thread fences before it).
- kv loads carry an L2::evict_first hint (streamed exactly once).

Per-row variable seq_lens, non-page-aligned tails, GQA ratios 1..8, head_dim 64/128.
"""

import math
import os

import torch
import torch.nn as nn

OP_TYPE = "attention"
SUPPORTED_PRECISIONS = ["bf16"]
HARDWARE_REQUIRED = ["H100"]

# --- Shape knobs (same contract as reference.py; check.py overrides reference's).
BATCH = 8
NUM_HEADS = 32
NUM_KV_HEADS = 8
HEAD_DIM = 128
SEQ_LEN = 1024
PAGE_SIZE = 16

_CUDA_SRC = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <ATen/cuda/EmptyTensor.h>
#include <cuda_bf16.h>
#include <cstdint>

#define DEV_INLINE __device__ __forceinline__
#define FULL_MASK 0xffffffffu

DEV_INLINE unsigned long long mk_policy() {
  unsigned long long policy;
  asm volatile("createpolicy.fractional.L2::evict_first.b64 %0;" : "=l"(policy));
  return policy;
}
DEV_INLINE void cp_async16_ef(void* smem_dst, const void* gsrc, unsigned long long policy) {
  unsigned dst = (unsigned)__cvta_generic_to_shared(smem_dst);
  asm volatile("cp.async.cg.shared.global.L2::cache_hint [%0], [%1], 16, 16, %2;\n" ::"r"(dst),
               "l"(gsrc), "l"(policy));
}
DEV_INLINE void cp_commit() { asm volatile("cp.async.commit_group;\n"); }
template <int N>
DEV_INLINE void cp_wait_all_but() {
  asm volatile("cp.async.wait_group %0;\n" ::"n"(N));
}
DEV_INLINE void ldm_x4(uint32_t* R, unsigned sa) {
  asm volatile("ldmatrix.sync.aligned.m8n8.x4.shared.b16 {%0, %1, %2, %3}, [%4];\n"
               : "=r"(R[0]), "=r"(R[1]), "=r"(R[2]), "=r"(R[3]) : "r"(sa));
}
DEV_INLINE void ldm_x4_t(uint32_t* R, unsigned sa) {
  asm volatile("ldmatrix.sync.aligned.trans.m8n8.x4.shared.b16 {%0, %1, %2, %3}, [%4];\n"
               : "=r"(R[0]), "=r"(R[1]), "=r"(R[2]), "=r"(R[3]) : "r"(sa));
}
DEV_INLINE void mma_qk(float* C, const uint32_t* A, const uint32_t* B) {
  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"(C[0]), "+f"(C[1]), "+f"(C[2]), "+f"(C[3])
      : "r"(A[0]), "r"(A[1]), "r"(A[2]), "r"(A[3]), "r"(B[0]), "r"(B[1]));
}
DEV_INLINE void mma_pv(float* C, const uint32_t* A, const uint32_t B) {
  asm volatile(
      "mma.sync.aligned.m16n8k8.row.col.f32.bf16.bf16.f32 "
      "{%0, %1, %2, %3}, {%4, %5}, {%6}, {%0, %1, %2, %3};\n"
      : "+f"(C[0]), "+f"(C[1]), "+f"(C[2]), "+f"(C[3])
      : "r"(A[0]), "r"(A[1]), "r"(B));
}

struct MmaParams {
  const __nv_bfloat16* __restrict__ q;
  const __nv_bfloat16* __restrict__ kv;
  const int* __restrict__ bt;
  const int* __restrict__ seq;
  __nv_bfloat16* __restrict__ out;
  float* __restrict__ ws_acc;      // (groups, S, G, D) fp32
  float* __restrict__ ws_ml;       // (groups, S, G, 2) fp32
  unsigned* __restrict__ counters; // (groups)
  int H;
  int Hkv;
  int G;
  int S;
  int bt_stride;
  int page_stride_elems;  // P*Hkv*2D
  int tok_stride_elems;   // Hkv*2D
  int pages_per_split;
  float scale_log2e;
};

constexpr float NEG_BIG = -1e30f;

// D: head dim (64/128). 4 warps. TW=8 tokens/warp/stage -> T=32 tokens/stage.
template <int D, int NSTAGES, bool DIRECT>
__global__ void __launch_bounds__(128) dec_mma(const __grid_constant__ MmaParams p) {
  constexpr int NW = 4;
  constexpr int TW = 8;
  constexpr int T = NW * TW;
  constexpr int ROWE = 2 * D;
  constexpr int ROWEP = ROWE + 8;  // +16B pad: kills ldmatrix bank conflicts
  constexpr int SLICES = D / 16;
  constexpr int NTILES = D / 8;
  constexpr int STAGE_ELEMS = T * ROWEP;

  extern __shared__ char smem_raw[];
  __nv_bfloat16* s_kv = reinterpret_cast<__nv_bfloat16*>(smem_raw);
  int* s_bt = reinterpret_cast<int*>(s_kv + NSTAGES * STAGE_ELEMS);

  const int split = blockIdx.x;
  const int gid = blockIdx.y;
  const int b = gid / p.Hkv;
  const int kvh = gid % p.Hkv;
  const int kv_len = p.seq[b];
  const int npages = (kv_len + 15) >> 4;
  const int pg0 = split * p.pages_per_split;
  const int pg1 = min(npages, pg0 + p.pages_per_split);
  const int tok0 = pg0 << 4;
  const int tok_end = min(kv_len, pg1 << 4);

  const int tid = threadIdx.x;
  const int warp = tid >> 5;
  const int lane = tid & 31;

  const int nchunk_pages = max(0, pg1 - pg0);
  for (int i = tid; i < nchunk_pages; i += NW * 32) {
    s_bt[i] = p.bt[b * p.bt_stride + pg0 + i];
  }

  const int total_tok = tok_end - tok0;
  const int total_stages = (total_tok + T - 1) / T;

  constexpr int LPR = ROWE * 2 / 16;
  constexpr int NITER = (T * LPR) / 128;
  const int row0 = tid / LPR;
  const int chunk = tid % LPR;
  const int elem_off = chunk * 8;

  unsigned long long policy = mk_policy();
  auto issue_stage = [&](int si, int slot) {
    __nv_bfloat16* dst0 = s_kv + slot * STAGE_ELEMS;
#pragma unroll
    for (int k = 0; k < NITER; ++k) {
      int row = row0 + k * (128 / LPR);
      int pg = (si * T + row) >> 4;
      int tokinpg = row & 15;
      bool valid = (si * T + row) < total_tok;
      int page = valid ? s_bt[pg] : 0;
      const __nv_bfloat16* src = p.kv + (unsigned)page * p.page_stride_elems +
                                 (unsigned)(tokinpg * p.tok_stride_elems) +
                                 (unsigned)(kvh * ROWE) + elem_off;
      cp_async16_ef(dst0 + row * ROWEP + chunk * 8, valid ? src : p.kv, policy);
    }
    cp_commit();
  };

  // ---- Q A-frags (m16k16 per slice): rows 0..G-1 real, else zero ----
  __syncthreads();  // publish s_bt before any issue
  // ALWAYS commit exactly NSTAGES-1 groups at preload (empty commits included) so
  // wait_group<NSTAGES-2> semantics hold even when total_stages < NSTAGES-1.
  for (int si = 0; si < NSTAGES - 1; ++si) {
    if (si < total_stages) issue_stage(si, si);
    else cp_commit();
  }
  uint32_t aQ[SLICES][4];
  {
    const int r0 = lane / 4;
    const int c0 = (lane % 4) * 2;
#pragma unroll
    for (int s = 0; s < SLICES; ++s) {
      uint32_t a[4] = {0u, 0u, 0u, 0u};
      if (r0 < p.G) {
        const __nv_bfloat16* qp = p.q + ((long)b * p.H + kvh * p.G + r0) * D + s * 16 + c0;
        a[0] = *reinterpret_cast<const uint32_t*>(qp);
        a[2] = *reinterpret_cast<const uint32_t*>(qp + 8);
      }
      if (r0 + 8 < p.G) {
        const __nv_bfloat16* qp = p.q + ((long)b * p.H + kvh * p.G + r0 + 8) * D + s * 16 + c0;
        a[1] = *reinterpret_cast<const uint32_t*>(qp);
        a[3] = *reinterpret_cast<const uint32_t*>(qp + 8);
      }
      const float sc = p.scale_log2e;
#pragma unroll
      for (int i = 0; i < 4; ++i) {
        float2 f = __bfloat1622float2(*reinterpret_cast<__nv_bfloat162*>(&a[i]));
        f.x *= sc;
        f.y *= sc;
        __nv_bfloat162 packed = __floats2bfloat162_rn(f.x, f.y);
        a[i] = *reinterpret_cast<uint32_t*>(&packed);
      }
#pragma unroll
      for (int i = 0; i < 4; ++i) aQ[s][i] = a[i];
    }
  }
  float oacc[NTILES][4];
  float m0 = NEG_BIG, m8 = NEG_BIG, l0 = 0.f, l8 = 0.f;
#pragma unroll
  for (int t = 0; t < NTILES; ++t) {
#pragma unroll
    for (int i = 0; i < 4; ++i) oacc[t][i] = 0.f;
  }

  const int r0 = lane / 4;
  const int c0 = (lane % 4) * 2;

  for (int si = 0; si < total_stages; ++si) {
    cp_wait_all_but<NSTAGES - 2>();
    __syncthreads();
    int ni0 = si + NSTAGES - 1;
    if (ni0 < total_stages) {
      issue_stage(ni0, ni0 % NSTAGES);
    } else {
      cp_commit();
    }
    const int slot = si % NSTAGES;
    const int tok_base = tok0 + si * T;
    const __nv_bfloat16* sbase = s_kv + slot * STAGE_ELEMS;
    (void)tok_base;

    // ---------- QK ----------
    float accs[4] = {0.f, 0.f, 0.f, 0.f};
    {
      const int trow = warp * TW;
      int tok = lane % 8;
      const __nv_bfloat16* tbase = sbase + (trow + tok) * ROWEP;
#pragma unroll
      for (int sp = 0; sp < SLICES / 2; ++sp) {
        uint32_t b[4];
        int j = lane / 8;
        unsigned sa = (unsigned)__cvta_generic_to_shared(tbase + (sp * 4 + j) * 8);
        ldm_x4(b, sa);
        mma_qk(accs, aQ[2 * sp], b);
        mma_qk(accs, aQ[2 * sp + 1], b + 2);
      }
    }
    // mask invalid tokens: cols c0, c0+1 of rows r0, r0+8
    {
      int tloc = si * T + warp * TW + c0;
      if (tloc >= total_tok) { accs[0] = NEG_BIG; accs[2] = NEG_BIG; }
      if (tloc + 1 >= total_tok) { accs[1] = NEG_BIG; accs[3] = NEG_BIG; }
    }
    // row max
    float m0n = fmaxf(fmaxf(accs[0], accs[1]), m0);
    float m8n = fmaxf(fmaxf(accs[2], accs[3]), m8);
#pragma unroll
    for (int off = 1; off <= 2; off <<= 1) {
      m0n = fmaxf(m0n, __shfl_xor_sync(FULL_MASK, m0n, off));
      m8n = fmaxf(m8n, __shfl_xor_sync(FULL_MASK, m8n, off));
    }
    m0n = fmaxf(m0n, m0);
    m8n = fmaxf(m8n, m8);
    float alpha0 = exp2f(m0 - m0n);
    float alpha8 = exp2f(m8 - m8n);
    m0 = m0n; m8 = m8n;
    l0 *= alpha0; l8 *= alpha8;
#pragma unroll
    for (int t = 0; t < NTILES; ++t) {
      oacc[t][0] *= alpha0; oacc[t][1] *= alpha0;
      oacc[t][2] *= alpha8; oacc[t][3] *= alpha8;
    }
    float p0 = exp2f(accs[0] - m0);
    float p1 = exp2f(accs[1] - m0);
    float p2 = exp2f(accs[2] - m8);
    float p3 = exp2f(accs[3] - m8);
    float ls0 = (p0 + p1), ls8 = (p2 + p3);
#pragma unroll
    for (int off = 1; off <= 2; off <<= 1) {
      ls0 += __shfl_xor_sync(FULL_MASK, ls0, off);
      ls8 += __shfl_xor_sync(FULL_MASK, ls8, off);
    }
    l0 += ls0; l8 += ls8;
    // pack p into A frag (m16k8)
    uint32_t aP[2];
    {
      __nv_bfloat162 pa = __floats2bfloat162_rn(p0, p1);
      __nv_bfloat162 pb = __floats2bfloat162_rn(p2, p3);
      aP[0] = *reinterpret_cast<uint32_t*>(&pa);
      aP[1] = *reinterpret_cast<uint32_t*>(&pb);
    }
    // ---------- PV ----------
    {
      int tok = lane % 8;
#pragma unroll
      for (int n4 = 0; n4 < NTILES / 4; ++n4) {
        uint32_t bv[4];
        int j = lane / 8;
        int chunk = D / 8 + n4 * 4 + j;
        unsigned sa = (unsigned)__cvta_generic_to_shared(sbase + (warp * TW + tok) * ROWEP + chunk * 8);
        ldm_x4_t(bv, sa);
#pragma unroll
        for (int nt = 0; nt < 4; ++nt) {
          mma_pv(oacc[n4 * 4 + nt], aP, bv[nt]);
        }
      }
    }
  }

  // barrier before repurposing smem for the merge buffers (lagging warps may still be
  // ldmatrix-ing the last stages' KV rows, which alias s_o/s_ml).
  cp_wait_all_but<0>();
  __syncthreads();

  // ---------- cross-warp merge ----------
  float* s_o = reinterpret_cast<float*>(smem_raw);
  float* s_ml = reinterpret_cast<float*>(smem_raw + NW * 16 * D * 4);
#pragma unroll
  for (int t = 0; t < NTILES; ++t) {
    float* dst = s_o + (warp * 16 + r0) * D + t * 8 + c0;
    dst[0] = oacc[t][0];
    dst[1] = oacc[t][1];
    dst = s_o + (warp * 16 + r0 + 8) * D + t * 8 + c0;
    dst[0] = oacc[t][2];
    dst[1] = oacc[t][3];
  }
  if ((lane & 3) == 0) {
    float* w = s_ml + (warp * 16 + r0) * 2;
    w[0] = m0; w[1] = l0;
    w[8 * 2] = m8; w[8 * 2 + 1] = l8;
  }
  __syncthreads();

  const int G = p.G;
  for (int item = tid; item < G * (D / 8); item += 128) {
    int r = item / (D / 8);
    int dc = (item % (D / 8)) * 8;
    float mw[NW], lw[NW];
    float M = NEG_BIG;
#pragma unroll
    for (int w = 0; w < NW; ++w) {
      mw[w] = s_ml[(w * 16 + r) * 2];
      lw[w] = s_ml[(w * 16 + r) * 2 + 1];
      M = fmaxf(M, mw[w]);
    }
    float ltot = 0.f;
    float acc[8];
#pragma unroll
    for (int e = 0; e < 8; ++e) acc[e] = 0.f;
    for (int w = 0; w < NW; ++w) {
      float aw = exp2f(mw[w] - M);
      ltot += lw[w] * aw;
      const float* ow = s_o + (w * 16 + r) * D + dc;
#pragma unroll
      for (int e = 0; e < 8; ++e) acc[e] += aw * ow[e];
    }
    int h = kvh * G + r;
    if constexpr (DIRECT) {
      float inv = ltot > 0.f ? 1.f / ltot : 0.f;
      __nv_bfloat162 outv[4];
#pragma unroll
      for (int e = 0; e < 4; ++e) {
        outv[e] = __floats2bfloat162_rn(acc[2 * e] * inv, acc[2 * e + 1] * inv);
      }
      *reinterpret_cast<uint4*>(p.out + ((long)b * p.H + h) * D + dc) =
          *reinterpret_cast<uint4*>(outv);
    } else {
      float* wa = p.ws_acc + (((long)gid * p.S + split) * G + r) * D + dc;
#pragma unroll
      for (int e = 0; e < 2; ++e) {
        *reinterpret_cast<float4*>(wa + 4 * e) =
            make_float4(acc[4 * e], acc[4 * e + 1], acc[4 * e + 2], acc[4 * e + 3]);
      }
      if (dc == 0) {
        float* wm = p.ws_ml + (((long)gid * p.S + split) * G + r) * 2;
        wm[0] = M;
        wm[1] = ltot;
      }
    }
  }

  if constexpr (!DIRECT) {
    __threadfence();  // every thread orders its own partial writes before the ticket
    __syncthreads();
    __shared__ unsigned s_ticket;
    if (tid == 0) {
      s_ticket = atomicAdd(&p.counters[gid], 1u);
    }
    __syncthreads();
    if (s_ticket != (unsigned)(p.S - 1)) return;
    if (tid == 0) p.counters[gid] = 0u;
    asm volatile("fence.acquire.gpu;" ::: "memory");

    // final log-sum-exp merge across splits
    for (int item = tid; item < G * (D / 8); item += 128) {
      int r = item / (D / 8);
      int dc = (item % (D / 8)) * 8;
      float M = NEG_BIG;
      for (int si = 0; si < p.S; ++si) {
        const float* wm = p.ws_ml + (((long)gid * p.S + si) * G + r) * 2;
        M = fmaxf(M, wm[0]);
      }
      float ltot = 0.f;
      float acc[8];
#pragma unroll
      for (int e = 0; e < 8; ++e) acc[e] = 0.f;
      for (int si = 0; si < p.S; ++si) {
        const float* wm = p.ws_ml + (((long)gid * p.S + si) * G + r) * 2;
        float aw = exp2f(wm[0] - M);
        ltot += wm[1] * aw;
        const float* wa = p.ws_acc + (((long)gid * p.S + si) * G + r) * D + dc;
#pragma unroll
        for (int e = 0; e < 8; ++e) acc[e] += aw * wa[e];
      }
      int h = kvh * G + r;
      float inv = ltot > 0.f ? 1.f / ltot : 0.f;
      __nv_bfloat162 outv[4];
#pragma unroll
      for (int e = 0; e < 4; ++e) {
        outv[e] = __floats2bfloat162_rn(acc[2 * e] * inv, acc[2 * e + 1] * inv);
      }
      *reinterpret_cast<uint4*>(p.out + ((long)b * p.H + h) * D + dc) =
          *reinterpret_cast<uint4*>(outv);
    }
  }
}

// ---------------- host ----------------

static inline int cdiv2(int a, int b) { return (a + b - 1) / b; }

torch::Tensor paged_decode(torch::Tensor q, torch::Tensor kv, torch::Tensor bt,
                           torch::Tensor seq) {
  const int B = q.size(0);
  const int H = q.size(1);
  const int D = q.size(2);
  const int Hkv = kv.size(2);
  const int P = kv.size(1);
  TORCH_CHECK(P == 16, "page size must be 16");
  const int G = H / Hkv;
  const int max_blocks = bt.size(1);
  const int groups = B * Hkv;

  auto out = at::empty({B, H, D}, q.options());
  if (B == 0 || H == 0) return out;
  TORCH_CHECK(G <= 8, "G>8 unsupported");
  TORCH_CHECK(D == 128 || D == 64, "D must be 64/128");

  // --- tuned (nstages, splits) per shape, heuristic fallback ---
  int nst = 3;
  int S = 1;
  auto setcfg = [&](int n, int s) { nst = n; S = s; };
  if (D == 128) {
    if (groups >= 96) setcfg(3, 1);
    else if (groups >= 48) setcfg(4, 1);
    else setcfg(3, std::max(2, std::min(96 / groups, max_blocks / 24)));
  } else {
    setcfg(4, groups >= 48 ? 1 : std::max(2, std::min(128 / groups, max_blocks / 24)));
  }
  S = std::min(std::max(S, 1), 32);
  S = std::min(S, max_blocks);
  const int pps = cdiv2(max_blocks, S);

  float* ws_acc_ptr = nullptr;
  float* ws_ml_ptr = nullptr;
  torch::Tensor ws;
  static torch::Tensor counters;
  if (S > 1) {
    long acc_elems = (long)groups * S * G * D;
    long ml_elems = (long)groups * S * G * 2;
    ws = at::empty({acc_elems + ml_elems}, q.options().dtype(at::kFloat));
    ws_acc_ptr = ws.data_ptr<float>();
    ws_ml_ptr = ws_acc_ptr + acc_elems;
    if (!counters.defined() || counters.numel() < groups) {
      counters = at::zeros({std::max(groups, 4096)}, q.options().dtype(at::kInt));
    }
  }

  MmaParams p;
  p.q = reinterpret_cast<const __nv_bfloat16*>(q.data_ptr());
  p.kv = reinterpret_cast<const __nv_bfloat16*>(kv.data_ptr());
  p.bt = bt.data_ptr<int>();
  p.seq = seq.data_ptr<int>();
  p.out = reinterpret_cast<__nv_bfloat16*>(out.data_ptr());
  p.ws_acc = ws_acc_ptr;
  p.ws_ml = ws_ml_ptr;
  p.counters = S > 1 ? reinterpret_cast<unsigned*>(counters.data_ptr<int>()) : nullptr;
  p.H = H;
  p.Hkv = Hkv;
  p.G = G;
  p.S = S;
  p.bt_stride = max_blocks;
  p.page_stride_elems = 16 * Hkv * 2 * D;
  p.tok_stride_elems = Hkv * 2 * D;
  p.pages_per_split = pps;
  p.scale_log2e = (float)(1.0 / sqrt((double)D) * 1.4426950408889634);

  dim3 grid(S, groups);
  auto stream = at::cuda::getCurrentCUDAStream();
  const int T = 32;
  int smem_stage = nst * T * (2 * D + 8) * 2;
  int smem_merge = 4 * 16 * (D + 2) * 4;
  int sm = std::max(smem_stage, smem_merge) + pps * 4;

  // per-instantiation smem-attribute flags: bit i = (D64:4|D128:0) + nst(3:0|4:1)*2 + direct(0|1)
  static int attr_done = 0;
  auto launch = [&](auto kern, int bit) {
    if (sm > 48 * 1024 && !(attr_done & (1 << bit))) {
      cudaFuncSetAttribute(kern, cudaFuncAttributeMaxDynamicSharedMemorySize, 227 * 1024 - 1024);
      attr_done |= (1 << bit);
    }
    kern<<<grid, dim3(128), sm, stream>>>(p);
  };

  if (D == 128) {
    if (nst == 4) {
      if (S == 1) launch(dec_mma<128, 4, true>, 3);
      else launch(dec_mma<128, 4, false>, 2);
    } else {
      if (S == 1) launch(dec_mma<128, 3, true>, 1);
      else launch(dec_mma<128, 3, false>, 0);
    }
  } else {
    if (S == 1) launch(dec_mma<64, 4, true>, 7);
    else launch(dec_mma<64, 4, false>, 6);
  }
  cudaError_t err = cudaGetLastError();
  TORCH_CHECK(err == cudaSuccess, "launch failed: ", cudaGetErrorString(err));
  return out;
}

// -------- lean python binding (METH_FASTCALL) --------
#include <torch/csrc/autograd/python_variable.h>
static PyObject* fast_decode(PyObject*, PyObject* const* args, Py_ssize_t nargs) {
  try {
    const at::Tensor& q = THPVariable_Unpack(args[0]);
    const at::Tensor& kv = THPVariable_Unpack(args[1]);
    const at::Tensor& bt = THPVariable_Unpack(args[2]);
    const at::Tensor& seq = THPVariable_Unpack(args[3]);
    at::Tensor out = paged_decode(q, kv, bt, seq);
    return THPVariable_Wrap(std::move(out));
  } catch (const std::exception& e) {
    PyErr_SetString(PyExc_RuntimeError, e.what());
    return nullptr;
  }
}
static PyMethodDef fast_methods[] = {
    {"decode", (PyCFunction)(void*)fast_decode, METH_FASTCALL, nullptr},
    {nullptr, nullptr, 0, nullptr}};
void register_fast(pybind11::module_& m) { PyModule_AddFunctions(m.ptr(), fast_methods); }
"""

_CPP_SRC = r"""
torch::Tensor paged_decode(torch::Tensor q, torch::Tensor kv, torch::Tensor bt, torch::Tensor seq);
void register_fast(pybind11::module_& m);
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
  m.def("paged_decode", &paged_decode);
  register_fast(m);
}
"""

_ext = None


def _get_ext():
    global _ext
    if _ext is None:
        os.environ["TORCH_CUDA_ARCH_LIST"] = "9.0a"
        from torch.utils.cpp_extension import load_inline

        _ext = load_inline(
            name="paged_attention_decode_ext",
            cpp_sources=[_CPP_SRC],
            cuda_sources=[_CUDA_SRC],
            functions=None,
            extra_cuda_cflags=["-O3", "--use_fast_math", "-std=c++17"],
            verbose=False,
        )
    return _ext


class Model(nn.Module):
    """Single-query paged attention decode (drop-in for reference.Model)."""

    def __init__(self, batch, num_heads, num_kv_heads, head_dim, seq_len, page_size):
        super().__init__()
        assert num_heads % num_kv_heads == 0, "num_heads must be a multiple of num_kv_heads (GQA)"
        self.batch = batch
        self.num_heads = num_heads
        self.num_kv_heads = num_kv_heads
        self.head_dim = head_dim
        self.seq_len = seq_len
        self.page_size = page_size
        self.group_size = num_heads // num_kv_heads
        self.scale = 1.0 / math.sqrt(head_dim)
        self.register_buffer("_dummy", torch.zeros(1, dtype=torch.bfloat16), persistent=False)
        self._fn = _get_ext().decode

    def forward(self, query, kv_cache, block_table, seq_lens):
        return self._fn(query, kv_cache, block_table, seq_lens)


def get_inputs():
    B, H, Hkv, D, L, P = BATCH, NUM_HEADS, NUM_KV_HEADS, HEAD_DIM, SEQ_LEN, PAGE_SIZE

    pages_per_seq = (L + P - 1) // P
    total_pages = max(B * pages_per_seq + 8, 64)

    query = torch.randn(B, H, D, dtype=torch.bfloat16) * 0.1
    kv_cache = torch.randn(total_pages, P, Hkv, 2 * D, dtype=torch.bfloat16) * 0.1

    perm = torch.randperm(total_pages)[: B * pages_per_seq].reshape(B, pages_per_seq).int()
    block_table = perm.contiguous()
    seq_lens = torch.full((B,), L, dtype=torch.int32)

    return [query, kv_cache, block_table, seq_lens]


def get_init_inputs():
    return [BATCH, NUM_HEADS, NUM_KV_HEADS, HEAD_DIM, SEQ_LEN, PAGE_SIZE]

20260716_091532_kinetic-claude_kinetic-0715_03_paged_attention