correct
28_FusedGroupNormSiLU.py
started
2026-05-11T09:40:29Z
timingkernelbench.timing
kernel mean / median / std
0.02 / — / 2.25e-04 μs
ref mean / median / std
0.03 / — / 0.02 μs
numerical precisioneval_kernel_against_ref
gpu memoryextended_metrics.measure_memory
kernel launches / fusiontorch.profiler
fusion ratio (ref / kernel)
6.00x
SOL (speed-of-light)nsight_hardware_counters
roofline / occupancynsight_hardware_counters
dram bandwidth
253.65 GB/s
warp stalls (top 5)nsight
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
import torch.nn.functional as F
# Default ModelNew is PyTorch-only. run_and_check with check_kernel=True and backend=cuda
# requires real CUDA (__global__ + load_inline/cpp_extension) in the kernel file—use
# check_kernel=False until you add a custom kernel implementation.
class Model(nn.Module):
"""
Fused GroupNorm + SiLU (Swish), a common post-conv / residual-branch pattern in
ResNet-family backbones (SiLU instead of ReLU) and in spatial blocks of diffusion
transformers (DiT) where features are (N, C, H, W).
Reference pipeline for a single fused kernel: normalize channels with
`GroupNorm`, then apply `SiLU` elementwise (`x * sigmoid(x)`).
"""
def __init__(
self,
num_channels: int,
num_groups: int,
eps: float = 1e-5,
affine: bool = True,
):
super().__init__()
assert num_channels % num_groups == 0
self.norm = nn.GroupNorm(
num_groups=num_groups,
num_channels=num_channels,
eps=eps,
affine=affine,
)
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
Args:
x: (N, C, H, W) feature map
Returns:
Same shape as x
"""
return F.silu(self.norm(x))
class ModelNew(nn.Module):
"""
KernelBench candidate entry point. Replace with a fused CUDA/Triton kernel;
default delegates to `Model` for harness smoke tests (same file as ref + kernel).
"""
def __init__(
self,
num_channels: int,
num_groups: int,
eps: float = 1e-5,
affine: bool = True,
):
super().__init__()
self._impl = Model(num_channels, num_groups, eps=eps, affine=affine)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self._impl(x)
batch_size = 8
num_channels = 256
num_groups = 32
height = 24
width = 24
def get_inputs():
p = popcorn_pri
x = torch.randn(p.jitter_int(batch_size), num_channels, p.jitter_int(height), p.jitter_int(width))
return [x]
def get_init_inputs():
return [num_channels, num_groups]
turn 0275.2s turn · 25.1s LLM · 250.1s tools · tokens in=1,383 · out=1,231 · total=2,919 · FINAL
reasoning
The task is to replace the forward computation of a PyTorch model with a custom CUDA kernel. The model is GroupNorm followed by SiLU activation.
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\ncuda_source = r'''\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n__global__ void fused_groupnorm_silu_kernel(\n const float* __restrict__ input,\n float* __restrict__ output,\n const float* __restrict__ gamma,\n const float* __restrict__ beta,\n int N, int C, int H, int W, int G, int C_per_G, float eps) {\n \n // Each block handles one (n, g) pair\n int ng = blockIdx.x;\n int n = ng / G;\n int g = ng % G;\n \n int HW = H * W;\n int group_size = C_per_G * HW;\n \n extern __shared__ float sdata[];\n \n // Compute mean\n float sum = 0.0f;\n for (int i = threadIdx.x; i < group_size; i += blockDim.x) {\n int c_in_g = i / HW;\n int hw = i % HW;\n int c = g * C_per_G + c_in_g;\n int idx = ((n * C + c) * HW) + hw;\n sum += input[idx];\n }\n \n sdata[threadIdx.x] = sum;\n __syncthreads();\n \n for (int s = blockDim.x / 2; s > 0; s >>= 1) {\n if (threadIdx.x < s) sdata[threadIdx.x] += sdata[threadIdx.x + s];\n __syncthreads();\n }\n float mean = sdata[0] / group_size;\n \n // Compute variance\n float var_sum = 0.0f;\n for (int i = threadIdx.x; i < group_size; i += blockDim.x) {\n int c_in_g = i / HW;\n int hw = i % HW;\n int c = g * C_per_G + c_in_g;\n int idx = ((n * C + c) * HW) + hw;\n float val = input[idx] - mean;\n var_sum += val * val;\n }\n \n sdata[threadIdx.x] = var_sum;\n __syncthreads();\n \n for (int s = blockDim.x / 2; s > 0; s >>= 1) {\n if (threadIdx.x < s) sdata[threadIdx.x] += sdata[threadIdx.x + s];\n __syncthreads();\n }\n float var = sdata[0] / group_size;\n float inv_std = rsqrtf(var + eps);\n \n // Normalize, apply affine and SiLU\n for (int i = threadIdx.x; i < group_size; i += blockDim.x) {\n int c_in_g = i / HW;\n int hw = i % HW;\n int c = g * C_per_G + c_in_g;\n int idx = ((n * C + c) * HW) + hw;\n float val = input[idx];\n float norm = (val - mean) * inv_std;\n if (gamma != nullptr && beta != nullptr) {\n norm = norm * gamma[c] + beta[c];\n }\n float silu = norm * (1.0f / (1.0f + expf(-norm)));\n output[idx] = silu;\n }\n}\n\ntorch::Tensor fused_groupnorm_silu_cuda(\n torch::Tensor input,\n torch::Tensor gamma,\n torch::Tensor beta,\n int num_groups,\n float eps) {\n \n auto N = input.size(0);\n auto C = input.size(1);\n auto H = input.size(2);\n auto W = input.size(3);\n int G = num_groups;\n int C_per_G = C / G;\n \n auto output = torch::empty_like(input);\n \n int num_blocks = N * G;\n int threads = 256;\n \n fused_groupnorm_silu_kernel<<<num_blocks, threads, threads * sizeof(float)>>>(\n input.data_ptr<float>(),\n output.data_ptr<float>(),\n gamma.defined() ? gamma.data_ptr<float>() : nullptr,\n beta.defined() ? beta.data_ptr<float>() : nullptr,\n N, C, H, W, G, C_per_G, eps\n );\n \n return output;\n}\n'''\n\ncpp_source = r'''\ntorch::Tensor fused_groupnorm_silu_cuda(\n torch::Tensor input,\n torch::Tensor gamma,\n torch::Tensor beta,\n int num_groups,\n float eps);\n'''\n\nmodule = load_inline(\n name='fused_gn_silu',\n cpp_sources=cpp_source,\n cuda_sources=cuda_source,\n functions=['fused_groupnorm_silu_cuda'],\n verbose=False\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, num_channels: int, num_groups: int, eps: float = 1e-5, affine: bool = True):\n super().__init__()\n self.num_groups = num_groups\n self.eps = eps\n self.affine = affine\n self.weight = nn.Parameter(torch.ones(num_channels)) if affine else None\n self.bias = nn.Parameter(torch.zeros(num_channels)) if affine else None\n \n def forward(self, x: torch.Tensor) -> torch.Tensor:\n gamma = self.weight if self.weight is not None else torch.empty(0, device=x.device)\n beta = self.bias if self.bias is not None else torch.empty(0, device=x.device)\n return module.fused_groupnorm_silu_cuda(x, gamma, beta, self.num_groups, self.eps)\n"
}
result ← submit_kernel (ok)
oksubmit_kernel
submit_kernel PASSED: (5 / 5) correctness trials all passed.
Kernel runtime: 0.02 μs
Runtime stats: mean=0.02μs median=?μs std=0.00μs
Numerical precision: max_abs_err=1.43e-06 mean_abs_err=2.37e-08 max_rel_err=1.25e-02
Memory: 9.0 MB peak (ref: 13.5 MB, ratio: 0.6668x)
Kernel launches: 2 (ref: 12, fusion ratio: 6.0)
Energy: 242.92 mJ/run (ref: 0.0 mJ, ratio: -1x)
SOL score: 0.1637
final submitted kernel (145 lines, 4,223 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
cuda_source = r'''
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
__global__ void fused_groupnorm_silu_kernel(
const float* __restrict__ input,
float* __restrict__ output,
const float* __restrict__ gamma,
const float* __restrict__ beta,
int N, int C, int H, int W, int G, int C_per_G, float eps) {
// Each block handles one (n, g) pair
int ng = blockIdx.x;
int n = ng / G;
int g = ng % G;
int HW = H * W;
int group_size = C_per_G * HW;
extern __shared__ float sdata[];
// Compute mean
float sum = 0.0f;
for (int i = threadIdx.x; i < group_size; i += blockDim.x) {
int c_in_g = i / HW;
int hw = i % HW;
int c = g * C_per_G + c_in_g;
int idx = ((n * C + c) * HW) + hw;
sum += input[idx];
}
sdata[threadIdx.x] = sum;
__syncthreads();
for (int s = blockDim.x / 2; s > 0; s >>= 1) {
if (threadIdx.x < s) sdata[threadIdx.x] += sdata[threadIdx.x + s];
__syncthreads();
}
float mean = sdata[0] / group_size;
// Compute variance
float var_sum = 0.0f;
for (int i = threadIdx.x; i < group_size; i += blockDim.x) {
int c_in_g = i / HW;
int hw = i % HW;
int c = g * C_per_G + c_in_g;
int idx = ((n * C + c) * HW) + hw;
float val = input[idx] - mean;
var_sum += val * val;
}
sdata[threadIdx.x] = var_sum;
__syncthreads();
for (int s = blockDim.x / 2; s > 0; s >>= 1) {
if (threadIdx.x < s) sdata[threadIdx.x] += sdata[threadIdx.x + s];
__syncthreads();
}
float var = sdata[0] / group_size;
float inv_std = rsqrtf(var + eps);
// Normalize, apply affine and SiLU
for (int i = threadIdx.x; i < group_size; i += blockDim.x) {
int c_in_g = i / HW;
int hw = i % HW;
int c = g * C_per_G + c_in_g;
int idx = ((n * C + c) * HW) + hw;
float val = input[idx];
float norm = (val - mean) * inv_std;
if (gamma != nullptr && beta != nullptr) {
norm = norm * gamma[c] + beta[c];
}
float silu = norm * (1.0f / (1.0f + expf(-norm)));
output[idx] = silu;
}
}
torch::Tensor fused_groupnorm_silu_cuda(
torch::Tensor input,
torch::Tensor gamma,
torch::Tensor beta,
int num_groups,
float eps) {
auto N = input.size(0);
auto C = input.size(1);
auto H = input.size(2);
auto W = input.size(3);
int G = num_groups;
int C_per_G = C / G;
auto output = torch::empty_like(input);
int num_blocks = N * G;
int threads = 256;
fused_groupnorm_silu_kernel<<<num_blocks, threads, threads * sizeof(float)>>>(
input.data_ptr<float>(),
output.data_ptr<float>(),
gamma.defined() ? gamma.data_ptr<float>() : nullptr,
beta.defined() ? beta.data_ptr<float>() : nullptr,
N, C, H, W, G, C_per_G, eps
);
return output;
}
'''
cpp_source = r'''
torch::Tensor fused_groupnorm_silu_cuda(
torch::Tensor input,
torch::Tensor gamma,
torch::Tensor beta,
int num_groups,
float eps);
'''
module = load_inline(
name='fused_gn_silu',
cpp_sources=cpp_source,
cuda_sources=cuda_source,
functions=['fused_groupnorm_silu_cuda'],
verbose=False
)
class ModelNew(nn.Module):
def __init__(self, num_channels: int, num_groups: int, eps: float = 1e-5, affine: bool = True):
super().__init__()
self.num_groups = num_groups
self.eps = eps
self.affine = affine
self.weight = nn.Parameter(torch.ones(num_channels)) if affine else None
self.bias = nn.Parameter(torch.zeros(num_channels)) if affine else None
def forward(self, x: torch.Tensor) -> torch.Tensor:
gamma = self.weight if self.weight is not None else torch.empty(0, device=x.device)
beta = self.bias if self.bias is not None else torch.empty(0, device=x.device)
return module.fused_groupnorm_silu_cuda(x, gamma, beta, self.num_groups, self.eps)