gpt-5.5-priority · original · l123_single_gpt
correct 71_Conv2d_Divide_LeakyReLU.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
1.69 μs
ref runtime
1.87 μs
speedup
1.11x
started
2026-05-05T02:03:52Z
timingkernelbench.timing
kernel runtime
1.69 μs
ref runtime
1.87 μs
speedup
1.11x
kernel mean / median / std
1.69 / — / 0.20 μs
ref mean / median / std
1.87 / — / 0.62 μs
numerical precisioneval_kernel_against_ref
max abs error
3.58e-07
mean abs error
8.41e-09
max rel error
19943.21
mean rel error
3.39e-05
gpu memoryextended_metrics.measure_memory
kernel peak
560.16 MB
ref peak
1056.29 MB
kernel / ref
0.53x
kernel launches / fusiontorch.profiler
kernel launches
1
ref launches
9
fusion ratio (ref / kernel)
9.00x
total cuda time
29 μs
energyNVML (pynvml)
kernel mJ / run
1208.62
ref mJ / run
241.76
ratio (ref / kernel)
0.20x
avg power
724.87 W
ref avg power
134.42 W
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.218
dram util %
10.1%
compute util %
21.8%
bottleneck
compute-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
339.72 GB/s
dram util %
10.1%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
48.1%
bottleneck
compute-bound
dominant pipe %
21.8%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
94.3%
ld sectors / req
1.08
st sectors / req
—
regs / thread
56
smem / block
11392 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
mio_throttle
204.5%
long_scoreboard
128.7%
short_scoreboard
112.5%
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, divides by a constant, and applies LeakyReLU.
    """
    def __init__(self, in_channels, out_channels, kernel_size, divisor):
        super(Model, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size)
        self.divisor = divisor

    def forward(self, x):
        x = self.conv(x)
        x = x / self.divisor
        x = torch.nn.functional.leaky_relu(x, negative_slope=0.01)
        return x

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

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

def get_init_inputs():
    return [in_channels, out_channels, kernel_size, divisor]
turn 0 · FINAL · 1 tool call(s) · 81.5s turn · 81.5s LLM · 9,950 chars (click to expand)
turn 081.5s turn · 81.5s LLM · 0.0s tools · FINAL
reasoning
**Analyzing global uniform load** I'm working with global uniform load that delivers one transaction per thread, which is interesting. I notice the weight is just 18KB in L1, and it seems OCG=4 reduces the input instruction compared to one occupancy count. I'm thinking about accumulating four times, then applying activation. It sounds like Cout needs to be a multiple of four, and there's a plan to handle the tail. Finally, I'm considering block sizes of around 256 threads over spatial dimensions. Flattened spatial usage could be beneficial too. **Considering kernel optimization** I'm wondering if I should use a divisor in the kernel to improve performance. It feels like division per output could slow things down, especially with lots of operations. Maybe I could precompute the inverse of the divisor as `1.0f/divisor` in C++ and pass that instead. If I'm using `divisor=2`, that should work exactly and speed things up. Let's explore this approach and see if it enhances efficiency!
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n\n_cpp_src = r\"\"\"\n#include <torch/extension.h>\n\ntorch::Tensor fused_conv_div_lrelu_forward(torch::Tensor x,\n torch::Tensor weight,\n torch::Tensor bias,\n double divisor);\n\"\"\"\n\n_cuda_src = r\"\"\"\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <c10/cuda/CUDAException.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n#define TILE 16\n#define IN_TILE 18\n#define CIN_FIXED 8\n#define K_FIXED 3\n#define OCG 8\n\n__global__ __launch_bounds__(256, 2)\nvoid conv3x3_c8_ocg8_kernel(const float* __restrict__ x,\n const float* __restrict__ w,\n const float* __restrict__ b,\n float* __restrict__ out,\n int N, int Cout, int H, int W,\n int OH, int OW,\n float inv_divisor) {\n __shared__ float sh[CIN_FIXED * IN_TILE * IN_TILE];\n\n const int tx = threadIdx.x;\n const int ty = threadIdx.y;\n const int tid = ty * TILE + tx;\n\n const int tile_ow = blockIdx.x * TILE;\n const int tile_oh = blockIdx.y * TILE;\n const int oc_groups = (Cout + OCG - 1) / OCG;\n const int group = blockIdx.z % oc_groups;\n const int n = blockIdx.z / oc_groups;\n const int oc0 = group * OCG;\n\n // Cooperative load of the input patch needed by this 16x16 output tile.\n #pragma unroll\n for (int idx = tid; idx < CIN_FIXED * IN_TILE * IN_TILE; idx += TILE * TILE) {\n int rem = idx;\n const int ci = rem / (IN_TILE * IN_TILE);\n rem -= ci * (IN_TILE * IN_TILE);\n const int sy = rem / IN_TILE;\n const int sx = rem - sy * IN_TILE;\n const int ih = tile_oh + sy;\n const int iw = tile_ow + sx;\n float v = 0.0f;\n if (ih < H && iw < W) {\n v = x[((n * CIN_FIXED + ci) * H + ih) * W + iw];\n }\n sh[idx] = v;\n }\n __syncthreads();\n\n const int oh = tile_oh + ty;\n const int ow = tile_ow + tx;\n if (oh >= OH || ow >= OW) return;\n\n float acc0 = (oc0 + 0 < Cout) ? b[oc0 + 0] : 0.0f;\n float acc1 = (oc0 + 1 < Cout) ? b[oc0 + 1] : 0.0f;\n float acc2 = (oc0 + 2 < Cout) ? b[oc0 + 2] : 0.0f;\n float acc3 = (oc0 + 3 < Cout) ? b[oc0 + 3] : 0.0f;\n float acc4 = (oc0 + 4 < Cout) ? b[oc0 + 4] : 0.0f;\n float acc5 = (oc0 + 5 < Cout) ? b[oc0 + 5] : 0.0f;\n float acc6 = (oc0 + 6 < Cout) ? b[oc0 + 6] : 0.0f;\n float acc7 = (oc0 + 7 < Cout) ? b[oc0 + 7] : 0.0f;\n\n #pragma unroll\n for (int ci = 0; ci < CIN_FIXED; ++ci) {\n #pragma unroll\n for (int ky = 0; ky < K_FIXED; ++ky) {\n #pragma unroll\n for (int kx = 0; kx < K_FIXED; ++kx) {\n const float xv = sh[(ci * IN_TILE + (ty + ky)) * IN_TILE + (tx + kx)];\n const int kbase = ((ci * K_FIXED + ky) * K_FIXED + kx);\n if (oc0 + 0 < Cout) acc0 = fmaf(xv, w[((oc0 + 0) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc0);\n if (oc0 + 1 < Cout) acc1 = fmaf(xv, w[((oc0 + 1) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc1);\n if (oc0 + 2 < Cout) acc2 = fmaf(xv, w[((oc0 + 2) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc2);\n if (oc0 + 3 < Cout) acc3 = fmaf(xv, w[((oc0 + 3) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc3);\n if (oc0 + 4 < Cout) acc4 = fmaf(xv, w[((oc0 + 4) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc4);\n if (oc0 + 5 < Cout) acc5 = fmaf(xv, w[((oc0 + 5) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc5);\n if (oc0 + 6 < Cout) acc6 = fmaf(xv, w[((oc0 + 6) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc6);\n if (oc0 + 7 < Cout) acc7 = fmaf(xv, w[((oc0 + 7) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc7);\n }\n }\n }\n\n const int spatial = OH * OW;\n const int out_base = (n * Cout * spatial) + oh * OW + ow;\n\n if (oc0 + 0 < Cout) { float v = acc0 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 0) * spatial] = v; }\n if (oc0 + 1 < Cout) { float v = acc1 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 1) * spatial] = v; }\n if (oc0 + 2 < Cout) { float v = acc2 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 2) * spatial] = v; }\n if (oc0 + 3 < Cout) { float v = acc3 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 3) * spatial] = v; }\n if (oc0 + 4 < Cout) { float v = acc4 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 4) * spatial] = v; }\n if (oc0 + 5 < Cout) { float v = acc5 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 5) * spatial] = v; }\n if (oc0 + 6 < Cout) { float v = acc6 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 6) * spatial] = v; }\n if (oc0 + 7 < Cout) { float v = acc7 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 7) * spatial] = v; }\n}\n\ntorch::Tensor fused_conv_div_lrelu_forward(torch::Tensor x,\n torch::Tensor weight,\n torch::Tensor bias,\n double divisor) {\n TORCH_CHECK(x.is_cuda(), \"x must be a CUDA tensor\");\n TORCH_CHECK(weight.is_cuda(), \"weight must be a CUDA tensor\");\n TORCH_CHECK(bias.is_cuda(), \"bias must be a CUDA tensor\");\n TORCH_CHECK(x.scalar_type() == torch::kFloat32, \"only float32 supported\");\n\n const int N = (int)x.size(0);\n const int C = (int)x.size(1);\n const int H = (int)x.size(2);\n const int W = (int)x.size(3);\n const int Cout = (int)weight.size(0);\n const int K = (int)weight.size(2);\n TORCH_CHECK(C == CIN_FIXED && K == K_FIXED && weight.size(3) == K_FIXED,\n \"this optimized kernel supports only C=8, K=3\");\n\n const int OH = H - K_FIXED + 1;\n const int OW = W - K_FIXED + 1;\n auto out = torch::empty({N, Cout, OH, OW}, x.options());\n\n dim3 block(TILE, TILE, 1);\n dim3 grid((OW + TILE - 1) / TILE,\n (OH + TILE - 1) / TILE,\n N * ((Cout + OCG - 1) / OCG));\n\n const float inv_div = 1.0f / (float)divisor;\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n conv3x3_c8_ocg8_kernel<<<grid, block, 0, stream>>>(\n x.data_ptr<float>(), weight.data_ptr<float>(), bias.data_ptr<float>(), out.data_ptr<float>(),\n N, Cout, H, W, OH, OW, inv_div);\n C10_CUDA_KERNEL_LAUNCH_CHECK();\n return out;\n}\n\"\"\"\n\n_ext = load_inline(\n name=\"fused_conv_div_lrelu_c8k3_v1\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"fused_conv_div_lrelu_forward\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\"],\n verbose=False,\n)\n\n\nclass ModelNew(nn.Module):\n \"\"\"Fused Conv2d / divisor / LeakyReLU implementation for the given model.\"\"\"\n def __init__(self, in_channels, out_channels, kernel_size, divisor):\n super(ModelNew, self).__init__()\n self.conv = nn.Conv2d(in_channels, out_channels, kernel_size)\n self.divisor = divisor\n\n def forward(self, x):\n return _ext.fused_conv_div_lrelu_forward(x, self.conv.weight, self.conv.bias, float(self.divisor))\n" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 1.69 μs Runtime stats: mean=1.69μs median=?μs std=0.20μs Numerical precision: max_abs_err=3.58e-07 mean_abs_err=8.41e-09 max_rel_err=1.99e+04 Memory: 560.16 MB peak (ref: 1056.29 MB, ratio: 0.5303x) Kernel launches: 1 (ref: 9, fusion ratio: 9.0) Energy: 1208.62 mJ/run (ref: 241.76 mJ, ratio: 0.2x) SOL score: 0.2184
final submitted kernel (170 lines, 7,334 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_div_lrelu_forward(torch::Tensor x,
                                           torch::Tensor weight,
                                           torch::Tensor bias,
                                           double divisor);
"""

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

