FW-GLM-5-1 · popcorn · pop_l123_st_gpt
correct 20_RotaryPositionEmbeddingBio.py
model
FW-GLM-5-1
turns / max
1 / 1
tool calls
1
tokens (total)
17288
wall clock
310.3s
truncated
yes
backend
cuda fp32
variant
popcorn
runtime
0.01 μs
ref runtime
0.09 μs
speedup
9.21x
started
2026-05-11T10:05:22Z
timingkernelbench.timing
kernel runtime
0.01 μs
ref runtime
0.09 μs
speedup
9.21x
kernel mean / median / std
0.01 / — / 1.89e-04 μs
ref mean / median / std
0.09 / — / 0.01 μs
numerical precisioneval_kernel_against_ref
max abs error
4.77e-07
mean abs error
1.17e-08
max rel error
0.02
mean rel error
6.12e-08
gpu memoryextended_metrics.measure_memory
kernel peak
41.00 MB
ref peak
45.13 MB
kernel / ref
0.91x
kernel launches / fusiontorch.profiler
kernel launches
5
ref launches
45
fusion ratio (ref / kernel)
9.00x
total cuda time
72 μs
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.219
dram util %
21.9%
compute util %
18.3%
bottleneck
memory-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
733.41 GB/s
dram util %
21.9%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
71.2%
bottleneck
memory-bound
dominant pipe %
18.3%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
56.1%
ld sectors / req
4.00
st sectors / req
—
regs / thread
28
smem / block
1024 B
block size
256
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
1169.3%
mio_throttle
499.7%
short_scoreboard
92.2%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
import math


class Model(nn.Module):
    """
    Rotary Position Embedding (RoPE) applied to protein residue sequences.
    Used in ESM-2 and similar protein language models.  Encodes absolute
    position via rotation of query/key pairs in 2-D subspaces, preserving
    relative-position information in dot-product attention.
    """

    def __init__(self, dim, max_seq_len=2048, base=10000.0):
        super().__init__()
        inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
        self.register_buffer("inv_freq", inv_freq)
        self.max_seq_len = max_seq_len
        self.dim = dim

    def _build_cos_sin(self, seq_len: int, device: torch.device):
        t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)
        freqs = torch.outer(t, self.inv_freq)
        emb = torch.cat([freqs, freqs], dim=-1)
        return emb.cos(), emb.sin()

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

    def forward(self, q: torch.Tensor, k: torch.Tensor) -> tuple:
        """
        Args:
            q: (B, H, N, D)  – queries
            k: (B, H, N, D)  – keys
        Returns:
            (q_rot, k_rot) each (B, H, N, D) with RoPE applied
        """
        N = q.shape[2]
        cos, sin = self._build_cos_sin(N, q.device)  # (N, D)
        cos = cos.unsqueeze(0).unsqueeze(0)
        sin = sin.unsqueeze(0).unsqueeze(0)
        q_rot = q * cos + self._rotate_half(q) * sin
        k_rot = k * cos + self._rotate_half(k) * sin
        return q_rot, k_rot


dim = 64
num_heads = 8
seq_len = 256
batch_size = 4


def get_inputs():
    p = popcorn_pri
    bs = p.jitter_int(batch_size)
    nh = p.jitter_int(num_heads)
    sl = p.jitter_int(seq_len)
    q = torch.randn(bs, nh, sl, dim)
    k = torch.randn(bs, nh, sl, dim)
    return [q, k]


def get_init_inputs():
    return [dim]
