compile_fail
15_GQAKVHeadExpansionAttention.py
started
2026-05-11T17:45:15Z
compile error: Syntax error in custom generated code or ModelNew not found
timingkernelbench.timing
kernel mean / median / std
—
reference PyTorch source (the prompt the agent sees)
import math
import torch
import torch.nn as nn
class Model(nn.Module):
"""
Causal grouped-query attention with KV-head expansion.
Query heads outnumber KV heads. Keys and values are expanded by repeating
each KV head across its assigned query-head group before causal attention.
"""
def __init__(self):
super().__init__()
def forward(
self,
q: torch.Tensor,
k: torch.Tensor,
v: torch.Tensor,
) -> torch.Tensor:
batch_size, seq_len, num_q_heads, head_dim = q.shape
num_kv_heads = k.shape[2]
group_size = num_q_heads // num_kv_heads
expanded_k = k.repeat_interleave(group_size, dim=2)
expanded_v = v.repeat_interleave(group_size, dim=2)
out = torch.zeros_like(q)
scale = 1.0 / math.sqrt(head_dim)
for b in range(batch_size):
for h in range(num_q_heads):
for t in range(seq_len):
q_t = q[b, t, h]
k_hist = expanded_k[b, : t + 1, h]
v_hist = expanded_v[b, : t + 1, h]
scores = (k_hist * q_t.unsqueeze(0)).sum(dim=-1) * scale
attn = torch.softmax(scores, dim=0)
out[b, t, h] = (attn.unsqueeze(-1) * v_hist).sum(dim=0)
return out
batch_size = 2
seq_len = 80
num_q_heads = 8
num_kv_heads = 2
head_dim = 32
def get_inputs():
p = popcorn_pri
q = torch.randn(p.jitter_int(batch_size), p.jitter_int(seq_len), p.jitter_int(num_q_heads), p.jitter_int(head_dim, align=8), dtype=torch.float32)
k = torch.randn(p.jitter_int(batch_size), p.jitter_int(seq_len), p.jitter_int(num_kv_heads), p.jitter_int(head_dim, align=8), dtype=torch.float32)
v = torch.randn(p.jitter_int(batch_size), p.jitter_int(seq_len), p.jitter_int(num_kv_heads), p.jitter_int(head_dim, align=8), dtype=torch.float32)
return [q, k, v]
def get_init_inputs():
return []
turn 017.6s turn · 17.6s LLM · 0.0s tools · tokens in=2,216 · out=913 · total=3,854
reasoning
The task is to replace the forward computation of this PyTorch model with a custom CUDA kernel. The model is a causal grouped-query attention with KV-head expansion.
The task requires replacing the forward computation of a PyTorch model with a custom CUDA kernel for a causal grouped-query attention mechanism. The new ModelNew class must match the original's constructor and forward signature, producing identical outputs numerically close in FP32.
The original model performs slow Python loops over batch, heads, and timesteps timestep steps, computing attention with softmax and weighted sum. It's inefficient, so a custom CUDA kernel will speed it up.
assistant```python
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
cuda_source = '''
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <math.h>
__global__ void gqa_causal_attn_kernel(
const float* __restrict__ q,
const float* __restrict__ k,
const float* __restrict__ v,
float* __restrict__ out,
int batch_size,
int seq_len,
int num_q_heads,
int num_kv_heads,
int head_dim,
int group_size
) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int total = batch_size * num_q_heads * seq_len;
if (idx >= total) return;
int t = idx % seq_len;
int h = (idx / seq_len) % num_q_heads;
int b = idx / (seq_len * num_q_heads);
int kv_h = h / group_size;
float scale = 1.0f / sqrtf((float)head_dim);
float scores[256]; // assume seq_len <= 256
float max_score = -1e30f;
for (int s = 0; s <= t; s++) {
float dot = 0.0f;
for (int d = 0; d < head_dim; d++) {
float q_val = q[((b * seq_len + t) * num_q_heads + h) * head_dim + d];
float k_val = k[((b * seq_len + s) * num_kv_heads + kv_h) * head_dim + d];
dot += q_val * k_val;
}
scores[s] = dot * scale;
if (scores[s] > max_score) max_score = scores[s];
}
float sum_exp = 0.0f;
for (int s = 0; s <= t; s++) {
scores[s] = expf(scores[s] - max_score);
sum_exp += scores[s];
}
for (int d = 0; d < head_dim; d++) {
float val = 0.0f;
for (int s = 0; s <= t; s++) {
float v_val = v[((b * seq_len + s) * num_kv_heads + kv_h) * head_dim + d];
val += (scores[s] / sum_exp) * v_val;
}
out[((b * seq_len + t) * num_q_heads + h) * head_dim + d] = val;
}
}
torch::Tensor gqa_causal_attn_cuda(
torch::Tensor q,
torch::Tensor k,
torch::Tensor v
) {
int batch_size = q.size(0);
int seq_len = q.size(1);
int num_q_heads = q.size(2);
int head_dim = q.size(3);
int num_kv_heads = k.size(2);
int group_size = num_q_heads / num_kv_heads;
auto out = torch::zeros_like(q);
int total = batch_size * num_q_heads * seq_len;
int threads = 256;
int blocks = (total + threads - 1) / threads;
gqa_causal_attn_kernel<<<blocks, threads>>>(
q.data_ptr<float>(),
k.data_ptr<float>(),
v.data_ptr<float>(),
out.data_ptr<float>(),
batch_size,
seq_len,
num_q_heads,
num_kv_heads,
head_dim,
group_size
);
return out;
}
'''
cpp_source = '''
torch::Tensor gqa_causal_attn_cuda(torch::Tensor q, torch::Tensor k, torch::Tensor v);
'''
module = load_inline(
name='gqa_attn',
cpp_sources=cpp_source,
cuda_sources=cuda_source,
functions=['gqa_causal_attn_cuda'],
verbose=False
)
class ModelNew(nn.Module):
def __init__(self):
super().__init__()
self.module = module
def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor:
return self.module.gqa_causal_attn_cuda(q, k, v)
```
turn 1214.4s turn · 7.4s LLM · 207.0s tools · tokens in=3,922 · out=920 · total=4,881
reasoning
The instruction says I must call at least one tool this turn. Since my previous response was the code without tools, now I need to use tools to verify.
call → compile_kernel
callcompile_kernel
{
"kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\ncuda_source = '''\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <math.h>\n\n__global__ void gqa_causal_attn_kernel(\n const float* __restrict__ q,\n const float* __restrict__ k,\n const float* __restrict__ v,\n float* __restrict__ out,\n int batch_size,\n int seq_len,\n int num_q_heads,\n int num_kv_heads,\n int head_dim,\n int group_size\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = batch_size * num_q_heads * seq_len;\n if (idx >= total) return;\n\n int t = idx % seq_len;\n int h = (idx / seq_len) % num_q_heads;\n int b = idx / (seq_len * num_q_heads);\n\n int kv_h = h / group_size;\n float scale = 1.0f / sqrtf((float)head_dim);\n\n float scores[256]; // assume seq_len <= 256\n float max_score = -1e30f;\n for (int s = 0; s <= t; s++) {\n float dot = 0.0f;\n for (int d = 0; d < head_dim; d++) {\n float q_val = q[((b * seq_len + t) * num_q_heads + h) * head_dim + d];\n float k_val = k[((b * seq_len + s) * num_kv_heads + kv_h) * head_dim + d];\n dot += q_val * k_val;\n }\n scores[s] = dot * scale;\n if (scores[s] > max_score) max_score = scores[s];\n }\n\n float sum_exp = 0.0f;\n for (int s = 0; s <= t; s++) {\n scores[s] = expf(scores[s] - max_score);\n sum_exp += scores[s];\n }\n\n for (int d = 0; d < head_dim; d++) {\n float val = 0.0f;\n for (int s = 0; s <= t; s++) {\n float v_val = v[((b * seq_len + s) * num_kv_heads + kv_h) * head_dim + d];\n val += (scores[s] / sum_exp) * v_val;\n }\n out[((b * seq_len + t) * num_q_heads + h) * head_dim + d] = val;\n }\n}\n\ntorch::Tensor gqa_causal_attn_cuda(\n torch::Tensor q,\n torch::Tensor k,\n torch::Tensor v\n) {\n int batch_size = q.size(0);\n int seq_len = q.size(1);\n int num_q_heads = q.size(2);\n int head_dim = q.size(3);\n int num_kv_heads = k.size(2);\n int group_size = num_q_heads / num_kv_heads;\n\n auto out = torch::zeros_like(q);\n\n int total = batch_size * num_q_heads * seq_len;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n\n gqa_causal_attn_kernel<<<blocks, threads>>>(\n q.data_ptr<float>(),\n k.data_ptr<float>(),\n v.data_ptr<float>(),\n out.data_ptr<float>(),\n batch_size,\n seq_len,\n num_q_heads,\n num_kv_heads,\n head_dim,\n group_size\n );\n\n return out;\n}\n'''\n\ncpp_source = '''\ntorch::Tensor gqa_causal_attn_cuda(torch::Tensor q, torch::Tensor k, torch::Tensor v);\n'''\n\nmodule = load_inline(\n name='gqa_attn',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['gqa_causal_attn_cuda'],\n verbose=False\n)\n\nclass ModelNew(nn.Module):\n def __init__(self):\n super().__init__()\n self.module = module\n\n def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor:\n return self.module.gqa_causal_attn_cuda(q, k, v)\n"
}
result ← compile_kernel (ok)
okcompile_kernel
compile_kernel PASSED: kernel compiled without errors.
turn 2311.4s turn · 8.4s LLM · 302.9s tools · tokens in=4,898 · out=921 · total=5,826
reasoning
Compilation succeeded. Now check correctness.
call → run_correctness
callrun_correctness
{
"kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\ncuda_source = '''\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <math.h>\n\n__global__ void gqa_causal_attn_kernel(\n const float* __restrict__ q,\n const float* __restrict__ k,\n const float* __restrict__ v,\n float* __restrict__ out,\n int batch_size,\n int seq_len,\n int num_q_heads,\n int num_kv_heads,\n int head_dim,\n int group_size\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = batch_size * num_q_heads * seq_len;\n if (idx >= total) return;\n\n int t = idx % seq_len;\n int h = (idx / seq_len) % num_q_heads;\n int b = idx / (seq_len * num_q_heads);\n\n int kv_h = h / group_size;\n float scale = 1.0f / sqrtf((float)head_dim);\n\n float scores[256]; // assume seq_len <= 256\n float max_score = -1e30f;\n for (int s = 0; s <= t; s++) {\n float dot = 0.0f;\n for (int d = 0; d < head_dim; d++) {\n float q_val = q[((b * seq_len + t) * num_q_heads + h) * head_dim + d];\n float k_val = k[((b * seq_len + s) * num_kv_heads + kv_h) * head_dim + d];\n dot += q_val * k_val;\n }\n scores[s] = dot * scale;\n if (scores[s] > max_score) max_score = scores[s];\n }\n\n float sum_exp = 0.0f;\n for (int s = 0; s <= t; s++) {\n scores[s] = expf(scores[s] - max_score);\n sum_exp += scores[s];\n }\n\n for (int d = 0; d < head_dim; d++) {\n float val = 0.0f;\n for (int s = 0; s <= t; s++) {\n float v_val = v[((b * seq_len + s) * num_kv_heads + kv_h) * head_dim + d];\n val += (scores[s] / sum_exp) * v_val;\n }\n out[((b * seq_len + t) * num_q_heads + h) * head_dim + d] = val;\n }\n}\n\ntorch::Tensor gqa_causal_attn_cuda(\n torch::Tensor q,\n torch::Tensor k,\n torch::Tensor v\n) {\n int batch_size = q.size(0);\n int seq_len = q.size(1);\n int num_q_heads = q.size(2);\n int head_dim = q.size(3);\n int num_kv_heads = k.size(2);\n int group_size = num_q_heads / num_kv_heads;\n\n auto out = torch::zeros_like(q);\n\n int total = batch_size * num_q_heads * seq_len;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n\n gqa_causal_attn_kernel<<<blocks, threads>>>(\n q.data_ptr<float>(),\n k.data_ptr<float>(),\n v.data_ptr<float>(),\n out.data_ptr<float>(),\n batch_size,\n seq_len,\n num_q_heads,\n num_kv_heads,\n head_dim,\n group_size\n );\n\n return out;\n}\n'''\n\ncpp_source = '''\ntorch::Tensor gqa_causal_attn_cuda(torch::Tensor q, torch::Tensor k, torch::Tensor v);\n'''\n\nmodule = load_inline(\n name='gqa_attn',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['gqa_causal_attn_cuda'],\n verbose=False\n)\n\nclass ModelNew(nn.Module):\n def __init__(self):\n super().__init__()\n self.module = module\n\n def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor:\n return self.module.gqa_causal_attn_cuda(q, k, v)\n"
}
result ← run_correctness (ok)
okrun_correctness
run_correctness PASSED: (3 / 3) trials all matched the reference.
Numerical precision: max_abs_err=8.34e-07 mean_abs_err=4.43e-08 max_rel_err=7.74e-02
turn 3225.2s turn · 9.6s LLM · 215.6s tools · tokens in=5,886 · out=921 · total=6,832 · FINAL
reasoning
Correctness passed with very small errors. Now I can submit this kernel as it's correct and faster than the slow Python reference.
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 = '''\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <math.h>\n\n__global__ void gqa_causal_attn_kernel(\n const float* __restrict__ q,\n const float* __restrict__ k,\n const float* __restrict__ v,\n float* __restrict__ out,\n int batch_size,\n int seq_len,\n int num_q_heads,\n int num_kv_heads,\n int head_dim,\n int group_size\n) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = batch_size * num_q_heads * seq_len;\n if (idx >= total) return;\n\n int t = idx % seq_len;\n int h = (idx / seq_len) % num_q_heads;\n int b = idx / (seq_len * num_q_heads);\n\n int kv_h = h / group_size;\n float scale = 1.0f / sqrtf((float)head_dim);\n\n float scores[256]; // assume seq_len <= 256\n float max_score = -1e30f;\n for (int s = 0; s <= t; s++) {\n float dot = 0.0f;\n for (int d = 0; d < head_dim; d++) {\n float q_val = q[((b * seq_len + t) * num_q_heads + h) * head_dim + d];\n float k_val = k[((b * seq_len + s) * num_kv_heads + kv_h) * head_dim + d];\n dot += q_val * k_val;\n }\n scores[s] = dot * scale;\n if (scores[s] > max_score) max_score = scores[s];\n }\n\n float sum_exp = 0.0f;\n for (int s = 0; s <= t; s++) {\n scores[s] = expf(scores[s] - max_score);\n sum_exp += scores[s];\n }\n\n for (int d = 0; d < head_dim; d++) {\n float val = 0.0f;\n for (int s = 0; s <= t; s++) {\n float v_val = v[((b * seq_len + s) * num_kv_heads + kv_h) * head_dim + d];\n val += (scores[s] / sum_exp) * v_val;\n }\n out[((b * seq_len + t) * num_q_heads + h) * head_dim + d] = val;\n }\n}\n\ntorch::Tensor gqa_causal_attn_cuda(\n torch::Tensor q,\n torch::Tensor k,\n torch::Tensor v\n) {\n int batch_size = q.size(0);\n int seq_len = q.size(1);\n int num_q_heads = q.size(2);\n int head_dim = q.size(3);\n int num_kv_heads = k.size(2);\n int group_size = num_q_heads / num_kv_heads;\n\n auto out = torch::zeros_like(q);\n\n int total = batch_size * num_q_heads * seq_len;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n\n gqa_causal_attn_kernel<<<blocks, threads>>>(\n q.data_ptr<float>(),\n k.data_ptr<float>(),\n v.data_ptr<float>(),\n out.data_ptr<float>(),\n batch_size,\n seq_len,\n num_q_heads,\n num_kv_heads,\n head_dim,\n group_size\n );\n\n return out;\n}\n'''\n\ncpp_source = '''\ntorch::Tensor gqa_causal_attn_cuda(torch::Tensor q, torch::Tensor k, torch::Tensor v);\n'''\n\nmodule = load_inline(\n name='gqa_attn',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['gqa_causal_attn_cuda'],\n verbose=False\n)\n\nclass ModelNew(nn.Module):\n def __init__(self):\n super().__init__()\n self.module = module\n\n def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor:\n return self.module.gqa_causal_attn_cuda(q, k, v)\n```"
}
result ← submit_kernel (fail)
failsubmit_kernel
submit_kernel FAILED: kernel did not compile.
Syntax error in custom generated code or ModelNew not found
final submitted kernel (117 lines, 3,112 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline
cuda_source = '''
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <math.h>
__global__ void gqa_causal_attn_kernel(
const float* __restrict__ q,
const float* __restrict__ k,
const float* __restrict__ v,
float* __restrict__ out,
int batch_size,
int seq_len,
int num_q_heads,
int num_kv_heads,
int head_dim,
int group_size
) {
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int total = batch_size * num_q_heads * seq_len;
if (idx >= total) return;
int t = idx % seq_len;
int h = (idx / seq_len) % num_q_heads;
int b = idx / (seq_len * num_q_heads);
int kv_h = h / group_size;
float scale = 1.0f / sqrtf((float)head_dim);
float scores[256]; // assume seq_len <= 256
float max_score = -1e30f;
for (int s = 0; s <= t; s++) {
float dot = 0.0f;
for (int d = 0; d < head_dim; d++) {
float q_val = q[((b * seq_len + t) * num_q_heads + h) * head_dim + d];
float k_val = k[((b * seq_len + s) * num_kv_heads + kv_h) * head_dim + d];
dot += q_val * k_val;
}
scores[s] = dot * scale;
if (scores[s] > max_score) max_score = scores[s];
}
float sum_exp = 0.0f;
for (int s = 0; s <= t; s++) {
scores[s] = expf(scores[s] - max_score);
sum_exp += scores[s];
}
for (int d = 0; d < head_dim; d++) {
float val = 0.0f;
for (int s = 0; s <= t; s++) {
float v_val = v[((b * seq_len + s) * num_kv_heads + kv_h) * head_dim + d];
val += (scores[s] / sum_exp) * v_val;
}
out[((b * seq_len + t) * num_q_heads + h) * head_dim + d] = val;
}
}
torch::Tensor gqa_causal_attn_cuda(
torch::Tensor q,
torch::Tensor k,
torch::Tensor v
) {
int batch_size = q.size(0);
int seq_len = q.size(1);
int num_q_heads = q.size(2);
int head_dim = q.size(3);
int num_kv_heads = k.size(2);
int group_size = num_q_heads / num_kv_heads;
auto out = torch::zeros_like(q);
int total = batch_size * num_q_heads * seq_len;
int threads = 256;
int blocks = (total + threads - 1) / threads;
gqa_causal_attn_kernel<<<blocks, threads>>>(
q.data_ptr<float>(),
k.data_ptr<float>(),
v.data_ptr<float>(),
out.data_ptr<float>(),
batch_size,
seq_len,
num_q_heads,
num_kv_heads,
head_dim,
group_size
);
return out;
}
'''
cpp_source = '''
torch::Tensor gqa_causal_attn_cuda(torch::Tensor q, torch::Tensor k, torch::Tensor v);
'''
module = load_inline(
name='gqa_attn',
cpp_sources=cpp_source,
cuda_sources=cuda_source,
functions=['gqa_causal_attn_cuda'],
verbose=False
)
class ModelNew(nn.Module):
def __init__(self):
super().__init__()
self.module = module
def forward(self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor) -> torch.Tensor:
return self.module.gqa_causal_attn_cuda(q, k, v)
```