gpt-5.5-priority · original · l123_single_gpt
correct 41_Gemm_BatchNorm_GELU_ReLU.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
11.30 μs
ref runtime
11.90 μs
speedup
1.05x
started
2026-05-05T02:25:14Z
timingkernelbench.timing
kernel runtime
11.30 μs
ref runtime
11.90 μs
speedup
1.05x
kernel mean / median / std
11.30 / — / 0.02 μs
ref mean / median / std
11.90 / — / 4.14 μs
numerical precisioneval_kernel_against_ref
max abs error
1.53e-05
mean abs error
6.38e-08
max rel error
180226.12
mean rel error
6.73e-04
gpu memoryextended_metrics.measure_memory
kernel peak
705.19 MB
ref peak
933.19 MB
kernel / ref
0.76x
kernel launches / fusiontorch.profiler
kernel launches
10
ref launches
22
fusion ratio (ref / kernel)
2.20x
total cuda time
21989 μs
energyNVML (pynvml)
kernel mJ / run
1689.12
ref mJ / run
1447.32
ratio (ref / kernel)
0.86x
avg power
149.30 W
ref avg power
125.80 W
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
1.000
dram util %
100.0%
compute util %
100.0%
bottleneck
compute-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
4638.84 GB/s
dram util %
100.0%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
280.4%
bottleneck
compute-bound
dominant pipe %
124.9%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
317.9%
ld sectors / req
44.06
st sectors / req
—
regs / thread
370
smem / block
43008 B
block size
1024
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
10570.9%
short_scoreboard
2220.5%
mio_throttle
1244.9%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn

class Model(nn.Module):
    """
    Model that performs a GEMM, BatchNorm, GELU, and ReLU in sequence.
    """
    def __init__(self, in_features, out_features):
        super(Model, self).__init__()
        self.gemm = nn.Linear(in_features, out_features)
        self.batch_norm = nn.BatchNorm1d(out_features)

    def forward(self, x):
        """
        Args:
            x (torch.Tensor): Input tensor of shape (batch_size, in_features).
        Returns:
            torch.Tensor: Output tensor of shape (batch_size, out_features).
        """
        x = self.gemm(x)
        x = self.batch_norm(x)
        x = torch.nn.functional.gelu(x)
        x = torch.relu(x)
        return x

batch_size = 16384
in_features = 4096
out_features = 4096

def get_inputs():
    return [torch.rand(batch_size, in_features)]

def get_init_inputs():
    return [in_features, out_features]