#define TILE 16
#define IN_TILE 18
#define CIN_FIXED 8
#define K_FIXED 3
#define OCG 8

__global__ __launch_bounds__(256, 2)
void conv3x3_c8_ocg8_kernel(const float* __restrict__ x,
                            const float* __restrict__ w,
                            const float* __restrict__ b,
                            float* __restrict__ out,
                            int N, int Cout, int H, int W,
                            int OH, int OW,
                            float inv_divisor) {
    __shared__ float sh[CIN_FIXED * IN_TILE * IN_TILE];

    const int tx = threadIdx.x;
    const int ty = threadIdx.y;
    const int tid = ty * TILE + tx;

    const int tile_ow = blockIdx.x * TILE;
    const int tile_oh = blockIdx.y * TILE;
    const int oc_groups = (Cout + OCG - 1) / OCG;
    const int group = blockIdx.z % oc_groups;
    const int n = blockIdx.z / oc_groups;
    const int oc0 = group * OCG;

    // Cooperative load of the input patch needed by this 16x16 output tile.
    #pragma unroll
    for (int idx = tid; idx < CIN_FIXED * IN_TILE * IN_TILE; idx += TILE * TILE) {
        int rem = idx;
        const int ci = rem / (IN_TILE * IN_TILE);
        rem -= ci * (IN_TILE * IN_TILE);
        const int sy = rem / IN_TILE;
        const int sx = rem - sy * IN_TILE;
        const int ih = tile_oh + sy;
        const int iw = tile_ow + sx;
        float v = 0.0f;
        if (ih < H && iw < W) {
            v = x[((n * CIN_FIXED + ci) * H + ih) * W + iw];
        }
        sh[idx] = v;
    }
    __syncthreads();

    const int oh = tile_oh + ty;
    const int ow = tile_ow + tx;
    if (oh >= OH || ow >= OW) return;

    float acc0 = (oc0 + 0 < Cout) ? b[oc0 + 0] : 0.0f;
    float acc1 = (oc0 + 1 < Cout) ? b[oc0 + 1] : 0.0f;
    float acc2 = (oc0 + 2 < Cout) ? b[oc0 + 2] : 0.0f;
    float acc3 = (oc0 + 3 < Cout) ? b[oc0 + 3] : 0.0f;
    float acc4 = (oc0 + 4 < Cout) ? b[oc0 + 4] : 0.0f;
    float acc5 = (oc0 + 5 < Cout) ? b[oc0 + 5] : 0.0f;
    float acc6 = (oc0 + 6 < Cout) ? b[oc0 + 6] : 0.0f;
    float acc7 = (oc0 + 7 < Cout) ? b[oc0 + 7] : 0.0f;

    #pragma unroll
    for (int ci = 0; ci < CIN_FIXED; ++ci) {
        #pragma unroll
        for (int ky = 0; ky < K_FIXED; ++ky) {
            #pragma unroll
            for (int kx = 0; kx < K_FIXED; ++kx) {
                const float xv = sh[(ci * IN_TILE + (ty + ky)) * IN_TILE + (tx + kx)];
                const int kbase = ((ci * K_FIXED + ky) * K_FIXED + kx);
                if (oc0 + 0 < Cout) acc0 = fmaf(xv, w[((oc0 + 0) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc0);
                if (oc0 + 1 < Cout) acc1 = fmaf(xv, w[((oc0 + 1) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc1);
                if (oc0 + 2 < Cout) acc2 = fmaf(xv, w[((oc0 + 2) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc2);
                if (oc0 + 3 < Cout) acc3 = fmaf(xv, w[((oc0 + 3) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc3);
                if (oc0 + 4 < Cout) acc4 = fmaf(xv, w[((oc0 + 4) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc4);
                if (oc0 + 5 < Cout) acc5 = fmaf(xv, w[((oc0 + 5) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc5);
                if (oc0 + 6 < Cout) acc6 = fmaf(xv, w[((oc0 + 6) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc6);
                if (oc0 + 7 < Cout) acc7 = fmaf(xv, w[((oc0 + 7) * CIN_FIXED * K_FIXED * K_FIXED) + kbase], acc7);
            }
        }
    }

    const int spatial = OH * OW;
    const int out_base = (n * Cout * spatial) + oh * OW + ow;

    if (oc0 + 0 < Cout) { float v = acc0 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 0) * spatial] = v; }
    if (oc0 + 1 < Cout) { float v = acc1 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 1) * spatial] = v; }
    if (oc0 + 2 < Cout) { float v = acc2 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 2) * spatial] = v; }
    if (oc0 + 3 < Cout) { float v = acc3 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 3) * spatial] = v; }
    if (oc0 + 4 < Cout) { float v = acc4 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 4) * spatial] = v; }
    if (oc0 + 5 < Cout) { float v = acc5 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 5) * spatial] = v; }
    if (oc0 + 6 < Cout) { float v = acc6 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 6) * spatial] = v; }
    if (oc0 + 7 < Cout) { float v = acc7 * inv_divisor; v = (v > 0.0f) ? v : v * 0.01f; out[out_base + (oc0 + 7) * spatial] = v; }
}

