incorrect
43_MinGPTCausalAttention.py
started
2026-05-05T01:00:00Z
correctness issue: Output mismatch
timingkernelbench.timing
kernel mean / median / std
—
ref mean / median / std
6.15 / — / 0.69 μs
SOL (speed-of-light)nsight_hardware_counters
roofline / occupancynsight_hardware_counters
dram bandwidth
1883.52 GB/s
warp stalls (top 5)nsight
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
# From https://github.com/karpathy/minGPT/blob/master/mingpt/model.py
class Model(nn.Module):
"""
A vanilla multi-head masked self-attention layer with a projection at the end.
It is possible to use torch.nn.MultiheadAttention here but I am including an
explicit implementation here to show that there is nothing too scary here.
"""
def __init__(self, n_embd, n_head, attn_pdrop, resid_pdrop, max_seqlen):
super().__init__()
assert n_embd % n_head == 0
# key, query, value projections for all heads, but in a batch
self.c_attn = nn.Linear(n_embd, 3 * n_embd)
# output projection
self.c_proj = nn.Linear(n_embd, n_embd)
# regularization
self.attn_dropout = nn.Dropout(attn_pdrop)
self.resid_dropout = nn.Dropout(resid_pdrop)
# causal mask to ensure that attention is only applied to the left in the input sequence
self.register_buffer("bias", torch.tril(torch.ones(max_seqlen, max_seqlen))
.view(1, 1, max_seqlen, max_seqlen))
self.n_head = n_head
self.n_embd = n_embd
def forward(self, x):
B, T, C = x.size() # batch size, sequence length, embedding dimensionality (n_embd)
# calculate query, key, values for all heads in batch and move head forward to be the batch dim
q, k ,v = self.c_attn(x).split(self.n_embd, dim=2)
k = k.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
q = q.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
v = v.view(B, T, self.n_head, C // self.n_head).transpose(1, 2) # (B, nh, T, hs)
# causal self-attention; Self-attend: (B, nh, T, hs) x (B, nh, hs, T) -> (B, nh, T, T)
att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))
att = att.masked_fill(self.bias[:,:,:T,:T] == 0, float('-inf'))
att = F.softmax(att, dim=-1)
att = self.attn_dropout(att)
y = att @ v # (B, nh, T, T) x (B, nh, T, hs) -> (B, nh, T, hs)
y = y.transpose(1, 2).contiguous().view(B, T, C) # re-assemble all head outputs side by side
# output projection
y = self.resid_dropout(self.c_proj(y))
return y
batch_size = 128
max_seqlen = 1024
seq_len = 512
n_embd = 768
n_head = 8
attn_pdrop = 0.0
resid_pdrop = 0.0
def get_inputs():
return [torch.rand(batch_size, seq_len, n_embd)]
def get_init_inputs():
return [n_embd, n_head, attn_pdrop, resid_pdrop, max_seqlen]turn 0 · FINAL · 1 tool call(s) · 75.3s turn · 75.3s LLM · 8,979 chars (click to expand)
turn 075.3s turn · 75.3s LLM · 0.0s tools · FINAL
reasoning
**Considering attention mechanisms**
I'm thinking about the SDPA flash, which might be close but not exact, and if the tolerance is okay. I'm noting that dropouts are at zero. I want to apply a linear transformation for QKV splitting and then transpose Q. I’ll use scaled dot-product attention with causal masks and a dropout probability of zero. After that, I’ll apply a projection, which seems to be a faster common replacement than the reference method.
**Exploring tensor operations**
I’m considering how torch.bmm handles fp32 and if it uses TF32. It seems SDPA math might be involved, but it could skip masked_fill for speed. Perhaps I should look into a custom kernel to improve performance. I’m also wondering about using torch.backends.cuda.sdp_kernel, but that doesn’t seem right. If the input is in fp32, maybe casting q, k, and v to torch.float16 for flash and converting them back could help?
call → submit_kernel
callsubmit_kernel
{
"kernel_code": "import math\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.cpp_extension import load_inline\n\n\n_cpp_src = r\"\"\"\n#include <torch/extension.h>\n\ntorch::Tensor causal_attention_cuda(torch::Tensor qkv, int64_t n_head);\n\ntorch::Tensor causal_attention(torch::Tensor qkv, int64_t n_head) {\n return causal_attention_cuda(qkv, n_head);\n}\n\"\"\"\n\n_cuda_src = r\"\"\"\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <math_constants.h>\n\n__inline__ __device__ float warp_reduce_sum(float v) {\n #pragma unroll\n for (int offset = 16; offset > 0; offset >>= 1) {\n v += __shfl_down_sync(0xffffffff, v, offset);\n }\n return v;\n}\n\n// One warp computes one (batch, head, query-position) row of causal attention.\n// Specialized for the small GPT head dimension used here (96), but written for D <= 128.\n__global__ void causal_attn_warp_kernel(\n const float* __restrict__ qkv,\n float* __restrict__ out,\n int B, int T, int C, int H, int D,\n float scale,\n long total_rows)\n{\n int lane = threadIdx.x & 31;\n int warp_in_block = threadIdx.x >> 5;\n int warps_per_block = blockDim.x >> 5;\n long row = ((long)blockIdx.x) * warps_per_block + warp_in_block;\n if (row >= total_rows) return;\n\n int t = (int)(row % T);\n long tmp = row / T;\n int h = (int)(tmp % H);\n int b = (int)(tmp / H);\n\n long q_base = ((long)b * T + t) * (long)(3 * C) + h * D;\n\n float qv0 = 0.0f, qv1 = 0.0f, qv2 = 0.0f, qv3 = 0.0f;\n int d0 = lane;\n int d1 = lane + 32;\n int d2 = lane + 64;\n int d3 = lane + 96;\n if (d0 < D) qv0 = qkv[q_base + d0];\n if (d1 < D) qv1 = qkv[q_base + d1];\n if (d2 < D) qv2 = qkv[q_base + d2];\n if (d3 < D) qv3 = qkv[q_base + d3];\n\n float m = -CUDART_INF_F;\n\n // Pass 1: maximum logit for a stable softmax.\n for (int j = 0; j <= t; ++j) {\n long k_base = ((long)b * T + j) * (long)(3 * C) + C + h * D;\n float dot = 0.0f;\n if (d0 < D) dot += qv0 * qkv[k_base + d0];\n if (d1 < D) dot += qv1 * qkv[k_base + d1];\n if (d2 < D) dot += qv2 * qkv[k_base + d2];\n if (d3 < D) dot += qv3 * qkv[k_base + d3];\n dot = warp_reduce_sum(dot);\n float score = __shfl_sync(0xffffffff, dot, 0) * scale;\n m = fmaxf(m, score);\n }\n\n float denom = 0.0f;\n float acc0 = 0.0f, acc1 = 0.0f, acc2 = 0.0f, acc3 = 0.0f;\n\n // Pass 2: denominator and weighted sum. Each lane owns up to 4 output channels.\n for (int j = 0; j <= t; ++j) {\n long base = ((long)b * T + j) * (long)(3 * C);\n long k_base = base + C + h * D;\n float dot = 0.0f;\n if (d0 < D) dot += qv0 * qkv[k_base + d0];\n if (d1 < D) dot += qv1 * qkv[k_base + d1];\n if (d2 < D) dot += qv2 * qkv[k_base + d2];\n if (d3 < D) dot += qv3 * qkv[k_base + d3];\n dot = warp_reduce_sum(dot);\n float score = __shfl_sync(0xffffffff, dot, 0) * scale;\n float w = expf(score - m);\n denom += w;\n\n long v_base = base + 2 * C + h * D;\n if (d0 < D) acc0 += w * qkv[v_base + d0];\n if (d1 < D) acc1 += w * qkv[v_base + d1];\n if (d2 < D) acc2 += w * qkv[v_base + d2];\n if (d3 < D) acc3 += w * qkv[v_base + d3];\n }\n\n float inv = 1.0f / denom;\n long o_base = ((long)b * T + t) * (long)C + h * D;\n if (d0 < D) out[o_base + d0] = acc0 * inv;\n if (d1 < D) out[o_base + d1] = acc1 * inv;\n if (d2 < D) out[o_base + d2] = acc2 * inv;\n if (d3 < D) out[o_base + d3] = acc3 * inv;\n}\n\ntorch::Tensor causal_attention_cuda(torch::Tensor qkv, int64_t n_head) {\n TORCH_CHECK(qkv.is_cuda(), \"qkv must be CUDA\");\n TORCH_CHECK(qkv.scalar_type() == at::kFloat, \"qkv must be float32\");\n TORCH_CHECK(qkv.dim() == 3, \"qkv must have shape [B,T,3C]\");\n TORCH_CHECK(qkv.is_contiguous(), \"qkv must be contiguous\");\n\n int B = (int)qkv.size(0);\n int T = (int)qkv.size(1);\n int C3 = (int)qkv.size(2);\n int C = C3 / 3;\n int H = (int)n_head;\n int D = C / H;\n TORCH_CHECK(C3 == 3 * C && C == H * D, \"invalid qkv/head dimensions\");\n TORCH_CHECK(D <= 128, \"head dimension too large for this kernel\");\n\n auto out = torch::empty({B, T, C}, qkv.options());\n long total_rows = (long)B * H * T;\n const int threads = 128; // 4 warps/block\n const int warps = threads / 32;\n dim3 grid((unsigned int)((total_rows + warps - 1) / warps));\n float scale = rsqrtf((float)D);\n\n causal_attn_warp_kernel<<<grid, threads, 0, at::cuda::getCurrentCUDAStream()>>>(\n qkv.data_ptr<float>(), out.data_ptr<float>(), B, T, C, H, D, scale, total_rows);\n C10_CUDA_KERNEL_LAUNCH_CHECK();\n return out;\n}\n\"\"\"\n\n_ext = load_inline(\n name=\"causal_attn_warp_ext\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"causal_attention\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\"],\n verbose=False,\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, n_embd, n_head, attn_pdrop, resid_pdrop, max_seqlen):\n super().__init__()\n assert n_embd % n_head == 0\n self.c_attn = nn.Linear(n_embd, 3 * n_embd)\n self.c_proj = nn.Linear(n_embd, n_embd)\n self.attn_dropout = nn.Dropout(attn_pdrop)\n self.resid_dropout = nn.Dropout(resid_pdrop)\n self.register_buffer(\n \"bias\",\n torch.tril(torch.ones(max_seqlen, max_seqlen)).view(1, 1, max_seqlen, max_seqlen),\n )\n self.n_head = n_head\n self.n_embd = n_embd\n\n def forward(self, x):\n B, T, C = x.size()\n qkv = self.c_attn(x).contiguous()\n\n # Fast fused causal attention for the benchmark path (float32 CUDA, p=0).\n if x.is_cuda and x.dtype is torch.float32 and (C // self.n_head) <= 128 and self.attn_dropout.p == 0.0:\n y = _ext.causal_attention(qkv, self.n_head)\n else:\n q, k, v = qkv.split(self.n_embd, dim=2)\n k = k.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)\n q = q.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)\n v = v.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)\n att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))\n att = att.masked_fill(self.bias[:, :, :T, :T] == 0, float('-inf'))\n att = F.softmax(att, dim=-1)\n att = self.attn_dropout(att)\n y = (att @ v).transpose(1, 2).contiguous().view(B, T, C)\n\n y = self.c_proj(y)\n y = self.resid_dropout(y)\n return y\n"
}
result ← submit_kernel (fail)
failsubmit_kernel
submit_kernel FAILED: correctness check did not pass ((0 / 5) trials).
correctness_issue: Output mismatch
max_difference: ['0.000314', '0.000313', '0.000316', '0.000322', '0.000308']
avg_difference: ['0.000016', '0.000016', '0.000016', '0.000016', '0.000016']
final submitted kernel (188 lines, 6,529 chars) — full source, untruncated
import math
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.cpp_extension import load_inline
_cpp_src = r"""
#include <torch/extension.h>
torch::Tensor causal_attention_cuda(torch::Tensor qkv, int64_t n_head);
torch::Tensor causal_attention(torch::Tensor qkv, int64_t n_head) {
return causal_attention_cuda(qkv, n_head);
}
"""
_cuda_src = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <math_constants.h>
__inline__ __device__ float warp_reduce_sum(float v) {
#pragma unroll
for (int offset = 16; offset > 0; offset >>= 1) {
v += __shfl_down_sync(0xffffffff, v, offset);
}
return v;
}
// One warp computes one (batch, head, query-position) row of causal attention.
// Specialized for the small GPT head dimension used here (96), but written for D <= 128.
__global__ void causal_attn_warp_kernel(
const float* __restrict__ qkv,
float* __restrict__ out,
int B, int T, int C, int H, int D,
float scale,
long total_rows)
{
int lane = threadIdx.x & 31;
int warp_in_block = threadIdx.x >> 5;
int warps_per_block = blockDim.x >> 5;
long row = ((long)blockIdx.x) * warps_per_block + warp_in_block;
if (row >= total_rows) return;
int t = (int)(row % T);
long tmp = row / T;
int h = (int)(tmp % H);
int b = (int)(tmp / H);
long q_base = ((long)b * T + t) * (long)(3 * C) + h * D;
float qv0 = 0.0f, qv1 = 0.0f, qv2 = 0.0f, qv3 = 0.0f;
int d0 = lane;
int d1 = lane + 32;
int d2 = lane + 64;
int d3 = lane + 96;
if (d0 < D) qv0 = qkv[q_base + d0];
if (d1 < D) qv1 = qkv[q_base + d1];
if (d2 < D) qv2 = qkv[q_base + d2];
if (d3 < D) qv3 = qkv[q_base + d3];
float m = -CUDART_INF_F;
// Pass 1: maximum logit for a stable softmax.
for (int j = 0; j <= t; ++j) {
long k_base = ((long)b * T + j) * (long)(3 * C) + C + h * D;
float dot = 0.0f;
if (d0 < D) dot += qv0 * qkv[k_base + d0];
if (d1 < D) dot += qv1 * qkv[k_base + d1];
if (d2 < D) dot += qv2 * qkv[k_base + d2];
if (d3 < D) dot += qv3 * qkv[k_base + d3];
dot = warp_reduce_sum(dot);
float score = __shfl_sync(0xffffffff, dot, 0) * scale;
m = fmaxf(m, score);
}
float denom = 0.0f;
float acc0 = 0.0f, acc1 = 0.0f, acc2 = 0.0f, acc3 = 0.0f;
// Pass 2: denominator and weighted sum. Each lane owns up to 4 output channels.
for (int j = 0; j <= t; ++j) {
long base = ((long)b * T + j) * (long)(3 * C);
long k_base = base + C + h * D;
float dot = 0.0f;
if (d0 < D) dot += qv0 * qkv[k_base + d0];
if (d1 < D) dot += qv1 * qkv[k_base + d1];
if (d2 < D) dot += qv2 * qkv[k_base + d2];
if (d3 < D) dot += qv3 * qkv[k_base + d3];
dot = warp_reduce_sum(dot);
float score = __shfl_sync(0xffffffff, dot, 0) * scale;
float w = expf(score - m);
denom += w;
long v_base = base + 2 * C + h * D;
if (d0 < D) acc0 += w * qkv[v_base + d0];
if (d1 < D) acc1 += w * qkv[v_base + d1];
if (d2 < D) acc2 += w * qkv[v_base + d2];
if (d3 < D) acc3 += w * qkv[v_base + d3];
}
float inv = 1.0f / denom;
long o_base = ((long)b * T + t) * (long)C + h * D;
if (d0 < D) out[o_base + d0] = acc0 * inv;
if (d1 < D) out[o_base + d1] = acc1 * inv;
if (d2 < D) out[o_base + d2] = acc2 * inv;
if (d3 < D) out[o_base + d3] = acc3 * inv;
}
torch::Tensor causal_attention_cuda(torch::Tensor qkv, int64_t n_head) {
TORCH_CHECK(qkv.is_cuda(), "qkv must be CUDA");
TORCH_CHECK(qkv.scalar_type() == at::kFloat, "qkv must be float32");
TORCH_CHECK(qkv.dim() == 3, "qkv must have shape [B,T,3C]");
TORCH_CHECK(qkv.is_contiguous(), "qkv must be contiguous");
int B = (int)qkv.size(0);
int T = (int)qkv.size(1);
int C3 = (int)qkv.size(2);
int C = C3 / 3;
int H = (int)n_head;
int D = C / H;
TORCH_CHECK(C3 == 3 * C && C == H * D, "invalid qkv/head dimensions");
TORCH_CHECK(D <= 128, "head dimension too large for this kernel");
auto out = torch::empty({B, T, C}, qkv.options());
long total_rows = (long)B * H * T;
const int threads = 128; // 4 warps/block
const int warps = threads / 32;
dim3 grid((unsigned int)((total_rows + warps - 1) / warps));
float scale = rsqrtf((float)D);
causal_attn_warp_kernel<<<grid, threads, 0, at::cuda::getCurrentCUDAStream()>>>(
qkv.data_ptr<float>(), out.data_ptr<float>(), B, T, C, H, D, scale, total_rows);
C10_CUDA_KERNEL_LAUNCH_CHECK();
return out;
}
"""
_ext = load_inline(
name="causal_attn_warp_ext",
cpp_sources=_cpp_src,
cuda_sources=_cuda_src,
functions=["causal_attention"],
extra_cflags=["-O3"],
extra_cuda_cflags=["-O3"],
verbose=False,
)
class ModelNew(nn.Module):
def __init__(self, n_embd, n_head, attn_pdrop, resid_pdrop, max_seqlen):
super().__init__()
assert n_embd % n_head == 0
self.c_attn = nn.Linear(n_embd, 3 * n_embd)
self.c_proj = nn.Linear(n_embd, n_embd)
self.attn_dropout = nn.Dropout(attn_pdrop)
self.resid_dropout = nn.Dropout(resid_pdrop)
self.register_buffer(
"bias",
torch.tril(torch.ones(max_seqlen, max_seqlen)).view(1, 1, max_seqlen, max_seqlen),
)
self.n_head = n_head
self.n_embd = n_embd
def forward(self, x):
B, T, C = x.size()
qkv = self.c_attn(x).contiguous()
# Fast fused causal attention for the benchmark path (float32 CUDA, p=0).
if x.is_cuda and x.dtype is torch.float32 and (C // self.n_head) <= 128 and self.attn_dropout.p == 0.0:
y = _ext.causal_attention(qkv, self.n_head)
else:
q, k, v = qkv.split(self.n_embd, dim=2)
k = k.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
q = q.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
v = v.view(B, T, self.n_head, C // self.n_head).transpose(1, 2)
att = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(k.size(-1)))
att = att.masked_fill(self.bias[:, :, :T, :T] == 0, float('-inf'))
att = F.softmax(att, dim=-1)
att = self.attn_dropout(att)
y = (att @ v).transpose(1, 2).contiguous().view(B, T, C)
y = self.c_proj(y)
y = self.resid_dropout(y)
return y