FW-GLM-5-1 · popcorn · pop_l123_default_gpt
correct 3_FusedCrossAttentionRoPEDropout.py
model
FW-GLM-5-1
turns / max
6 / 12
tool calls
6
tokens (total)
79902
wall clock
654.5s
truncated
no
backend
cuda fp32
variant
popcorn
runtime
0.53 μs
ref runtime
0.59 μs
speedup
1.13x
started
2026-05-11T14:16:51Z
timingkernelbench.timing
kernel runtime
0.53 μs
ref runtime
0.59 μs
speedup
1.13x
kernel mean / median / std
0.53 / — / 0.60 μs
ref mean / median / std
0.59 / — / 1.32 μs
numerical precisioneval_kernel_against_ref
max abs error
5.59e-08
mean abs error
7.77e-09
max rel error
0.08
mean rel error
2.70e-06
gpu memoryextended_metrics.measure_memory
kernel peak
39.52 MB
ref peak
39.00 MB
kernel / ref
1.01x
kernel launches / fusiontorch.profiler
kernel launches
95
ref launches
145
fusion ratio (ref / kernel)
1.53x
total cuda time
2335 μs
SOL (speed-of-light)heuristic_from_speedup
SOL score (0–1)
0.113
dram util %
—
compute util %
—
bottleneck
unknown
dominant pipe
—
arithmetic intensity
—
ridge point
—
roofline / occupancyheuristic
dram bandwidth
—
dram util %
—
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
—
bottleneck
—
dominant pipe %
—
arithmetic intensity
—
ridge point
—
L1 hit %
—
L2 hit %
—
ld sectors / req
—
st sectors / req
—
regs / thread
—
smem / block
—
block size
—
peak bw
—
peak fp32 tflops
—
peak fp16 tflops
—
reference PyTorch source (the prompt the agent sees)
import math

import torch
import torch.nn as nn
import torch.nn.functional as F

# Default ModelNew is PyTorch-only. run_and_check: use check_kernel=False with backend=cuda
# until the candidate file contains a real CUDA kernel (see kernel_static_checker).

class Model(nn.Module):
    """
    Single-layer multi-head cross-attention with RoPE on Q and K, attention
    dropout, and output projection—intended as a fusion target for robotics
    stacks (e.g. proprio / action queries attending to vision or memory).

    Pipeline (conceptually one fused kernel): linear Q from query stream,
    linear K/V from key–value stream → reshape heads → apply RoPE separately
    along query positions (T_q) and key positions (T_k) → scaled dot-product,
    softmax, dropout on weights → aggregate values → output projection.
    """

    def __init__(
        self,
        dim: int,
        num_heads: int,
        dropout: float = 0.0,
        rope_base: float = 10000.0,
    ):
        super().__init__()
        assert dim % num_heads == 0
        self.dim = dim
        self.num_heads = num_heads
        self.head_dim = dim // num_heads
        assert self.head_dim % 2 == 0, "RoPE requires an even head dimension"

        self.q_proj = nn.Linear(dim, dim, bias=False)
        self.k_proj = nn.Linear(dim, dim, bias=False)
        self.v_proj = nn.Linear(dim, dim, bias=False)
        self.out_proj = nn.Linear(dim, dim, bias=False)
        self.dropout = nn.Dropout(dropout)
        self.scale = 1.0 / math.sqrt(self.head_dim)

        inv_freq = 1.0 / (
            rope_base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)
        )
        self.register_buffer("inv_freq", inv_freq, persistent=False)

    @staticmethod
    def _rotate_half(x: torch.Tensor) -> torch.Tensor:
        x1, x2 = x.chunk(2, dim=-1)
        return torch.cat([-x2, x1], dim=-1)

    def _rope(self, x: torch.Tensor, seq_len: int) -> torch.Tensor:
        """Apply RoPE along the sequence dimension (last-but-one after head). x: (B, H, L, d)."""
        t = torch.arange(seq_len, device=x.device, dtype=self.inv_freq.dtype)
        freqs = torch.outer(t, self.inv_freq)
        emb = torch.cat([freqs, freqs], dim=-1)
        cos = emb.cos().view(1, 1, seq_len, self.head_dim)
        sin = emb.sin().view(1, 1, seq_len, self.head_dim)
        return x * cos + self._rotate_half(x) * sin

    def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:
        """
        Args:
            x_query: (B, T_q, dim) — e.g. robot or decoder tokens
            x_kv:    (B, T_k, dim) — e.g. encoder / memory tokens
        Returns:
            (B, T_q, dim)
        """
        B, T_q, _ = x_query.shape
        _, T_k, _ = x_kv.shape
        h, d = self.num_heads, self.head_dim

        q = self.q_proj(x_query).view(B, T_q, h, d).transpose(1, 2)
        k = self.k_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)
        v = self.v_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)

        q = self._rope(q, T_q)
        k = self._rope(k, T_k)

        attn = torch.matmul(q, k.transpose(-2, -1)) * self.scale
        attn = F.softmax(attn, dim=-1)
        attn = self.dropout(attn)
        out = torch.matmul(attn, v)
        out = out.transpose(1, 2).contiguous().view(B, T_q, self.dim)
        return self.out_proj(out)


class ModelNew(nn.Module):
    """
    KernelBench candidate entry point (`eval_kernel_against_ref` / `run_and_check`).
    Replace this class with a fused CUDA/Triton (etc.) implementation; the default
    delegates to `Model` so you can smoke-test the harness with the same file for
    both `ref_arch_src_path` and `kernel_src_path`.
    """

    def __init__(
        self,
        dim: int,
        num_heads: int,
        dropout: float = 0.0,
        rope_base: float = 10000.0,
    ):
        super().__init__()
        self._impl = Model(dim, num_heads, dropout, rope_base)

    def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:
        return self._impl(x_query, x_kv)


batch_size = 4
seq_len_q = 64
seq_len_kv = 128
dim = 256
num_heads = 8
# Use 0.0 so `run_and_check` / multi-trial correctness matches: the harness does not
# re-seed RNG between the reference forward and `ModelNew` forward, so attention
# dropout > 0 would make outputs differ even for identical math.
dropout_p = 0.0


def get_inputs():
    p = popcorn_pri
    bs = p.jitter_int(batch_size)
    tq = p.jitter_int(seq_len_q)
    tk = p.jitter_int(seq_len_kv)
    x_query = torch.randn(bs, tq, dim)
    x_kv = torch.randn(bs, tk, dim)
    return [x_query, x_kv]


def get_init_inputs():
    return [dim, num_heads, dropout_p]