turn 0 · FINAL · 1 tool call(s) · 74.9s turn · 74.9s LLM · 12,745 chars (click to expand)
turn 074.9s turn · 74.9s LLM · 0.0s tools · FINAL
reasoning
**Considering implementation updates** I need to think about how to implement the update for correctness. Should I update from a custom kernel or consider using torch's BatchNorm? Maybe I can utilize torch.ops. If I go with torch.nn.functional.linear, then I'll need a custom BatchNorm. For training, I need to compute the mean and variance across N=16,384 for C=4,096. This will yield an output y with 67 million floats, and I need to make reductions per column. **Considering vectorization** I’m thinking about implementing vectorization across columns. There’s the idea of using a block to handle feature tiles with 32 columns and rows; I need to reduce N. Since C equals 4096, I can map blocks and compute partial sums. Although this involves more code, it might be simpler using torch.mean or var instead. If I factor in BatchNorm and activation functions, I can potentially optimize during eval mode. However, I'm unsure if typical benchmarks account for this. It’s a bit complex! **Designing for forward computation** I’m looking into the task around forward computation, considering it’s likely for when the model is in eval mode. I need to design a solution that handles both training and evaluation. In the forward pass, if it's training, I might want to call an extension for BatchNorm, but for speed, I could implement eval optimizations as well. I should also consider implementing a fused kernel for high performance during eval, while ensuring correctness. It’s tricky, but I think I’m on the right track! **Considering CUDA bindings** I'm thinking about placing wrappers in my C++ sources that include declarations for the CUDA launcher. It seems that using C++ wrappers with Torch and CUDA launchers could work. Maybe it would be easier to keep host functions in the C++ sources and have kernel launchers in CUDA instead? I need to ensure that the binding function can find the declarations in my C++. This means I'd use my C++ sources with prototypes and function definitions calling `bn_forward_cuda` as stated.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import 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 bn_gelu_relu_forward(torch::Tensor x,\n torch::Tensor gamma,\n torch::Tensor beta,\n torch::Tensor running_mean,\n torch::Tensor running_var,\n bool training,\n double eps,\n double momentum);\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\n#define COL_TILE 32\n#define ROW_TILE 16\n#define Y_THREADS 8\n\n__global__ void partial_sum_kernel(const float* __restrict__ x,\n float* __restrict__ partial,\n float* __restrict__ partial2,\n int N, int C, int tiles) {\n int col0 = blockIdx.x * COL_TILE;\n int tile = blockIdx.y;\n int tx = threadIdx.x; // 0..31, feature within tile\n int ty = threadIdx.y; // 0..7, row reducer\n int c = col0 + tx;\n int row0 = tile * ROW_TILE;\n\n float s = 0.0f;\n float q = 0.0f;\n if (c < C) {\n #pragma unroll\n for (int r = ty; r < ROW_TILE; r += Y_THREADS) {\n int n = row0 + r;\n if (n < N) {\n float v = x[(long long)n * C + c];\n s += v;\n q += v * v;\n }\n }\n }\n\n __shared__ float sh[Y_THREADS][COL_TILE];\n __shared__ float sh2[Y_THREADS][COL_TILE];\n sh[ty][tx] = s;\n sh2[ty][tx] = q;\n __syncthreads();\n\n if (ty == 0 && c < C) {\n float ss = 0.0f;\n float qq = 0.0f;\n #pragma unroll\n for (int k = 0; k < Y_THREADS; ++k) {\n ss += sh[k][tx];\n qq += sh2[k][tx];\n }\n partial[(long long)tile * C + c] = ss;\n partial2[(long long)tile * C + c] = qq;\n }\n}\n\n__global__ void finalize_stats_kernel(const float* __restrict__ partial,\n const float* __restrict__ partial2,\n float* __restrict__ mean,\n float* __restrict__ invstd,\n float* __restrict__ running_mean,\n float* __restrict__ running_var,\n int N, int C, int tiles,\n float eps, float momentum) {\n int c = blockIdx.x;\n int tid = threadIdx.x;\n float s = 0.0f;\n float q = 0.0f;\n for (int t = tid; t < tiles; t += blockDim.x) {\n float a = partial[(long long)t * C + c];\n float b = partial2[(long long)t * C + c];\n s += a;\n q += b;\n }\n\n __shared__ float ss[256];\n __shared__ float qq[256];\n ss[tid] = s;\n qq[tid] = q;\n __syncthreads();\n\n for (int stride = 128; stride > 0; stride >>= 1) {\n if (tid < stride) {\n ss[tid] += ss[tid + stride];\n qq[tid] += qq[tid + stride];\n }\n __syncthreads();\n }\n\n if (tid == 0) {\n float n_inv = 1.0f / (float)N;\n float m = ss[0] * n_inv;\n float var = qq[0] * n_inv - m * m;\n var = var < 0.0f ? 0.0f : var;\n float inv = rsqrtf(var + eps);\n mean[c] = m;\n invstd[c] = inv;\n // PyTorch BatchNorm updates running_var with the unbiased variance.\n float unb = (N > 1) ? (var * ((float)N / (float)(N - 1))) : var;\n running_mean[c] = running_mean[c] * (1.0f - momentum) + m * momentum;\n running_var[c] = running_var[c] * (1.0f - momentum) + unb * momentum;\n }\n}\n\n__device__ __forceinline__ float gelu_relu_exact(float z) {\n if (z <= 0.0f) return 0.0f;\n const float inv_sqrt2 = 0.70710678118654752440f;\n return 0.5f * z * (1.0f + erff(z * inv_sqrt2));\n}\n\n__global__ void norm_act_train_kernel(float* __restrict__ x,\n const float* __restrict__ mean,\n const float* __restrict__ invstd,\n const float* __restrict__ gamma,\n const float* __restrict__ beta,\n long long total, int C) {\n long long idx = (long long)blockIdx.x * blockDim.x + threadIdx.x;\n long long stride = (long long)blockDim.x * gridDim.x;\n for (; idx < total; idx += stride) {\n int c = (int)(idx % C);\n float z = (x[idx] - mean[c]) * invstd[c];\n z = z * gamma[c] + beta[c];\n x[idx] = gelu_relu_exact(z);\n }\n}\n\n__global__ void norm_act_eval_kernel(float* __restrict__ x,\n const float* __restrict__ running_mean,\n const float* __restrict__ running_var,\n const float* __restrict__ gamma,\n const float* __restrict__ beta,\n long long total, int C, float eps) {\n long long idx = (long long)blockIdx.x * blockDim.x + threadIdx.x;\n long long stride = (long long)blockDim.x * gridDim.x;\n for (; idx < total; idx += stride) {\n int c = (int)(idx % C);\n float z = (x[idx] - running_mean[c]) * rsqrtf(running_var[c] + eps);\n z = z * gamma[c] + beta[c];\n x[idx] = gelu_relu_exact(z);\n }\n}\n\ntorch::Tensor bn_gelu_relu_forward(torch::Tensor x,\n torch::Tensor gamma,\n torch::Tensor beta,\n torch::Tensor running_mean,\n torch::Tensor running_var,\n bool training,\n double eps,\n double momentum) {\n TORCH_CHECK(x.is_cuda(), \"x must be CUDA\");\n TORCH_CHECK(x.scalar_type() == torch::kFloat32, \"only float32 supported\");\n TORCH_CHECK(x.dim() == 2, \"x must be 2D\");\n const int N = (int)x.size(0);\n const int C = (int)x.size(1);\n const long long total = (long long)N * C;\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n\n const int threads = 256;\n int blocks = (int)((total + threads - 1) / threads);\n if (blocks > 65535) blocks = 65535;\n\n if (training) {\n int tiles = (N + ROW_TILE - 1) / ROW_TILE;\n auto opts = x.options();\n torch::Tensor partial = torch::empty({tiles, C}, opts);\n torch::Tensor partial2 = torch::empty({tiles, C}, opts);\n torch::Tensor mean = torch::empty({C}, opts);\n torch::Tensor invstd = torch::empty({C}, opts);\n\n dim3 block(COL_TILE, Y_THREADS);\n dim3 grid((C + COL_TILE - 1) / COL_TILE, tiles);\n partial_sum_kernel<<<grid, block, 0, stream>>>(\n x.data_ptr<float>(), partial.data_ptr<float>(), partial2.data_ptr<float>(), N, C, tiles);\n finalize_stats_kernel<<<C, 256, 0, stream>>>(\n partial.data_ptr<float>(), partial2.data_ptr<float>(), mean.data_ptr<float>(), invstd.data_ptr<float>(),\n running_mean.data_ptr<float>(), running_var.data_ptr<float>(), N, C, tiles, (float)eps, (float)momentum);\n norm_act_train_kernel<<<blocks, threads, 0, stream>>>(\n x.data_ptr<float>(), mean.data_ptr<float>(), invstd.data_ptr<float>(), gamma.data_ptr<float>(), beta.data_ptr<float>(), total, C);\n } else {\n norm_act_eval_kernel<<<blocks, threads, 0, stream>>>(\n x.data_ptr<float>(), running_mean.data_ptr<float>(), running_var.data_ptr<float>(), gamma.data_ptr<float>(), beta.data_ptr<float>(), total, C, (float)eps);\n }\n return x;\n}\n\"\"\"\n\n_ext = load_inline(\n name=\"bn_gelu_relu_ext_v1\",\n cpp_sources=[_cpp_src],\n cuda_sources=[_cuda_src],\n functions=[\"bn_gelu_relu_forward\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\"],\n with_cuda=True,\n verbose=False,\n)\n\n\nclass ModelNew(nn.Module):\n \"\"\"GEMM + BatchNorm1d + GELU + ReLU, with fused CUDA post-processing.\"\"\"\n def __init__(self, in_features, out_features):\n super(ModelNew, self).__init__()\n self.gemm = nn.Linear(in_features, out_features)\n self.batch_norm = nn.BatchNorm1d(out_features)\n\n def forward(self, x):\n y = F.linear(x, self.gemm.weight, self.gemm.bias)\n bn = self.batch_norm\n if self.training and bn.track_running_stats:\n # Match BatchNorm's bookkeeping for fixed momentum (the default used here).\n bn.num_batches_tracked.add_(1)\n return _ext.bn_gelu_relu_forward(\n y,\n bn.weight,\n bn.bias,\n bn.running_mean,\n bn.running_var,\n bool(self.training),\n float(bn.eps),\n float(bn.momentum if bn.momentum is not None else 0.0),\n )\n" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 11.30 μs Runtime stats: mean=11.30μs median=?μs std=0.02μs Numerical precision: max_abs_err=1.53e-05 mean_abs_err=6.38e-08 max_rel_err=1.80e+05 Memory: 705.19 MB peak (ref: 933.19 MB, ratio: 0.7557x) Kernel launches: 10 (ref: 22, fusion ratio: 2.2) Energy: 1689.12 mJ/run (ref: 1447.32 mJ, ratio: 0.8568x) SOL score: 1.0
final submitted kernel (241 lines, 8,983 chars) — full source, untruncated
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 bn_gelu_relu_forward(torch::Tensor x,
                                   torch::Tensor gamma,
                                   torch::Tensor beta,
                                   torch::Tensor running_mean,
                                   torch::Tensor running_var,
                                   bool training,
                                   double eps,
                                   double momentum);
