KernelBench hard · RTX PRO 6000

Paged Attention GPT-5.6 Sol

56.5%geomean peak fraction across shapes

manually audited: clean

Genuine one-launch CUDA paged-attention decode. Each producer block owns one (batch, KV-head, sequence-split), uses four or eight query-head warps to share live paged K/V loads, performs fp32 online softmax and value accumulation, then publishes fp32 split state. The last arriving split for each (batch, KV-head) merges all splits and writes the live bf16 output. The final solution contains no CUDA graph replay, output/input cache, distribution detector, fabricated result, grader mutation, or forbidden vLLM, FlashInfer, or SDPA call. Correctness passed every canonical shape and seed under nominal, 0.01x-small, and 8x-large query/KV regimes. The canonical 0.5655 is supported by the archived solution-only benchmark and independently recomputes from its five reported fractions.

harnesscodexagent session25mtotal wall26mcheck73sbenchmark7soutput tokens38,793gpu-lock wait28sgpu-lock held52sregimememory

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

8×32×8×128×1024×160.034 ms54.6%0.98 TB/s · 55% of 1.8 TB/s HBM · also 4 TFLOPS (1% of compute)
32×32×8×128×2048×160.190 ms78.5%1.41 TB/s · 78% of 1.8 TB/s HBM · also 6 TFLOPS (1% of compute)
4×64×8×128×4096×160.074 ms50.5%0.91 TB/s · 51% of 1.8 TB/s HBM · also 7 TFLOPS (1% of compute)
16×32×8×128×1535×160.079 ms71.1%1.28 TB/s · 71% of 1.8 TB/s HBM · also 5 TFLOPS (1% of compute)
8×16×4×64×2000×160.024 ms37.6%0.68 TB/s · 38% of 1.8 TB/s HBM · also 3 TFLOPS (1% of compute)

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

geomean(54.6% · 78.5% · 50.5% · 71.1% · 37.6%) = 56.5%

Kernel source (redacted)
"""CUDA paged-attention decode specialized for the benchmark's GQA shapes.

The first kernel assigns one CUDA block to a (batch, KV-head, sequence-split)
triple.  Its four or eight warps are the query heads which share that KV head,
so each packed K/V page is fetched from DRAM only once.  Every warp maintains
an online-softmax numerator for its query head.  A small second kernel merges
the sequence splits and writes bf16 output.
"""

from __future__ import annotations

import math
import os

import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline


os.environ.setdefault("MAX_JOBS", "4")


_CPP = r"""
#include <torch/extension.h>

void paged_attention_cuda(
    torch::Tensor query,
    torch::Tensor kv_cache,
    torch::Tensor block_table,
    torch::Tensor seq_lens,
    torch::Tensor partial,
    torch::Tensor stats,
    torch::Tensor counters,
    torch::Tensor output,
    int64_t num_kv_heads,
    int64_t max_seq_len,
    int64_t page_size,
    int64_t num_splits);

PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
  m.def("run", &paged_attention_cuda, "shared-KV paged attention (CUDA)");
}
"""


_CUDA = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <c10/cuda/CUDAException.h>
#include <cuda.h>
#include <cuda_bf16.h>
#include <cuda_runtime.h>
#include <math.h>
#include <stdint.h>

__device__ __forceinline__ void copy16_async(void* shared_dst, const void* global_src) {
  const uint32_t shared_addr = static_cast<uint32_t>(__cvta_generic_to_shared(shared_dst));
  asm volatile("cp.async.cg.shared.global [%0], [%1], 16;\n" ::
               "r"(shared_addr), "l"(global_src));
}

__device__ __forceinline__ void async_commit() {
  asm volatile("cp.async.commit_group;\n" ::);
}

__device__ __forceinline__ void async_wait_all() {
  asm volatile("cp.async.wait_group 0;\n" ::);
}

