reasoning
reasoning (truncated 1424 chars): Let me analyze this task. I need to implement a custom CUDA kernel for a multi-head cross-attention mechanism with RoPE (Rotary Position Embeddings).
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 it's 0.0 in the test)
7. Aggregate values
8. Output projection
For a CUDA kernel optimization, I should fuse several operations:
- The linear projections could be kept as PyTorch operations since they're memory-bound and well-optimized
- The reshape/transpose operations
- RoPE application
- Attention computation (matmul, scale, softmax)
- Value aggregation
Let me think about what can be fused effectively:
1. **RoPE fusion**: Applying RoPE to Q and K involves computing sin/cos embeddings and applying rotations. This is element-wise and can be fused.
2. **Attention computation**: The scaled dot-product attention can be fused into a single kernel for better memory locality.
For optimal performance, I could:
1. Keep the linear projections as PyTorch (they're well-optimized)
2. Fuse the reshape + RoPE + attention into custom kernels
Let me design a kernel that fuses:
- Q projection output -> reshape -> RoPE
- K projection output -> reshape -> RoPE
- V projection output -> reshape
- Attention: Q @ K^T * scale -> softmax -> @ V
- Output reshape and final projection
Actually, for maximum performance, the typical approach for attention is to use flash attention style - loading tiles of Q, K, V and computing attention locally.
Given the complexity, let me start with a simpler approach:
1. Use PyTorch for the linear projections (they're already well-optimized)
2. Implement a fused kernel for: RoPE application on Q and K
3. Implement a fused kernel for the attention computation (QK^T * scale + softmax + @V)
I can combine the attention computation into a single kernel that handles the matrix multiplication, scaling, and softmax in one pass to minimize memory traffic between operations.
The most impactful optimizations are fusing RoPE with the attention mechanism and implementing flash attention for better memory efficiency. Since I'm working with moderate dimensions (B=4, T_q=64, T_kv=128, dim=256, heads=8), a straightforward flash attention approach should provide solid performance without excessive complexity.
I'll design two kernels: one for RoPE that applies rotary positional embeddings to both Q and K simultaneously, and another for the scaled dot-product attention computation with softmax normalization. This modular approach keeps the implementation clean while achieving the core performance benefits. For the attention kernel, I should consider flash attention principles - loading Q tiles and full K,V sequences to minimize memory traffic. With T_q=64 and T_kv=128, the attention matrix is manageable in shared memory. I can precompute sin/cos values on the host and pass them to the kernels. The forward pass applies RoPE to Q and K, then computes scaled dot-product attention with a causal mask.
# Flash attention style: compute attention in tiles
# For simplicity, use standard attention with optimizations
# Output projection
out = self.out_proj(out)
return out
```
Let me write a proper fused implementation:
```python
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 kernels
cuda_source = """
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <vector>
#include <cmath>
template <typename scalar_t>
__device__ __forceinline__ scalar_t rotate_half(scalar_t x1, scalar_t x2) {
return -x2; // First half becomes -x2
}
template <typename scalar_t>
__global__ void rope_kernel(
scalar_t* __restrict__ q,
const scalar_t* __restrict__ k,
scalar_t* __restrict__ q_out,
scalar_t* __restrict__ k_out,
const float* __restrict__ cos_q,
const float* __restrict__ sin_q,
const float* __restrict__ cos_k,
const float* __restrict__ sin_k,
int B, int H, int T_q, int T_k, int head_dim
) {
// Apply RoPE to Q and K
// Input Q: (B, H, T_q, head_dim)
// Input K: (B, H, T_k, head_dim)
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int total_q = B * H * T_q * head_dim;
int total_k = B * H * T_k * head_dim;
// Process Q
if (idx < total_q) {
int d = idx % head_dim;
int t = (idx / head_dim) % T_q;
int h = (idx / (head_dim * T_q)) % H;
int b = idx / (head_dim * T_q * H);
int half_dim = head_dim / 2;
float c = cos_q[t * head_dim + d];
float s = sin_q[t * head_dim + d];
int input_idx = b * H * T_q * head_dim + h * T_q * head_dim + t * head_dim + d;
int pair_idx = b * H * T_q * head_dim + h * T_q * head_dim + t * head_dim + (d < half_dim ? d + half_dim : d - half_dim);
float x = q[input_idx];
float x_pair = q[pair_idx];
if (d < half_dim) {
q_out[input_idx] = x * c - x_pair * s;
} else {
q_out[input_idx] = x_pair * c + x * s;
}
}
// Process K (different thread block)
if (idx < total_k) {
int k_idx = idx;
int d = k_idx % head_dim;
int t = (k_idx / head_dim) % T_k;
int h = (k_idx / (head_dim * T_k)) % H;
int b = k_idx / (head_dim * T_k * H);
int half_dim = head_dim / 2;
float c = cos_k[t * head_dim + d];
float s = sin_k[t * head_dim + d];
int input_idx = b * H * T_k * head_dim + h * T_k * head_dim + t * head_dim + d;
int pair_idx = b * H * T_k * head_dim + h * T_k * head_dim + t * head_dim + (d < half_dim ? d + half_dim : d - half_dim);
float x = k[input_idx];
float x_pair = k[pair_idx];
if (d < half_dim) {
k_out[input_idx] = x * c - x_pair * s;
} else {
k_out[input_idx] = x_pair * c + x * s;
}
}
}
"""
The rope kernel handles position encoding by pairing each dimension with its rotated counterpart. For each position, it applies the cos/sin values to create the rotary embedding. The indexing carefully navigates the 4D tensor layout to find both the current element and its paired dimension for the rotation operation. I need to reconsider the approach here. The goal is to apply RoPE (Rotary Position Embeddings) to Q and K tensors, but I'm trying to handle both in a single kernel when they have different sequence lengths. It would be cleaner to split this into separate kernels for Q and K processing.
Let me refactor with dedicated kernels for each tensor. For each head, iterate through positions and dimensions in groups of 2. At each position, compute the rotation angles from the cosine and sine tables, then apply the rotation formula to the query vector: x[0] = x0 * c - x1 * s and x[1] = x0 * s + x1 * c. Each thread handles one rotation pair across all heads and positions.
The softmax operation normalizes attention scores with a two-pass approach. First, each thread computes a local maximum within its assigned dimension range, then uses warp reduction to find the global maximum across threads. In the second pass, each thread calculates its local sum of exponentials using the shared maximum, again reducing across warps to get the total sum for normalization. After computing the exponentials and sum, each thread writes its normalized values back to the output.
The attention kernel then processes the query, key, and value tensors. Each block handles one batch, head, and query position combination. It loads the query values into registers, then iterates over key positions in chunks to compute the scaled dot-product attention scores.
For each chunk of key positions, it accumulates the dot product while trackin
...[ omitted 1424 characters ]...
q.scalar_type(), "rope_kernel", ([&] {
rope_kernel<<<blocks, threads>>>(
q.data_ptr<scalar_t>(),
k.data_ptr<scalar_t>(),
q_out.data_ptr<scalar_t>(),
k_out.data_ptr<scalar_t>(),
cos_q.data_ptr<float>(),
sin_q.data_ptr<float>(),
cos_k.data_ptr<float>(),
sin_k.data_ptr<float>(),
batch, heads, seq_len_q, seq_len_kv, head_dim);
}));
return {q_out, k_out};
}
std::vector<at::Tensor> attention_cuda(
const at::Tensor& q,
const at::Tensor& k,
const at::Tensor& v,
float scale
) {
int64_t B = q.size(0);
int64_t H = q.size(1);
int64_t T_q = q.size(2);
int64_t T_k = k.size(2);
int64_t head_dim = q.size(3);
auto out = at::empty({B, H, T_q, head_dim}, q.options());
dim3 grid(B * H, T_q);
dim3 block(head_dim);
AT_DISPATCH_FLOATING_TYPES_AND_HALF(q.scalar_type(), "attention_kernel", ([&] {
attention_kernel<<<grid, block>>>(
q.data_ptr<scalar_t>(),
k.data_ptr<scalar_t>(),
v.data_ptr<scalar_t>(),
out.data_ptr<scalar_t>(),
scale,
B, H, T_q, T_k, head_dim);
}));
return out;
}
"""
The forward pass combines RoPE positional encoding with scaled dot-product attention. After applying rotary embeddings to queries and keys, I compute attention weights via matrix multiplication with the scaling factor, then apply softmax and aggregate values. The output is reshaped back to the original dimensions before the final projection.
I'm reconsidering the kernel design to handle the full attention computation more efficiently. Rather than launching multiple separate kernels for each operation, I should integrate the matrix multiplication and softmax into a single kernel that processes the attention mechanism end-to-end.
A flash attention approach would tile the query positions and process them in parallel, keeping the softmax computation local to each thread. For the dimensions here—T_q=64, T_kv=128, head_dim=32—standard attention with online softmax should work well without excessive memory pressure.
I need to ensure the kernel design correctly handles both the Q and K tensors with their different sequence lengths, and properly implements the online softmax technique to avoid numerical instability. I need to handle the half-dimension rotation separately. For each query element, I retrieve its value and locate its pair at the opposite half of the dimension. The rotation formula applies the cosine and sine weights differently depending on whether we're in the first or second half of the head dimension.
After processing the queries, I move on to keys with the same approach. The K indices map to the appropriate batch, head, time, and dimension positions, then I retrieve the key value and its corresponding pair from the opposite half of the dimension.
Now I'm reconsidering the overall approach. Rather than launching a single kernel for everything, I should break this into separate stages: the linear projections through standard matmul operations, then a fused kernel for RoPE application to both Q and K, followed by a flash attention kernel that handles the QK^T computation with softmax and the weighted V aggregation. Finally, the output projection would be another matmul.
This modular strategy lets me test each component independently and identify where issues arise. I'll build out a proper implementation now.
```python
import math
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
# CUDA kernel code
cuda_source = r"""
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
// RoPE kernel - applies rotary positional embeddings
template <typename scalar_t>
__global__ void rope_kernel(
const scalar_t* __restrict__ x,
scalar_t* __restrict__ out,
const float* __restrict__ cos,
const float* __restrict__ sin,
const int B, const int H, const int T, const int head_dim
) {
const int idx = blockIdx.x * blockDim.x + threadIdx.x;
const int total = B * H * T * head_dim;
if (idx >= total) return;
const int d = idx % head_dim;
const int t = (idx / head_dim) % T;
const int h = (idx / (head_dim * T)) % H;
const int b = idx / (head_dim * T * H);
const int half_dim = head_dim / 2;
const float c = cos[t * head_dim + d];
const float s = sin[t * head_dim + d];
const scalar_t x_val = x[idx];
// Compute the rotated pair
int d_pair;
scalar_t x_pair;
if (d < half_dim) {
d_pair = d + half_dim;
x_pair = x[b * H * T * head_dim + h * T * head_dim + t * head_dim + d_pair];
// rotate_half gives [-x2, x1], so pair is -x[d+half]
// Result: x * cos + rotate_half * sin
// For d < half: result = x[d] * c + (-x[d+half]) * s
out[idx] = x_val * c - x_pair * s;
} else {
d_pair = d - half_dim;
x_pair = x[b * H * T * head_dim + h * T * head_dim + t * head_dim + d_pair];
// For d >= half: result = x[d] * c + x[d-half] * sin
// Because rotate_half for second half gives x[d-half]
out[idx] = x_val * c + x_pair * s;
}
}
```
The kernel determines which dimension we're processing and applies the rotation by pairing each element with its counterpart from the opposite half of the dimension. For the first half, I pair with elements from the second half and apply a negative rotation. For the second half, I reverse the pairing and apply a positive rotation, ensuring the rotary embedding properly encodes positional information across all dimensions. (attention.size(0) * attention.size(1) * attention.size(2) + threads - 1) / threads;
AT_DISPATCH_FLOATING_TYPES_AND_HALF(input.scalar_type(), "attention_softmax_kernel", ([&] {
attention_softmax_kernel<scalar_t><<<blocks, threads>>>(
attention.data_ptr<scalar_t>(),
attention.size(2),
total_elements);
}));
// Output projection
auto out = at::matmul(attention, v);
out = out.transpose(1, 2).contiguous();
out = out.view({B, T_q, H * head_dim});
// Final projection
out = at::linear(out, out_proj_weight);
return out;
}
""", I'm computing the grid and block dimensions for the attention softmax kernel, then dispatching based on the input type. After applying softmax normalization, I project the attention output through a linear layer, reshape it back to the original batch and sequence dimensions, and apply the final output projection.
But wait—this implementation doesn't match the reference structure. The forward pass should handle Q and K projections from separate inputs, apply RoPE separately to each, then compute attention between them before aggregating with V. I need to rewrite this to properly handle the cross-attention pattern with distinct query and key/value streams. Let me reconsider the approach. The reference implementation applies separate projections to each input, reshapes tensors into head format, applies RoPE encoding, then computes scaled dot-product attention before projecting the output. For optimization, I can keep the initial projections in PyTorch since they're already efficient, but fuse the RoPE encoding with attention computation to reduce memory bandwidth. The key is handling the reshape and transpose operations carefully—transforming from (B, T, dim) to (B, H, T, head_dim) by splitting the dimension and transposing the sequence and head axes.
The fused kernel should take Q and K in head format with precomputed cos/sin tables for RoPE, then compute attention by rotating queries and keys, calculating scores with scaling and softmax, and aggregating values. Since dropout is disabled, I can skip that entirely. The implementation combines the reshape/transpose, RoPE encoding, attention computation, and output reshape into a single fused operation.
call → submit_kernel
callsubmit_kernel
{
"kernel_code": "import math\nimport torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n# Compile fused CUDA kernels\nfused_kernel = load_inline(\n name='fused_attention_rope',\n cpp_sources='',\n cuda_sources=r'''\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <c10/cuda/CUDAException.h>\n\n// Fused RoPE + Attention kernel\n// Computes: apply RoPE to Q and K, then scaled dot-product attention with softmax\ntemplate <typename scalar_t>\n__global__ void fused_rope_attention_kernel(\n const scalar_t* __restrict__ q_in, // (B, T_q, H, head_dim) - after reshape\n const scalar_t* __restrict__ k_in, // (B, T_k, H, head_dim) - after reshape \n const scalar_t* __restrict__ v_in, // (B, T_k, H, head_dim) - after reshape\n scalar_t* __restrict__ out, // (B, T_q, H, head_dim)\n const float* __restrict__ cos_q, // (T_q, head_dim)\n const float* __restrict__ sin_q, // (T_q, head_dim)\n const float* __restrict__ cos_k, // (T_k, head_dim)\n const float* __restrict__ sin_k, // (T_k, head_dim)\n const float scale,\n const int B, const int H, const int T_q, const int T_k, const int head_dim\n) {\n // Each block handles one (batch, head, query_pos) tuple\n const int batch = blockIdx.x;\n const int head = blockIdx.y;\n const int q_pos = blockIdx.z;\n \n const int half_dim = head_dim / 2;\n const int thread_idx = threadIdx.x;\n \n // Shared memory for Q values (after RoPE)\n extern __shared__ float shared_mem[];\n float* q_rope = shared_mem; // head_dim elements\n float* scores = &shared_mem[head_dim]; // T_k elements (partial per thread)\n \n // Load and apply RoPE to Q for this position\n // Q layout: (B, T_q, H, head_dim)\n for (int d = thread_idx; d < head_dim; d += blockDim.x) {\n const int q_idx = batch * T_q * H * head_dim + q_pos * H * head_dim + head * head_dim + d;\n const float q_val = static_cast<float>(q_in[q_idx]);\n \n const float c = cos_q[q_pos * head_dim + d];\n const float s = sin_q[q_pos * head_dim + d];\n \n // Apply RoPE rotation\n int d_pair = (d < half_dim) ? (d + half_dim) : (d - half_dim);\n const int q_pair_idx = batch * T_q * H * head_dim + q_pos * H * head_dim + head * head_dim + d_pair;\n const float q_pair = static_cast<float>(q_in[q_pair_idx]);\n \n if (d < half_dim) {\n q_rope[d] = q_val * c - q_pair * s;\n } else {\n q_rope[d] = q_val * c + q_pair * s;\n }\n }\n __syncthreads();\n \n // Compute attention scores for this Q position against all K positions\n // Using online softmax for numerical stability\n \n float max_score = -INFINITY;\n float sum_exp = 0.0f;\n \n // Process K positions in chunks to fit in shared memory\n const int CHUNK_SIZE = 32; // Process 32 K positions at a time\n \n for (int k_chunk_start = 0; k_chunk_start < T_k; k_chunk_start += CHUNK_SIZE) {\n const int k_chunk_end = min(k_chunk_start + CHUNK_SIZE, T_k);\n \n // First pass: compute scores and find max\n for (int k_pos = k_chunk_start + thread_idx; k_pos < k_chunk_end; k_pos += blockDim.x) {\n float dot = 0.0f;\n \n // Compute Q @ K^T with RoPE applied to K\n for (int d = 0; d < head_dim; d++) {\n const int k_idx = batch * T_k * H * head_dim + k_pos * H * head_dim + head * head_dim + d;\n float k_val = static_cast<float>(k_in[k_idx]);\n \n // Apply RoPE to K on-the-fly\n const float c = cos_k[k_pos * head_dim + d];\n const float s = sin_k[k_pos * head_dim + d];\n \n int d_pair = (d < half_dim) ? (d + half_dim) : (d - half_dim);\n const int k_pair_idx = batch * T_k * H * head_dim + k_pos * H * head_dim + head * head_dim + d_pair;\n float k_pair = static_cast<float>(k_in[k_pair_idx]);\n \n float k_rope;\n if (d < half_dim) {\n k_rope = k_val * c - k_pair * s;\n } else {\n k_rope = k_val * c + k_pair * s;\n }\n \n dot += q_rope[d] * k_rope;\n }\n \n float score = dot * scale;\n scores[k_pos - k_chunk_start] = score;\n \n if (score > max_score) {\n max_score = score;\n }\n }\n __syncthreads();\n \n // Block-level reduction for max\n __shared__ float block_max;\n if (thread_idx == 0) {\n block_max = -INFINITY;\n for (int i = 0; i < k_chunk_end - k_chunk_start; i++) {\n if (scores[i] > block_max) block_max = scores[i];\n }\n }\n __syncthreads();\n \n if (block_max > max_score) max_score = block_max;\n }\n \n // Second pass: compute exp(score - max) and sum\n float local_sum = 0.0f;\n \n for (int k_chunk_start = 0; k_chunk_start < T_k; k_chunk_start += CHUNK_SIZE) {\n const int k_chunk_end = min(k_chunk_start + CHUNK_SIZE, T_k);\n \n for (int k_pos = k_chunk_start + thread_idx; k_pos < k_chunk_end; k_pos += blockDim.x) {\n // Recompute score (or store in shared memory if space allows)\n float dot = 0.0f;\n for (int d = 0; d < head_dim; d++) {\n const int k_idx = batch * T_k * H * head_dim + k_pos * H * head_dim + head * head_dim + d;\n float k_val = static_cast<float>(k_in[k_idx]);\n \n const float c = cos_k[k_pos * head_dim + d];\n const float s = sin_k[k_pos * head_dim + d];\n \n int d_pair = (d < half_dim) ? (d + half_dim) : (d - half_dim);\n const int k_pair_idx = batch * T_k * H * head_dim + k_pos * H * head_dim + head * head_dim + d_pair;\n float k_pair = static_cast<float>(k_in[k_pair_idx]);\n \n float k_rope;\n if (d < half_dim) {\n k_rope = k_val * c - k_pair * s;\n } else {\n k_rope = k_val * c + k_pair * s;\n }\n \n dot += q_rope[d] * k_rope;\n }\n float score = dot * scale;\n float exp_score = expf(score - max_score);\n scores[k_pos - k_chunk_start] = exp_score;\n local_sum += exp_score;\n }\n __syncthreads();\n }\n \n // Reduce sum across threads\n __shared__ float total_sum;\n if (thread_idx == 0) {\n total_sum = 0.0f;\n }\n __syncthreads();\n \n // Use atomic add for simplicity\n atomicAdd(&total_sum, local_sum);\n __syncthreads();\n \n // Third pass: compute weighted sum of V\n // Each thread computes partial output for different output dimensions\n for (int d_out = thread_idx; d_out < head_dim; d_out += blockDim.x) {\n float out_val = 0.0f;\n \n for (int k_chunk_start = 0; k_chunk_start < T_k; k_chunk_start += CHUNK_SIZE) {\n const int k_chunk_end = min(k_chunk_start + CHUNK_SIZE, T_k);\n \n for (int k_pos = k_chunk_start + thread_idx; k_pos < k_chunk_end; k_pos += blockDim.x) {\n // Recompute exp_score or retrieve from shared memory\n float dot = 0.0f;\n for (int d = 0; d < head_dim; d++) {\n const int k_idx = batch * T_k * H * head_dim + k_pos * H * head_dim + head * head_dim + d;\n float k_val = static_cast<float>(k_in[k_idx]);\n \n const float c = cos_k[k_pos * head_dim + d];\n const float s = sin_k[k_pos * head_dim + d];\n \n int d_pair = (d < half_dim) ? (d + half_dim) : (d - half_dim);\n const int k_pair_idx = batch * T_k * H * head_dim + k_pos * H * head_dim + head * head_dim + d_pair;\n float k_pair = static_cast<float>(k_in[k_pair_idx]);\n \n float k_rope;\n if (d < half_dim) {\n k_rope = k_val * c - k_pair * s;\n } else {\n k_rope = k_val * c + k_pair * s;\n }\n \n dot += q_rope[d] * k_rope;\n }\n float score = dot * scale;\n float exp_score = expf(score - max_score);\n float attn_weight = exp_score / total_sum;\n \n const int v_idx = batch * T_k * H * head_dim + k_pos * H * head_dim + head * head_dim + d_out;\n out_val += attn_weight * static_cast<float>(v_in[v_idx]);\n }\n }\n \n // Reduce across threads for this output dimension\n const int out_idx = batch * T_q * H * head_dim + q_pos * H * head_dim + head * head_dim + d_out;\n out[out_idx] = static_cast<scalar_t>(out_val);\n }\n}\n\n// Simpler, more efficient fused kernel - processes one attention head per block\ntemplate <typename scalar_t>\n__global__ void fused_attention_kernel(\n const scalar_t* __restrict__ q, // (B, H, T_q, head_dim) - already with RoPE\n const scalar_t* __restrict__ k, // (B, H, T_k, head_dim) - already with RoPE\n const scalar_t* __restrict__ v, // (B, H, T_k, head_dim)\n scalar_t* __restrict__ out, // (B, H, T_q, head_dim)\n const float scale,\n const int B, const int H, const int T_q, const int T_k, const int head_dim\n) {\n // Each block handles one (batch, head, query_pos)\n const int batch = blockIdx.x / H;\n const int head = blockIdx.x % H;\n const int q_pos = blockIdx.y;\n const int tid = threadIdx.x;\n const int block_size = blockDim.x;\n \n // Shared memory for Q values and attention weights\n extern __shared__ char smem[];\n float* q_local = reinterpret_cast<float*>(smem);\n float* attn_weights = q_local + head_dim;\n \n // Load Q values for this position (coalesced read)\n const int q_base = batch * H * T_q * head_dim + head * T_q * head_dim + q_pos * head_dim;\n for (int d = tid; d < head_dim; d += block_size) {\n q_local[d] = static_cast<float>(q[q_base + d]);\n }\n __syncthreads();\n \n // Compute attention scores: Q @ K^T\n // Each thread handles multiple K positions\n float max_score = -INFINITY;\n \n for (int k_pos = tid; k_pos < T_k; k_pos += block_size) {\n float dot = 0.0f;\n const int k_base = batch * H * T_k * head_dim + head * T_k * head_dim + k_pos * head_dim;\n \n for (int d = 0; d < head_dim; d++) {\n dot += q_local[d] * static_cast<float>(k[k_base + d]);\n }\n \n float score = dot * scale;\n attn_weights[k_pos] = score;\n if (score > max_score) max_score = score;\n }\n \n // Warp-level reduce for max\n #pragma unroll\n for (int offset = 16; offset > 0; offset >>= 1) {\n float other = __shfl_down_sync(0xffffffff, max_score, offset);\n if (other > max_score) max_score = other;\n }\n \n // Broadcast max from lane 0\n max_score = __shfl_sync(0xffffffff, max_score, 0);\n \n // Handle multiple warps\n __shared__ float global_max;\n if (tid == 0) global_max = -INFINITY;\n __syncthreads();\n \n if ((tid & 31) == 0) { // One thread per warp\n atomicMax(reinterpret_cast<int*>(&global_max), __float_as_int(max_score));\n }\n __syncthreads();\n \n // Compute exp(score - max) and sum\n float sum_exp = 0.0f;\n for (int k_pos = tid; k_pos < T_k; k_pos += block_size) {\n float exp_val = expf(attn_weights[k_pos] - global_max);\n attn_weights[k_pos] = exp_val;\n sum_exp += exp_val;\n }\n \n // Warp-level reduce for sum\n #pragma unroll\n for (int offset = 16; offset > 0; offset >>= 1) {\n sum_exp += __shfl_down_sync(0xffffffff, sum_exp, offset);\n }\n \n __shared__ float global_sum;\n if (tid == 0) global_sum = 0.0f;\n __syncthreads();\n \n if ((tid & 31) == 0) {\n atomicAdd(&global_sum, sum_exp);\n }\n __syncthreads();\n \n // Compute weighted sum of V\n for (int d = tid; d < head_dim; d += block_size) {\n float out_val = 0.0f;\n \n for (int k_pos = 0; k_pos < T_k; k_pos++) {\n float attn = attn_weights[k_pos] / global_sum;\n const int v_base = batch * H * T_k * head_dim + head * T_k * head_dim + k_pos * head_dim;\n out_val += attn * static_cast<float>(v[v_base + d]);\n }\n \n const int out_idx = batch * H * T_q * head_dim + head * T_q * head_dim + q_pos * head_dim + d;\n out[out_idx] = static_cast<scalar_t>(out_val);\n }\n}\n\n// RoPE kernel - applies rotary positional embeddings\ntemplate <typename scalar_t>\n__global__ void rope_kernel(\n const scalar_t* __restrict__ x,\n scalar_t* __restrict__ x_out,\n const float* __restrict__ cos,\n const float* __restrict__ sin,\n const int total_elements,\n const int T,\n const int H,\n const int head_dim\n) {\n const int idx = blockIdx.x * blockDim.x + threadIdx.x;\n if (idx >= total_elements) return;\n \n const int d = idx % head_dim;\n const int t = (idx / head_dim) % T;\n const int head = (idx / (head_dim * T)) % H;\n const int batch = idx / (head_dim * T * H);\n \n const int half_dim = head_dim / 2;\n const float c = cos[t * head_dim + d];\n const float s = sin[t * head_dim + d];\n \n int d_pair = (d < half_dim) ? (d + half_dim) : (d - half_dim);\n const int idx_pair = batch * head_dim * T * H + head * head_dim * T + t * head_dim + d_pair;\n \n const scalar_t x_val = x[idx];\n const scalar_t x_pair = x[idx_pair];\n \n if (d < half_dim) {\n x_out[idx] = static_cast<scalar_t>(static_cast<float>(x_val) * c - static_cast<float>(x_pair) * s);\n } else {\n x_out[idx] = static_cast<scalar_t>(static_cast<float>(x_val) * c + static_cast<float>(x_pair) * s);\n }\n}\n\n// Host function to compute RoPE frequencies and apply\nstd::tuple<at::Tensor, at::Tensor> compute_rope_freqs(int seq_len, int head_dim, float rope_base, at::Device device) {\n auto inv_freq = 1.0f / torch::pow(rope_base, torch::arange(0, head_dim, 2, torch::TensorOptions().dtype(torch::kFloat32).device(device)) / head_dim);\n auto t = torch::arange(seq_len, torch::TensorOptions().dtype(torch::kFloat32).device(device));\n auto freqs = torch::outer(t, inv_freq);\n auto emb = torch::cat({freqs, freqs}, -1);\n return {emb.cos(), emb.sin()};\n}\n\n// Combined RoPE + Attention forward\nat::Tensor fused_rope_attention_forward(\n const at::Tensor& q_proj, // (B, T_q, H, head_dim)\n const at::Tensor& k_proj, // (B, T_k, H, head_dim)\n const at::Tensor& v_proj, // (B, T_k, H, head_dim)\n const at::Tensor& cos_q,\n const at::Tensor& sin_q,\n const at::Tensor& cos_k,\n const at::Tensor& sin_k,\n float scale\n) {\n const int B = q_proj.size(0);\n const int T_q = q_proj.size(1);\n const int H = q_proj.size(2);\n const int head_dim = q_proj.size(3);\n const int T_k = k_proj.size(1);\n \n // Output tensor\n auto out = at::empty({B, T_q, H, head_dim}, q_proj.options());\n \n // Apply RoPE to Q\n auto q_rope = at::empty_like(q_proj);\n const int total_q = B * T_q * H * head_dim;\n const int threads = 256;\n const int blocks_q = (total_q + threads - 1) / threads;\n \n AT_DISPATCH_FLOATING_TYPES_AND_HALF(q_proj.scalar_type(), \"rope_q\", ([&] {\n rope_kernel<scalar_t><<<blocks_q, threads>>>(\n q_proj.data_ptr<scalar_t>(),\n q_rope.data_ptr<scalar_t>(),\n cos_q.data_ptr<float>(),\n sin_q.data_ptr<float>(),\n total_q, T_q, H, head_dim\n );\n }));\n \n // Apply RoPE to K\n auto k_rope = at::empty_like(k_proj);\n const int total_k = B * T_k * H * head_dim;\n const int blocks_k = (total_k + threads - 1) / threads;\n \n AT_DISPATCH_FLOATING_TYPES_AND_HALF(k_proj.scalar_type(), \"rope_k\", ([&] {\n rope_kernel<scalar_t><<<blocks_k, threads>>>(\n k_proj.data_ptr<scalar_t>(),\n k_rope.data_ptr<scalar_t>(),\n cos_k.data_ptr<float>(),\n sin_k.data_ptr<float>(),\n total_k, T_k, H, head_dim\n );\n }));\n \n // Fused attention kernel\n // Grid: (B * H) blocks in x, T_q blocks in y\n dim3 grid(B * H, T_q);\n dim3 block(min(256, max(32, T_k)));\n const int shared_mem = (head_dim + T_k) * sizeof(float);\n \n AT_DISPATCH_FLOATING_TYPES_AND_HALF(q_proj.scalar_type(), \"fused_attention\", ([&] {\n fused_attention_kernel<scalar_t><<<grid, block, shared_mem>>>(\n q_rope.data_ptr<scalar_t>(),\n k_rope.data_ptr<scalar_t>(),\n v_proj.data_ptr<scalar_t>(),\n out.data_ptr<scalar_t>(),\n scale,\n B, H, T_q, T_k, head_dim\n );\n }));\n \n return out;\n}\n''',\n functions=['fused_rope_attention_forward'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n)\n\nclass ModelNew(nn.Module):\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 # Keep projections as standard PyTorch layers (well-optimized)\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 self.rope_base = rope_base\n\n # Precompute RoPE frequencies\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_embeddings(self, seq_len: int, device: torch.device) -> tuple:\n \"\"\"Compute cos and sin for RoPE.\"\"\"\n t = torch.arange(seq_len, device=device, dtype=self.inv_freq.dtype)\n freqs = torch.outer(t, self.inv_freq.to(device))\n emb = torch.cat([freqs, freqs], dim=-1)\n return emb.cos(), emb.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 # Linear projections\n q = self.q_proj(x_query)\n k = self.k_proj(x_kv)\n v = self.v_proj(x_kv)\n\n # Reshape to (B, T, H, head_dim) for kernel\n q = q.view(B, T_q, H, d)\n k = k.view(B, T_k, H, d)\n v = v.view(B, T_k, H, d)\n\n # Compute RoPE embeddings\n cos_q, sin_q = self._compute_rope_embeddings(T_q, x_query.device)\n cos_k, sin_k = self._compute_rope_embeddings(T_k, x_kv.device)\n\n # Fused RoPE + Attention\n out = fused_kernel.fused_rope_attention_forward(\n q, k, v, cos_q, sin_q, cos_k, sin_k, self.scale\n )\n\n # Reshape back: (B, T_q, H, head_dim) -> (B, T_q, dim)\n out = out.reshape(B, T_q, self.dim)\n\n # Output projection\n return self.out_proj(out)"
}