turn 017.8s turn · 3.6s LLM · 14.2s tools · tokens in=2,486 · out=287 · total=2,773
reasoning
Let me analyze this task. I need to implement a custom CUDA kernel that replaces a multi-head cross-attention with RoPE (Rotary Position Embedding) operation. The reference implementation does: 1. Linear projections for Q, K, V 2. Reshape to multi-head format 3. Apply RoPE to Q and K 4. Scaled dot-product attention 5. Softmax 6. Dropout (but in this case it's 0.0) 7. Aggregate values 8. Output projection Let me first get the GPU specs to understand what hardware I'm targeting, then write a fused CUDA kernel. The key operations to fuse: 1. Q projection + reshape + RoPE 2. K projection + reshape + RoPE 3. V projection + reshape 4. Scaled attention (Q @ K.T) 5. Softmax 6. Attention @ V 7. Output projection This is quite complex. Let me start by understanding the dimensions: - batch_size = 4 - seq_len_q = 64 - seq_len_kv = 128 - dim = 256 - num_heads = 8 - head_dim = 32 Let me write a CUDA kernel that fuses these operations. I'll need to be careful about memory access patterns and ensure correctness. First, let me get the GPU specs.
call → get_gpu_specs
callget_gpu_specs {}
result ← get_gpu_specs (ok)
okget_gpu_specs GPU specs for NVIDIA H100 80GB HBM3: total memory (runtime): 79.1 GB spec entry: H100 GPU Architecture: Hopper GPU Memory: 80GB Memory Bandwidth: 3.35 TB/s FP64 TFLOPS: 34 FP64 Tensor Core TFLOPS: 67 FP32 TFLOPS: 67 TF32 Tensor Core TFLOPS: 989 with sparsity BFLOAT16 Tensore Core TFLOPS: 1979 with sparsity FP16 Tensor Core TFLOPS: 1979 with sparsity FP8 Tensor Core TFLOPS: 3958 with sparsity INT8 Tensor Core TOPS: 3958 with sparsity Register File Size: 64K 32-bit registers per SM Maximum number of registers per thread: 255 Maximum number of thread blocks per SM: 32 Shared memory capacity per SM: 228 KB Maximum shared memory per thread block: 227 KB
turn 1 · 1 tool call(s) · 70.5s turn · 39.7s LLM · tokens in=2,734 · out=4,460 · total=7,194 · 22,686 chars (click to expand)
turn 170.5s turn · 39.7s LLM · 30.8s tools · tokens in=2,734 · out=4,460 · total=7,194
reasoning
Let me analyze the problem. I need to implement a fused CUDA kernel for: 1. Linear projections (Q from query, K/V from key-value streams) 2. Reshape to heads 3. Apply RoPE to Q and K separately 4. Scaled dot-product attention 5. Softmax 6. Dropout (but it's 0.0 in the test) 7. Aggregate values 8. Output projection The key operations that could benefit from fusion: - Linear projections (matrix multiplications) - RoPE application - Attention computation - Output projection For the H100 with its high tensor core throughput, I need to make sure I'm using tensor cores efficiently. Let me start with a kernel that fuses the key operations. I'll start by writing a kernel that handles: 1. All projections using cuBLAS (which is already optimized) 2. RoPE fusion 3. Attention fusion Actually, let me think about this more carefully. The forward pass consists of: 1. `q_proj(x_query)` - matmul (B, T_q, dim) x (dim, dim) -> (B, T_q, dim) 2. `k_proj(x_kv)` - matmul (B, T_k, dim) x (dim, dim) -> (B, T_k, dim) 3. `v_proj(x_kv)` - matmul (B, T_k, dim) x (dim, dim) -> (B, T_k, dim) 4. Reshape and transpose for Q, K, V 5. RoPE on Q and K 6. Attention: matmul(Q, K^T) * scale -> (B, H, T_q, T_k) 7. Softmax 8. Dropout (p=0) 9. matmul(attn, V) -> (B, H, T_q, d) 10. Transpose and reshape 11. `out_proj(out)` - matmul (B, T_q, dim) x (dim, dim) -> (B, T_q, dim) The main opportunity for fusion is combining: - RoPE computation and application (compute sin/cos once, apply to Q and K) - Softmax with the attention matrix multiplication - Potentially fusing the output reshape with the output projection Let me write a kernel that: 1. Uses PyTorch's built-in operations for the projections (they're already well-optimized) 2. Fuses RoPE application to Q and K 3. Fuses softmax with attention 4. Fuses the final operations Actually, for maximum performance on H100, I should consider: 1. Using Flash Attention for the attention computation (or a similar optimized version) 2. Fusing RoPE into the projection outputs 3. Fusing output projection Let me write a first version that focuses on correctness, then optimize. Let me start with a simple fused kernel that handles: 1. All operations in a single kernel call where possible 2. Using shared memory for intermediate results 3. Using tensor cores where possible Actually, let me be more pragmatic. The most expensive operations are the matrix multiplications. PyTorch already uses cuBLAS which is highly optimized. The best opportunities for fusion are: 1. Fusing RoPE with the reshape/transposes 2. Fusing the attention softmax 3. Fusing the output reshape with projection Let me write a kernel that uses custom CUDA for the non-GEMM operations and relies on PyTorch/cuBLAS for the GEMMs. Wait, but the task says to replace the forward with a custom CUDA kernel. I should write a kernel that does everything in CUDA. Let me write a comprehensive kernel that: 1. Does the projections using shared memory and tensor cores 2. Applies RoPE 3. Computes attention 4. Does the output projection For the attention part, I'll implement a tiled approach similar to Flash Attention. Let me start:
assistantI'll analyze this multi-head cross-attention with RoPE and create an optimized CUDA kernel. Let me start by understanding the operations and then implement a fused kernel.
call → compile_kernel
callcompile_kernel { "kernel_code": "import math\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.cpp_extension import load_inline\n\n# Compile the CUDA kernel\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n// RoPE kernel: applies rotary position embeddings\n// x: (B, H, L, d) - input tensor\n// cos, sin: (L, d/2) - position embeddings\n// out: (B, H, L, d) - output tensor\n__global__ void rope_kernel(\n const float* __restrict__ x,\n const float* __restrict__ cos,\n const float* __restrict__ sin,\n float* __restrict__ out,\n int B, int H, int L, int d\n) {\n int half_d = d / 2;\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * L * half_d;\n \n if (idx < total) {\n int b = idx / (H * L * half_d);\n int h = (idx / (L * half_d)) % H;\n int l = (idx / half_d) % L;\n int i = idx % half_d;\n \n int base_idx = b * H * L * d + h * L * d + l * d;\n float x1 = x[base_idx + i];\n float x2 = x[base_idx + i + half_d];\n float c = cos[l * half_d + i];\n float s = sin[l * half_d + i];\n \n out[base_idx + i] = x1 * c - x2 * s;\n out[base_idx + i + half_d] = x1 * s + x2 * c;\n }\n}\n\n// Softmax kernel for attention weights\n// attn: (B, H, T_q, T_k) - attention scores\n// scale: scaling factor\n__global__ void softmax_kernel(\n float* __restrict__ attn,\n int B, int H, int T_q, int T_k,\n float scale\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q;\n \n if (idx < total) {\n int b = idx / (H * T_q);\n int h = (idx / T_q) % H;\n int q = idx % T_q;\n \n int row_offset = b * H * T_q * T_k + h * T_q * T_k + q * T_k;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int k = 0; k < T_k; k++) {\n float val = attn[row_offset + k] * scale;\n if (val > max_val) max_val = val;\n }\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int k = 0; k < T_k; k++) {\n float val = expf(attn[row_offset + k] * scale - max_val);\n attn[row_offset + k] = val;\n sum += val;\n }\n \n // Normalize\n float inv_sum = 1.0f / sum;\n for (int k = 0; k < T_k; k++) {\n attn[row_offset + k] *= inv_sum;\n }\n }\n}\n\n// Attention matrix multiplication: Q @ K^T\n__global__ void qk_kernel(\n const float* __restrict__ q,\n const float* __restrict__ k,\n float* __restrict__ attn,\n int B, int H, int T_q, int T_k, int d\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q * T_k;\n \n if (idx < total) {\n int b = idx / (H * T_q * T_k);\n int h = (idx / (T_q * T_k)) % H;\n int q = (idx / T_k) % T_q;\n int kv = idx % T_k;\n \n int q_offset = b * H * T_q * d + h * T_q * d + q * d;\n int k_offset = b * H * T_k * d + h * T_k * d + kv * d;\n \n float sum = 0.0f;\n for (int i = 0; i < d; i++) {\n sum += q[q_offset + i] * k[k_offset + i];\n }\n \n attn[idx] = sum;\n }\n}\n\n// Attention-value multiplication: attn @ V\n__global__ void av_kernel(\n const float* __restrict__ attn,\n const float* __restrict__ v,\n float* __restrict__ out,\n int B, int H, int T_q, int T_k, int d\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q * d;\n \n if (idx < total) {\n int b = idx / (H * T_q * d);\n int h = (idx / (T_q * d)) % H;\n int q = (idx / d) % T_q;\n int i = idx % d;\n \n int attn_offset = b * H * T_q * T_k + h * T_q * T_k + q * T_k;\n \n float sum = 0.0f;\n for (int kv = 0; kv < T_k; kv++) {\n int v_offset = b * H * T_k * d + h * T_k * d + kv * d + i;\n sum += attn[attn_offset + kv] * v[v_offset];\n }\n \n out[idx] = sum;\n }\n}\n\n// Host functions\nvoid rope_cuda(\n const torch::Tensor& x,\n const torch::Tensor& cos,\n const torch::Tensor& sin,\n torch::Tensor& out\n) {\n int B = x.size(0);\n int H = x.size(1);\n int L = x.size(2);\n int d = x.size(3);\n \n int total = B * H * L * (d / 2);\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n rope_kernel<<<blocks, threads>>>(\n x.data_ptr<float>(),\n cos.data_ptr<float>(),\n sin.data_ptr<float>(),\n out.data_ptr<float>(),\n B, H, L, d\n );\n}\n\nvoid softmax_cuda(\n torch::Tensor& attn,\n int B, int H, int T_q, int T_k,\n float scale\n) {\n int total = B * H * T_q;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n softmax_kernel<<<blocks, threads>>>(\n attn.data_ptr<float>(),\n B, H, T_q, T_k, scale\n );\n}\n\nvoid qk_cuda(\n const torch::Tensor& q,\n const torch::Tensor& k,\n torch::Tensor& attn,\n int B, int H, int T_q, int T_k, int d\n) {\n int total = B * H * T_q * T_k;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n qk_kernel<<<blocks, threads>>>(\n q.data_ptr<float>(),\n k.data_ptr<float>(),\n attn.data_ptr<float>(),\n B, H, T_q, T_k, d\n );\n}\n\nvoid av_cuda(\n const torch::Tensor& attn,\n const torch::Tensor& v,\n torch::Tensor& out,\n int B, int H, int T_q, int T_k, int d\n) {\n int total = B * H * T_q * d;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n av_kernel<<<blocks, threads>>>(\n attn.data_ptr<float>(),\n v.data_ptr<float>(),\n out.data_ptr<float>(),\n B, H, T_q, T_k, d\n );\n}\n\nPYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {\n m.def(\"rope_cuda\", &rope_cuda, \"RoPE CUDA kernel\");\n m.def(\"softmax_cuda\", &softmax_cuda, \"Softmax CUDA kernel\");\n m.def(\"qk_cuda\", &qk_cuda, \"QK CUDA kernel\");\n m.def(\"av_cuda\", &av_cuda, \"AV CUDA kernel\");\n}\n\"\"\"\n\ncpp_source = \"\"\n\n# Load the extension\nattention_ext = load_inline(\n name='attention_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rope_cuda', 'softmax_cuda', 'qk_cuda', 'av_cuda'],\n extra_cuda_cflags=['-O3'],\n verbose=False\n)\n\n\nclass Model(nn.Module):\n \"\"\"\n Single-layer multi-head cross-attention with RoPE on Q and K, attention\n dropout, and output projection\u2014intended as a fusion target for robotics\n stacks (e.g. proprio / action queries attending to vision or memory).\n \"\"\"\n\n def __init__(\n self,\n dim: int,\n num_heads: int,\n dropout: float = 0.0,\n rope_base: float = 10000.0,\n ):\n super().__init__()\n assert dim % num_heads == 0\n self.dim = dim\n self.num_heads = num_heads\n self.head_dim = dim // num_heads\n assert self.head_dim % 2 == 0, \"RoPE requires an even head dimension\"\n\n self.q_proj = nn.Linear(dim, dim, bias=False)\n self.k_proj = nn.Linear(dim, dim, bias=False)\n self.v_proj = nn.Linear(dim, dim, bias=False)\n self.out_proj = nn.Linear(dim, dim, bias=False)\n self.dropout = nn.Dropout(dropout)\n self.scale = 1.0 / math.sqrt(self.head_dim)\n\n inv_freq = 1.0 / (\n rope_base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)\n )\n self.register_buffer(\"inv_freq\", inv_freq, persistent=False)\n\n @staticmethod\n def _rotate_half(x: torch.Tensor) -> torch.Tensor:\n x1, x2 = x.chunk(2, dim=-1)\n return torch.cat([-x2, x1], dim=-1)\n\n def _rope(self, x: torch.Tensor, seq_len: int) -> torch.Tensor:\n \"\"\"Apply RoPE along the sequence dimension (last-but-one after head). x: (B, H, L, d).\"\"\"\n t = torch.arange(seq_len, device=x.device, dtype=self.inv_freq.dtype)\n freqs = torch.outer(t, self.inv_freq)\n emb = torch.cat([freqs, freqs], dim=-1)\n cos = emb.cos().view(1, 1, seq_len, self.head_dim)\n sin = emb.sin().view(1, 1, seq_len, self.head_dim)\n return x * cos + self._rotate_half(x) * sin\n\n def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:\n B, T_q, _ = x_query.shape\n _, T_k, _ = x_kv.shape\n h, d = self.num_heads, self.head_dim\n\n q = self.q_proj(x_query).view(B, T_q, h, d).transpose(1, 2)\n k = self.k_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)\n v = self.v_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)\n\n q = self._rope(q, T_q)\n k = self._rope(k, T_k)\n\n attn = torch.matmul(q, k.transpose(-2, -1)) * self.scale\n attn = F.softmax(attn, dim=-1)\n attn = self.dropout(attn)\n out = torch.matmul(attn, v)\n out = out.transpose(1, 2).contiguous().view(B, T_q, self.dim)\n return self.out_proj(out)\n\n\nclass ModelNew(nn.Module):\n \"\"\"\n Custom CUDA implementation of multi-head cross-attention with RoPE.\n \"\"\"\n\n def __init__(\n self,\n dim: int,\n num_heads: int,\n dropout: float = 0.0,\n rope_base: float = 10000.0,\n ):\n super().__init__()\n assert dim % num_heads == 0\n self.dim = dim\n self.num_heads = num_heads\n self.head_dim = dim // num_heads\n assert self.head_dim % 2 == 0, \"RoPE requires an even head dimension\"\n\n self.q_proj = nn.Linear(dim, dim, bias=False)\n self.k_proj = nn.Linear(dim, dim, bias=False)\n self.v_proj = nn.Linear(dim, dim, bias=False)\n self.out_proj = nn.Linear(dim, dim, bias=False)\n self.dropout = nn.Dropout(dropout)\n self.scale = 1.0 / math.sqrt(self.head_dim)\n\n inv_freq = 1.0 / (\n rope_base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)\n )\n self.register_buffer(\"inv_freq\", inv_freq, persistent=False)\n\n def _compute_rope_emb(self, seq_len: int, device):\n \"\"\"Compute RoPE embeddings for a sequence length.\"\"\"\n t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)\n freqs = torch.outer(t, self.inv_freq)\n cos = freqs.cos() # (seq_len, head_dim/2)\n sin = freqs.sin() # (seq_len, head_dim/2)\n return cos, sin\n\n def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:\n B, T_q, _ = x_query.shape\n _, T_k, _ = x_kv.shape\n h, d = self.num_heads, self.head_dim\n\n # Project and reshape\n q = self.q_proj(x_query).view(B, T_q, h, d).transpose(1, 2).contiguous()\n k = self.k_proj(x_kv).view(B, T_k, h, d).transpose(1, 2).contiguous()\n v = self.v_proj(x_kv).view(B, T_k, h, d).transpose(1, 2).contiguous()\n\n # Compute RoPE embeddings\n cos_q, sin_q = self._compute_rope_emb(T_q, x_query.device)\n cos_k, sin_k = self._compute_rope_emb(T_k, x_kv.device)\n\n # Apply RoPE using custom CUDA kernel\n q_rope = torch.empty_like(q)\n k_rope = torch.empty_like(k)\n attention_ext.rope_cuda(q, cos_q, sin_q, q_rope)\n attention_ext.rope_cuda(k, cos_k, sin_k, k_rope)\n\n # Compute attention scores using custom CUDA kernel\n attn = torch.empty(B, h, T_q, T_k, device=x_query.device, dtype=x_query.dtype)\n attention_ext.qk_cuda(q_rope, k_rope, attn, B, h, T_q, T_k, d)\n\n # Apply softmax using custom CUDA kernel\n attention_ext.softmax_cuda(attn, B, h, T_q, T_k, self.scale)\n\n # Apply dropout (p=0 in test, so no-op)\n attn = self.dropout(attn)\n\n # Compute attention output using custom CUDA kernel\n attn_out = torch.empty(B, h, T_q, d, device=x_query.device, dtype=x_query.dtype)\n attention_ext.av_cuda(attn, v, attn_out, B, h, T_q, T_k, d)\n\n # Transpose and reshape for output projection\n out = attn_out.transpose(1, 2).contiguous().view(B, T_q, self.dim)\n\n return self.out_proj(out)\n\n\nbatch_size = 4\nseq_len_q = 64\nseq_len_kv = 128\ndim = 256\nnum_heads = 8\ndropout_p = 0.0\n\n\ndef get_inputs():\n x_query = torch.randn(batch_size, seq_len_q, dim)\n x_kv = torch.randn(batch_size, seq_len_kv, dim)\n return [x_query, x_kv]\n\n\ndef get_init_inputs():\n return [dim, num_heads, dropout_p]" }
result ← compile_kernel (fail)
failcompile_kernel compile_kernel FAILED: RuntimeError. RuntimeError: Error building extension 'attention_ext': [1/3] /opt/conda/bin/x86_64-conda-linux-gnu-c++ -MMD -MF main.o.d -DTORCH_EXTENSION_NAME=attention_ext -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -fPIC -std=c++17 -c /scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_2_problem_3_cache/k_b9f9966f112b/attention_ext/main.cpp -o main.o FAILED: [code=1] main.o /opt/conda/bin/x86_64-conda-linux-gnu-c++ -MMD -MF main.o.d -DTORCH_EXTENSION_NAME=attention_ext -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -fPIC -std=c++17 -c /scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_2_problem_3_cache/k_b9f9966f112b/attention_ext/main.cpp -o main.o /scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_2_problem_3_cache/k_b9f9966f112b/attention_ext/main.cpp: In function 'void pybind11_init_attention_ext(pybind11::module_&)': /scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_2_problem_3_cache/k_b9f9966f112b/attention_ext/main.cpp:4:48: error: 'rope_cuda' was not declared in this scope 4 | m.def("rope_cuda", torch::wrap_pybind_function(rope_cuda), "rope_cuda"); | ^~~~~~~~~ /scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_2_problem_3_cache/k_b9f9966f112b/attention_ext/main.cpp:5:51: error: 'softmax_cuda' was not declared in this scope 5 | m.def("softmax_cuda", torch::wrap_pybind_function(softmax_cuda), "softmax_cuda"); | ^~~~~~~~~~~~ /scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_2_problem_3_cache/k_b9f9966f112b/attention_ext/main.cpp:6:46: error: 'qk_cuda' was not declared in this scope 6 | m.def("qk_cuda", torch::wrap_pybind_function(qk_cuda), "qk_cuda"); | ^~~~~~~ /scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_2_problem_3_cache/k_b9f9966f112b/attention_ext/main.cpp:7:46: error: 'av_cuda' was not declared in this scope 7 | m.def("av_cuda", torch::wrap_pybind_function(av_cuda), "av_cuda"); | ^~~~~~~ [2/3] /usr/local/cuda/bin/nvcc -MD -MF cuda.cuda.o.d -ccbin /opt/conda/bin/x86_64-conda-linux-gnu-cc -DTORCH_EXTENSION_NAME=attention_ext -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -D__CUDA_NO_HALF_OPERATORS__ -D__CUDA_NO_HALF_CONVERSIONS__ -D__CUDA_NO_BFLOAT16_CONVERSIONS__ -D__CUDA_NO_HALF2_OPERATORS__ --expt-relaxed-constexpr -gencode=arch=compute_90,code=compute_90 -gencode=arch=compute_90,code=sm_90 --compiler-options '-fPIC' -O3 -std=c++17 -c /scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_2_problem_3_cache/k_b9f9966f112b/attention_ext/cuda.cu -o cuda.cuda.o FAILED: [code=2] cuda.cuda.o /usr/local/cuda/bin/nvcc -MD -MF cuda.cuda.o.d -ccbin /opt/conda/bin/x86_64-conda-linux-gnu-cc -DTORCH_EXTENSION_NAME=attention_ext -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -D__CUDA_NO_HALF_OPERATORS__ -D__CUDA_NO_HALF_CONVERSIONS__ -D__CUDA_NO_BFLOAT16_CONVERSIONS__ -D__CUDA_NO_HALF2_OPERATORS__ --expt-relaxed-constexpr -gencode=arch=compute_90,code=compute_90 -gencode=arch=compute_90,code=sm_90 --compiler-options '-fPIC' -O3 -std=c++17 -c /scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_2_problem_3_cache/k_b9f9966f112b/attention_ext/cuda.cu -o cuda.cuda.o nvcc warning : incompatible redefinition for option 'compiler-bindir', the last value of this option was used /scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_2_problem_3_cache/k_b9f9966f112b/attention_ext/cuda.cu(104): error: expression must have pointer-to-object type but it has type "int" sum += q[q_offset + i] * k[k_offset + i]; ^ 1 error detected in the compilation of "/scratch/abaner/KernelBench/runs/pop_l123_default_gpt/popcorn/FW-GLM-5-1/level_2_problem_3_cache/k_b9f9966f112b/attention_ext/cuda.cu". ninja: build stopped: subcommand failed.
turn 2 · 1 tool call(s) · 109.3s turn · 30.1s LLM · tokens in=8,014 · out=3,794 · total=11,808 · 14,736 chars (click to expand)
turn 2109.3s turn · 30.1s LLM · 79.3s tools · tokens in=8,014 · out=3,794 · total=11,808
reasoning
I need to fix the compilation errors. The issue is that I'm using `q` and `k` as variable names in the kernel, which shadow the function parameters. Let me rewrite the kernel without the PYBIND11_MODULE block and with correct variable naming.
assistantI need to fix the compilation errors. Let me rewrite the kernel without the PYBIND11_MODULE block and fix the variable naming issues.
call → compile_kernel
callcompile_kernel { "kernel_code": "import math\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.cpp_extension import load_inline\n\n# Compile the CUDA kernel\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n// RoPE kernel: applies rotary position embeddings\n// x: (B, H, L, d) - input tensor\n// cos, sin: (L, d/2) - position embeddings\n// out: (B, H, L, d) - output tensor\n__global__ void rope_kernel(\n const float* __restrict__ x_ptr,\n const float* __restrict__ cos_ptr,\n const float* __restrict__ sin_ptr,\n float* __restrict__ out_ptr,\n int B, int H, int L, int d\n) {\n int half_d = d / 2;\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * L * half_d;\n \n if (idx < total) {\n int b = idx / (H * L * half_d);\n int h = (idx / (L * half_d)) % H;\n int l = (idx / half_d) % L;\n int i = idx % half_d;\n \n int base_idx = b * H * L * d + h * L * d + l * d;\n float x1 = x_ptr[base_idx + i];\n float x2 = x_ptr[base_idx + i + half_d];\n float c = cos_ptr[l * half_d + i];\n float s = sin_ptr[l * half_d + i];\n \n out_ptr[base_idx + i] = x1 * c - x2 * s;\n out_ptr[base_idx + i + half_d] = x1 * s + x2 * c;\n }\n}\n\n// Softmax kernel for attention weights\n// attn: (B, H, T_q, T_k) - attention scores\n// scale: scaling factor\n__global__ void softmax_kernel(\n float* __restrict__ attn_ptr,\n int B, int H, int T_q, int T_k,\n float scale\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q;\n \n if (idx < total) {\n int b = idx / (H * T_q);\n int h = (idx / T_q) % H;\n int q_idx = idx % T_q;\n \n int row_offset = b * H * T_q * T_k + h * T_q * T_k + q_idx * T_k;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int k_idx = 0; k_idx < T_k; k_idx++) {\n float val = attn_ptr[row_offset + k_idx] * scale;\n if (val > max_val) max_val = val;\n }\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int k_idx = 0; k_idx < T_k; k_idx++) {\n float val = expf(attn_ptr[row_offset + k_idx] * scale - max_val);\n attn_ptr[row_offset + k_idx] = val;\n sum += val;\n }\n \n // Normalize\n float inv_sum = 1.0f / sum;\n for (int k_idx = 0; k_idx < T_k; k_idx++) {\n attn_ptr[row_offset + k_idx] *= inv_sum;\n }\n }\n}\n\n// Attention matrix multiplication: Q @ K^T\n__global__ void qk_kernel(\n const float* __restrict__ q_ptr,\n const float* __restrict__ k_ptr,\n float* __restrict__ attn_ptr,\n int B, int H, int T_q, int T_k, int d\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q * T_k;\n \n if (idx < total) {\n int b = idx / (H * T_q * T_k);\n int h = (idx / (T_q * T_k)) % H;\n int q_idx = (idx / T_k) % T_q;\n int kv_idx = idx % T_k;\n \n int q_offset = b * H * T_q * d + h * T_q * d + q_idx * d;\n int k_offset = b * H * T_k * d + h * T_k * d + kv_idx * d;\n \n float sum = 0.0f;\n for (int i = 0; i < d; i++) {\n sum += q_ptr[q_offset + i] * k_ptr[k_offset + i];\n }\n \n attn_ptr[idx] = sum;\n }\n}\n\n// Attention-value multiplication: attn @ V\n__global__ void av_kernel(\n const float* __restrict__ attn_ptr,\n const float* __restrict__ v_ptr,\n float* __restrict__ out_ptr,\n int B, int H, int T_q, int T_k, int d\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q * d;\n \n if (idx < total) {\n int b = idx / (H * T_q * d);\n int h = (idx / (T_q * d)) % H;\n int q_idx = (idx / d) % T_q;\n int i = idx % d;\n \n int attn_offset = b * H * T_q * T_k + h * T_q * T_k + q_idx * T_k;\n \n float sum = 0.0f;\n for (int kv_idx = 0; kv_idx < T_k; kv_idx++) {\n int v_offset = b * H * T_k * d + h * T_k * d + kv_idx * d + i;\n sum += attn_ptr[attn_offset + kv_idx] * v_ptr[v_offset];\n }\n \n out_ptr[idx] = sum;\n }\n}\n\n// Host functions\nvoid rope_cuda(\n const torch::Tensor& x,\n const torch::Tensor& cos,\n const torch::Tensor& sin,\n torch::Tensor& out\n) {\n int B = x.size(0);\n int H = x.size(1);\n int L = x.size(2);\n int d = x.size(3);\n \n int total = B * H * L * (d / 2);\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n rope_kernel<<<blocks, threads>>>(\n x.data_ptr<float>(),\n cos.data_ptr<float>(),\n sin.data_ptr<float>(),\n out.data_ptr<float>(),\n B, H, L, d\n );\n}\n\nvoid softmax_cuda(\n torch::Tensor& attn,\n int B, int H, int T_q, int T_k,\n float scale\n) {\n int total = B * H * T_q;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n softmax_kernel<<<blocks, threads>>>(\n attn.data_ptr<float>(),\n B, H, T_q, T_k, scale\n );\n}\n\nvoid qk_cuda(\n const torch::Tensor& q,\n const torch::Tensor& k,\n torch::Tensor& attn,\n int B, int H, int T_q, int T_k, int d\n) {\n int total = B * H * T_q * T_k;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n qk_kernel<<<blocks, threads>>>(\n q.data_ptr<float>(),\n k.data_ptr<float>(),\n attn.data_ptr<float>(),\n B, H, T_q, T_k, d\n );\n}\n\nvoid av_cuda(\n const torch::Tensor& attn,\n const torch::Tensor& v,\n torch::Tensor& out,\n int B, int H, int T_q, int T_k, int d\n) {\n int total = B * H * T_q * d;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n av_kernel<<<blocks, threads>>>(\n attn.data_ptr<float>(),\n v.data_ptr<float>(),\n out.data_ptr<float>(),\n B, H, T_q, T_k, d\n );\n}\n\"\"\"\n\ncpp_source = \"\"\"\nvoid rope_cuda(const torch::Tensor& x, const torch::Tensor& cos, const torch::Tensor& sin, torch::Tensor& out);\nvoid softmax_cuda(torch::Tensor& attn, int B, int H, int T_q, int T_k, float scale);\nvoid qk_cuda(const torch::Tensor& q, const torch::Tensor& k, torch::Tensor& attn, int B, int H, int T_q, int T_k, int d);\nvoid av_cuda(const torch::Tensor& attn, const torch::Tensor& v, torch::Tensor& out, int B, int H, int T_q, int T_k, int d);\n\"\"\"\n\n# Load the extension\nattention_ext = load_inline(\n name='attention_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rope_cuda', 'softmax_cuda', 'qk_cuda', 'av_cuda'],\n extra_cuda_cflags=['-O3'],\n verbose=False\n)\n\n\nclass Model(nn.Module):\n \"\"\"\n Single-layer multi-head cross-attention with RoPE on Q and K, attention\n dropout, and output projection\u2014intended as a fusion target for robotics\n stacks (e.g. proprio / action queries attending to vision or memory).\n \"\"\"\n\n def __init__(\n self,\n dim: int,\n num_heads: int,\n dropout: float = 0.0,\n rope_base: float = 10000.0,\n ):\n super().__init__()\n assert dim % num_heads == 0\n self.dim = dim\n self.num_heads = num_heads\n self.head_dim = dim // num_heads\n assert self.head_dim % 2 == 0, \"RoPE requires an even head dimension\"\n\n self.q_proj = nn.Linear(dim, dim, bias=False)\n self.k_proj = nn.Linear(dim, dim, bias=False)\n self.v_proj = nn.Linear(dim, dim, bias=False)\n self.out_proj = nn.Linear(dim, dim, bias=False)\n self.dropout = nn.Dropout(dropout)\n self.scale = 1.0 / math.sqrt(self.head_dim)\n\n inv_freq = 1.0 / (\n rope_base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)\n )\n self.register_buffer(\"inv_freq\", inv_freq, persistent=False)\n\n @staticmethod\n def _rotate_half(x: torch.Tensor) -> torch.Tensor:\n x1, x2 = x.chunk(2, dim=-1)\n return torch.cat([-x2, x1], dim=-1)\n\n def _rope(self, x: torch.Tensor, seq_len: int) -> torch.Tensor:\n \"\"\"Apply RoPE along the sequence dimension (last-but-one after head). x: (B, H, L, d).\"\"\"\n t = torch.arange(seq_len, device=x.device, dtype=self.inv_freq.dtype)\n freqs = torch.outer(t, self.inv_freq)\n emb = torch.cat([freqs, freqs], dim=-1)\n cos = emb.cos().view(1, 1, seq_len, self.head_dim)\n sin = emb.sin().view(1, 1, seq_len, self.head_dim)\n return x * cos + self._rotate_half(x) * sin\n\n def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:\n B, T_q, _ = x_query.shape\n _, T_k, _ = x_kv.shape\n h, d = self.num_heads, self.head_dim\n\n q = self.q_proj(x_query).view(B, T_q, h, d).transpose(1, 2)\n k = self.k_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)\n v = self.v_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)\n\n q = self._rope(q, T_q)\n k = self._rope(k, T_k)\n\n attn = torch.matmul(q, k.transpose(-2, -1)) * self.scale\n attn = F.softmax(attn, dim=-1)\n attn = self.dropout(attn)\n out = torch.matmul(attn, v)\n out = out.transpose(1, 2).contiguous().view(B, T_q, self.dim)\n return self.out_proj(out)\n\n\nclass ModelNew(nn.Module):\n \"\"\"\n Custom CUDA implementation of multi-head cross-attention with RoPE.\n \"\"\"\n\n def __init__(\n self,\n dim: int,\n num_heads: int,\n dropout: float = 0.0,\n rope_base: float = 10000.0,\n ):\n super().__init__()\n assert dim % num_heads == 0\n self.dim = dim\n self.num_heads = num_heads\n self.head_dim = dim // num_heads\n assert self.head_dim % 2 == 0, \"RoPE requires an even head dimension\"\n\n self.q_proj = nn.Linear(dim, dim, bias=False)\n self.k_proj = nn.Linear(dim, dim, bias=False)\n self.v_proj = nn.Linear(dim, dim, bias=False)\n self.out_proj = nn.Linear(dim, dim, bias=False)\n self.dropout = nn.Dropout(dropout)\n self.scale = 1.0 / math.sqrt(self.head_dim)\n\n inv_freq = 1.0 / (\n rope_base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)\n )\n self.register_buffer(\"inv_freq\", inv_freq, persistent=False)\n\n def _compute_rope_emb(self, seq_len: int, device):\n \"\"\"Compute RoPE embeddings for a sequence length.\"\"\"\n t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)\n freqs = torch.outer(t, self.inv_freq)\n cos = freqs.cos() # (seq_len, head_dim/2)\n sin = freqs.sin() # (seq_len, head_dim/2)\n return cos, sin\n\n def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:\n B, T_q, _ = x_query.shape\n _, T_k, _ = x_kv.shape\n h, d = self.num_heads, self.head_dim\n\n # Project and reshape\n q = self.q_proj(x_query).view(B, T_q, h, d).transpose(1, 2).contiguous()\n k = self.k_proj(x_kv).view(B, T_k, h, d).transpose(1, 2).contiguous()\n v = self.v_proj(x_kv).view(B, T_k, h, d).transpose(1, 2).contiguous()\n\n # Compute RoPE embeddings\n cos_q, sin_q = self._compute_rope_emb(T_q, x_query.device)\n cos_k, sin_k = self._compute_rope_emb(T_k, x_kv.device)\n\n # Apply RoPE using custom CUDA kernel\n q_rope = torch.empty_like(q)\n k_rope = torch.empty_like(k)\n attention_ext.rope_cuda(q, cos_q, sin_q, q_rope)\n attention_ext.rope_cuda(k, cos_k, sin_k, k_rope)\n\n # Compute attention scores using custom CUDA kernel\n attn = torch.empty(B, h, T_q, T_k, device=x_query.device, dtype=x_query.dtype)\n attention_ext.qk_cuda(q_rope, k_rope, attn, B, h, T_q, T_k, d)\n\n # Apply softmax using custom CUDA kernel\n attention_ext.softmax_cuda(attn, B, h, T_q, T_k, self.scale)\n\n # Apply dropout (p=0 in test, so no-op)\n attn = self.dropout(attn)\n\n # Compute attention output using custom CUDA kernel\n attn_out = torch.empty(B, h, T_q, d, device=x_query.device, dtype=x_query.dtype)\n attention_ext.av_cuda(attn, v, attn_out, B, h, T_q, T_k, d)\n\n # Transpose and reshape for output projection\n out = attn_out.transpose(1, 2).contiguous().view(B, T_q, self.dim)\n\n return self.out_proj(out)\n\n\nbatch_size = 4\nseq_len_q = 64\nseq_len_kv = 128\ndim = 256\nnum_heads = 8\ndropout_p = 0.0\n\n\ndef get_inputs():\n x_query = torch.randn(batch_size, seq_len_q, dim)\n x_kv = torch.randn(batch_size, seq_len_kv, dim)\n return [x_query, x_kv]\n\n\ndef get_init_inputs():\n return [dim, num_heads, dropout_p]" }
result ← compile_kernel (ok)
okcompile_kernel compile_kernel PASSED: kernel compiled without errors.
turn 3 · 1 tool call(s) · 30.8s turn · 30.8s LLM · tokens in=11,768 · out=3,738 · total=15,506 · 14,577 chars (click to expand)
turn 330.8s turn · 30.8s LLM · 0.1s tools · tokens in=11,768 · out=3,738 · total=15,506
reasoning
Great, the kernel compiled. Now let me check correctness.
assistantThe kernel compiled successfully. Let me check correctness now.
call → run_correctness
callrun_correctness { "kernel_code": "import math\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.cpp_extension import load_inline\n\n# Compile the CUDA kernel\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n// RoPE kernel: applies rotary position embeddings\n// x: (B, H, L, d) - input tensor\n// cos, sin: (L, d/2) - position embeddings\n// out: (B, H, L, d) - output tensor\n__global__ void rope_kernel(\n const float* __restrict__ x_ptr,\n const float* __restrict__ cos_ptr,\n const float* __restrict__ sin_ptr,\n float* __restrict__ out_ptr,\n int B, int H, int L, int d\n) {\n int half_d = d / 2;\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * L * half_d;\n \n if (idx < total) {\n int b = idx / (H * L * half_d);\n int h = (idx / (L * half_d)) % H;\n int l = (idx / half_d) % L;\n int i = idx % half_d;\n \n int base_idx = b * H * L * d + h * L * d + l * d;\n float x1 = x_ptr[base_idx + i];\n float x2 = x_ptr[base_idx + i + half_d];\n float c = cos_ptr[l * half_d + i];\n float s = sin_ptr[l * half_d + i];\n \n out_ptr[base_idx + i] = x1 * c - x2 * s;\n out_ptr[base_idx + i + half_d] = x1 * s + x2 * c;\n }\n}\n\n// Softmax kernel for attention weights\n// attn: (B, H, T_q, T_k) - attention scores\n// scale: scaling factor\n__global__ void softmax_kernel(\n float* __restrict__ attn_ptr,\n int B, int H, int T_q, int T_k,\n float scale\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q;\n \n if (idx < total) {\n int b = idx / (H * T_q);\n int h = (idx / T_q) % H;\n int q_idx = idx % T_q;\n \n int row_offset = b * H * T_q * T_k + h * T_q * T_k + q_idx * T_k;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int k_idx = 0; k_idx < T_k; k_idx++) {\n float val = attn_ptr[row_offset + k_idx] * scale;\n if (val > max_val) max_val = val;\n }\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int k_idx = 0; k_idx < T_k; k_idx++) {\n float val = expf(attn_ptr[row_offset + k_idx] * scale - max_val);\n attn_ptr[row_offset + k_idx] = val;\n sum += val;\n }\n \n // Normalize\n float inv_sum = 1.0f / sum;\n for (int k_idx = 0; k_idx < T_k; k_idx++) {\n attn_ptr[row_offset + k_idx] *= inv_sum;\n }\n }\n}\n\n// Attention matrix multiplication: Q @ K^T\n__global__ void qk_kernel(\n const float* __restrict__ q_ptr,\n const float* __restrict__ k_ptr,\n float* __restrict__ attn_ptr,\n int B, int H, int T_q, int T_k, int d\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q * T_k;\n \n if (idx < total) {\n int b = idx / (H * T_q * T_k);\n int h = (idx / (T_q * T_k)) % H;\n int q_idx = (idx / T_k) % T_q;\n int kv_idx = idx % T_k;\n \n int q_offset = b * H * T_q * d + h * T_q * d + q_idx * d;\n int k_offset = b * H * T_k * d + h * T_k * d + kv_idx * d;\n \n float sum = 0.0f;\n for (int i = 0; i < d; i++) {\n sum += q_ptr[q_offset + i] * k_ptr[k_offset + i];\n }\n \n attn_ptr[idx] = sum;\n }\n}\n\n// Attention-value multiplication: attn @ V\n__global__ void av_kernel(\n const float* __restrict__ attn_ptr,\n const float* __restrict__ v_ptr,\n float* __restrict__ out_ptr,\n int B, int H, int T_q, int T_k, int d\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q * d;\n \n if (idx < total) {\n int b = idx / (H * T_q * d);\n int h = (idx / (T_q * d)) % H;\n int q_idx = (idx / d) % T_q;\n int i = idx % d;\n \n int attn_offset = b * H * T_q * T_k + h * T_q * T_k + q_idx * T_k;\n \n float sum = 0.0f;\n for (int kv_idx = 0; kv_idx < T_k; kv_idx++) {\n int v_offset = b * H * T_k * d + h * T_k * d + kv_idx * d + i;\n sum += attn_ptr[attn_offset + kv_idx] * v_ptr[v_offset];\n }\n \n out_ptr[idx] = sum;\n }\n}\n\n// Host functions\nvoid rope_cuda(\n const torch::Tensor& x,\n const torch::Tensor& cos,\n const torch::Tensor& sin,\n torch::Tensor& out\n) {\n int B = x.size(0);\n int H = x.size(1);\n int L = x.size(2);\n int d = x.size(3);\n \n int total = B * H * L * (d / 2);\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n rope_kernel<<<blocks, threads>>>(\n x.data_ptr<float>(),\n cos.data_ptr<float>(),\n sin.data_ptr<float>(),\n out.data_ptr<float>(),\n B, H, L, d\n );\n}\n\nvoid softmax_cuda(\n torch::Tensor& attn,\n int B, int H, int T_q, int T_k,\n float scale\n) {\n int total = B * H * T_q;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n softmax_kernel<<<blocks, threads>>>(\n attn.data_ptr<float>(),\n B, H, T_q, T_k, scale\n );\n}\n\nvoid qk_cuda(\n const torch::Tensor& q,\n const torch::Tensor& k,\n torch::Tensor& attn,\n int B, int H, int T_q, int T_k, int d\n) {\n int total = B * H * T_q * T_k;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n qk_kernel<<<blocks, threads>>>(\n q.data_ptr<float>(),\n k.data_ptr<float>(),\n attn.data_ptr<float>(),\n B, H, T_q, T_k, d\n );\n}\n\nvoid av_cuda(\n const torch::Tensor& attn,\n const torch::Tensor& v,\n torch::Tensor& out,\n int B, int H, int T_q, int T_k, int d\n) {\n int total = B * H * T_q * d;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n av_kernel<<<blocks, threads>>>(\n attn.data_ptr<float>(),\n v.data_ptr<float>(),\n out.data_ptr<float>(),\n B, H, T_q, T_k, d\n );\n}\n\"\"\"\n\ncpp_source = \"\"\"\nvoid rope_cuda(const torch::Tensor& x, const torch::Tensor& cos, const torch::Tensor& sin, torch::Tensor& out);\nvoid softmax_cuda(torch::Tensor& attn, int B, int H, int T_q, int T_k, float scale);\nvoid qk_cuda(const torch::Tensor& q, const torch::Tensor& k, torch::Tensor& attn, int B, int H, int T_q, int T_k, int d);\nvoid av_cuda(const torch::Tensor& attn, const torch::Tensor& v, torch::Tensor& out, int B, int H, int T_q, int T_k, int d);\n\"\"\"\n\n# Load the extension\nattention_ext = load_inline(\n name='attention_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rope_cuda', 'softmax_cuda', 'qk_cuda', 'av_cuda'],\n extra_cuda_cflags=['-O3'],\n verbose=False\n)\n\n\nclass Model(nn.Module):\n \"\"\"\n Single-layer multi-head cross-attention with RoPE on Q and K, attention\n dropout, and output projection\u2014intended as a fusion target for robotics\n stacks (e.g. proprio / action queries attending to vision or memory).\n \"\"\"\n\n def __init__(\n self,\n dim: int,\n num_heads: int,\n dropout: float = 0.0,\n rope_base: float = 10000.0,\n ):\n super().__init__()\n assert dim % num_heads == 0\n self.dim = dim\n self.num_heads = num_heads\n self.head_dim = dim // num_heads\n assert self.head_dim % 2 == 0, \"RoPE requires an even head dimension\"\n\n self.q_proj = nn.Linear(dim, dim, bias=False)\n self.k_proj = nn.Linear(dim, dim, bias=False)\n self.v_proj = nn.Linear(dim, dim, bias=False)\n self.out_proj = nn.Linear(dim, dim, bias=False)\n self.dropout = nn.Dropout(dropout)\n self.scale = 1.0 / math.sqrt(self.head_dim)\n\n inv_freq = 1.0 / (\n rope_base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)\n )\n self.register_buffer(\"inv_freq\", inv_freq, persistent=False)\n\n @staticmethod\n def _rotate_half(x: torch.Tensor) -> torch.Tensor:\n x1, x2 = x.chunk(2, dim=-1)\n return torch.cat([-x2, x1], dim=-1)\n\n def _rope(self, x: torch.Tensor, seq_len: int) -> torch.Tensor:\n \"\"\"Apply RoPE along the sequence dimension (last-but-one after head). x: (B, H, L, d).\"\"\"\n t = torch.arange(seq_len, device=x.device, dtype=self.inv_freq.dtype)\n freqs = torch.outer(t, self.inv_freq)\n emb = torch.cat([freqs, freqs], dim=-1)\n cos = emb.cos().view(1, 1, seq_len, self.head_dim)\n sin = emb.sin().view(1, 1, seq_len, self.head_dim)\n return x * cos + self._rotate_half(x) * sin\n\n def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:\n B, T_q, _ = x_query.shape\n _, T_k, _ = x_kv.shape\n h, d = self.num_heads, self.head_dim\n\n q = self.q_proj(x_query).view(B, T_q, h, d).transpose(1, 2)\n k = self.k_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)\n v = self.v_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)\n\n q = self._rope(q, T_q)\n k = self._rope(k, T_k)\n\n attn = torch.matmul(q, k.transpose(-2, -1)) * self.scale\n attn = F.softmax(attn, dim=-1)\n attn = self.dropout(attn)\n out = torch.matmul(attn, v)\n out = out.transpose(1, 2).contiguous().view(B, T_q, self.dim)\n return self.out_proj(out)\n\n\nclass ModelNew(nn.Module):\n \"\"\"\n Custom CUDA implementation of multi-head cross-attention with RoPE.\n \"\"\"\n\n def __init__(\n self,\n dim: int,\n num_heads: int,\n dropout: float = 0.0,\n rope_base: float = 10000.0,\n ):\n super().__init__()\n assert dim % num_heads == 0\n self.dim = dim\n self.num_heads = num_heads\n self.head_dim = dim // num_heads\n assert self.head_dim % 2 == 0, \"RoPE requires an even head dimension\"\n\n self.q_proj = nn.Linear(dim, dim, bias=False)\n self.k_proj = nn.Linear(dim, dim, bias=False)\n self.v_proj = nn.Linear(dim, dim, bias=False)\n self.out_proj = nn.Linear(dim, dim, bias=False)\n self.dropout = nn.Dropout(dropout)\n self.scale = 1.0 / math.sqrt(self.head_dim)\n\n inv_freq = 1.0 / (\n rope_base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)\n )\n self.register_buffer(\"inv_freq\", inv_freq, persistent=False)\n\n def _compute_rope_emb(self, seq_len: int, device):\n \"\"\"Compute RoPE embeddings for a sequence length.\"\"\"\n t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)\n freqs = torch.outer(t, self.inv_freq)\n cos = freqs.cos() # (seq_len, head_dim/2)\n sin = freqs.sin() # (seq_len, head_dim/2)\n return cos, sin\n\n def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:\n B, T_q, _ = x_query.shape\n _, T_k, _ = x_kv.shape\n h, d = self.num_heads, self.head_dim\n\n # Project and reshape\n q = self.q_proj(x_query).view(B, T_q, h, d).transpose(1, 2).contiguous()\n k = self.k_proj(x_kv).view(B, T_k, h, d).transpose(1, 2).contiguous()\n v = self.v_proj(x_kv).view(B, T_k, h, d).transpose(1, 2).contiguous()\n\n # Compute RoPE embeddings\n cos_q, sin_q = self._compute_rope_emb(T_q, x_query.device)\n cos_k, sin_k = self._compute_rope_emb(T_k, x_kv.device)\n\n # Apply RoPE using custom CUDA kernel\n q_rope = torch.empty_like(q)\n k_rope = torch.empty_like(k)\n attention_ext.rope_cuda(q, cos_q, sin_q, q_rope)\n attention_ext.rope_cuda(k, cos_k, sin_k, k_rope)\n\n # Compute attention scores using custom CUDA kernel\n attn = torch.empty(B, h, T_q, T_k, device=x_query.device, dtype=x_query.dtype)\n attention_ext.qk_cuda(q_rope, k_rope, attn, B, h, T_q, T_k, d)\n\n # Apply softmax using custom CUDA kernel\n attention_ext.softmax_cuda(attn, B, h, T_q, T_k, self.scale)\n\n # Apply dropout (p=0 in test, so no-op)\n attn = self.dropout(attn)\n\n # Compute attention output using custom CUDA kernel\n attn_out = torch.empty(B, h, T_q, d, device=x_query.device, dtype=x_query.dtype)\n attention_ext.av_cuda(attn, v, attn_out, B, h, T_q, T_k, d)\n\n # Transpose and reshape for output projection\n out = attn_out.transpose(1, 2).contiguous().view(B, T_q, self.dim)\n\n return self.out_proj(out)\n\n\nbatch_size = 4\nseq_len_q = 64\nseq_len_kv = 128\ndim = 256\nnum_heads = 8\ndropout_p = 0.0\n\n\ndef get_inputs():\n x_query = torch.randn(batch_size, seq_len_q, dim)\n x_kv = torch.randn(batch_size, seq_len_kv, dim)\n return [x_query, x_kv]\n\n\ndef get_init_inputs():\n return [dim, num_heads, dropout_p]" }
result ← run_correctness (ok)
okrun_correctness run_correctness PASSED: (5 / 5) trials all matched the reference. Numerical precision: max_abs_err=5.59e-08 mean_abs_err=7.77e-09 max_rel_err=8.21e-02
turn 4 · 1 tool call(s) · 30.9s turn · 30.9s LLM · tokens in=15,551 · out=3,858 · total=19,409 · 15,005 chars (click to expand)
turn 430.9s turn · 30.9s LLM · 0.0s tools · tokens in=15,551 · out=3,858 · total=19,409
reasoning
The kernel is correct. The maximum relative error is 8.21e-02 which seems high - let me check if this is acceptable. For fp32, the tolerance is atol=rtol=1e-4, which means we need relative error <= 1e-4 * (|ref| + |out|) + 1e-4. But since the max_rel_err is computed as max(|out - ref| / |ref|), the high relative error could be due to very small reference values (close to 0). Let me check the static check now and then submit if everything looks good.
assistantExcellent! The kernel passes correctness. Let me run a static check before submitting.
call → static_check
callstatic_check { "kernel_code": "import math\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.cpp_extension import load_inline\n\n# Compile the CUDA kernel\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n// RoPE kernel: applies rotary position embeddings\n// x: (B, H, L, d) - input tensor\n// cos, sin: (L, d/2) - position embeddings\n// out: (B, H, L, d) - output tensor\n__global__ void rope_kernel(\n const float* __restrict__ x_ptr,\n const float* __restrict__ cos_ptr,\n const float* __restrict__ sin_ptr,\n float* __restrict__ out_ptr,\n int B, int H, int L, int d\n) {\n int half_d = d / 2;\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * L * half_d;\n \n if (idx < total) {\n int b = idx / (H * L * half_d);\n int h = (idx / (L * half_d)) % H;\n int l = (idx / half_d) % L;\n int i = idx % half_d;\n \n int base_idx = b * H * L * d + h * L * d + l * d;\n float x1 = x_ptr[base_idx + i];\n float x2 = x_ptr[base_idx + i + half_d];\n float c = cos_ptr[l * half_d + i];\n float s = sin_ptr[l * half_d + i];\n \n out_ptr[base_idx + i] = x1 * c - x2 * s;\n out_ptr[base_idx + i + half_d] = x1 * s + x2 * c;\n }\n}\n\n// Softmax kernel for attention weights\n// attn: (B, H, T_q, T_k) - attention scores\n// scale: scaling factor\n__global__ void softmax_kernel(\n float* __restrict__ attn_ptr,\n int B, int H, int T_q, int T_k,\n float scale\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q;\n \n if (idx < total) {\n int b = idx / (H * T_q);\n int h = (idx / T_q) % H;\n int q_idx = idx % T_q;\n \n int row_offset = b * H * T_q * T_k + h * T_q * T_k + q_idx * T_k;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int k_idx = 0; k_idx < T_k; k_idx++) {\n float val = attn_ptr[row_offset + k_idx] * scale;\n if (val > max_val) max_val = val;\n }\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int k_idx = 0; k_idx < T_k; k_idx++) {\n float val = expf(attn_ptr[row_offset + k_idx] * scale - max_val);\n attn_ptr[row_offset + k_idx] = val;\n sum += val;\n }\n \n // Normalize\n float inv_sum = 1.0f / sum;\n for (int k_idx = 0; k_idx < T_k; k_idx++) {\n attn_ptr[row_offset + k_idx] *= inv_sum;\n }\n }\n}\n\n// Attention matrix multiplication: Q @ K^T\n__global__ void qk_kernel(\n const float* __restrict__ q_ptr,\n const float* __restrict__ k_ptr,\n float* __restrict__ attn_ptr,\n int B, int H, int T_q, int T_k, int d\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q * T_k;\n \n if (idx < total) {\n int b = idx / (H * T_q * T_k);\n int h = (idx / (T_q * T_k)) % H;\n int q_idx = (idx / T_k) % T_q;\n int kv_idx = idx % T_k;\n \n int q_offset = b * H * T_q * d + h * T_q * d + q_idx * d;\n int k_offset = b * H * T_k * d + h * T_k * d + kv_idx * d;\n \n float sum = 0.0f;\n for (int i = 0; i < d; i++) {\n sum += q_ptr[q_offset + i] * k_ptr[k_offset + i];\n }\n \n attn_ptr[idx] = sum;\n }\n}\n\n// Attention-value multiplication: attn @ V\n__global__ void av_kernel(\n const float* __restrict__ attn_ptr,\n const float* __restrict__ v_ptr,\n float* __restrict__ out_ptr,\n int B, int H, int T_q, int T_k, int d\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q * d;\n \n if (idx < total) {\n int b = idx / (H * T_q * d);\n int h = (idx / (T_q * d)) % H;\n int q_idx = (idx / d) % T_q;\n int i = idx % d;\n \n int attn_offset = b * H * T_q * T_k + h * T_q * T_k + q_idx * T_k;\n \n float sum = 0.0f;\n for (int kv_idx = 0; kv_idx < T_k; kv_idx++) {\n int v_offset = b * H * T_k * d + h * T_k * d + kv_idx * d + i;\n sum += attn_ptr[attn_offset + kv_idx] * v_ptr[v_offset];\n }\n \n out_ptr[idx] = sum;\n }\n}\n\n// Host functions\nvoid rope_cuda(\n const torch::Tensor& x,\n const torch::Tensor& cos,\n const torch::Tensor& sin,\n torch::Tensor& out\n) {\n int B = x.size(0);\n int H = x.size(1);\n int L = x.size(2);\n int d = x.size(3);\n \n int total = B * H * L * (d / 2);\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n rope_kernel<<<blocks, threads>>>(\n x.data_ptr<float>(),\n cos.data_ptr<float>(),\n sin.data_ptr<float>(),\n out.data_ptr<float>(),\n B, H, L, d\n );\n}\n\nvoid softmax_cuda(\n torch::Tensor& attn,\n int B, int H, int T_q, int T_k,\n float scale\n) {\n int total = B * H * T_q;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n softmax_kernel<<<blocks, threads>>>(\n attn.data_ptr<float>(),\n B, H, T_q, T_k, scale\n );\n}\n\nvoid qk_cuda(\n const torch::Tensor& q,\n const torch::Tensor& k,\n torch::Tensor& attn,\n int B, int H, int T_q, int T_k, int d\n) {\n int total = B * H * T_q * T_k;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n qk_kernel<<<blocks, threads>>>(\n q.data_ptr<float>(),\n k.data_ptr<float>(),\n attn.data_ptr<float>(),\n B, H, T_q, T_k, d\n );\n}\n\nvoid av_cuda(\n const torch::Tensor& attn,\n const torch::Tensor& v,\n torch::Tensor& out,\n int B, int H, int T_q, int T_k, int d\n) {\n int total = B * H * T_q * d;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n av_kernel<<<blocks, threads>>>(\n attn.data_ptr<float>(),\n v.data_ptr<float>(),\n out.data_ptr<float>(),\n B, H, T_q, T_k, d\n );\n}\n\"\"\"\n\ncpp_source = \"\"\"\nvoid rope_cuda(const torch::Tensor& x, const torch::Tensor& cos, const torch::Tensor& sin, torch::Tensor& out);\nvoid softmax_cuda(torch::Tensor& attn, int B, int H, int T_q, int T_k, float scale);\nvoid qk_cuda(const torch::Tensor& q, const torch::Tensor& k, torch::Tensor& attn, int B, int H, int T_q, int T_k, int d);\nvoid av_cuda(const torch::Tensor& attn, const torch::Tensor& v, torch::Tensor& out, int B, int H, int T_q, int T_k, int d);\n\"\"\"\n\n# Load the extension\nattention_ext = load_inline(\n name='attention_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rope_cuda', 'softmax_cuda', 'qk_cuda', 'av_cuda'],\n extra_cuda_cflags=['-O3'],\n verbose=False\n)\n\n\nclass Model(nn.Module):\n \"\"\"\n Single-layer multi-head cross-attention with RoPE on Q and K, attention\n dropout, and output projection\u2014intended as a fusion target for robotics\n stacks (e.g. proprio / action queries attending to vision or memory).\n \"\"\"\n\n def __init__(\n self,\n dim: int,\n num_heads: int,\n dropout: float = 0.0,\n rope_base: float = 10000.0,\n ):\n super().__init__()\n assert dim % num_heads == 0\n self.dim = dim\n self.num_heads = num_heads\n self.head_dim = dim // num_heads\n assert self.head_dim % 2 == 0, \"RoPE requires an even head dimension\"\n\n self.q_proj = nn.Linear(dim, dim, bias=False)\n self.k_proj = nn.Linear(dim, dim, bias=False)\n self.v_proj = nn.Linear(dim, dim, bias=False)\n self.out_proj = nn.Linear(dim, dim, bias=False)\n self.dropout = nn.Dropout(dropout)\n self.scale = 1.0 / math.sqrt(self.head_dim)\n\n inv_freq = 1.0 / (\n rope_base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)\n )\n self.register_buffer(\"inv_freq\", inv_freq, persistent=False)\n\n @staticmethod\n def _rotate_half(x: torch.Tensor) -> torch.Tensor:\n x1, x2 = x.chunk(2, dim=-1)\n return torch.cat([-x2, x1], dim=-1)\n\n def _rope(self, x: torch.Tensor, seq_len: int) -> torch.Tensor:\n \"\"\"Apply RoPE along the sequence dimension (last-but-one after head). x: (B, H, L, d).\"\"\"\n t = torch.arange(seq_len, device=x.device, dtype=self.inv_freq.dtype)\n freqs = torch.outer(t, self.inv_freq)\n emb = torch.cat([freqs, freqs], dim=-1)\n cos = emb.cos().view(1, 1, seq_len, self.head_dim)\n sin = emb.sin().view(1, 1, seq_len, self.head_dim)\n return x * cos + self._rotate_half(x) * sin\n\n def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:\n B, T_q, _ = x_query.shape\n _, T_k, _ = x_kv.shape\n h, d = self.num_heads, self.head_dim\n\n q = self.q_proj(x_query).view(B, T_q, h, d).transpose(1, 2)\n k = self.k_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)\n v = self.v_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)\n\n q = self._rope(q, T_q)\n k = self._rope(k, T_k)\n\n attn = torch.matmul(q, k.transpose(-2, -1)) * self.scale\n attn = F.softmax(attn, dim=-1)\n attn = self.dropout(attn)\n out = torch.matmul(attn, v)\n out = out.transpose(1, 2).contiguous().view(B, T_q, self.dim)\n return self.out_proj(out)\n\n\nclass ModelNew(nn.Module):\n \"\"\"\n Custom CUDA implementation of multi-head cross-attention with RoPE.\n \"\"\"\n\n def __init__(\n self,\n dim: int,\n num_heads: int,\n dropout: float = 0.0,\n rope_base: float = 10000.0,\n ):\n super().__init__()\n assert dim % num_heads == 0\n self.dim = dim\n self.num_heads = num_heads\n self.head_dim = dim // num_heads\n assert self.head_dim % 2 == 0, \"RoPE requires an even head dimension\"\n\n self.q_proj = nn.Linear(dim, dim, bias=False)\n self.k_proj = nn.Linear(dim, dim, bias=False)\n self.v_proj = nn.Linear(dim, dim, bias=False)\n self.out_proj = nn.Linear(dim, dim, bias=False)\n self.dropout = nn.Dropout(dropout)\n self.scale = 1.0 / math.sqrt(self.head_dim)\n\n inv_freq = 1.0 / (\n rope_base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)\n )\n self.register_buffer(\"inv_freq\", inv_freq, persistent=False)\n\n def _compute_rope_emb(self, seq_len: int, device):\n \"\"\"Compute RoPE embeddings for a sequence length.\"\"\"\n t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)\n freqs = torch.outer(t, self.inv_freq)\n cos = freqs.cos() # (seq_len, head_dim/2)\n sin = freqs.sin() # (seq_len, head_dim/2)\n return cos, sin\n\n def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:\n B, T_q, _ = x_query.shape\n _, T_k, _ = x_kv.shape\n h, d = self.num_heads, self.head_dim\n\n # Project and reshape\n q = self.q_proj(x_query).view(B, T_q, h, d).transpose(1, 2).contiguous()\n k = self.k_proj(x_kv).view(B, T_k, h, d).transpose(1, 2).contiguous()\n v = self.v_proj(x_kv).view(B, T_k, h, d).transpose(1, 2).contiguous()\n\n # Compute RoPE embeddings\n cos_q, sin_q = self._compute_rope_emb(T_q, x_query.device)\n cos_k, sin_k = self._compute_rope_emb(T_k, x_kv.device)\n\n # Apply RoPE using custom CUDA kernel\n q_rope = torch.empty_like(q)\n k_rope = torch.empty_like(k)\n attention_ext.rope_cuda(q, cos_q, sin_q, q_rope)\n attention_ext.rope_cuda(k, cos_k, sin_k, k_rope)\n\n # Compute attention scores using custom CUDA kernel\n attn = torch.empty(B, h, T_q, T_k, device=x_query.device, dtype=x_query.dtype)\n attention_ext.qk_cuda(q_rope, k_rope, attn, B, h, T_q, T_k, d)\n\n # Apply softmax using custom CUDA kernel\n attention_ext.softmax_cuda(attn, B, h, T_q, T_k, self.scale)\n\n # Apply dropout (p=0 in test, so no-op)\n attn = self.dropout(attn)\n\n # Compute attention output using custom CUDA kernel\n attn_out = torch.empty(B, h, T_q, d, device=x_query.device, dtype=x_query.dtype)\n attention_ext.av_cuda(attn, v, attn_out, B, h, T_q, T_k, d)\n\n # Transpose and reshape for output projection\n out = attn_out.transpose(1, 2).contiguous().view(B, T_q, self.dim)\n\n return self.out_proj(out)\n\n\nbatch_size = 4\nseq_len_q = 64\nseq_len_kv = 128\ndim = 256\nnum_heads = 8\ndropout_p = 0.0\n\n\ndef get_inputs():\n x_query = torch.randn(batch_size, seq_len_q, dim)\n x_kv = torch.randn(batch_size, seq_len_kv, dim)\n return [x_query, x_kv]\n\n\ndef get_init_inputs():\n return [dim, num_heads, dropout_p]" }
result ← static_check (ok)
okstatic_check static_check PASSED (with warnings): WARNING: Uses torch computation op: torch.matmul WARNING: Uses torch.nn compute layer (only containers, Parameter, init allowed)
turn 5 · FINAL · 1 tool call(s) · 395.1s turn · 30.4s LLM · tokens in=19,320 · out=3,892 · total=23,212 · 15,524 chars (click to expand)
turn 5395.1s turn · 30.4s LLM · 364.7s tools · tokens in=19,320 · out=3,892 · total=23,212 · FINAL
reasoning
The static check passed with warnings about using torch.matmul and torch.nn layers. These warnings are about the Linear layers (q_proj, k_proj, v_proj, out_proj) which are fine - we're supposed to keep those as PyTorch layers. But wait, looking at my implementation more carefully, I'm not actually using torch.matmul in my ModelNew - I'm using my custom CUDA kernels for QK and AV matmuls. The warning might be from the Model class which I included for reference. Let me now submit the kernel for timing. This initial implementation uses simple element-wise kernels which may not be super fast, but let me get a baseline.
assistantThe static check passed. The warnings are about using PyTorch Linear layers, which is expected since we're replacing only the attention computation, not the projections. Let me submit for timing to see how it performs.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import math\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.cpp_extension import load_inline\n\n# Compile the CUDA kernel\ncuda_source = \"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n// RoPE kernel: applies rotary position embeddings\n// x: (B, H, L, d) - input tensor\n// cos, sin: (L, d/2) - position embeddings\n// out: (B, H, L, d) - output tensor\n__global__ void rope_kernel(\n const float* __restrict__ x_ptr,\n const float* __restrict__ cos_ptr,\n const float* __restrict__ sin_ptr,\n float* __restrict__ out_ptr,\n int B, int H, int L, int d\n) {\n int half_d = d / 2;\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * L * half_d;\n \n if (idx < total) {\n int b = idx / (H * L * half_d);\n int h = (idx / (L * half_d)) % H;\n int l = (idx / half_d) % L;\n int i = idx % half_d;\n \n int base_idx = b * H * L * d + h * L * d + l * d;\n float x1 = x_ptr[base_idx + i];\n float x2 = x_ptr[base_idx + i + half_d];\n float c = cos_ptr[l * half_d + i];\n float s = sin_ptr[l * half_d + i];\n \n out_ptr[base_idx + i] = x1 * c - x2 * s;\n out_ptr[base_idx + i + half_d] = x1 * s + x2 * c;\n }\n}\n\n// Softmax kernel for attention weights\n// attn: (B, H, T_q, T_k) - attention scores\n// scale: scaling factor\n__global__ void softmax_kernel(\n float* __restrict__ attn_ptr,\n int B, int H, int T_q, int T_k,\n float scale\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q;\n \n if (idx < total) {\n int b = idx / (H * T_q);\n int h = (idx / T_q) % H;\n int q_idx = idx % T_q;\n \n int row_offset = b * H * T_q * T_k + h * T_q * T_k + q_idx * T_k;\n \n // Find max for numerical stability\n float max_val = -INFINITY;\n for (int k_idx = 0; k_idx < T_k; k_idx++) {\n float val = attn_ptr[row_offset + k_idx] * scale;\n if (val > max_val) max_val = val;\n }\n \n // Compute exp and sum\n float sum = 0.0f;\n for (int k_idx = 0; k_idx < T_k; k_idx++) {\n float val = expf(attn_ptr[row_offset + k_idx] * scale - max_val);\n attn_ptr[row_offset + k_idx] = val;\n sum += val;\n }\n \n // Normalize\n float inv_sum = 1.0f / sum;\n for (int k_idx = 0; k_idx < T_k; k_idx++) {\n attn_ptr[row_offset + k_idx] *= inv_sum;\n }\n }\n}\n\n// Attention matrix multiplication: Q @ K^T\n__global__ void qk_kernel(\n const float* __restrict__ q_ptr,\n const float* __restrict__ k_ptr,\n float* __restrict__ attn_ptr,\n int B, int H, int T_q, int T_k, int d\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q * T_k;\n \n if (idx < total) {\n int b = idx / (H * T_q * T_k);\n int h = (idx / (T_q * T_k)) % H;\n int q_idx = (idx / T_k) % T_q;\n int kv_idx = idx % T_k;\n \n int q_offset = b * H * T_q * d + h * T_q * d + q_idx * d;\n int k_offset = b * H * T_k * d + h * T_k * d + kv_idx * d;\n \n float sum = 0.0f;\n for (int i = 0; i < d; i++) {\n sum += q_ptr[q_offset + i] * k_ptr[k_offset + i];\n }\n \n attn_ptr[idx] = sum;\n }\n}\n\n// Attention-value multiplication: attn @ V\n__global__ void av_kernel(\n const float* __restrict__ attn_ptr,\n const float* __restrict__ v_ptr,\n float* __restrict__ out_ptr,\n int B, int H, int T_q, int T_k, int d\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * H * T_q * d;\n \n if (idx < total) {\n int b = idx / (H * T_q * d);\n int h = (idx / (T_q * d)) % H;\n int q_idx = (idx / d) % T_q;\n int i = idx % d;\n \n int attn_offset = b * H * T_q * T_k + h * T_q * T_k + q_idx * T_k;\n \n float sum = 0.0f;\n for (int kv_idx = 0; kv_idx < T_k; kv_idx++) {\n int v_offset = b * H * T_k * d + h * T_k * d + kv_idx * d + i;\n sum += attn_ptr[attn_offset + kv_idx] * v_ptr[v_offset];\n }\n \n out_ptr[idx] = sum;\n }\n}\n\n// Host functions\nvoid rope_cuda(\n const torch::Tensor& x,\n const torch::Tensor& cos,\n const torch::Tensor& sin,\n torch::Tensor& out\n) {\n int B = x.size(0);\n int H = x.size(1);\n int L = x.size(2);\n int d = x.size(3);\n \n int total = B * H * L * (d / 2);\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n rope_kernel<<<blocks, threads>>>(\n x.data_ptr<float>(),\n cos.data_ptr<float>(),\n sin.data_ptr<float>(),\n out.data_ptr<float>(),\n B, H, L, d\n );\n}\n\nvoid softmax_cuda(\n torch::Tensor& attn,\n int B, int H, int T_q, int T_k,\n float scale\n) {\n int total = B * H * T_q;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n softmax_kernel<<<blocks, threads>>>(\n attn.data_ptr<float>(),\n B, H, T_q, T_k, scale\n );\n}\n\nvoid qk_cuda(\n const torch::Tensor& q,\n const torch::Tensor& k,\n torch::Tensor& attn,\n int B, int H, int T_q, int T_k, int d\n) {\n int total = B * H * T_q * T_k;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n qk_kernel<<<blocks, threads>>>(\n q.data_ptr<float>(),\n k.data_ptr<float>(),\n attn.data_ptr<float>(),\n B, H, T_q, T_k, d\n );\n}\n\nvoid av_cuda(\n const torch::Tensor& attn,\n const torch::Tensor& v,\n torch::Tensor& out,\n int B, int H, int T_q, int T_k, int d\n) {\n int total = B * H * T_q * d;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n \n av_kernel<<<blocks, threads>>>(\n attn.data_ptr<float>(),\n v.data_ptr<float>(),\n out.data_ptr<float>(),\n B, H, T_q, T_k, d\n );\n}\n\"\"\"\n\ncpp_source = \"\"\"\nvoid rope_cuda(const torch::Tensor& x, const torch::Tensor& cos, const torch::Tensor& sin, torch::Tensor& out);\nvoid softmax_cuda(torch::Tensor& attn, int B, int H, int T_q, int T_k, float scale);\nvoid qk_cuda(const torch::Tensor& q, const torch::Tensor& k, torch::Tensor& attn, int B, int H, int T_q, int T_k, int d);\nvoid av_cuda(const torch::Tensor& attn, const torch::Tensor& v, torch::Tensor& out, int B, int H, int T_q, int T_k, int d);\n\"\"\"\n\n# Load the extension\nattention_ext = load_inline(\n name='attention_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rope_cuda', 'softmax_cuda', 'qk_cuda', 'av_cuda'],\n extra_cuda_cflags=['-O3'],\n verbose=False\n)\n\n\nclass Model(nn.Module):\n \"\"\"\n Single-layer multi-head cross-attention with RoPE on Q and K, attention\n dropout, and output projection\u2014intended as a fusion target for robotics\n stacks (e.g. proprio / action queries attending to vision or memory).\n \"\"\"\n\n def __init__(\n self,\n dim: int,\n num_heads: int,\n dropout: float = 0.0,\n rope_base: float = 10000.0,\n ):\n super().__init__()\n assert dim % num_heads == 0\n self.dim = dim\n self.num_heads = num_heads\n self.head_dim = dim // num_heads\n assert self.head_dim % 2 == 0, \"RoPE requires an even head dimension\"\n\n self.q_proj = nn.Linear(dim, dim, bias=False)\n self.k_proj = nn.Linear(dim, dim, bias=False)\n self.v_proj = nn.Linear(dim, dim, bias=False)\n self.out_proj = nn.Linear(dim, dim, bias=False)\n self.dropout = nn.Dropout(dropout)\n self.scale = 1.0 / math.sqrt(self.head_dim)\n\n inv_freq = 1.0 / (\n rope_base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)\n )\n self.register_buffer(\"inv_freq\", inv_freq, persistent=False)\n\n @staticmethod\n def _rotate_half(x: torch.Tensor) -> torch.Tensor:\n x1, x2 = x.chunk(2, dim=-1)\n return torch.cat([-x2, x1], dim=-1)\n\n def _rope(self, x: torch.Tensor, seq_len: int) -> torch.Tensor:\n \"\"\"Apply RoPE along the sequence dimension (last-but-one after head). x: (B, H, L, d).\"\"\"\n t = torch.arange(seq_len, device=x.device, dtype=self.inv_freq.dtype)\n freqs = torch.outer(t, self.inv_freq)\n emb = torch.cat([freqs, freqs], dim=-1)\n cos = emb.cos().view(1, 1, seq_len, self.head_dim)\n sin = emb.sin().view(1, 1, seq_len, self.head_dim)\n return x * cos + self._rotate_half(x) * sin\n\n def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:\n B, T_q, _ = x_query.shape\n _, T_k, _ = x_kv.shape\n h, d = self.num_heads, self.head_dim\n\n q = self.q_proj(x_query).view(B, T_q, h, d).transpose(1, 2)\n k = self.k_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)\n v = self.v_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)\n\n q = self._rope(q, T_q)\n k = self._rope(k, T_k)\n\n attn = torch.matmul(q, k.transpose(-2, -1)) * self.scale\n attn = F.softmax(attn, dim=-1)\n attn = self.dropout(attn)\n out = torch.matmul(attn, v)\n out = out.transpose(1, 2).contiguous().view(B, T_q, self.dim)\n return self.out_proj(out)\n\n\nclass ModelNew(nn.Module):\n \"\"\"\n Custom CUDA implementation of multi-head cross-attention with RoPE.\n \"\"\"\n\n def __init__(\n self,\n dim: int,\n num_heads: int,\n dropout: float = 0.0,\n rope_base: float = 10000.0,\n ):\n super().__init__()\n assert dim % num_heads == 0\n self.dim = dim\n self.num_heads = num_heads\n self.head_dim = dim // num_heads\n assert self.head_dim % 2 == 0, \"RoPE requires an even head dimension\"\n\n self.q_proj = nn.Linear(dim, dim, bias=False)\n self.k_proj = nn.Linear(dim, dim, bias=False)\n self.v_proj = nn.Linear(dim, dim, bias=False)\n self.out_proj = nn.Linear(dim, dim, bias=False)\n self.dropout = nn.Dropout(dropout)\n self.scale = 1.0 / math.sqrt(self.head_dim)\n\n inv_freq = 1.0 / (\n rope_base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)\n )\n self.register_buffer(\"inv_freq\", inv_freq, persistent=False)\n\n def _compute_rope_emb(self, seq_len: int, device):\n \"\"\"Compute RoPE embeddings for a sequence length.\"\"\"\n t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)\n freqs = torch.outer(t, self.inv_freq)\n cos = freqs.cos() # (seq_len, head_dim/2)\n sin = freqs.sin() # (seq_len, head_dim/2)\n return cos, sin\n\n def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:\n B, T_q, _ = x_query.shape\n _, T_k, _ = x_kv.shape\n h, d = self.num_heads, self.head_dim\n\n # Project and reshape\n q = self.q_proj(x_query).view(B, T_q, h, d).transpose(1, 2).contiguous()\n k = self.k_proj(x_kv).view(B, T_k, h, d).transpose(1, 2).contiguous()\n v = self.v_proj(x_kv).view(B, T_k, h, d).transpose(1, 2).contiguous()\n\n # Compute RoPE embeddings\n cos_q, sin_q = self._compute_rope_emb(T_q, x_query.device)\n cos_k, sin_k = self._compute_rope_emb(T_k, x_kv.device)\n\n # Apply RoPE using custom CUDA kernel\n q_rope = torch.empty_like(q)\n k_rope = torch.empty_like(k)\n attention_ext.rope_cuda(q, cos_q, sin_q, q_rope)\n attention_ext.rope_cuda(k, cos_k, sin_k, k_rope)\n\n # Compute attention scores using custom CUDA kernel\n attn = torch.empty(B, h, T_q, T_k, device=x_query.device, dtype=x_query.dtype)\n attention_ext.qk_cuda(q_rope, k_rope, attn, B, h, T_q, T_k, d)\n\n # Apply softmax using custom CUDA kernel\n attention_ext.softmax_cuda(attn, B, h, T_q, T_k, self.scale)\n\n # Apply dropout (p=0 in test, so no-op)\n attn = self.dropout(attn)\n\n # Compute attention output using custom CUDA kernel\n attn_out = torch.empty(B, h, T_q, d, device=x_query.device, dtype=x_query.dtype)\n attention_ext.av_cuda(attn, v, attn_out, B, h, T_q, T_k, d)\n\n # Transpose and reshape for output projection\n out = attn_out.transpose(1, 2).contiguous().view(B, T_q, self.dim)\n\n return self.out_proj(out)\n\n\nbatch_size = 4\nseq_len_q = 64\nseq_len_kv = 128\ndim = 256\nnum_heads = 8\ndropout_p = 0.0\n\n\ndef get_inputs():\n x_query = torch.randn(batch_size, seq_len_q, dim)\n x_kv = torch.randn(batch_size, seq_len_kv, dim)\n return [x_query, x_kv]\n\n\ndef get_init_inputs():\n return [dim, num_heads, dropout_p]" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 0.53 μs Runtime stats: mean=0.53μs median=?μs std=0.60μs Numerical precision: max_abs_err=5.59e-08 mean_abs_err=7.77e-09 max_rel_err=8.21e-02 Memory: 39.52 MB peak (ref: 39.0 MB, ratio: 1.0134x) Kernel launches: 95 (ref: 145, fusion ratio: 1.5263) SOL score: 0.1125
final submitted kernel (399 lines, 12,557 chars) — full source, untruncated
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.cpp_extension import load_inline

# Compile the CUDA kernel
cuda_source = """
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <cmath>

// RoPE kernel: applies rotary position embeddings
// x: (B, H, L, d) - input tensor
// cos, sin: (L, d/2) - position embeddings
// out: (B, H, L, d) - output tensor
__global__ void rope_kernel(
    const float* __restrict__ x_ptr,
    const float* __restrict__ cos_ptr,
    const float* __restrict__ sin_ptr,
    float* __restrict__ out_ptr,
    int B, int H, int L, int d
) {
    int half_d = d / 2;
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    int total = B * H * L * half_d;
    
    if (idx < total) {
        int b = idx / (H * L * half_d);
        int h = (idx / (L * half_d)) % H;
        int l = (idx / half_d) % L;
        int i = idx % half_d;
        
        int base_idx = b * H * L * d + h * L * d + l * d;
        float x1 = x_ptr[base_idx + i];
        float x2 = x_ptr[base_idx + i + half_d];
        float c = cos_ptr[l * half_d + i];
        float s = sin_ptr[l * half_d + i];
        
        out_ptr[base_idx + i] = x1 * c - x2 * s;
        out_ptr[base_idx + i + half_d] = x1 * s + x2 * c;
    }
}

// Softmax kernel for attention weights
// attn: (B, H, T_q, T_k) - attention scores
// scale: scaling factor
__global__ void softmax_kernel(
    float* __restrict__ attn_ptr,
    int B, int H, int T_q, int T_k,
    float scale
) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    int total = B * H * T_q;
    
    if (idx < total) {
        int b = idx / (H * T_q);
        int h = (idx / T_q) % H;
        int q_idx = idx % T_q;
        
        int row_offset = b * H * T_q * T_k + h * T_q * T_k + q_idx * T_k;
        
        // Find max for numerical stability
        float max_val = -INFINITY;
        for (int k_idx = 0; k_idx < T_k; k_idx++) {
            float val = attn_ptr[row_offset + k_idx] * scale;
            if (val > max_val) max_val = val;
        }
        
        // Compute exp and sum
        float sum = 0.0f;
        for (int k_idx = 0; k_idx < T_k; k_idx++) {
            float val = expf(attn_ptr[row_offset + k_idx] * scale - max_val);
            attn_ptr[row_offset + k_idx] = val;
            sum += val;
        }
        
        // Normalize
        float inv_sum = 1.0f / sum;
        for (int k_idx = 0; k_idx < T_k; k_idx++) {
            attn_ptr[row_offset + k_idx] *= inv_sum;
        }
    }
}

// Attention matrix multiplication: Q @ K^T
__global__ void qk_kernel(
    const float* __restrict__ q_ptr,
    const float* __restrict__ k_ptr,
    float* __restrict__ attn_ptr,
    int B, int H, int T_q, int T_k, int d
) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    int total = B * H * T_q * T_k;
    
    if (idx < total) {
        int b = idx / (H * T_q * T_k);
        int h = (idx / (T_q * T_k)) % H;
        int q_idx = (idx / T_k) % T_q;
        int kv_idx = idx % T_k;
        
        int q_offset = b * H * T_q * d + h * T_q * d + q_idx * d;
        int k_offset = b * H * T_k * d + h * T_k * d + kv_idx * d;
        
        float sum = 0.0f;
        for (int i = 0; i < d; i++) {
            sum += q_ptr[q_offset + i] * k_ptr[k_offset + i];
        }
        
        attn_ptr[idx] = sum;
    }
}

// Attention-value multiplication: attn @ V
__global__ void av_kernel(
    const float* __restrict__ attn_ptr,
    const float* __restrict__ v_ptr,
    float* __restrict__ out_ptr,
    int B, int H, int T_q, int T_k, int d
) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    int total = B * H * T_q * d;
    
    if (idx < total) {
        int b = idx / (H * T_q * d);
        int h = (idx / (T_q * d)) % H;
        int q_idx = (idx / d) % T_q;
        int i = idx % d;
        
        int attn_offset = b * H * T_q * T_k + h * T_q * T_k + q_idx * T_k;
        
        float sum = 0.0f;
        for (int kv_idx = 0; kv_idx < T_k; kv_idx++) {
            int v_offset = b * H * T_k * d + h * T_k * d + kv_idx * d + i;
            sum += attn_ptr[attn_offset + kv_idx] * v_ptr[v_offset];
        }
        
        out_ptr[idx] = sum;
    }
}

// Host functions
void rope_cuda(
    const torch::Tensor& x,
    const torch::Tensor& cos,
    const torch::Tensor& sin,
    torch::Tensor& out
) {
    int B = x.size(0);
    int H = x.size(1);
    int L = x.size(2);
    int d = x.size(3);
    
    int total = B * H * L * (d / 2);
    int threads = 256;
    int blocks = (total + threads - 1) / threads;
    
    rope_kernel<<<blocks, threads>>>(
        x.data_ptr<float>(),
        cos.data_ptr<float>(),
        sin.data_ptr<float>(),
        out.data_ptr<float>(),
        B, H, L, d
    );
}

void softmax_cuda(
    torch::Tensor& attn,
    int B, int H, int T_q, int T_k,
    float scale
) {
    int total = B * H * T_q;
    int threads = 256;
    int blocks = (total + threads - 1) / threads;
    
    softmax_kernel<<<blocks, threads>>>(
        attn.data_ptr<float>(),
        B, H, T_q, T_k, scale
    );
}

void qk_cuda(
    const torch::Tensor& q,
    const torch::Tensor& k,
    torch::Tensor& attn,
    int B, int H, int T_q, int T_k, int d
) {
    int total = B * H * T_q * T_k;
    int threads = 256;
    int blocks = (total + threads - 1) / threads;
    
    qk_kernel<<<blocks, threads>>>(
        q.data_ptr<float>(),
        k.data_ptr<float>(),
        attn.data_ptr<float>(),
        B, H, T_q, T_k, d
    );
}

void av_cuda(
    const torch::Tensor& attn,
    const torch::Tensor& v,
    torch::Tensor& out,
    int B, int H, int T_q, int T_k, int d
) {
    int total = B * H * T_q * d;
    int threads = 256;
    int blocks = (total + threads - 1) / threads;
    
    av_kernel<<<blocks, threads>>>(
        attn.data_ptr<float>(),
        v.data_ptr<float>(),
        out.data_ptr<float>(),
        B, H, T_q, T_k, d
    );
}
"""

cpp_source = """
void rope_cuda(const torch::Tensor& x, const torch::Tensor& cos, const torch::Tensor& sin, torch::Tensor& out);
void softmax_cuda(torch::Tensor& attn, int B, int H, int T_q, int T_k, float scale);
void qk_cuda(const torch::Tensor& q, const torch::Tensor& k, torch::Tensor& attn, int B, int H, int T_q, int T_k, int d);
void av_cuda(const torch::Tensor& attn, const torch::Tensor& v, torch::Tensor& out, int B, int H, int T_q, int T_k, int d);
"""

# Load the extension
attention_ext = load_inline(
    name='attention_ext',
    cpp_sources=cpp_source,
    cuda_sources=cuda_source,
    functions=['rope_cuda', 'softmax_cuda', 'qk_cuda', 'av_cuda'],
    extra_cuda_cflags=['-O3'],
    verbose=False
)


class Model(nn.Module):
    """
    Single-layer multi-head cross-attention with RoPE on Q and K, attention
    dropout, and output projection—intended as a fusion target for robotics
    stacks (e.g. proprio / action queries attending to vision or memory).
    """

    def __init__(
        self,
        dim: int,
        num_heads: int,
        dropout: float = 0.0,
        rope_base: float = 10000.0,
    ):
        super().__init__()
        assert dim % num_heads == 0
        self.dim = dim
        self.num_heads = num_heads
        self.head_dim = dim // num_heads
        assert self.head_dim % 2 == 0, "RoPE requires an even head dimension"

        self.q_proj = nn.Linear(dim, dim, bias=False)
        self.k_proj = nn.Linear(dim, dim, bias=False)
        self.v_proj = nn.Linear(dim, dim, bias=False)
        self.out_proj = nn.Linear(dim, dim, bias=False)
        self.dropout = nn.Dropout(dropout)
        self.scale = 1.0 / math.sqrt(self.head_dim)

        inv_freq = 1.0 / (
            rope_base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)
        )
        self.register_buffer("inv_freq", inv_freq, persistent=False)

    @staticmethod
    def _rotate_half(x: torch.Tensor) -> torch.Tensor:
        x1, x2 = x.chunk(2, dim=-1)
        return torch.cat([-x2, x1], dim=-1)

    def _rope(self, x: torch.Tensor, seq_len: int) -> torch.Tensor:
        """Apply RoPE along the sequence dimension (last-but-one after head). x: (B, H, L, d)."""
        t = torch.arange(seq_len, device=x.device, dtype=self.inv_freq.dtype)
        freqs = torch.outer(t, self.inv_freq)
        emb = torch.cat([freqs, freqs], dim=-1)
        cos = emb.cos().view(1, 1, seq_len, self.head_dim)
        sin = emb.sin().view(1, 1, seq_len, self.head_dim)
        return x * cos + self._rotate_half(x) * sin

    def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:
        B, T_q, _ = x_query.shape
        _, T_k, _ = x_kv.shape
        h, d = self.num_heads, self.head_dim

        q = self.q_proj(x_query).view(B, T_q, h, d).transpose(1, 2)
        k = self.k_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)
        v = self.v_proj(x_kv).view(B, T_k, h, d).transpose(1, 2)

        q = self._rope(q, T_q)
        k = self._rope(k, T_k)

        attn = torch.matmul(q, k.transpose(-2, -1)) * self.scale
        attn = F.softmax(attn, dim=-1)
        attn = self.dropout(attn)
        out = torch.matmul(attn, v)
        out = out.transpose(1, 2).contiguous().view(B, T_q, self.dim)
        return self.out_proj(out)


class ModelNew(nn.Module):
    """
    Custom CUDA implementation of multi-head cross-attention with RoPE.
    """

    def __init__(
        self,
        dim: int,
        num_heads: int,
        dropout: float = 0.0,
        rope_base: float = 10000.0,
    ):
        super().__init__()
        assert dim % num_heads == 0
        self.dim = dim
        self.num_heads = num_heads
        self.head_dim = dim // num_heads
        assert self.head_dim % 2 == 0, "RoPE requires an even head dimension"

        self.q_proj = nn.Linear(dim, dim, bias=False)
        self.k_proj = nn.Linear(dim, dim, bias=False)
        self.v_proj = nn.Linear(dim, dim, bias=False)
        self.out_proj = nn.Linear(dim, dim, bias=False)
        self.dropout = nn.Dropout(dropout)
        self.scale = 1.0 / math.sqrt(self.head_dim)

        inv_freq = 1.0 / (
            rope_base ** (torch.arange(0, self.head_dim, 2).float() / self.head_dim)
        )
        self.register_buffer("inv_freq", inv_freq, persistent=False)

    def _compute_rope_emb(self, seq_len: int, device):
        """Compute RoPE embeddings for a sequence length."""
        t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)
        freqs = torch.outer(t, self.inv_freq)
        cos = freqs.cos()  # (seq_len, head_dim/2)
        sin = freqs.sin()  # (seq_len, head_dim/2)
        return cos, sin

    def forward(self, x_query: torch.Tensor, x_kv: torch.Tensor) -> torch.Tensor:
        B, T_q, _ = x_query.shape
        _, T_k, _ = x_kv.shape
        h, d = self.num_heads, self.head_dim

        # Project and reshape
        q = self.q_proj(x_query).view(B, T_q, h, d).transpose(1, 2).contiguous()
        k = self.k_proj(x_kv).view(B, T_k, h, d).transpose(1, 2).contiguous()
        v = self.v_proj(x_kv).view(B, T_k, h, d).transpose(1, 2).contiguous()

        # Compute RoPE embeddings
        cos_q, sin_q = self._compute_rope_emb(T_q, x_query.device)
        cos_k, sin_k = self._compute_rope_emb(T_k, x_kv.device)

        # Apply RoPE using custom CUDA kernel
        q_rope = torch.empty_like(q)
        k_rope = torch.empty_like(k)
        attention_ext.rope_cuda(q, cos_q, sin_q, q_rope)
        attention_ext.rope_cuda(k, cos_k, sin_k, k_rope)

        # Compute attention scores using custom CUDA kernel
        attn = torch.empty(B, h, T_q, T_k, device=x_query.device, dtype=x_query.dtype)
        attention_ext.qk_cuda(q_rope, k_rope, attn, B, h, T_q, T_k, d)

        # Apply softmax using custom CUDA kernel
        attention_ext.softmax_cuda(attn, B, h, T_q, T_k, self.scale)

        # Apply dropout (p=0 in test, so no-op)
        attn = self.dropout(attn)

        # Compute attention output using custom CUDA kernel
        attn_out = torch.empty(B, h, T_q, d, device=x_query.device, dtype=x_query.dtype)
        attention_ext.av_cuda(attn, v, attn_out, B, h, T_q, T_k, d)

        # Transpose and reshape for output projection
        out = attn_out.transpose(1, 2).contiguous().view(B, T_q, self.dim)

        return self.out_proj(out)


batch_size = 4
seq_len_q = 64
seq_len_kv = 128
dim = 256
num_heads = 8
dropout_p = 0.0


def get_inputs():
    x_query = torch.randn(batch_size, seq_len_q, dim)
    x_kv = torch.randn(batch_size, seq_len_kv, dim)
    return [x_query, x_kv]


def get_init_inputs():
    return [dim, num_heads, dropout_p]