template<int HEAD_DIM, int GROUP_SIZE, int PAGE_SIZE>
__global__ void paged_split_kernel(
    const __nv_bfloat16* __restrict__ query,
    const __nv_bfloat16* __restrict__ kv_cache,
    const int* __restrict__ block_table,
    const int* __restrict__ seq_lens,
    float* __restrict__ partial,
    float* __restrict__ stats,
    int* __restrict__ counters,
    __nv_bfloat16* __restrict__ output,
    int batch,
    int num_heads,
    int num_kv_heads,
    int max_pages,
    int num_splits,
    float scale) {

  // One warp owns one Q head. All GROUP_SIZE warps reuse this shared KV tile.
  extern __shared__ __align__(16) unsigned char shared_raw[];
  __nv_bfloat16* tile = reinterpret_cast<__nv_bfloat16*>(shared_raw);

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

  int z = blockIdx.x;
  const int split = z % num_splits;
  z /= num_splits;
  const int kv_head = z % num_kv_heads;
  const int b = z / num_kv_heads;
  const int q_head = kv_head * GROUP_SIZE + warp;

  constexpr int PAIRS_PER_LANE = HEAD_DIM / 64;
  float q0[PAIRS_PER_LANE];
  float q1[PAIRS_PER_LANE];
  float acc0[PAIRS_PER_LANE];
  float acc1[PAIRS_PER_LANE];

  const __nv_bfloat162* q2 = reinterpret_cast<const __nv_bfloat162*>(
      query + (static_cast<int64_t>(b) * num_heads + q_head) * HEAD_DIM);
#pragma unroll
  for (int j = 0; j < PAIRS_PER_LANE; ++j) {
    const int pair_idx = lane + 32 * j;
    const float2 qf = __bfloat1622float2(q2[pair_idx]);
    q0[j] = qf.x;
    q1[j] = qf.y;
    acc0[j] = 0.0f;
    acc1[j] = 0.0f;
  }

  float running_max = -INFINITY;
  float running_sum = 0.0f;
  const int pages_per_split = (max_pages + num_splits - 1) / num_splits;
  const int first_page = split * pages_per_split;
  const int last_page = min(max_pages, first_page + pages_per_split);
  const int seq_len = seq_lens[b];
  const int table_stride = max_pages;

  // Double-buffer half-pages. The asynchronous load of the next eight tokens
  // overlaps all QK, softmax, and PV work on the current eight, while the two
  // buffers together use no more shared memory than the old full-page tile.
  constexpr int TILE_TOKENS = PAGE_SIZE / 2;
  constexpr int VECS_PER_TOKEN = (2 * HEAD_DIM) / 8;
  constexpr int VECS_PER_TILE = TILE_TOKENS * VECS_PER_TOKEN;
  constexpr int SCALARS_PER_TILE = TILE_TOKENS * 2 * HEAD_DIM;
  uint4* shared_vec = reinterpret_cast<uint4*>(tile);

  const int num_tiles = (last_page - first_page) * 2;
  if (num_tiles > 0) {
    // Prime buffer zero.
    const int physical_page = block_table[b * table_stride + first_page];
    for (int vi = tid; vi < VECS_PER_TILE; vi += GROUP_SIZE * 32) {
      const int token_in_tile = vi / VECS_PER_TOKEN;
      const int vec_in_token = vi - token_in_tile * VECS_PER_TOKEN;
      const int64_t scalar_offset =
          (((static_cast<int64_t>(physical_page) * PAGE_SIZE + token_in_tile)
             * num_kv_heads + kv_head) * (2 * HEAD_DIM))
          + vec_in_token * 8;
      copy16_async(shared_vec + vi, kv_cache + scalar_offset);
    }
    async_commit();
    async_wait_all();
    __syncthreads();

    for (int tile_idx = 0; tile_idx < num_tiles; ++tile_idx) {
      const int buffer = tile_idx & 1;
      const int logical_page = first_page + (tile_idx >> 1);
      const int half_page = tile_idx & 1;

      // Launch the other buffer before doing any arithmetic on this one.
      if (tile_idx + 1 < num_tiles) {
        const int next_tile = tile_idx + 1;
        const int next_page = first_page + (next_tile >> 1);
        const int next_half = next_tile & 1;
        const int next_physical = block_table[b * table_stride + next_page];
        for (int vi = tid; vi < VECS_PER_TILE; vi += GROUP_SIZE * 32) {
          const int token_in_tile = vi / VECS_PER_TOKEN;
          const int vec_in_token = vi - token_in_tile * VECS_PER_TOKEN;
          const int token_in_page = next_half * TILE_TOKENS + token_in_tile;
          const int64_t scalar_offset =
              (((static_cast<int64_t>(next_physical) * PAGE_SIZE + token_in_page)
                 * num_kv_heads + kv_head) * (2 * HEAD_DIM))
              + vec_in_token * 8;
          copy16_async(shared_vec + (1 - buffer) * VECS_PER_TILE + vi,
                       kv_cache + scalar_offset);
        }
        async_commit();
      }

      const __nv_bfloat162* tile2 = reinterpret_cast<const __nv_bfloat162*>(
          tile + buffer * SCALARS_PER_TILE);

      // Compute all eight scores first. Lane t retains token t's score; this
      // costs one scalar register per lane and lets us rescale the accumulator
      // once per tile instead of once per token.
      float lane_score = -INFINITY;
#pragma unroll
      for (int token_in_tile = 0; token_in_tile < TILE_TOKENS; ++token_in_tile) {
        const int token =
            logical_page * PAGE_SIZE + half_page * TILE_TOKENS + token_in_tile;
        if (token < seq_len) {
          float dot = 0.0f;
#pragma unroll
          for (int j = 0; j < PAIRS_PER_LANE; ++j) {
            const int pair_idx = lane + 32 * j;
            const float2 kf = __bfloat1622float2(
                tile2[token_in_tile * HEAD_DIM + pair_idx]);
            dot = fmaf(q0[j], kf.x, dot);
            dot = fmaf(q1[j], kf.y, dot);
          }
#pragma unroll
          for (int delta = 16; delta > 0; delta >>= 1) {
            dot += __shfl_down_sync(0xffffffffu, dot, delta);
          }
          const float score = __shfl_sync(0xffffffffu, dot, 0) * scale;
          if (lane == token_in_tile) lane_score = score;
        }
      }

      float tile_max = lane_score;
#pragma unroll
      for (int delta = 16; delta > 0; delta >>= 1) {
        tile_max = fmaxf(tile_max,
                         __shfl_down_sync(0xffffffffu, tile_max, delta));
      }
      float old_scale = 0.0f;
      if (lane == 0) {
        const float next_max = fmaxf(running_max, tile_max);
        old_scale = __expf(running_max - next_max);
        running_sum *= old_scale;
        running_max = next_max;
      }
      old_scale = __shfl_sync(0xffffffffu, old_scale, 0);
      const float tile_running_max =
          __shfl_sync(0xffffffffu, running_max, 0);
#pragma unroll
      for (int j = 0; j < PAIRS_PER_LANE; ++j) {
        acc0[j] *= old_scale;
        acc1[j] *= old_scale;
      }

      // Accumulate the tile's V vectors with weights relative to tile max.
#pragma unroll
      for (int token_in_tile = 0; token_in_tile < TILE_TOKENS; ++token_in_tile) {
        const int token =
            logical_page * PAGE_SIZE + half_page * TILE_TOKENS + token_in_tile;
        if (token < seq_len) {
          float token_scale = 0.0f;
          if (lane == token_in_tile) {
            token_scale = __expf(lane_score - tile_running_max);
          }
          token_scale = __shfl_sync(0xffffffffu, token_scale, token_in_tile);
          if (lane == 0) running_sum += token_scale;
#pragma unroll
          for (int j = 0; j < PAIRS_PER_LANE; ++j) {
            const int pair_idx = lane + 32 * j;
            const float2 vf = __bfloat1622float2(
                tile2[token_in_tile * HEAD_DIM + HEAD_DIM / 2 + pair_idx]);
            acc0[j] = fmaf(token_scale, vf.x, acc0[j]);
            acc1[j] = fmaf(token_scale, vf.y, acc1[j]);
          }
        }
      }
      if (tile_idx + 1 < num_tiles) {
        async_wait_all();
        __syncthreads();
      }
    }
  }

  const int64_t partial_base =
      ((static_cast<int64_t>(b) * num_heads + q_head) * num_splits + split)
      * HEAD_DIM;
#pragma unroll
  for (int j = 0; j < PAIRS_PER_LANE; ++j) {
    const int pair_idx = lane + 32 * j;
    reinterpret_cast<float2*>(partial + partial_base)[pair_idx] =
        make_float2(acc0[j], acc1[j]);
  }
  if (lane == 0) {
    const int64_t si =
        ((static_cast<int64_t>(b) * num_heads + q_head) * num_splits + split) * 2;
    stats[si] = running_max;
    stats[si + 1] = running_sum;
  }

  // The last split to publish for this (batch, KV-head) performs the reduction
  // for all of its GQA warps. This replaces a second kernel launch. Every
  // producer thread fences its own partial writes before the ticket is taken.
  __syncthreads();
  __threadfence();
  __syncthreads();
  int* last_flag = reinterpret_cast<int*>(tile);
  if (tid == 0) {
    const int counter_idx = b * num_kv_heads + kv_head;
    const int ticket = atomicAdd(counters + counter_idx, 1);
    if (ticket == num_splits - 1) {
      atomicExch(counters + counter_idx, 0);
      *last_flag = 1;
    } else {
      *last_flag = 0;
    }
  }
  __syncthreads();

  const bool do_merge = (*last_flag != 0);
  __syncthreads();
  if (do_merge) {
    float* weights = reinterpret_cast<float*>(tile) + warp * num_splits;
    if (lane == 0) {
      float global_max = -INFINITY;
      for (int s = 0; s < num_splits; ++s) {
        const float m = stats[
            ((static_cast<int64_t>(b) * num_heads + q_head) * num_splits + s) * 2];
        global_max = fmaxf(global_max, m);
      }
      float denom = 0.0f;
      for (int s = 0; s < num_splits; ++s) {
        const int64_t si =
            ((static_cast<int64_t>(b) * num_heads + q_head) * num_splits + s) * 2;
        const float local_sum = stats[si + 1];
        const float w = local_sum == 0.0f ? 0.0f : __expf(stats[si] - global_max);
        weights[s] = w;
        denom = fmaf(local_sum, w, denom);
      }
      const float inv_denom = 1.0f / denom;
      for (int s = 0; s < num_splits; ++s) weights[s] *= inv_denom;
    }
    __syncwarp();

    float out0[PAIRS_PER_LANE] = {0.0f};
    float out1[PAIRS_PER_LANE] = {0.0f};
    for (int s = 0; s < num_splits; ++s) {
      const float w = weights[s];
      const int64_t base =
          ((static_cast<int64_t>(b) * num_heads + q_head) * num_splits + s)
          * HEAD_DIM;
#pragma unroll
      for (int j = 0; j < PAIRS_PER_LANE; ++j) {
        const int pair_idx = lane + 32 * j;
        const float2 x = reinterpret_cast<const float2*>(partial + base)[pair_idx];
        out0[j] = fmaf(w, x.x, out0[j]);
        out1[j] = fmaf(w, x.y, out1[j]);
      }
    }
    __nv_bfloat162* out2 = reinterpret_cast<__nv_bfloat162*>(
        output + (static_cast<int64_t>(b) * num_heads + q_head) * HEAD_DIM);
#pragma unroll
    for (int j = 0; j < PAIRS_PER_LANE; ++j) {
      const int pair_idx = lane + 32 * j;
      out2[pair_idx] = __float22bfloat162_rn(make_float2(out0[j], out1[j]));
    }
  }
}


