correct
97_Matmul_BatchNorm_BiasAdd_Divide_Swish.py
started
2026-05-05T00:01:57Z
timingkernelbench.timing
kernel mean / median / std
2.75 / — / 0.01 μs
ref mean / median / std
2.85 / — / 0.01 μs
numerical precisioneval_kernel_against_ref
gpu memoryextended_metrics.measure_memory
kernel launches / fusiontorch.profiler
fusion ratio (ref / kernel)
1.20x
energyNVML (pynvml)
ratio (ref / kernel)
0.50x
SOL (speed-of-light)nsight_hardware_counters
roofline / occupancynsight_hardware_counters
dram bandwidth
5607.65 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):
"""
Model that performs a matrix multiplication, batch normalization, bias addition, division, and Swish activation.
"""
def __init__(self, in_features, out_features, bn_eps=1e-5, bn_momentum=0.1, bias_shape=(1,), divide_value=1.0):
super(Model, self).__init__()
self.matmul = nn.Linear(in_features, out_features)
self.bn = nn.BatchNorm1d(out_features, eps=bn_eps, momentum=bn_momentum)
self.bias = nn.Parameter(torch.randn(bias_shape))
self.divide_value = divide_value
def forward(self, x):
x = self.matmul(x)
x = self.bn(x)
x = x + self.bias
x = x / self.divide_value
x = x * torch.sigmoid(x)
return x
batch_size = 1024
in_features = 8192
out_features = 8192
bn_eps = 1e-5
bn_momentum = 0.1
bias_shape = (1,)
divide_value = 1.0
def get_inputs():
return [torch.rand(batch_size, in_features)]
def get_init_inputs():
return [in_features, out_features, bn_eps, bn_momentum, bias_shape, divide_value]turn 0 · FINAL · 1 tool call(s) · 60.1s turn · 60.1s LLM · 7,326 chars (click to expand)
turn 060.1s turn · 60.1s LLM · 0.0s tools · FINAL
reasoning
**Considering optimized reductions**
I'm focusing on PyTorch's batch normalization and its use of optimized reductions. I think our simple strided reduction could be a bit slow, since it involves 8192 columns and 1024 loads, which amounts to 8 million uncoalesced transactions. I’m wondering if that might still be manageable. PyTorch's batch normalization might only take around 0.1 milliseconds, so maybe we're in the right ballpark. It's an interesting comparison for performance!
**Considering compilation issues**
I'm noting that NVCC might compile heavy. That suggests it could be handling larger workloads or code complexities. I wonder if there are specific optimizations I could explore to streamline the compilation process. Maybe looking into parallel compilation or reducing dependency sizes could help? It's interesting to think about how different configurations might influence performance. I'll need to dig deeper to see if this heaviness is common or if there are ways to alleviate it.
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_forward(torch::Tensor input,\n torch::Tensor weight,\n torch::Tensor lin_bias,\n torch::Tensor bn_weight,\n torch::Tensor bn_bias,\n torch::Tensor running_mean,\n torch::Tensor running_var,\n torch::Tensor extra_bias,\n bool training,\n double eps,\n double momentum,\n double divide_value);\n'''\n\n_cuda_src = r'''\n#include <torch/extension.h>\n#include <ATen/ATen.h>\n#include <ATen/ops/linear.h>\n#include <ATen/ops/batch_norm.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <c10/cuda/CUDAException.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n__global__ void bias_swish_kernel(const float* __restrict__ in,\n const float* __restrict__ bias,\n float* __restrict__ out,\n long long total,\n int C,\n long long bias_numel,\n float inv_div) {\n long long idx = (long long)blockIdx.x * blockDim.x + threadIdx.x;\n if (idx >= total) return;\n\n float b;\n if (bias_numel == 1) {\n b = bias[0];\n } else if (bias_numel == (long long)C) {\n b = bias[idx % C];\n } else if (bias_numel == total) {\n b = bias[idx];\n } else {\n // Covers the only broadcast patterns used by this benchmark (scalar) and\n // common (C,) / full-size cases. For unusual shapes, fall back to a\n // cyclic index rather than reading out of bounds.\n b = bias[idx % bias_numel];\n }\n\n float x = (in[idx] + b) * inv_div;\n float s = 1.0f / (1.0f + expf(-x));\n out[idx] = x * s;\n}\n\ntorch::Tensor fused_forward(torch::Tensor input,\n torch::Tensor weight,\n torch::Tensor lin_bias,\n torch::Tensor bn_weight,\n torch::Tensor bn_bias,\n torch::Tensor running_mean,\n torch::Tensor running_var,\n torch::Tensor extra_bias,\n bool training,\n double eps,\n double momentum,\n double divide_value) {\n // Keep the numerically sensitive / highly optimized GEMM and BatchNorm in\n // ATen/cuDNN, and fuse the remaining bias/divide/Swish chain into one CUDA\n // pass. This removes several full-tensor PyTorch kernels from the forward.\n auto y = at::linear(input, weight, lin_bias);\n auto z = at::batch_norm(y, bn_weight, bn_bias, running_mean, running_var,\n training, momentum, eps, true);\n\n auto out = torch::empty_like(z);\n const long long total = z.numel();\n if (total == 0) return out;\n\n const int C = (z.dim() >= 2) ? (int)z.size(1) : (int)total;\n const int threads = 256;\n const int blocks = (int)((total + threads - 1) / threads);\n float inv_div = 1.0f / static_cast<float>(divide_value);\n\n bias_swish_kernel<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>(\n z.data_ptr<float>(), extra_bias.data_ptr<float>(), out.data_ptr<float>(),\n total, C, extra_bias.numel(), inv_div);\n C10_CUDA_KERNEL_LAUNCH_CHECK();\n return out;\n}\n'''\n\n_ext = load_inline(\n name=\"bn_linear_swish_fused_ext_v1\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"fused_forward\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\"],\n verbose=False,\n)\n\n\nclass ModelNew(nn.Module):\n \"\"\"\n Drop-in replacement for the reference module. Parameters and buffers are\n named identically so state_dict copying works unchanged.\n \"\"\"\n def __init__(self, in_features, out_features, bn_eps=1e-5, bn_momentum=0.1, bias_shape=(1,), divide_value=1.0):\n super(ModelNew, self).__init__()\n self.matmul = nn.Linear(in_features, out_features)\n self.bn = nn.BatchNorm1d(out_features, eps=bn_eps, momentum=bn_momentum)\n self.bias = nn.Parameter(torch.randn(bias_shape))\n self.divide_value = divide_value\n\n def forward(self, x):\n return _ext.fused_forward(\n x,\n self.matmul.weight,\n self.matmul.bias,\n self.bn.weight,\n self.bn.bias,\n self.bn.running_mean,\n self.bn.running_var,\n self.bias,\n self.bn.training,\n float(self.bn.eps),\n float(self.bn.momentum if self.bn.momentum is not None else 0.1),\n float(self.divide_value),\n )\n"
}
result ← submit_kernel (ok)
oksubmit_kernel
submit_kernel PASSED: (5 / 5) correctness trials all passed.
Kernel runtime: 2.75 μs
Runtime stats: mean=2.75μs median=?μs std=0.01μs
Numerical precision: max_abs_err=0.00e+00 mean_abs_err=0.00e+00 max_rel_err=0.00e+00
Memory: 673.31 MB peak (ref: 673.31 MB, ratio: 1.0x)
Kernel launches: 15 (ref: 18, fusion ratio: 1.2)
Energy: 483.42 mJ/run (ref: 241.92 mJ, ratio: 0.5004x)
SOL score: 1.0
final submitted kernel (136 lines, 4,925 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_forward(torch::Tensor input,
torch::Tensor weight,
torch::Tensor lin_bias,
torch::Tensor bn_weight,
torch::Tensor bn_bias,
torch::Tensor running_mean,
torch::Tensor running_var,
torch::Tensor extra_bias,
bool training,
double eps,
double momentum,
double divide_value);
'''
_cuda_src = r'''
#include <torch/extension.h>
#include <ATen/ATen.h>
#include <ATen/ops/linear.h>
#include <ATen/ops/batch_norm.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAException.h>
#include <cuda.h>
#include <cuda_runtime.h>
__global__ void bias_swish_kernel(const float* __restrict__ in,
const float* __restrict__ bias,
float* __restrict__ out,
long long total,
int C,
long long bias_numel,
float inv_div) {
long long idx = (long long)blockIdx.x * blockDim.x + threadIdx.x;
if (idx >= total) return;
float b;
if (bias_numel == 1) {
b = bias[0];
} else if (bias_numel == (long long)C) {
b = bias[idx % C];
} else if (bias_numel == total) {
b = bias[idx];
} else {
// Covers the only broadcast patterns used by this benchmark (scalar) and
// common (C,) / full-size cases. For unusual shapes, fall back to a
// cyclic index rather than reading out of bounds.
b = bias[idx % bias_numel];
}
float x = (in[idx] + b) * inv_div;
float s = 1.0f / (1.0f + expf(-x));
out[idx] = x * s;
}
torch::Tensor fused_forward(torch::Tensor input,
torch::Tensor weight,
torch::Tensor lin_bias,
torch::Tensor bn_weight,
torch::Tensor bn_bias,
torch::Tensor running_mean,
torch::Tensor running_var,
torch::Tensor extra_bias,
bool training,
double eps,
double momentum,
double divide_value) {
// Keep the numerically sensitive / highly optimized GEMM and BatchNorm in
// ATen/cuDNN, and fuse the remaining bias/divide/Swish chain into one CUDA
// pass. This removes several full-tensor PyTorch kernels from the forward.
auto y = at::linear(input, weight, lin_bias);
auto z = at::batch_norm(y, bn_weight, bn_bias, running_mean, running_var,
training, momentum, eps, true);
auto out = torch::empty_like(z);
const long long total = z.numel();
if (total == 0) return out;
const int C = (z.dim() >= 2) ? (int)z.size(1) : (int)total;
const int threads = 256;
const int blocks = (int)((total + threads - 1) / threads);
float inv_div = 1.0f / static_cast<float>(divide_value);
bias_swish_kernel<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>(
z.data_ptr<float>(), extra_bias.data_ptr<float>(), out.data_ptr<float>(),
total, C, extra_bias.numel(), inv_div);
C10_CUDA_KERNEL_LAUNCH_CHECK();
return out;
}
'''
_ext = load_inline(
name="bn_linear_swish_fused_ext_v1",
cpp_sources=_cpp_src,
cuda_sources=_cuda_src,
functions=["fused_forward"],
extra_cflags=["-O3"],
extra_cuda_cflags=["-O3"],
verbose=False,
)
class ModelNew(nn.Module):
"""
Drop-in replacement for the reference module. Parameters and buffers are
named identically so state_dict copying works unchanged.
"""
def __init__(self, in_features, out_features, bn_eps=1e-5, bn_momentum=0.1, bias_shape=(1,), divide_value=1.0):
super(ModelNew, self).__init__()
self.matmul = nn.Linear(in_features, out_features)
self.bn = nn.BatchNorm1d(out_features, eps=bn_eps, momentum=bn_momentum)
self.bias = nn.Parameter(torch.randn(bias_shape))
self.divide_value = divide_value
def forward(self, x):
return _ext.fused_forward(
x,
self.matmul.weight,
self.matmul.bias,
self.bn.weight,
self.bn.bias,
self.bn.running_mean,
self.bn.running_var,
self.bias,
self.bn.training,
float(self.bn.eps),
float(self.bn.momentum if self.bn.momentum is not None else 0.1),
float(self.divide_value),
)