call → compile_kernel
callcompile_kernel
{
"kernel_code": "import math\nimport torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n# CUDA kernel for multi-head cross-attention with RoPE\ncuda_source = r\"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n// Constants\n#define WARP_SIZE 32\n#define MAX_THREADS_PER_BLOCK 1024\n\n// Helper for RoPE rotation\n__device__ __forceinline__ void apply_rope_element(\n float& out_re, float& out_im,\n float in_re, float in_im,\n float cos_theta, float sin_theta\n) {\n out_re = in_re * cos_theta - in_im * sin_theta;\n out_im = in_re * sin_theta + in_im * cos_theta;\n}\n\n// Kernel to compute RoPE frequencies\n__global__ void compute_rope_freqs_kernel(\n float* __restrict__ cos_vals,\n float* __restrict__ sin_vals,\n const float* __restrict__ inv_freq,\n int seq_len, int head_dim\n) {\n int pos = blockIdx.x * blockDim.x + threadIdx.x;\n int half_dim = head_dim / 2;\n \n if (pos < seq_len * half_dim) {\n int t = pos / half_dim;\n int d = pos % half_dim;\n \n float theta = (float)t * inv_freq[d];\n float cos_theta = cosf(theta);\n float sin_theta = sinf(theta);\n \n // Store for both halves of head_dim\n cos_vals[t * head_dim + d] = cos_theta;\n cos_vals[t * head_dim + d + half_dim] = cos_theta;\n sin_vals[t * head_dim + d] = sin_theta;\n sin_vals[t * head_dim + d + half_dim] = sin_theta;\n }\n}\n\n// Fused Q/K projection + reshape + RoPE kernel\ntemplate <int HEAD_DIM, int BLOCK_SIZE>\n__global__ void qk_proj_rope_kernel(\n const float* __restrict__ input,\n const float* __restrict__ weight,\n float* __restrict__ output,\n const float* __restrict__ cos_vals,\n const float* __restrict__ sin_vals,\n int B, int T, int H, int dim\n) {\n int batch_idx = blockIdx.x;\n int head_idx = blockIdx.y;\n int token_idx = blockIdx.z;\n \n int tid = threadIdx.x;\n int head_offset = head_idx * HEAD_DIM;\n \n // Shared memory for weight tile and cos/sin\n __shared__ float weight_tile[HEAD_DIM * HEAD_DIM];\n __shared__ float cos_tile[HEAD_DIM];\n __shared__ float sin_tile[HEAD_DIM];\n \n // Load cos/sin values for this position\n if (tid < HEAD_DIM) {\n cos_tile[tid] = cos_vals[token_idx * HEAD_DIM + tid];\n sin_tile[tid] = sin_vals[token_idx * HEAD_DIM + tid];\n }\n \n // Load weights for this head\n // weight shape: (dim, dim), we need weight[head_offset:head_offset+HEAD_DIM, :]\n for (int i = tid; i < HEAD_DIM * HEAD_DIM; i += BLOCK_SIZE) {\n int row = i / HEAD_DIM;\n int col = i % HEAD_DIM;\n // weight is (out_dim, in_dim) = (dim, dim)\n // We want weight[head_offset + row, col] for head_idx\n // But wait, for head_idx, we need weight[head_idx*HEAD_DIM + row, :] actually\n // No, weight is (dim, dim), each head gets HEAD_DIM output dimensions\n // So for linear: output = input @ weight.T\n // output[b, head_idx*HEAD_DIM + i] = sum_j input[b, j] * weight[head_idx*HEAD_DIM + i, j]\n // We're computing for one head, so we need weight rows [head_offset : head_offset + HEAD_DIM]\n weight_tile[i] = weight[(head_offset + row) * dim + col];\n }\n \n __syncthreads();\n \n // Load input values for this token\n // input shape: (B, T, dim)\n // Each thread will compute partial sums\n float result[HEAD_DIM];\n \n // Initialize result\n for (int i = 0; i < HEAD_DIM; i++) {\n result[i] = 0.0f;\n }\n \n // Compute projection: output = input @ weight.T\n // For each input dimension\n for (int k = tid; k < dim; k += BLOCK_SIZE) {\n float in_val = input[batch_idx * T * dim + token_idx * dim + k];\n for (int i = 0; i < HEAD_DIM; i++) {\n result[i] += in_val * weight_tile[i * dim + k];\n }\n }\n \n // Reduce within block\n __shared__ float shared_results[BLOCK_SIZE * HEAD_DIM];\n \n for (int i = 0; i < HEAD_DIM; i++) {\n shared_results[tid * HEAD_DIM + i] = result[i];\n }\n __syncthreads();\n \n // Tree reduction\n for (int stride = BLOCK_SIZE / 2; stride > 0; stride /= 2) {\n if (tid < stride) {\n for (int i = 0; i < HEAD_DIM; i++) {\n shared_results[tid * HEAD_DIM + i] += shared_results[(tid + stride) * HEAD_DIM + i];\n }\n }\n __syncthreads();\n }\n \n // Apply RoPE and store result\n // output shape: (B, H, T, HEAD_DIM)\n if (tid == 0) {\n for (int i = 0; i < HEAD_DIM; i++) {\n float val = shared_results[i];\n // RoPE: rotate pairs\n int half_dim = HEAD_DIM / 2;\n float rotated;\n if (i < half_dim) {\n // x1 * cos - x2 * sin\n int pair_idx = i + half_dim;\n float x2 = shared_results[pair_idx];\n rotated = val * cos_tile[i] - x2 * sin_tile[i];\n } else {\n // x1 * sin + x2 * cos\n int pair_idx = i - half_dim;\n float x1 = shared_results[pair_idx];\n rotated = val * cos_tile[i] + x1 * sin_tile[i];\n }\n output[batch_idx * H * T * HEAD_DIM + head_idx * T * HEAD_DIM + token_idx * HEAD_DIM + i] = rotated;\n }\n }\n}\n\n// V projection kernel (no RoPE needed)\ntemplate <int HEAD_DIM, int BLOCK_SIZE>\n__global__ void v_proj_kernel(\n const float* __restrict__ input,\n const float* __restrict__ weight,\n float* __restrict__ output,\n int B, int T, int H, int dim\n) {\n int batch_idx = blockIdx.x;\n int head_idx = blockIdx.y;\n int token_idx = blockIdx.z;\n \n int tid = threadIdx.x;\n int head_offset = head_idx * HEAD_DIM;\n \n // Shared memory for weight tile\n __shared__ float weight_tile[HEAD_DIM * HEAD_DIM];\n \n // Load weights for this head\n for (int i = tid; i < HEAD_DIM * HEAD_DIM; i += BLOCK_SIZE) {\n int row = i / HEAD_DIM;\n int col = i % HEAD_DIM;\n weight_tile[i] = weight[(head_offset + row) * dim + col];\n }\n \n __syncthreads();\n \n // Compute projection\n float result[HEAD_DIM];\n for (int i = 0; i < HEAD_DIM; i++) {\n result[i] = 0.0f;\n }\n \n for (int k = tid; k < dim; k += BLOCK_SIZE) {\n float in_val = input[batch_idx * T * dim + token_idx * dim + k];\n for (int i = 0; i < HEAD_DIM; i++) {\n result[i] += in_val * weight_tile[i * dim + k];\n }\n }\n \n // Reduce\n __shared__ float shared_results[BLOCK_SIZE * HEAD_DIM];\n for (int i = 0; i < HEAD_DIM; i++) {\n shared_results[tid * HEAD_DIM + i] = result[i];\n }\n __syncthreads();\n \n for (int stride = BLOCK_SIZE / 2; stride > 0; stride /= 2) {\n if (tid < stride) {\n for (int i = 0; i < HEAD_DIM; i++) {\n shared_results[tid * HEAD_DIM + i] += shared_results[(tid + stride) * HEAD_DIM + i];\n }\n }\n __syncthreads();\n }\n \n // Store result\n if (tid == 0) {\n for (int i = 0; i < HEAD_DIM; i++) {\n output[batch_idx * H * T * HEAD_DIM + head_idx * T * HEAD_DIM + token_idx * HEAD_DIM + i] = shared_results[i];\n }\n }\n}\n\n// Attention kernel: compute attention scores, softmax, and aggregate values\ntemplate <int HEAD_DIM, int BLOCK_SIZE>\n__global__ void attention_kernel(\n const float* __restrict__ q, // (B, H, T_q, HEAD_DIM)\n const float* __restrict__ k, // (B, H, T_k, HEAD_DIM)\n const float* __restrict__ v, // (B, H, T_k, HEAD_DIM)\n float* __restrict__ attn_out, // (B, T_q, H, HEAD_DIM)\n float scale,\n int B, int H, int T_q, int T_k\n) {\n int batch_idx = blockIdx.x;\n int head_idx = blockIdx.y;\n int query_idx = blockIdx.z;\n int tid = threadIdx.x;\n \n int q_offset = batch_idx * H * T_q * HEAD_DIM + head_idx * T_q * HEAD_DIM + query_idx * HEAD_DIM;\n \n // Load query vector into registers\n float q_vec[HEAD_DIM];\n for (int i = 0; i < HEAD_DIM; i++) {\n q_vec[i] = q[q_offset + i];\n }\n \n // Shared memory for k, v tiles and attention scores\n extern __shared__ float shared_mem[];\n float* k_tile = shared_mem; // T_k x HEAD_DIM\n float* v_tile = &k_tile[T_k * HEAD_DIM]; // T_k x HEAD_DIM\n float* scores = &v_tile[T_k * HEAD_DIM]; // T_k\n float* max_vals = &scores[T_k]; // for softmax\n float* sum_vals = &max_vals[1]; // for softmax\n \n // Compute attention scores in tiles\n float max_score = -INFINITY;\n float sum_exp = 0.0f;\n \n // Process T_k keys in tiles\n for (int k_tile_start = 0; k_tile_start < T_k; k_tile_start += BLOCK_SIZE) {\n int k_tile_size = min(BLOCK_SIZE, T_k - k_tile_start);\n \n // Load k and v tiles cooperatively\n for (int i = tid; i < k_tile_size * HEAD_DIM; i += BLOCK_SIZE) {\n int local_k = i / HEAD_DIM;\n int local_d = i % HEAD_DIM;\n int global_k = k_tile_start + local_k;\n int k_offset = batch_idx * H * T_k * HEAD_DIM + head_idx * T_k * HEAD_DIM + global_k * HEAD_DIM;\n k_tile[i] = k[k_offset + local_d];\n v_tile[i] = v[k_offset + local_d];\n }\n \n __syncthreads();\n \n // Compute dot products for this tile\n for (int k_idx = tid; k_idx < k_tile_size; k_idx += BLOCK_SIZE) {\n float dot = 0.0f;\n for (int d = 0; d < HEAD_DIM; d++) {\n dot += q_vec[d] * k_tile[k_idx * HEAD_DIM + d];\n }\n scores[k_idx] = dot * scale;\n max_score = max(max_score, scores[k_idx]);\n }\n \n __syncthreads();\n }\n \n // Block reduction for max\n __shared__ float block_max[BLOCK_SIZE];\n block_max[tid] = (tid < T_k) ? scores[tid] : -INFINITY;\n __syncthreads();\n \n for (int stride = BLOCK_SIZE / 2; stride > 0; stride /= 2) {\n if (tid < stride) {\n block_max[tid] = max(block_max[tid], block_max[tid + stride]);\n }\n __syncthreads();\n }\n \n max_score = block_max[0];\n \n // Compute exp(scores - max) and sum\n float local_sum = 0.0f;\n for (int k_idx = tid; k_idx < T_k; k_idx += BLOCK_SIZE) {\n scores[k_idx] = expf(scores[k_idx] - max_score);\n local_sum += scores[k_idx];\n }\n \n // Block reduction for sum\n __shared__ float block_sum[BLOCK_SIZE];\n block_sum[tid] = local_sum;\n __syncthreads();\n \n for (int stride = BLOCK_SIZE / 2; stride > 0; stride /= 2) {\n if (tid < stride) {\n block_sum[tid] += block_sum[tid + stride];\n }\n __syncthreads();\n }\n \n sum_exp = block_sum[0];\n float inv_sum = 1.0f / sum_exp;\n \n // Normalize scores\n for (int k_idx = tid; k_idx < T_k; k_idx += BLOCK_SIZE) {\n scores[k_idx] *= inv_sum;\n }\n \n __syncthreads();\n \n // Aggregate values\n float out_vec[HEAD_DIM];\n for (int i = 0; i < HEAD_DIM; i++) {\n out_vec[i] = 0.0f;\n }\n \n for (int k_idx = 0; k_idx < T_k; k_idx++) {\n float attn_weight = scores[k_idx];\n for (int d = 0; d < HEAD_DIM; d++) {\n out_vec[d] += attn_weight * v_tile[k_idx * HEAD_DIM + d];\n }\n }\n \n // Store result (transposed: B, T_q, H, HEAD_DIM)\n if (tid == 0) {\n int out_offset = batch_idx * T_q * H * HEAD_DIM + query_idx * H * HEAD_DIM + head_idx * HEAD_DIM;\n for (int i = 0; i < HEAD_DIM; i++) {\n attn_out[out_offset + i] = out_vec[i];\n }\n }\n}\n\n// Output projection kernel\ntemplate <int HEAD_DIM, int BLOCK_SIZE>\n__global__ void out_proj_kernel(\n const float* __restrict__ input, // (B, T_q, H, HEAD_DIM)\n const float* __restrict__ weight, // (dim, dim)\n float* __restrict__ output, // (B, T_q, dim)\n int B, int T_q, int H, int dim\n) {\n int batch_idx = blockIdx.x;\n int token_idx = blockIdx.y;\n \n int tid = threadIdx.x;\n \n // Each thread handles a subset of output dimensions\n // output = input.reshape(B, T_q, dim) @ weight.T\n \n // Load input for this token\n __shared__ float in_vec_shared[256]; // max dim\n \n for (int i = tid; i < dim; i += BLOCK_SIZE) {\n int head_idx = i / HEAD_DIM;\n int head_dim_idx = i % HEAD_DIM;\n int in_offset = batch_idx * T_q * H * HEAD_DIM + token_idx * H * HEAD_DIM + head_idx * HEAD_DIM + head_dim_idx;\n in_vec_shared[i] = input[in_offset];\n }\n __syncthreads();\n \n // Compute output = input @ weight.T\n // Each thread computes multiple output elements\n for (int out_idx = tid; out_idx < dim; out_idx += BLOCK_SIZE) {\n float sum = 0.0f;\n for (int in_idx = 0; in_idx < dim; in_idx++) {\n sum += in_vec_shared[in_idx] * weight[out_idx * dim + in_idx];\n }\n output[batch_idx * T_q * dim + token_idx * dim + out_idx] = sum;\n }\n}\n\n// Host functions\nvoid compute_rope_freqs(\n at::Tensor cos_vals,\n at::Tensor sin_vals,\n at::Tensor inv_freq,\n int seq_len,\n int head_dim\n) {\n int half_dim = head_dim / 2;\n int total = seq_len * half_dim;\n const int threads = 256;\n const int blocks = (total + threads - 1) / threads;\n \n compute_rope_freqs_kernel<<<blocks, threads>>>(\n cos_vals.data_ptr<float>(),\n sin_vals.data_ptr<float>(),\n inv_freq.data_ptr<float>(),\n seq_len, head_dim\n );\n}\n\nvoid qk_proj_rope(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor output,\n at::Tensor cos_vals,\n at::Tensor sin_vals,\n int B, int T, int H, int dim, int head_dim\n) {\n const int threads = 256;\n dim3 blocks(B, H, T);\n \n if (head_dim == 32) {\n qk_proj_rope_kernel<32, threads><<<blocks, threads>>>(\n input.data_ptr<float>(),\n weight.data_ptr<float>(),\n output.data_ptr<float>(),\n cos_vals.data_ptr<float>(),\n sin_vals.data_ptr<float>(),\n B, T, H, dim\n );\n } else if (head_dim == 64) {\n qk_proj_rope_kernel<64, threads><<<blocks, threads>>>(\n input.data_ptr<float>(),\n weight.data_ptr<float>(),\n output.data_ptr<float>(),\n cos_vals.data_ptr<float>(),\n sin_vals.data_ptr<float>(),\n B, T, H, dim\n );\n } else {\n qk_proj_rope_kernel<32, threads><<<blocks, threads>>>(\n input.data_ptr<float>(),\n weight.data_ptr<float>(),\n output.data_ptr<float>(),\n cos_vals.data_ptr<float>(),\n sin_vals.data_ptr<float>(),\n B, T, H, dim\n );\n }\n}\n\nvoid v_proj(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor output,\n int B, int T, int H, int dim, int head_dim\n) {\n const int threads = 256;\n dim3 blocks(B, H, T);\n \n if (head_dim == 32) {\n v_proj_kernel<32, threads><<<blocks, threads>>>(\n input.data_ptr<float>(),\n weight.data_ptr<float>(),\n output.data_ptr<float>(),\n B, T, H, dim\n );\n } else if (head_dim == 64) {\n v_proj_kernel<64, threads><<<blocks, threads>>>(\n input.data_ptr<float>(),\n weight.data_ptr<float>(),\n output.data_ptr<float>(),\n B, T, H, dim\n );\n } else {\n v_proj_kernel<32, threads><<<blocks, threads>>>(\n input.data_ptr<float>(),\n weight.data_ptr<float>(),\n output.data_ptr<float>(),\n B, T, H, dim\n );\n }\n}\n\nvoid attention(\n at::Tensor q,\n at::Tensor k,\n at::Tensor v,\n at::Tensor attn_out,\n float scale,\n int B, int H, int T_q, int T_k, int head_dim\n) {\n const int threads = 256;\n dim3 blocks(B, H, T_q);\n \n // Shared memory: k_tile + v_tile + scores + max + sum\n size_t shared_mem = T_k * head_dim * 2 * sizeof(float) + T_k * sizeof(float) + 2 * sizeof(float);\n \n if (head_dim == 32) {\n attention_kernel<32, threads><<<blocks, threads, shared_mem>>>(\n q.data_ptr<float>(),\n k.data_ptr<float>(),\n v.data_ptr<float>(),\n attn_out.data_ptr<float>(),\n scale,\n B, H, T_q, T_k\n );\n } else if (head_dim == 64) {\n attention_kernel<64, threads><<<blocks, threads, shared_mem>>>(\n q.data_ptr<float>(),\n k.data_ptr<float>(),\n v.data_ptr<float>(),\n attn_out.data_ptr<float>(),\n scale,\n B, H, T_q, T_k\n );\n } else {\n attention_kernel<32, threads><<<blocks, threads, shared_mem>>>(\n q.data_ptr<float>(),\n k.data_ptr<float>(),\n v.data_ptr<float>(),\n attn_out.data_ptr<float>(),\n scale,\n B, H, T_q, T_k\n );\n }\n}\n\nvoid out_proj(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor output,\n int B, int T_q, int H, int dim\n) {\n const int threads = 256;\n dim3 blocks(B, T_q);\n \n out_proj_kernel<32, threads><<<blocks, threads>>>(\n input.data_ptr<float>(),\n weight.data_ptr<float>(),\n output.data_ptr<float>(),\n B, T_q, H, dim\n );\n}\n\"\"\"\n\ncpp_source = r\"\"\"\nvoid compute_rope_freqs(\n at::Tensor cos_vals,\n at::Tensor sin_vals,\n at::Tensor inv_freq,\n int seq_len,\n int head_dim\n);\n\nvoid qk_proj_rope(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor output,\n at::Tensor cos_vals,\n at::Tensor sin_vals,\n int B, int T, int H, int dim, int head_dim\n);\n\nvoid v_proj(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor output,\n int B, int T, int H, int dim, int head_dim\n);\n\nvoid attention(\n at::Tensor q,\n at::Tensor k,\n at::Tensor v,\n at::Tensor attn_out,\n float scale,\n int B, int H, int T_q, int T_k, int head_dim\n);\n\nvoid out_proj(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor output,\n int B, int T_q, int H, int dim\n);\n\"\"\"\n\n# Load the CUDA extension\nattention_ext = load_inline(\n name='attention_ext',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['compute_rope_freqs', 'qk_proj_rope', 'v_proj', 'attention', 'out_proj'],\n extra_cuda_cflags=['-O3'],\n verbose=True\n)\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 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 self.ext = attention_ext\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 # Compute RoPE frequencies for Q and K\n cos_q = torch.empty(T_q, d, dtype=torch.float32, device=x_query.device)\n sin_q = torch.empty(T_q, d, dtype=torch.float32, device=x_query.device)\n cos_k = torch.empty(T_k, d, dtype=torch.float32, device=x_query.device)\n sin_k = torch.empty(T_k, d, dtype=torch.float32, device=x_query.device)\n \n self.ext.compute_rope_freqs(cos_q, sin_q, self.inv_freq, T_q, d)\n self.ext.compute_rope_freqs(cos_k, sin_k, self.inv_freq, T_k, d)\n \n # Project and apply RoPE to Q\n q = torch.empty(B, H, T_q, d, dtype=torch.float32, device=x_query.device)\n self.ext.qk_proj_rope(\n x_query.float(), self.q_proj.weight.float(), q, cos_q, sin_q,\n B, T_q, H, self.dim, d\n )\n \n # Project and apply RoPE to K\n k = torch.empty(B, H, T_k, d, dtype=torch.float32, device=x_kv.device)\n self.ext.qk_proj_rope(\n x_kv.float(), self.k_proj.weight.float(), k, cos_k, sin_k,\n B, T_k, H, self.dim, d\n )\n \n # Project V (no RoPE)\n v = torch.empty(B, H, T_k, d, dtype=torch.float32, device=x_kv.device)\n self.ext.v_proj(\n x_kv.float(), self.v_proj.weight.float(), v,\n B, T_k, H, self.dim, d\n )\n \n # Attention\n attn_out = torch.empty(B, T_q, H, d, dtype=torch.float32, device=x_query.device)\n self.ext.attention(\n q, k, v, attn_out, self.scale,\n B, H, T_q, T_k, d\n )\n \n # Output projection\n out = torch.empty(B, T_q, self.dim, dtype=torch.float32, device=x_query.device)\n self.ext.out_proj(\n attn_out, self.out_proj.weight.float(), out,\n B, T_q, H, self.dim\n )\n \n return out"
}