template<int HEAD_DIM, int WARPS_PER_BLOCK=4>
__global__ void merge_splits_kernel(
    const float* __restrict__ partial,
    const float* __restrict__ stats,
    __nv_bfloat16* __restrict__ output,
    int total_heads,
    int num_splits) {
  const int warp = threadIdx.x >> 5;
  const int lane = threadIdx.x & 31;
  const int h = blockIdx.x * WARPS_PER_BLOCK + warp;
  if (h >= total_heads) return;

  __shared__ float weights[WARPS_PER_BLOCK][64];
  if (lane == 0) {
    float global_max = -INFINITY;
    for (int s = 0; s < num_splits; ++s) {
      const float m = stats[(static_cast<int64_t>(h) * num_splits + s) * 2];
      global_max = fmaxf(global_max, m);
    }
    float denom = 0.0f;
    for (int s = 0; s < num_splits; ++s) {
      const int64_t si = (static_cast<int64_t>(h) * num_splits + s) * 2;
      const float local_sum = stats[si + 1];
      const float w = local_sum == 0.0f ? 0.0f : __expf(stats[si] - global_max);
      weights[warp][s] = w;
      denom = fmaf(local_sum, w, denom);
    }
    const float inv_denom = 1.0f / denom;
    for (int s = 0; s < num_splits; ++s) weights[warp][s] *= inv_denom;
  }
  __syncwarp();

  constexpr int PAIRS_PER_LANE = HEAD_DIM / 64;
  float out0[PAIRS_PER_LANE] = {0.0f};
  float out1[PAIRS_PER_LANE] = {0.0f};
  for (int s = 0; s < num_splits; ++s) {
    const float w = weights[warp][s];
    const int64_t base = (static_cast<int64_t>(h) * num_splits + s) * HEAD_DIM;
#pragma unroll
    for (int j = 0; j < PAIRS_PER_LANE; ++j) {
      const int pair_idx = lane + 32 * j;
      const float2 x = reinterpret_cast<const float2*>(partial + base)[pair_idx];
      out0[j] = fmaf(w, x.x, out0[j]);
      out1[j] = fmaf(w, x.y, out1[j]);
    }
  }
  __nv_bfloat162* out2 = reinterpret_cast<__nv_bfloat162*>(
      output + static_cast<int64_t>(h) * HEAD_DIM);
#pragma unroll
  for (int j = 0; j < PAIRS_PER_LANE; ++j) {
    const int pair_idx = lane + 32 * j;
    out2[pair_idx] = __float22bfloat162_rn(make_float2(out0[j], out1[j]));
  }
}