torch::Tensor fused_conv_div_lrelu_forward(torch::Tensor x,
                                           torch::Tensor weight,
                                           torch::Tensor bias,
                                           double divisor) {
    TORCH_CHECK(x.is_cuda(), "x must be a CUDA tensor");
    TORCH_CHECK(weight.is_cuda(), "weight must be a CUDA tensor");
    TORCH_CHECK(bias.is_cuda(), "bias must be a CUDA tensor");
    TORCH_CHECK(x.scalar_type() == torch::kFloat32, "only float32 supported");

    const int N = (int)x.size(0);
    const int C = (int)x.size(1);
    const int H = (int)x.size(2);
    const int W = (int)x.size(3);
    const int Cout = (int)weight.size(0);
    const int K = (int)weight.size(2);
    TORCH_CHECK(C == CIN_FIXED && K == K_FIXED && weight.size(3) == K_FIXED,
                "this optimized kernel supports only C=8, K=3");

    const int OH = H - K_FIXED + 1;
    const int OW = W - K_FIXED + 1;
    auto out = torch::empty({N, Cout, OH, OW}, x.options());

    dim3 block(TILE, TILE, 1);
    dim3 grid((OW + TILE - 1) / TILE,
              (OH + TILE - 1) / TILE,
              N * ((Cout + OCG - 1) / OCG));

    const float inv_div = 1.0f / (float)divisor;
    cudaStream_t stream = at::cuda::getCurrentCUDAStream();
    conv3x3_c8_ocg8_kernel<<<grid, block, 0, stream>>>(
        x.data_ptr<float>(), weight.data_ptr<float>(), bias.data_ptr<float>(), out.data_ptr<float>(),
        N, Cout, H, W, OH, OW, inv_div);
    C10_CUDA_KERNEL_LAUNCH_CHECK();
    return out;
}
"""

_ext = load_inline(
    name="fused_conv_div_lrelu_c8k3_v1",
    cpp_sources=_cpp_src,
    cuda_sources=_cuda_src,
    functions=["fused_conv_div_lrelu_forward"],
    extra_cflags=["-O3"],
    extra_cuda_cflags=["-O3"],
    verbose=False,
)


class ModelNew(nn.Module):
    """Fused Conv2d / divisor / LeakyReLU implementation for the given model."""
    def __init__(self, in_channels, out_channels, kernel_size, divisor):
        super(ModelNew, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size)
        self.divisor = divisor

    def forward(self, x):
        return _ext.fused_conv_div_lrelu_forward(x, self.conv.weight, self.conv.bias, float(self.divisor))