turn 0 · FINAL · 1 tool call(s) · 310.3s turn · 142.9s LLM · tokens in=1,354 · out=15,934 · total=17,288 · 21,408 chars (click to expand)
turn 0310.3s turn · 142.9s LLM · 167.4s tools · tokens in=1,354 · out=15,934 · total=17,288 · FINAL · reasoning clipped
reasoning
reasoning (truncated 37470 chars): Let me analyze this task. I need to implement a custom CUDA kernel for Rotary Position Embedding (RoPE). The reference implementation: 1. Takes query `q` and key `k` tensors of shape (B, H, N, D) 2. Builds cosine and sine embeddings based on position 3. Applies rotation to both q and k using: - `q_rot = q * cos + rotate_half(q) * sin` - where `rotate_half` splits x into two halves and concatenates [-x2, x1] Let me write an optimized CUDA kernel for this: 1. The `inv_freq` is precomputed and registered as a buffer 2. For each forward pass: - Build cos/sin based on sequence length N - Apply the rotation formula The rotation formula for position p and dimension pair (2i, 2i+1): - For a pair (x_{2i}, x_{2i+1}): - x'_{2i} = x_{2i} * cos(θ_{i,p}) - x_{2i+1} * sin(θ_{i,p}) - x'_{2i+1} = x_{2i} * sin(θ_{i,p}) + x_{2i+1} * cos(θ_{i,p}) The `rotate_half` function takes x and returns [-x2, x1] where x1 is the first half and x2 is the second half. So `q * cos + rotate_half(q) * sin` effectively applies: - For first half: q1 * cos - q2 * sin (since rotate_half gives -q2 for first half) - For second half: q1 * sin + q2 * cos (since rotate_half gives q1 for second half) This is the rotation formula. Let me write an optimized CUDA kernel: ```python import torch import torch.nn as nn from torch.utils.cpp_extension import load_inline # CUDA kernel code cuda_source = """ #include <cuda.h> #include <cuda_runtime.h> #include <math.h> __global__ void rope_kernel( const float* __restrict__ q, const float* __restrict__ k, const float* __restrict__ inv_freq, float* __restrict__ q_out, float* __restrict__ k_out, int batch_size, int num_heads, int seq_len, int dim, int half_dim ) { // Each thread handles one element int idx = blockIdx.x * blockDim.x + threadIdx.x; int total = batch_size * num_heads * seq_len * dim; if (idx >= total) return; // Decode indices int d = idx % dim; int n = (idx / dim) % seq_len; // int h = (idx / dim / seq_len) % num_heads; // Not needed since cos/sin don't depend on h // int b = idx / dim / seq_len / num_heads; // Not needed for same reason // Build frequency for this position and dimension int freq_idx = d % half_dim; float freq = n * inv_freq[freq_idx]; float cos_val = cosf(freq); float sin_val = sinf(freq); // Apply rotation float val_q = q[idx]; float val_k = k[idx]; // Determine the other half's index int other_d; float sign; if (d < half_dim) { // First half: pair with d + half_dim other_d = d + half_dim; sign = -1.0f; // rotate_half gives -x2 for first half // q_rot[idx] = q[idx] * cos + (-q[other]) * sin } else { // Second half: pair with d - half_dim other_d = d - half_dim; sign = 1.0f; // rotate_half gives x1 for second half // q_rot[idx] = q[idx] * cos + q[other] * sin } // Need to compute the other index int other_idx = idx - d + other_d; float val_q_other = q[other_idx]; float val_k_other = k[other_idx]; q_out[idx] = val_q * cos_val + sign * val_q_other * sin_val; k_out[idx] = val_k * cos_val + sign * val_k_other * sin_val; } void rope_forward( const torch::Tensor& q, const torch::Tensor& k, const torch::Tensor& inv_freq, torch::Tensor& q_out, torch::Tensor& k_out ) { int batch_size = q.size(0); int num_heads = q.size(1); int seq_len = q.size(2); int dim = q.size(3); int half_dim = dim / 2; int total = batch_size * num_heads * seq_len * dim; int threads = 256; int blocks = (total + threads - 1) / threads; rope_kernel<<<blocks, threads>>>( q.data_ptr<float>(), k.data_ptr<float>(), inv_freq.data_ptr<float>(), q_out.data_ptr<float>(), k_out.data_ptr<float>(), batch_size, num_heads, seq_len, dim, half_dim ); } """ cpp_source = """ void rope_forward( const torch::Tensor& q, const torch::Tensor& k, const torch::Tensor& inv_freq, torch::Tensor& q_out, torch::Tensor& k_out ); """ rope_extension = load_inline( name='rope_extension', cpp_sources=cpp_source, cuda_sources=cuda_source, functions=['rope_forward'], verbose=True ) class ModelNew(nn.Module): def __init__(self, dim, max_seq_len=2048, base=10000.0): super().__init__() inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim)) self.register_buffer("inv_freq", inv_freq) self.max_seq_len = max_seq_len self.dim = dim def forward(self, q: torch.Tensor, k: torch.Tensor) -> tuple: q_out = torch.empty_like(q) k_out = torch.empty_like(k) rope_extension.rope_forward(q, k, self.inv_freq, q_out, k_out) return q_out, k_out ``` Wait, I need to think more carefully about memory access patterns. The current approach has each thread access two elements that might be far apart in memory, which is not ideal. Let me reconsider. A better approach might be to: 1. Precompute cos and sin values on GPU 2. Then apply the rotation Or, process pairs of elements together in one thread. Actually, let me look at the memory layout more carefully: - q shape: (B, H, N, D) - For a given (b, h, n), all D elements are contiguous in memory - For rotation, we need pairs (d, d+half_dim) for d < half_dim A better kernel would process pairs together: ```cuda __global__ void rope_kernel( const float* __restrict__ q, const float* __restrict__ k, const float* __restrict__ inv_freq, float* __restrict__ q_out, float* __restrict__ k_out, int batch_size, int num_heads, int seq_len, int dim ) { int half_dim = dim / 2; // Each thread handles one pair (d, d+half_dim) for one (b, h, n) int idx = blockIdx.x * blockDim.x + threadIdx.x; int total_pairs = batch_size * num_heads * seq_len * half_dim; if (idx >= total_pairs) return; // Decode indices int d = idx % half_dim; int n = (idx / half_dim) % seq_len; int h = (idx / half_dim / seq_len) % num_heads; int b = idx / half_dim / seq_len / num_heads; // Base index for this (b, h, n) int base = ((b * num_heads + h) * seq_len + n) * dim; // Indices for the pair int idx1 = base + d; // first half int idx2 = base + d + half_dim; // second half // Build frequency for this position and dimension pair float freq = n * inv_freq[d]; float cos_val = cosf(freq); float sin_val = sinf(freq); // Apply rotation float q1 = q[idx1], q2 = q[idx2]; float k1 = k[idx1], k2 = k[idx2]; // q1' = q1 * cos - q2 * sin // q2' = q1 * sin + q2 * cos q_out[idx1] = q1 * cos_val - q2 * sin_val; q_out[idx2] = q1 * sin_val + q2 * cos_val; k_out[idx1] = k1 * cos_val - k2 * sin_val; k_out[idx2] = k1 * sin_val + k2 * cos_val; } ``` This is cleaner - each thread handles a pair, reads both q and k values, and writes both outputs. But wait, I can also precompute cos/sin once and reuse. Let me think about optimizations: 1. Precompute cos/sin embeddings once per forward call - this avoids redundant trig computation 2. Use vectorized loads/stores (float4) for better memory throughput 3. Process multiple pairs per thread for better ILP Actually, let me look at the problem again. The cos and sin values depend only on n (position) and d (dimension), not on b or h. So we can precompute them and reuse. Let me write a version that: 1. Precomputes cos/sin in a separate kernel (or on CPU if small enough) 2. Uses a main kernel that applies the rotation with the precomputed values But for simplicity and to ensure correctness first, let me start with the basic version and optimize later. Actually, the simplest approach that should work and be faster than PyTorch: ```python import torch import torch.nn as nn from torch.utils.cpp_ext ...[ omitted 37470 characters ]... (base ** (torch.arange(0, dim, 2).float() / dim)) self.register_buffer("inv_freq", inv_freq) self.max_seq_len = max_seq_len self.dim = dim def forward(self, q: torch.Tensor, k: torch.Tensor) -> tuple: q_out = torch.empty_like(q) k_out = torch.empty_like(k) rope_extension.rope_forward(q, k, self.inv_freq, q_out, k_out) return q_out, k_out ``` This looks good. Let me think if there are any other optimizations I can add... One potential optimization: use more threads per block for better occupancy. 256 threads per block is standard, but we could try 512 or 1024 if the registers allow. Another optimization: process multiple pairs per thread. For example, each thread could handle 4 or 8 pairs, which reduces the number of threads and increases work per thread. Let me try a version where each thread handles 4 pairs: ```cuda __global__ void rope_kernel_unrolled( const float* __restrict__ q, const float* __restrict__ k, const float* __restrict__ inv_freq, float* __restrict__ q_out, float* __restrict__ k_out, const int batch_size, const int num_heads, const int seq_len, const int dim ) { const int half_dim = dim / 2; const int pairs_per_thread = 4; const int idx = blockIdx.x * blockDim.x + threadIdx.x; const int total_threads = batch_size * num_heads * seq_len * (half_dim / pairs_per_thread); if (idx >= total_threads) return; const int d_base = (idx % (half_dim / pairs_per_thread)) * pairs_per_thread; const int n = (idx / (half_dim / pairs_per_thread)) % seq_len; const int h = (idx / (half_dim / pairs_per_thread) / seq_len) % num_heads; const int b = idx / (half_dim / pairs_per_thread) / seq_len / num_heads; const int base = ((b * num_heads + h) * seq_len + n) * dim; #pragma unroll for (int i = 0; i < pairs_per_thread; i++) { const int d = d_base + i; const int idx1 = base + d; const int idx2 = base + d + half_dim; const float freq = static_cast<float>(n) * inv_freq[d]; const float cos_val = cosf(freq); const float sin_val = sinf(freq); const float q1 = q[idx1]; const float q2 = q[idx2]; const float k1 = k[idx1]; const float k2 = k[idx2]; q_out[idx1] = q1 * cos_val - q2 * sin_val; q_out[idx2] = q1 * sin_val + q2 * cos_val; k_out[idx1] = k1 * cos_val - k2 * sin_val; k_out[idx2] = k1 * sin_val + k2 * cos_val; } } ``` This version processes 4 pairs per thread, which should reduce kernel launch overhead and improve instruction-level parallelism. But wait, for half_dim = 32, pairs_per_thread = 4 means we process all 32 pairs with 8 threads per (b, h, n). This might reduce parallelism too much for small problem sizes. Let me stick with the simpler 1 pair per thread version for now. It should be fast enough. Actually, let me reconsider. The problem has: - batch_size = 4 - num_heads = 8 - seq_len = 256 - dim = 64, half_dim = 32 Total pairs = 4 * 8 * 256 * 32 = 262,144 With 256 threads per block and 1 pair per thread, we need 1024 blocks. This is a decent amount of parallelism. With 4 pairs per thread, we'd need 256 blocks. This might be too few for some GPUs. Let me stick with 1 pair per thread for simplicity and good parallelism. Alright, I think my implementation is ready. Let me finalize it. Actually, I just realized I need to handle the case where `inv_freq` might not be on the GPU. Let me add a check: ```python def forward(self, q: torch.Tensor, k: torch.Tensor) -> tuple: inv_freq = self.inv_freq.to(q.device) q_out = torch.empty_like(q) k_out = torch.empty_like(k) rope_extension.rope_forward(q, k, inv_freq, q_out, k_out) return q_out, k_out ``` This ensures inv_freq is on the same device as q and k. Actually, since inv_freq is registered as a buffer, it should be moved automatically when the model is moved. But adding `.to(q.device)` is a safe practice. Let me finalize: ```python import torch import torch.nn as nn from torch.utils.cpp_extension import load_inline cuda_source = r""" #include <cuda.h> #include <cuda_runtime.h> #include <ATen/cuda/CUDAContext.h> #include <cmath> __global__ void rope_kernel( const float* __restrict__ q, const float* __restrict__ k, const float* __restrict__ inv_freq, float* __restrict__ q_out, float* __restrict__ k_out, const int batch_size, const int num_heads, const int seq_len, const int dim ) { const int half_dim = dim / 2; const int idx = blockIdx.x * blockDim.x + threadIdx.x; const int total_pairs = batch_size * num_heads * seq_len * half_dim; if (idx >= total_pairs) return; const int d = idx % half_dim; const int n = (idx / half_dim) % seq_len; const int h = (idx / half_dim / seq_len) % num_heads; const int b = idx / half_dim / seq_len / num_heads; const int base = ((b * num_heads + h) * seq_len + n) * dim; const int idx1 = base + d; const int idx2 = base + d + half_dim; const float freq = static_cast<float>(n) * inv_freq[d]; const float cos_val = cosf(freq); const float sin_val = sinf(freq); const float q1 = q[idx1]; const float q2 = q[idx2]; const float k1 = k[idx1]; const float k2 = k[idx2]; q_out[idx1] = q1 * cos_val - q2 * sin_val; q_out[idx2] = q1 * sin_val + q2 * cos_val; k_out[idx1] = k1 * cos_val - k2 * sin_val; k_out[idx2] = k1 * sin_val + k2 * cos_val; } void rope_forward( const torch::Tensor& q, const torch::Tensor& k, const torch::Tensor& inv_freq, torch::Tensor& q_out, torch::Tensor& k_out ) { const int batch_size = static_cast<int>(q.size(0)); const int num_heads = static_cast<int>(q.size(1)); const int seq_len = static_cast<int>(q.size(2)); const int dim = static_cast<int>(q.size(3)); const int half_dim = dim / 2; const int total_pairs = batch_size * num_heads * seq_len * half_dim; const int threads = 256; const int blocks = (total_pairs + threads - 1) / threads; rope_kernel<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>( q.data_ptr<float>(), k.data_ptr<float>(), inv_freq.data_ptr<float>(), q_out.data_ptr<float>(), k_out.data_ptr<float>(), batch_size, num_heads, seq_len, dim ); } """ cpp_source = r""" void rope_forward( const torch::Tensor& q, const torch::Tensor& k, const torch::Tensor& inv_freq, torch::Tensor& q_out, torch::Tensor& k_out ); """ rope_extension = load_inline( name='rope_extension', cpp_sources=cpp_source, cuda_sources=cuda_source, functions=['rope_forward'], extra_cuda_cflags=['-O3'], verbose=False ) class ModelNew(nn.Module): def __init__(self, dim, max_seq_len=2048, base=10000.0): super().__init__() inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim)) self.register_buffer("inv_freq", inv_freq) self.max_seq_len = max_seq_len self.dim = dim def forward(self, q: torch.Tensor, k: torch.Tensor) -> tuple: inv_freq = self.inv_freq.to(q.device) q_out = torch.empty_like(q) k_out = torch.empty_like(k) rope_extension.rope_forward(q, k, inv_freq, q_out, k_out) return q_out, k_out ``` This looks complete. Let me double-check the logic: 1. In the kernel, each thread handles one pair (d, d+half_dim) for one (b, h, n). 2. The cos and sin values are computed directly from n * inv_freq[d]. 3. The rotation formula is applied: q_rot[d] = q[d] * cos - q[d+half_dim] * sin, q_rot[d+half_dim] = q[d] * sin + q[d+half_dim] * cos. 4. The same is done for k. This matches the reference implementation. I'm confident this implementation is correct and should be faster than the PyTorch reference. Let me submit it!
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\ncuda_source = r\"\"\"\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <cmath>\n\n__global__ void rope_kernel(\n const float* __restrict__ q,\n const float* __restrict__ k,\n const float* __restrict__ inv_freq,\n float* __restrict__ q_out,\n float* __restrict__ k_out,\n const int batch_size,\n const int num_heads,\n const int seq_len,\n const int dim\n) {\n const int half_dim = dim / 2;\n \n const int idx = blockIdx.x * blockDim.x + threadIdx.x;\n const int total_pairs = batch_size * num_heads * seq_len * half_dim;\n \n if (idx >= total_pairs) return;\n \n const int d = idx % half_dim;\n const int n = (idx / half_dim) % seq_len;\n const int h = (idx / half_dim / seq_len) % num_heads;\n const int b = idx / half_dim / seq_len / num_heads;\n \n const int base = ((b * num_heads + h) * seq_len + n) * dim;\n const int idx1 = base + d;\n const int idx2 = base + d + half_dim;\n \n const float freq = static_cast<float>(n) * inv_freq[d];\n const float cos_val = cosf(freq);\n const float sin_val = sinf(freq);\n \n const float q1 = q[idx1];\n const float q2 = q[idx2];\n const float k1 = k[idx1];\n const float k2 = k[idx2];\n \n q_out[idx1] = q1 * cos_val - q2 * sin_val;\n q_out[idx2] = q1 * sin_val + q2 * cos_val;\n k_out[idx1] = k1 * cos_val - k2 * sin_val;\n k_out[idx2] = k1 * sin_val + k2 * cos_val;\n}\n\nvoid rope_forward(\n const torch::Tensor& q,\n const torch::Tensor& k,\n const torch::Tensor& inv_freq,\n torch::Tensor& q_out,\n torch::Tensor& k_out\n) {\n const int batch_size = static_cast<int>(q.size(0));\n const int num_heads = static_cast<int>(q.size(1));\n const int seq_len = static_cast<int>(q.size(2));\n const int dim = static_cast<int>(q.size(3));\n \n const int half_dim = dim / 2;\n const int total_pairs = batch_size * num_heads * seq_len * half_dim;\n const int threads = 256;\n const int blocks = (total_pairs + threads - 1) / threads;\n \n rope_kernel<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>(\n q.data_ptr<float>(),\n k.data_ptr<float>(),\n inv_freq.data_ptr<float>(),\n q_out.data_ptr<float>(),\n k_out.data_ptr<float>(),\n batch_size,\n num_heads,\n seq_len,\n dim\n );\n}\n\"\"\"\n\ncpp_source = r\"\"\"\nvoid rope_forward(\n const torch::Tensor& q,\n const torch::Tensor& k,\n const torch::Tensor& inv_freq,\n torch::Tensor& q_out,\n torch::Tensor& k_out\n);\n\"\"\"\n\nrope_extension = load_inline(\n name='rope_extension',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['rope_forward'],\n extra_cuda_cflags=['-O3'],\n verbose=False\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, dim, max_seq_len=2048, base=10000.0):\n super().__init__()\n inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))\n self.register_buffer(\"inv_freq\", inv_freq)\n self.max_seq_len = max_seq_len\n self.dim = dim\n\n def forward(self, q: torch.Tensor, k: torch.Tensor) -> tuple:\n inv_freq = self.inv_freq.to(q.device)\n q_out = torch.empty_like(q)\n k_out = torch.empty_like(k)\n rope_extension.rope_forward(q, k, inv_freq, q_out, k_out)\n return q_out, k_out" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 0.01 μs Runtime stats: mean=0.01μs median=?μs std=0.00μs Numerical precision: max_abs_err=4.77e-07 mean_abs_err=1.17e-08 max_rel_err=1.78e-02 Memory: 41.0 MB peak (ref: 45.13 MB, ratio: 0.9086x) Kernel launches: 5 (ref: 45, fusion ratio: 9.0) SOL score: 0.2189
final submitted kernel (116 lines, 3,380 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

cuda_source = r"""
#include <cuda.h>
#include <cuda_runtime.h>
#include <ATen/cuda/CUDAContext.h>
#include <cmath>

__global__ void rope_kernel(
    const float* __restrict__ q,
    const float* __restrict__ k,
    const float* __restrict__ inv_freq,
    float* __restrict__ q_out,
    float* __restrict__ k_out,
    const int batch_size,
    const int num_heads,
    const int seq_len,
    const int dim
) {
    const int half_dim = dim / 2;
    
    const int idx = blockIdx.x * blockDim.x + threadIdx.x;
    const int total_pairs = batch_size * num_heads * seq_len * half_dim;
    
    if (idx >= total_pairs) return;
    
    const int d = idx % half_dim;
    const int n = (idx / half_dim) % seq_len;
    const int h = (idx / half_dim / seq_len) % num_heads;
    const int b = idx / half_dim / seq_len / num_heads;
    
    const int base = ((b * num_heads + h) * seq_len + n) * dim;
    const int idx1 = base + d;
    const int idx2 = base + d + half_dim;
    
    const float freq = static_cast<float>(n) * inv_freq[d];
    const float cos_val = cosf(freq);
    const float sin_val = sinf(freq);
    
    const float q1 = q[idx1];
    const float q2 = q[idx2];
    const float k1 = k[idx1];
    const float k2 = k[idx2];
    
    q_out[idx1] = q1 * cos_val - q2 * sin_val;
    q_out[idx2] = q1 * sin_val + q2 * cos_val;
    k_out[idx1] = k1 * cos_val - k2 * sin_val;
    k_out[idx2] = k1 * sin_val + k2 * cos_val;
}

void rope_forward(
    const torch::Tensor& q,
    const torch::Tensor& k,
    const torch::Tensor& inv_freq,
    torch::Tensor& q_out,
    torch::Tensor& k_out
) {
    const int batch_size = static_cast<int>(q.size(0));
    const int num_heads = static_cast<int>(q.size(1));
    const int seq_len = static_cast<int>(q.size(2));
    const int dim = static_cast<int>(q.size(3));
    
    const int half_dim = dim / 2;
    const int total_pairs = batch_size * num_heads * seq_len * half_dim;
    const int threads = 256;
    const int blocks = (total_pairs + threads - 1) / threads;
    
    rope_kernel<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>(
        q.data_ptr<float>(),
        k.data_ptr<float>(),
        inv_freq.data_ptr<float>(),
        q_out.data_ptr<float>(),
        k_out.data_ptr<float>(),
        batch_size,
        num_heads,
        seq_len,
        dim
    );
}
"""

cpp_source = r"""
void rope_forward(
    const torch::Tensor& q,
    const torch::Tensor& k,
    const torch::Tensor& inv_freq,
    torch::Tensor& q_out,
    torch::Tensor& k_out
);
"""

rope_extension = load_inline(
    name='rope_extension',
    cpp_sources=cpp_source,
    cuda_sources=cuda_source,
    functions=['rope_forward'],
    extra_cuda_cflags=['-O3'],
    verbose=False
)

class ModelNew(nn.Module):
    def __init__(self, dim, max_seq_len=2048, base=10000.0):
        super().__init__()
        inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2).float() / dim))
        self.register_buffer("inv_freq", inv_freq)
        self.max_seq_len = max_seq_len
        self.dim = dim

    def forward(self, q: torch.Tensor, k: torch.Tensor) -> tuple:
        inv_freq = self.inv_freq.to(q.device)
        q_out = torch.empty_like(q)
        k_out = torch.empty_like(k)
        rope_extension.rope_forward(q, k, inv_freq, q_out, k_out)
        return q_out, k_out