template<int HEAD_DIM, int GROUP_SIZE>
void launch_typed(
    const __nv_bfloat16* query,
    const __nv_bfloat16* kv_cache,
    const int* block_table,
    const int* seq_lens,
    float* partial,
    float* stats,
    int* counters,
    __nv_bfloat16* output,
    int batch,
    int num_heads,
    int num_kv_heads,
    int max_seq_len,
    int num_splits,
    cudaStream_t stream) {
  constexpr int PAGE_SIZE = 16;
  const int max_pages = (max_seq_len + PAGE_SIZE - 1) / PAGE_SIZE;
  const int blocks = batch * num_kv_heads * num_splits;
  const int threads = GROUP_SIZE * 32;
  const size_t smem = PAGE_SIZE * 2 * HEAD_DIM * sizeof(__nv_bfloat16);
  paged_split_kernel<HEAD_DIM, GROUP_SIZE, PAGE_SIZE>
      <<<blocks, threads, smem, stream>>>(
          query, kv_cache, block_table, seq_lens, partial, stats, counters, output,
          batch, num_heads, num_kv_heads, max_pages, num_splits,
          rsqrtf(static_cast<float>(HEAD_DIM)));
}


void paged_attention_cuda(
    torch::Tensor query,
    torch::Tensor kv_cache,
    torch::Tensor block_table,
    torch::Tensor seq_lens,
    torch::Tensor partial,
    torch::Tensor stats,
    torch::Tensor counters,
    torch::Tensor output,
    int64_t num_kv_heads,
    int64_t max_seq_len,
    int64_t page_size,
    int64_t num_splits) {
  TORCH_CHECK(query.is_cuda() && kv_cache.is_cuda(), "inputs must be CUDA tensors");
  TORCH_CHECK(query.scalar_type() == at::kBFloat16, "query must be bf16");
  TORCH_CHECK(kv_cache.scalar_type() == at::kBFloat16, "KV cache must be bf16");
  TORCH_CHECK(block_table.scalar_type() == at::kInt, "block table must be int32");
  TORCH_CHECK(seq_lens.scalar_type() == at::kInt, "sequence lengths must be int32");
  TORCH_CHECK(page_size == 16, "this kernel is specialized for page_size=16");

  const int batch = query.size(0);
  const int num_heads = query.size(1);
  const int head_dim = query.size(2);
  const int group_size = num_heads / static_cast<int>(num_kv_heads);
  const c10::cuda::CUDAGuard device_guard(query.device());
  cudaStream_t stream = at::cuda::getCurrentCUDAStream();

  const auto* q = reinterpret_cast<const __nv_bfloat16*>(query.data_ptr<at::BFloat16>());
  const auto* kv = reinterpret_cast<const __nv_bfloat16*>(kv_cache.data_ptr<at::BFloat16>());
  const int* bt = block_table.data_ptr<int>();
  const int* sl = seq_lens.data_ptr<int>();
  float* p = partial.data_ptr<float>();
  float* st = stats.data_ptr<float>();
  int* ctr = counters.data_ptr<int>();
  auto* out = reinterpret_cast<__nv_bfloat16*>(output.data_ptr<at::BFloat16>());

  if (head_dim == 128 && group_size == 4) {
    launch_typed<128, 4>(q, kv, bt, sl, p, st, ctr, out, batch, num_heads,
                         num_kv_heads, max_seq_len, num_splits, stream);
  } else if (head_dim == 128 && group_size == 8) {
    launch_typed<128, 8>(q, kv, bt, sl, p, st, ctr, out, batch, num_heads,
                         num_kv_heads, max_seq_len, num_splits, stream);
  } else if (head_dim == 64 && group_size == 4) {
    launch_typed<64, 4>(q, kv, bt, sl, p, st, ctr, out, batch, num_heads,
                        num_kv_heads, max_seq_len, num_splits, stream);
  } else {
    TORCH_CHECK(false, "unsupported head_dim/group_size combination");
  }
  C10_CUDA_KERNEL_LAUNCH_CHECK();
}
"""


_ext = load_inline(
    name="paged_attention_sm120_sharedkv_v8",
    cpp_sources=_CPP,
    cuda_sources=_CUDA,
    functions=None,
    extra_cflags=["-O3"],
    extra_cuda_cflags=["-O3", "--use_fast_math", "--extra-device-vectorization"],
    with_cuda=True,
    verbose=False,
)


class Model(nn.Module):
    def __init__(
        self,
        batch: int,
        num_heads: int,
        num_kv_heads: int,
        head_dim: int,
        seq_len: int,
        page_size: int,
    ):
        super().__init__()
        assert num_heads % num_kv_heads == 0
        assert page_size == 16
        group_size = num_heads // num_kv_heads
        assert (head_dim, group_size) in ((128, 4), (128, 8), (64, 4))

        self.num_kv_heads = num_kv_heads
        self.seq_len = seq_len
        self.page_size = page_size

        # Cold-cache profiling on the 188-SM target shows that roughly 1K
        # producer blocks are needed to hide page-gather and barrier latency.
        # The B=32 case reaches that point with eight splits; the smaller grids
        # benefit from the full sixteen.
        base_blocks = batch * num_kv_heads
        max_pages = (seq_len + page_size - 1) // page_size
        if head_dim == 64:
            scheduled_splits = 32
        elif base_blocks >= 128:
            scheduled_splits = 8
        else:
            scheduled_splits = 16
        self.num_splits = min(64, max_pages, scheduled_splits)

        self.register_buffer(
            "_partial",
            torch.empty(batch, num_heads, self.num_splits, head_dim, dtype=torch.float32),
            persistent=False,
        )
        self.register_buffer(
            "_stats",
            torch.empty(batch, num_heads, self.num_splits, 2, dtype=torch.float32),
            persistent=False,
        )
        self.register_buffer(
            "_counters",
            torch.zeros(batch, num_kv_heads, dtype=torch.int32),
            persistent=False,
        )
        self.register_buffer(
            "_output",
            torch.empty(batch, num_heads, head_dim, dtype=torch.bfloat16),
            persistent=False,
        )

    def forward(
        self,
        query: torch.Tensor,
        kv_cache: torch.Tensor,
        block_table: torch.Tensor,
        seq_lens: torch.Tensor,
    ) -> torch.Tensor:
        _ext.run(
            query, kv_cache, block_table, seq_lens,
            self._partial, self._stats, self._counters, self._output,
            self.num_kv_heads, self.seq_len, self.page_size, self.num_splits,
        )
        return self._output


# Keep the exact data-generation interface used by reference.py.
BATCH = 8
NUM_HEADS = 32
NUM_KV_HEADS = 8
HEAD_DIM = 128
SEQ_LEN = 1024
PAGE_SIZE = 16


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
    block_table = torch.randperm(total_pages)[: B * pages_per_seq].reshape(B, pages_per_seq).int()
    seq_lens = torch.full((B,), L, dtype=torch.int32)
    return [query, kv_cache, block_table.contiguous(), seq_lens]


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

20260709_174740_codex_gpt-5.6-sol_03_paged_attention