gpt-5.5-priority · original · l123_single_gpt
correct 67_Conv2d_GELU_GlobalAvgPool.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
7.58 μs
ref runtime
6.53 μs
speedup
0.86x
started
2026-05-05T02:34:27Z
timingkernelbench.timing
kernel runtime
7.58 μs
ref runtime
6.53 μs
speedup
0.86x
kernel mean / median / std
7.58 / — / 0.16 μs
ref mean / median / std
6.53 / — / 0.02 μs
numerical precisioneval_kernel_against_ref
max abs error
1.79e-07
mean abs error
5.67e-09
max rel error
3.64e-07
mean rel error
5.07e-08
gpu memoryextended_metrics.measure_memory
kernel peak
288.07 MB
ref peak
4320.29 MB
kernel / ref
0.07x
kernel launches / fusiontorch.profiler
kernel launches
1
ref launches
14
fusion ratio (ref / kernel)
14.00x
total cuda time
23 μs
energyNVML (pynvml)
kernel mJ / run
969.28
ref mJ / run
2179.68
ratio (ref / kernel)
2.25x
avg power
128.04 W
ref avg power
333.77 W
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.270
dram util %
3.5%
compute util %
27.0%
bottleneck
compute-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
116.34 GB/s
dram util %
3.5%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
33.2%
bottleneck
compute-bound
dominant pipe %
27.0%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
56.8%
ld sectors / req
1.42
st sectors / req
—
regs / thread
80
smem / block
9216 B
block size
256
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
372.5%
short_scoreboard
45.6%
mio_throttle
0.0%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn

class Model(nn.Module):
    """
    Simple model that performs a convolution, applies GELU, and then performs global average pooling.
    """
    def __init__(self, in_channels, out_channels, kernel_size):
        super(Model, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size)

    def forward(self, x):
        """
        Args:
            x: Input tensor of shape (batch_size, in_channels, height, width)
        Returns:
            Output tensor of shape (batch_size, out_channels)
        """
        x = self.conv(x)
        x = torch.nn.functional.gelu(x)
        x = torch.nn.functional.adaptive_avg_pool2d(x, 1)
        x = x.squeeze(-1).squeeze(-1)
        return x

batch_size = 128
in_channels = 8
out_channels = 64
height, width = 256, 256
kernel_size = 3

def get_inputs():
    return [torch.rand(batch_size, in_channels, height, width)]

def get_init_inputs():
    return [in_channels, out_channels, kernel_size]
