gpt-5.5-priority · original · l123_single_gpt
incorrect 50_ReLUSelfAttention.py
model
gpt-5.5-priority
turns / max
1 / 1
tool calls
1
tokens (total)
0
wall clock
—
truncated
no
backend
cuda fp32
variant
original
runtime
—
ref runtime
5.13 μs
speedup
—
started
2026-05-05T00:51:45Z
correctness issue: Output mismatch
timingkernelbench.timing
kernel runtime
—
ref runtime
5.13 μs
speedup
—
kernel mean / median / std
—
ref mean / median / std
5.13 / — / 0.02 μs
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.940
dram util %
15.8%
compute util %
94.0%
bottleneck
compute-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
527.72 GB/s
dram util %
15.8%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
108.6%
bottleneck
compute-bound
dominant pipe %
94.0%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
175.4%
ld sectors / req
7.84
st sectors / req
—
regs / thread
286
smem / block
35840 B
block size
192
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
3518.7%
short_scoreboard
605.7%
mio_throttle
0.6%
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)