"""

_cuda_src = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <math.h>

#define COL_TILE 32
#define ROW_TILE 16
#define Y_THREADS 8

__global__ void partial_sum_kernel(const float* __restrict__ x,
                                   float* __restrict__ partial,
                                   float* __restrict__ partial2,
                                   int N, int C, int tiles) {
    int col0 = blockIdx.x * COL_TILE;
    int tile = blockIdx.y;
    int tx = threadIdx.x;  // 0..31, feature within tile
    int ty = threadIdx.y;  // 0..7, row reducer
    int c = col0 + tx;
    int row0 = tile * ROW_TILE;

    float s = 0.0f;
    float q = 0.0f;
    if (c < C) {
        #pragma unroll
        for (int r = ty; r < ROW_TILE; r += Y_THREADS) {
            int n = row0 + r;
            if (n < N) {
                float v = x[(long long)n * C + c];
                s += v;
                q += v * v;
            }
        }
    }

    __shared__ float sh[Y_THREADS][COL_TILE];
    __shared__ float sh2[Y_THREADS][COL_TILE];
    sh[ty][tx] = s;
    sh2[ty][tx] = q;
    __syncthreads();

    if (ty == 0 && c < C) {
        float ss = 0.0f;
        float qq = 0.0f;
        #pragma unroll
        for (int k = 0; k < Y_THREADS; ++k) {
            ss += sh[k][tx];
            qq += sh2[k][tx];
        }
        partial[(long long)tile * C + c] = ss;
        partial2[(long long)tile * C + c] = qq;
    }
}

__global__ void finalize_stats_kernel(const float* __restrict__ partial,
                                      const float* __restrict__ partial2,
                                      float* __restrict__ mean,
                                      float* __restrict__ invstd,
                                      float* __restrict__ running_mean,
                                      float* __restrict__ running_var,
                                      int N, int C, int tiles,
                                      float eps, float momentum) {
    int c = blockIdx.x;
    int tid = threadIdx.x;
    float s = 0.0f;
    float q = 0.0f;
    for (int t = tid; t < tiles; t += blockDim.x) {
        float a = partial[(long long)t * C + c];
        float b = partial2[(long long)t * C + c];
        s += a;
        q += b;
    }

    __shared__ float ss[256];
    __shared__ float qq[256];
    ss[tid] = s;
    qq[tid] = q;
    __syncthreads();

    for (int stride = 128; stride > 0; stride >>= 1) {
        if (tid < stride) {
            ss[tid] += ss[tid + stride];
            qq[tid] += qq[tid + stride];
        }
        __syncthreads();
    }

    if (tid == 0) {
        float n_inv = 1.0f / (float)N;
        float m = ss[0] * n_inv;
        float var = qq[0] * n_inv - m * m;
        var = var < 0.0f ? 0.0f : var;
        float inv = rsqrtf(var + eps);
        mean[c] = m;
        invstd[c] = inv;
        // PyTorch BatchNorm updates running_var with the unbiased variance.
        float unb = (N > 1) ? (var * ((float)N / (float)(N - 1))) : var;
        running_mean[c] = running_mean[c] * (1.0f - momentum) + m * momentum;
        running_var[c] = running_var[c] * (1.0f - momentum) + unb * momentum;
    }
}

__device__ __forceinline__ float gelu_relu_exact(float z) {
    if (z <= 0.0f) return 0.0f;
    const float inv_sqrt2 = 0.70710678118654752440f;
    return 0.5f * z * (1.0f + erff(z * inv_sqrt2));
}

__global__ void norm_act_train_kernel(float* __restrict__ x,
                                      const float* __restrict__ mean,
                                      const float* __restrict__ invstd,
                                      const float* __restrict__ gamma,
                                      const float* __restrict__ beta,
                                      long long total, int C) {
    long long idx = (long long)blockIdx.x * blockDim.x + threadIdx.x;
    long long stride = (long long)blockDim.x * gridDim.x;
    for (; idx < total; idx += stride) {
        int c = (int)(idx % C);
        float z = (x[idx] - mean[c]) * invstd[c];
        z = z * gamma[c] + beta[c];
        x[idx] = gelu_relu_exact(z);
    }
}

__global__ void norm_act_eval_kernel(float* __restrict__ x,
                                     const float* __restrict__ running_mean,
                                     const float* __restrict__ running_var,
                                     const float* __restrict__ gamma,
                                     const float* __restrict__ beta,
                                     long long total, int C, float eps) {
    long long idx = (long long)blockIdx.x * blockDim.x + threadIdx.x;
    long long stride = (long long)blockDim.x * gridDim.x;
    for (; idx < total; idx += stride) {
        int c = (int)(idx % C);
        float z = (x[idx] - running_mean[c]) * rsqrtf(running_var[c] + eps);
        z = z * gamma[c] + beta[c];
        x[idx] = gelu_relu_exact(z);
    }
}

torch::Tensor bn_gelu_relu_forward(torch::Tensor x,
                                   torch::Tensor gamma,
                                   torch::Tensor beta,
                                   torch::Tensor running_mean,
                                   torch::Tensor running_var,
                                   bool training,
                                   double eps,
                                   double momentum) {
    TORCH_CHECK(x.is_cuda(), "x must be CUDA");
    TORCH_CHECK(x.scalar_type() == torch::kFloat32, "only float32 supported");
    TORCH_CHECK(x.dim() == 2, "x must be 2D");
    const int N = (int)x.size(0);
    const int C = (int)x.size(1);
    const long long total = (long long)N * C;
    cudaStream_t stream = at::cuda::getCurrentCUDAStream();

    const int threads = 256;
    int blocks = (int)((total + threads - 1) / threads);
    if (blocks > 65535) blocks = 65535;

    if (training) {
        int tiles = (N + ROW_TILE - 1) / ROW_TILE;
        auto opts = x.options();
        torch::Tensor partial = torch::empty({tiles, C}, opts);
        torch::Tensor partial2 = torch::empty({tiles, C}, opts);
        torch::Tensor mean = torch::empty({C}, opts);
        torch::Tensor invstd = torch::empty({C}, opts);

        dim3 block(COL_TILE, Y_THREADS);
        dim3 grid((C + COL_TILE - 1) / COL_TILE, tiles);
        partial_sum_kernel<<<grid, block, 0, stream>>>(
            x.data_ptr<float>(), partial.data_ptr<float>(), partial2.data_ptr<float>(), N, C, tiles);
        finalize_stats_kernel<<<C, 256, 0, stream>>>(
            partial.data_ptr<float>(), partial2.data_ptr<float>(), mean.data_ptr<float>(), invstd.data_ptr<float>(),
            running_mean.data_ptr<float>(), running_var.data_ptr<float>(), N, C, tiles, (float)eps, (float)momentum);
        norm_act_train_kernel<<<blocks, threads, 0, stream>>>(
            x.data_ptr<float>(), mean.data_ptr<float>(), invstd.data_ptr<float>(), gamma.data_ptr<float>(), beta.data_ptr<float>(), total, C);
    } else {
        norm_act_eval_kernel<<<blocks, threads, 0, stream>>>(
            x.data_ptr<float>(), running_mean.data_ptr<float>(), running_var.data_ptr<float>(), gamma.data_ptr<float>(), beta.data_ptr<float>(), total, C, (float)eps);
    }
    return x;
}
"""

_ext = load_inline(
    name="bn_gelu_relu_ext_v1",
    cpp_sources=[_cpp_src],
    cuda_sources=[_cuda_src],
    functions=["bn_gelu_relu_forward"],
    extra_cflags=["-O3"],
    extra_cuda_cflags=["-O3"],
    with_cuda=True,
    verbose=False,
)


class ModelNew(nn.Module):
    """GEMM + BatchNorm1d + GELU + ReLU, with fused CUDA post-processing."""
    def __init__(self, in_features, out_features):
        super(ModelNew, self).__init__()
        self.gemm = nn.Linear(in_features, out_features)
        self.batch_norm = nn.BatchNorm1d(out_features)

    def forward(self, x):
        y = F.linear(x, self.gemm.weight, self.gemm.bias)
        bn = self.batch_norm
        if self.training and bn.track_running_stats:
            # Match BatchNorm's bookkeeping for fixed momentum (the default used here).
            bn.num_batches_tracked.add_(1)
        return _ext.bn_gelu_relu_forward(
            y,
            bn.weight,
            bn.bias,
            bn.running_mean,
            bn.running_var,
            bool(self.training),
            float(bn.eps),
            float(bn.momentum if bn.momentum is not None else 0.0),
        )