turn 0 · FINAL · 1 tool call(s) · 86.7s turn · 86.7s LLM · 9,568 chars (click to expand)
turn 086.7s turn · 86.7s LLM · 0.0s tools · FINAL
reasoning
**Considering convolution optimization** I'm thinking about computing all convolution values. Maybe I’ll use im2col GEMM, but I need to incorporate gelu and reduce operations. An optimized tiled convolution might work well since I have 64 output channels and 8 input channels. For each N and spatial tile, I could calculate those channels with shared input tiles and weights. I should also compute partial sums across all channels and consider using atomics or a two-stage process for efficiency. **Optimizing convolution operations** Each thread accumulates four sums over roughly 252 pixels by 72. For each pixel, I'll load 72 inputs once and perform four FMAs (fused multiply-adds) per load, which should improve arithmetic intensity and reduce input loads by a factor of four. After computing the convolution for four output channels per pixel, I'll add the GELU function, using four accumulators. It could be beneficial to group eight channels to reuse input, although the erf in GELU is computationally expensive. I'll consider using an approximation for efficiency. **Planning reduction in PyTorch** I’m confirming that PyTorch uses the error function (erf) for its calculations. I need the output to be an average, so I'll implement a reduction within the block for eight sums. With 256 threads, that means I'll have shared memory for 8 times 256 floats. Using block reduction seems like a good approach. The grid would be organized with (N, groups=8), and the kernel would loop over the hardware's spatially strided dimensions, ensuring that H out equals H minus K plus one. **Exploring function approximations** I'm thinking about how I could exploit average values over spatial data. For small convolution values, I wonder if using a GELU activation function could be approximated with a tanh function. It’s interesting to consider how these functions might interact and what implications that has for performance. I should explore this further to see if this approximation could yield better results in certain conditions. There's quite a bit to unpack here! **Implementing fast erf approximation** I'm looking into using a fast approximation for the error function (erf) with a maximum error of about 1.5e-7. A good choice seems to be the Abramowitz and Stegun method, which incorporates __expf for better speed than erff. The approximation consists of determining the sign, and taking the absolute value of x, followed by some polynomial calculations. It should be faster and meet the accuracy requirements, even better than tanh, so I need to implement fast_erff_approx properly.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n_cpp_src = r'''\n#include <torch/extension.h>\n\ntorch::Tensor fused_conv_gelu_pool(torch::Tensor x, torch::Tensor w, torch::Tensor b);\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\n// Fast high-accuracy erf approximation (max abs error about 1.5e-7 for erf).\n// This is much faster than libdevice erff and is easily within the required fp32 tolerance\n// after the final spatial average.\n__device__ __forceinline__ float fast_erf(float x) {\n const float a1 = 0.254829592f;\n const float a2 = -0.284496736f;\n const float a3 = 1.421413741f;\n const float a4 = -1.453152027f;\n const float a5 = 1.061405429f;\n const float p = 0.327591100f;\n\n float ax = fabsf(x);\n float t = 1.0f / (1.0f + p * ax);\n float y = (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t;\n y = 1.0f - y * __expf(-ax * ax);\n return copysignf(y, x);\n}\n\n__device__ __forceinline__ float gelu_fast(float x) {\n return 0.5f * x * (1.0f + fast_erf(x * 0.70710678118654752440f));\n}\n\ntemplate<int CPG, int BLOCK>\n__global__ void conv3_gelu_gap_kernel(const float* __restrict__ x,\n const float* __restrict__ w,\n const float* __restrict__ b,\n float* __restrict__ out,\n int N, int H, int W, int Cout) {\n int n = blockIdx.x;\n int cg = blockIdx.y;\n int co0 = cg * CPG;\n int tid = threadIdx.x;\n int Ho = H - 2;\n int Wo = W - 2;\n int HWo = Ho * Wo;\n\n float sum[CPG];\n #pragma unroll\n for (int j = 0; j < CPG; ++j) sum[j] = 0.0f;\n\n // Fixed problem uses Cin=8, K=3. One block computes one batch item and\n // a group of output channels, reusing the same input loads for all channels.\n for (int idx = tid; idx < HWo; idx += BLOCK) {\n int oy = idx / Wo;\n int ox = idx - oy * Wo;\n\n float acc[CPG];\n #pragma unroll\n for (int j = 0; j < CPG; ++j) {\n int co = co0 + j;\n acc[j] = (co < Cout) ? __ldg(b + co) : 0.0f;\n }\n\n #pragma unroll\n for (int ci = 0; ci < 8; ++ci) {\n const float* xp = x + ((n * 8 + ci) * H + oy) * W + ox;\n\n float v0 = __ldg(xp + 0 * W + 0);\n float v1 = __ldg(xp + 0 * W + 1);\n float v2 = __ldg(xp + 0 * W + 2);\n float v3 = __ldg(xp + 1 * W + 0);\n float v4 = __ldg(xp + 1 * W + 1);\n float v5 = __ldg(xp + 1 * W + 2);\n float v6 = __ldg(xp + 2 * W + 0);\n float v7 = __ldg(xp + 2 * W + 1);\n float v8 = __ldg(xp + 2 * W + 2);\n\n #pragma unroll\n for (int j = 0; j < CPG; ++j) {\n int co = co0 + j;\n if (co < Cout) {\n const float* wp = w + ((co * 8 + ci) * 3) * 3;\n float a = acc[j];\n a = fmaf(v0, __ldg(wp + 0), a);\n a = fmaf(v1, __ldg(wp + 1), a);\n a = fmaf(v2, __ldg(wp + 2), a);\n a = fmaf(v3, __ldg(wp + 3), a);\n a = fmaf(v4, __ldg(wp + 4), a);\n a = fmaf(v5, __ldg(wp + 5), a);\n a = fmaf(v6, __ldg(wp + 6), a);\n a = fmaf(v7, __ldg(wp + 7), a);\n a = fmaf(v8, __ldg(wp + 8), a);\n acc[j] = a;\n }\n }\n }\n\n #pragma unroll\n for (int j = 0; j < CPG; ++j) {\n if (co0 + j < Cout) sum[j] += gelu_fast(acc[j]);\n }\n }\n\n __shared__ float smem[CPG][BLOCK];\n #pragma unroll\n for (int j = 0; j < CPG; ++j) smem[j][tid] = sum[j];\n __syncthreads();\n\n for (int stride = BLOCK >> 1; stride > 0; stride >>= 1) {\n if (tid < stride) {\n #pragma unroll\n for (int j = 0; j < CPG; ++j) smem[j][tid] += smem[j][tid + stride];\n }\n __syncthreads();\n }\n\n if (tid == 0) {\n float inv = 1.0f / (float)HWo;\n #pragma unroll\n for (int j = 0; j < CPG; ++j) {\n int co = co0 + j;\n if (co < Cout) out[n * Cout + co] = smem[j][0] * inv;\n }\n }\n}\n\ntorch::Tensor fused_conv_gelu_pool(torch::Tensor x, torch::Tensor w, torch::Tensor b) {\n const int N = (int)x.size(0);\n const int H = (int)x.size(2);\n const int W = (int)x.size(3);\n const int Cout = (int)w.size(0);\n\n auto out = torch::empty({N, Cout}, x.options());\n\n constexpr int CPG = 8;\n constexpr int BLOCK = 256;\n dim3 block(BLOCK);\n dim3 grid(N, (Cout + CPG - 1) / CPG);\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n conv3_gelu_gap_kernel<CPG, BLOCK><<<grid, block, 0, stream>>>(\n x.data_ptr<float>(), w.data_ptr<float>(), b.data_ptr<float>(), out.data_ptr<float>(),\n N, H, W, Cout);\n return out;\n}\n'''\n\n_ext = load_inline(\n name='fused_conv_gelu_gap_ext_v1',\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=['fused_conv_gelu_pool'],\n extra_cflags=['-O3'],\n extra_cuda_cflags=['-O3'],\n verbose=False,\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, in_channels, out_channels, kernel_size):\n super(ModelNew, self).__init__()\n self.conv = nn.Conv2d(in_channels, out_channels, kernel_size)\n\n def forward(self, x):\n return _ext.fused_conv_gelu_pool(x, self.conv.weight, self.conv.bias)\n" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 7.58 μs Runtime stats: mean=7.58μs median=?μs std=0.16μs Numerical precision: max_abs_err=1.79e-07 mean_abs_err=5.67e-09 max_rel_err=3.64e-07 Memory: 288.07 MB peak (ref: 4320.29 MB, ratio: 0.0667x) Kernel launches: 1 (ref: 14, fusion ratio: 14.0) Energy: 969.28 mJ/run (ref: 2179.68 mJ, ratio: 2.2488x) SOL score: 0.2701
final submitted kernel (170 lines, 5,525 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

_cpp_src = r'''
#include <torch/extension.h>

torch::Tensor fused_conv_gelu_pool(torch::Tensor x, torch::Tensor w, torch::Tensor b);
'''

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

// Fast high-accuracy erf approximation (max abs error about 1.5e-7 for erf).
// This is much faster than libdevice erff and is easily within the required fp32 tolerance
// after the final spatial average.
__device__ __forceinline__ float fast_erf(float x) {
    const float a1 =  0.254829592f;
    const float a2 = -0.284496736f;
    const float a3 =  1.421413741f;
    const float a4 = -1.453152027f;
    const float a5 =  1.061405429f;
    const float p  =  0.327591100f;

    float ax = fabsf(x);
    float t = 1.0f / (1.0f + p * ax);
    float y = (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t;
    y = 1.0f - y * __expf(-ax * ax);
    return copysignf(y, x);
}

__device__ __forceinline__ float gelu_fast(float x) {
    return 0.5f * x * (1.0f + fast_erf(x * 0.70710678118654752440f));
}

template<int CPG, int BLOCK>
__global__ void conv3_gelu_gap_kernel(const float* __restrict__ x,
                                      const float* __restrict__ w,
                                      const float* __restrict__ b,
                                      float* __restrict__ out,
                                      int N, int H, int W, int Cout) {
    int n = blockIdx.x;
    int cg = blockIdx.y;
    int co0 = cg * CPG;
    int tid = threadIdx.x;
    int Ho = H - 2;
    int Wo = W - 2;
    int HWo = Ho * Wo;

    float sum[CPG];
    #pragma unroll
    for (int j = 0; j < CPG; ++j) sum[j] = 0.0f;

    // Fixed problem uses Cin=8, K=3.  One block computes one batch item and
    // a group of output channels, reusing the same input loads for all channels.
    for (int idx = tid; idx < HWo; idx += BLOCK) {
        int oy = idx / Wo;
        int ox = idx - oy * Wo;

        float acc[CPG];
        #pragma unroll
        for (int j = 0; j < CPG; ++j) {
            int co = co0 + j;
            acc[j] = (co < Cout) ? __ldg(b + co) : 0.0f;
        }

        #pragma unroll
        for (int ci = 0; ci < 8; ++ci) {
            const float* xp = x + ((n * 8 + ci) * H + oy) * W + ox;

            float v0 = __ldg(xp + 0 * W + 0);
            float v1 = __ldg(xp + 0 * W + 1);
            float v2 = __ldg(xp + 0 * W + 2);
            float v3 = __ldg(xp + 1 * W + 0);
            float v4 = __ldg(xp + 1 * W + 1);
            float v5 = __ldg(xp + 1 * W + 2);
            float v6 = __ldg(xp + 2 * W + 0);
            float v7 = __ldg(xp + 2 * W + 1);
            float v8 = __ldg(xp + 2 * W + 2);

            #pragma unroll
            for (int j = 0; j < CPG; ++j) {
                int co = co0 + j;
                if (co < Cout) {
                    const float* wp = w + ((co * 8 + ci) * 3) * 3;
                    float a = acc[j];
                    a = fmaf(v0, __ldg(wp + 0), a);
                    a = fmaf(v1, __ldg(wp + 1), a);
                    a = fmaf(v2, __ldg(wp + 2), a);
                    a = fmaf(v3, __ldg(wp + 3), a);
                    a = fmaf(v4, __ldg(wp + 4), a);
                    a = fmaf(v5, __ldg(wp + 5), a);
                    a = fmaf(v6, __ldg(wp + 6), a);
                    a = fmaf(v7, __ldg(wp + 7), a);
                    a = fmaf(v8, __ldg(wp + 8), a);
                    acc[j] = a;
                }
            }
        }

        #pragma unroll
        for (int j = 0; j < CPG; ++j) {
            if (co0 + j < Cout) sum[j] += gelu_fast(acc[j]);
        }
    }

    __shared__ float smem[CPG][BLOCK];
    #pragma unroll
    for (int j = 0; j < CPG; ++j) smem[j][tid] = sum[j];
    __syncthreads();

    for (int stride = BLOCK >> 1; stride > 0; stride >>= 1) {
        if (tid < stride) {
            #pragma unroll
            for (int j = 0; j < CPG; ++j) smem[j][tid] += smem[j][tid + stride];
        }
        __syncthreads();
    }

    if (tid == 0) {
        float inv = 1.0f / (float)HWo;
        #pragma unroll
        for (int j = 0; j < CPG; ++j) {
            int co = co0 + j;
            if (co < Cout) out[n * Cout + co] = smem[j][0] * inv;
        }
    }
}

torch::Tensor fused_conv_gelu_pool(torch::Tensor x, torch::Tensor w, torch::Tensor b) {
    const int N = (int)x.size(0);
    const int H = (int)x.size(2);
    const int W = (int)x.size(3);
    const int Cout = (int)w.size(0);

    auto out = torch::empty({N, Cout}, x.options());

    constexpr int CPG = 8;
    constexpr int BLOCK = 256;
    dim3 block(BLOCK);
    dim3 grid(N, (Cout + CPG - 1) / CPG);
    cudaStream_t stream = at::cuda::getCurrentCUDAStream();
    conv3_gelu_gap_kernel<CPG, BLOCK><<<grid, block, 0, stream>>>(
        x.data_ptr<float>(), w.data_ptr<float>(), b.data_ptr<float>(), out.data_ptr<float>(),
        N, H, W, Cout);
    return out;
}
'''

_ext = load_inline(
    name='fused_conv_gelu_gap_ext_v1',
    cpp_sources=_cpp_src,
    cuda_sources=_cuda_src,
    functions=['fused_conv_gelu_pool'],
    extra_cflags=['-O3'],
    extra_cuda_cflags=['-O3'],
    verbose=False,
)

class ModelNew(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size):
        super(ModelNew, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size)

    def forward(self, x):
        return _ext.fused_conv_gelu_pool(x, self.conv.weight, self.conv.bias)