incorrect
50_ReLUSelfAttention.py
started
2026-05-05T00:51:45Z
correctness issue: Output mismatch
timingkernelbench.timing
kernel mean / median / std
—
ref mean / median / std
5.13 / — / 0.02 μs
SOL (speed-of-light)nsight_hardware_counters
roofline / occupancynsight_hardware_counters
dram bandwidth
527.72 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 NewGELU(nn.Module):
"""
Implementation of the GELU activation function currently in Google BERT repo (identical to OpenAI GPT).
Reference: Gaussian Error Linear Units (GELU) paper: https://arxiv.org/abs/1606.08415
"""
def __init__(self):
super(NewGELU, self).__init__()
def forward(self, x):
return 0.5 * x * (1.0 + torch.tanh(math.sqrt(2.0 / math.pi) * (x + 0.044715 * torch.pow(x, 3.0))))
class Model(nn.Module):
"""
A multi-head masked self-attention layer with a projection at the end that uses ReLU instead of Softmax.
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, 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)
# 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.relu(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
return y
batch_size = 16
max_seqlen = 1024
n_embd = 768 # Hidden dimension, typical for BERT-base size
n_head = 12 # Number of attention heads, typical for BERT-base size
def get_inputs():
return [torch.rand(batch_size, max_seqlen, n_embd)]
def get_init_inputs():
return [n_embd, n_head, max_seqlen]turn 0 · FINAL · 1 tool call(s) · 81.7s turn · 81.7s LLM · 7,847 chars (click to expand)
turn 081.7s turn · 81.7s LLM · 0.0s tools · FINAL
reasoning
**Considering tolerances and errors**
I'm contemplating the tolerance level of 1e-4, wondering if maybe they’ve disabled TF32. I might need to use torch.bmm as a reference since I’m aiming for a custom and faster approach. If I stick to full fp32 dot products, the order could differ. I’m concerned about potential errors after summation, especially if they exceed 1e-4. It’s crucial to address these details for better performance and accuracy.
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 relu_causal_attn_forward(torch::Tensor qkv, int64_t n_head);\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.h>\n\nstatic __forceinline__ __device__ float warp_sum(float v) {\n v += __shfl_down_sync(0xffffffff, v, 16);\n v += __shfl_down_sync(0xffffffff, v, 8);\n v += __shfl_down_sync(0xffffffff, v, 4);\n v += __shfl_down_sync(0xffffffff, v, 2);\n v += __shfl_down_sync(0xffffffff, v, 1);\n return v;\n}\n\n// Specialized for head size 64. One block computes one (batch, head, query-position).\n// Warp 0 writes output dims 0..31 and warp 1 writes dims 32..63. The QK dot is\n// recomputed by both warps, avoiding shared-memory reductions/barriers and allowing\n// a uniform branch to skip V loads for negative ReLUed attention scores.\n__global__ __launch_bounds__(64) void relu_causal_attn_hs64_kernel(\n const float* __restrict__ qkv,\n float* __restrict__ out,\n int B, int T, int C, int nH)\n{\n const int hs = 64;\n const int tid = threadIdx.x;\n const int lane = tid & 31;\n const int warp = tid >> 5; // 0 or 1\n const int od = warp * 32 + lane; // output dimension within head\n\n int bid = blockIdx.x;\n const int t = bid % T;\n bid /= T;\n const int h = bid % nH;\n const int b = bid / nH;\n\n const int head_base = h * hs;\n const long long token_stride = (long long)3 * C;\n const long long q_base = ((long long)b * T + t) * token_stride + head_base;\n const float q0 = qkv[q_base + lane];\n const float q1 = qkv[q_base + lane + 32];\n\n const float scale = 0.125f; // 1/sqrt(64)\n float acc = 0.0f;\n\n const long long b_base = (long long)b * T * token_stride;\n for (int j = 0; j <= t; ++j) {\n const long long kj = b_base + (long long)j * token_stride + C + head_base;\n float prod = fmaf(q1, qkv[kj + lane + 32], q0 * qkv[kj + lane]);\n float s = warp_sum(prod) * scale;\n // ReLU after causal masking. Branch is uniform within each warp.\n if (s > 0.0f) {\n const long long vj = b_base + (long long)j * token_stride + 2LL * C + head_base;\n acc = fmaf(s, qkv[vj + od], acc);\n }\n }\n\n const long long out_idx = ((long long)b * T + t) * C + head_base + od;\n out[out_idx] = acc;\n}\n\n// Generic slow fallback for unusual head sizes. Kept as CUDA code (not a PyTorch\n// reference route); the benchmarked problem uses hs=64.\n__global__ void relu_causal_attn_generic_kernel(\n const float* __restrict__ qkv,\n float* __restrict__ out,\n int B, int T, int C, int nH, int hs)\n{\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = B * T * C;\n if (idx >= total) return;\n int d = idx % C;\n int t = (idx / C) % T;\n int b = idx / (C * T);\n int h = d / hs;\n int hd = d - h * hs;\n const float scale = rsqrtf((float)hs);\n const long long token_stride = (long long)3 * C;\n const long long q_base = ((long long)b * T + t) * token_stride + h * hs;\n const long long b_base = (long long)b * T * token_stride;\n float acc = 0.0f;\n for (int j = 0; j <= t; ++j) {\n const long long k_base = b_base + (long long)j * token_stride + C + h * hs;\n float s = 0.0f;\n for (int r = 0; r < hs; ++r) s = fmaf(qkv[q_base + r], qkv[k_base + r], s);\n s *= scale;\n if (s > 0.0f) {\n const long long v_base = b_base + (long long)j * token_stride + 2LL * C + h * hs;\n acc = fmaf(s, qkv[v_base + hd], acc);\n }\n }\n out[idx] = acc;\n}\n\ntorch::Tensor relu_causal_attn_forward(torch::Tensor qkv, int64_t n_head) {\n TORCH_CHECK(qkv.is_cuda(), \"qkv must be a CUDA tensor\");\n TORCH_CHECK(qkv.scalar_type() == at::kFloat, \"only float32 is supported\");\n TORCH_CHECK(qkv.dim() == 3, \"qkv must have shape [B,T,3*C]\");\n TORCH_CHECK(qkv.is_contiguous(), \"qkv must be contiguous\");\n const int B = (int)qkv.size(0);\n const int T = (int)qkv.size(1);\n const int C3 = (int)qkv.size(2);\n TORCH_CHECK(C3 % 3 == 0, \"last dimension must be 3*C\");\n const int C = C3 / 3;\n const int nH = (int)n_head;\n TORCH_CHECK(C % nH == 0, \"C must be divisible by n_head\");\n const int hs = C / nH;\n\n auto out = torch::empty({B, T, C}, qkv.options());\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n if (hs == 64) {\n dim3 block(64);\n dim3 grid((unsigned int)(B * nH * T));\n relu_causal_attn_hs64_kernel<<<grid, block, 0, stream>>>(\n qkv.data_ptr<float>(), out.data_ptr<float>(), B, T, C, nH);\n } else {\n int threads = 128;\n int total = B * T * C;\n int blocks = (total + threads - 1) / threads;\n relu_causal_attn_generic_kernel<<<blocks, threads, 0, stream>>>(\n qkv.data_ptr<float>(), out.data_ptr<float>(), B, T, C, nH, hs);\n }\n C10_CUDA_KERNEL_LAUNCH_CHECK();\n return out;\n}\n\"\"\"\n\n_relu_attn_ext = load_inline(\n name=\"relu_causal_attn_ext_v1\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"relu_causal_attn_forward\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\", \"--use_fast_math\"],\n verbose=False,\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, n_embd, n_head, 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.register_buffer(\"bias\", torch.tril(torch.ones(max_seqlen, max_seqlen)).view(1, 1, max_seqlen, max_seqlen))\n self.n_head = n_head\n self.n_embd = n_embd\n\n def forward(self, x):\n qkv = self.c_attn(x)\n return _relu_attn_ext.relu_causal_attn_forward(qkv.contiguous(), self.n_head)\n"
}
result ← submit_kernel (fail)
failsubmit_kernel
submit_kernel FAILED: correctness check did not pass ((0 / 5) trials).
correctness_issue: Output mismatch
max_difference: ['1114.563354', '1103.593628', '1248.857422', '1157.764648', '1153.604248']
avg_difference: ['7.938283', '7.904490', '7.923929', '7.913528', '7.921648']
final submitted kernel (166 lines, 5,921 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 relu_causal_attn_forward(torch::Tensor qkv, int64_t n_head);
"""
_cuda_src = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <math.h>
static __forceinline__ __device__ float warp_sum(float v) {
v += __shfl_down_sync(0xffffffff, v, 16);
v += __shfl_down_sync(0xffffffff, v, 8);
v += __shfl_down_sync(0xffffffff, v, 4);
v += __shfl_down_sync(0xffffffff, v, 2);
v += __shfl_down_sync(0xffffffff, v, 1);
return v;
}
// Specialized for head size 64. One block computes one (batch, head, query-position).
// Warp 0 writes output dims 0..31 and warp 1 writes dims 32..63. The QK dot is
// recomputed by both warps, avoiding shared-memory reductions/barriers and allowing
// a uniform branch to skip V loads for negative ReLUed attention scores.
__global__ __launch_bounds__(64) void relu_causal_attn_hs64_kernel(
const float* __restrict__ qkv,
float* __restrict__ out,
int B, int T, int C, int nH)
{
const int hs = 64;
const int tid = threadIdx.x;
const int lane = tid & 31;
const int warp = tid >> 5; // 0 or 1
const int od = warp * 32 + lane; // output dimension within head
int bid = blockIdx.x;
const int t = bid % T;
bid /= T;
const int h = bid % nH;
const int b = bid / nH;
const int head_base = h * hs;
const long long token_stride = (long long)3 * C;
const long long q_base = ((long long)b * T + t) * token_stride + head_base;
const float q0 = qkv[q_base + lane];
const float q1 = qkv[q_base + lane + 32];
const float scale = 0.125f; // 1/sqrt(64)
float acc = 0.0f;
const long long b_base = (long long)b * T * token_stride;
for (int j = 0; j <= t; ++j) {
const long long kj = b_base + (long long)j * token_stride + C + head_base;
float prod = fmaf(q1, qkv[kj + lane + 32], q0 * qkv[kj + lane]);
float s = warp_sum(prod) * scale;
// ReLU after causal masking. Branch is uniform within each warp.
if (s > 0.0f) {
const long long vj = b_base + (long long)j * token_stride + 2LL * C + head_base;
acc = fmaf(s, qkv[vj + od], acc);
}
}
const long long out_idx = ((long long)b * T + t) * C + head_base + od;
out[out_idx] = acc;
}
// Generic slow fallback for unusual head sizes. Kept as CUDA code (not a PyTorch
// reference route); the benchmarked problem uses hs=64.
__global__ void relu_causal_attn_generic_kernel(
const float* __restrict__ qkv,
float* __restrict__ out,
int B, int T, int C, int nH, int hs)
{
int idx = blockIdx.x * blockDim.x + threadIdx.x;
int total = B * T * C;
if (idx >= total) return;
int d = idx % C;
int t = (idx / C) % T;
int b = idx / (C * T);
int h = d / hs;
int hd = d - h * hs;
const float scale = rsqrtf((float)hs);
const long long token_stride = (long long)3 * C;
const long long q_base = ((long long)b * T + t) * token_stride + h * hs;
const long long b_base = (long long)b * T * token_stride;
float acc = 0.0f;
for (int j = 0; j <= t; ++j) {
const long long k_base = b_base + (long long)j * token_stride + C + h * hs;
float s = 0.0f;
for (int r = 0; r < hs; ++r) s = fmaf(qkv[q_base + r], qkv[k_base + r], s);
s *= scale;
if (s > 0.0f) {
const long long v_base = b_base + (long long)j * token_stride + 2LL * C + h * hs;
acc = fmaf(s, qkv[v_base + hd], acc);
}
}
out[idx] = acc;
}
torch::Tensor relu_causal_attn_forward(torch::Tensor qkv, int64_t n_head) {
TORCH_CHECK(qkv.is_cuda(), "qkv must be a CUDA tensor");
TORCH_CHECK(qkv.scalar_type() == at::kFloat, "only float32 is supported");
TORCH_CHECK(qkv.dim() == 3, "qkv must have shape [B,T,3*C]");
TORCH_CHECK(qkv.is_contiguous(), "qkv must be contiguous");
const int B = (int)qkv.size(0);
const int T = (int)qkv.size(1);
const int C3 = (int)qkv.size(2);
TORCH_CHECK(C3 % 3 == 0, "last dimension must be 3*C");
const int C = C3 / 3;
const int nH = (int)n_head;
TORCH_CHECK(C % nH == 0, "C must be divisible by n_head");
const int hs = C / nH;
auto out = torch::empty({B, T, C}, qkv.options());
cudaStream_t stream = at::cuda::getCurrentCUDAStream();
if (hs == 64) {
dim3 block(64);
dim3 grid((unsigned int)(B * nH * T));
relu_causal_attn_hs64_kernel<<<grid, block, 0, stream>>>(
qkv.data_ptr<float>(), out.data_ptr<float>(), B, T, C, nH);
} else {
int threads = 128;
int total = B * T * C;
int blocks = (total + threads - 1) / threads;
relu_causal_attn_generic_kernel<<<blocks, threads, 0, stream>>>(
qkv.data_ptr<float>(), out.data_ptr<float>(), B, T, C, nH, hs);
}
C10_CUDA_KERNEL_LAUNCH_CHECK();
return out;
}
"""
_relu_attn_ext = load_inline(
name="relu_causal_attn_ext_v1",
cpp_sources=_cpp_src,
cuda_sources=_cuda_src,
functions=["relu_causal_attn_forward"],
extra_cflags=["-O3"],
extra_cuda_cflags=["-O3", "--use_fast_math"],
verbose=False,
)
class ModelNew(nn.Module):
def __init__(self, n_embd, n_head, 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.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):
qkv = self.c_attn(x)
return _relu_attn_ext.relu_causal_attn_forward(qkv.contiguous(), self.n_head)