correct
71_Conv2d_Divide_LeakyReLU.py
started
2026-05-05T02:03:52Z
timingkernelbench.timing
kernel mean / median / std
1.69 / — / 0.20 μs
ref mean / median / std
1.87 / — / 0.62 μs
numerical precisioneval_kernel_against_ref
gpu memoryextended_metrics.measure_memory
kernel launches / fusiontorch.profiler
fusion ratio (ref / kernel)
9.00x
energyNVML (pynvml)
ratio (ref / kernel)
0.20x
SOL (speed-of-light)nsight_hardware_counters
roofline / occupancynsight_hardware_counters
dram bandwidth
339.72 GB/s
warp stalls (top 5)nsight
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))