gpt-5.5-priority · original · l123_single_gpt
incorrect 43_MinGPTCausalAttention.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
6.15 μs
speedup
—
started
2026-05-05T01:00:00Z
correctness issue: Output mismatch
timingkernelbench.timing
kernel runtime
—
ref runtime
6.15 μs
speedup
—
kernel mean / median / std
—
ref mean / median / std
6.15 / — / 0.69 μs
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
1.000
dram util %
56.2%
compute util %
100.0%
bottleneck
compute-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
1883.52 GB/s
dram util %
56.2%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
99.0%
bottleneck
compute-bound
dominant pipe %
179.0%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
228.7%
ld sectors / req
12.00
st sectors / req
—
regs / thread
547
smem / block
70656 B
block size
384
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
1648.6%
short_scoreboard
342.5%
mio_throttle
0.5%
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