correct
28_FusedGroupNormSiLU.py
started
2026-05-11T12:17:29Z
timingkernelbench.timing
kernel mean / median / std
0.01 / — / 0.00 μs
ref mean / median / std
0.03 / — / 0.00 μ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
736.24 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 0358.4s turn · 3.4s LLM · 355.0s tools · tokens in=1,755 · out=248 · total=2,003
reasoning
Let me analyze this task. I need to create a custom CUDA kernel that fuses GroupNorm + SiLU (Swish) activation.
The reference implementation:
1. Applies GroupNorm to normalize channels
2. Applies SiLU activation (x * sigmoid(x))
GroupNorm operation:
- Divides channels into groups
- Normalizes each group independently
- If affine=True, applies scale (gamma) and shift (beta) parameters
For a tensor of shape (N, C, H, W) with num_groups:
- Each sample has C channels divided into num_groups groups
- Each group has C/num_groups channels
- For each (n, g) pair, we normalize over (C/num_groups * H * W) elements
The normalization formula:
mean = mean of elements in group
var = variance of elements in group
x_norm = (x - mean) / sqrt(var + eps)
if affine: output = gamma * x_norm + beta
Then SiLU: output = x_norm * sigmoid(x_norm)
Let me first get GPU specs to understand the hardware constraints.
assistantI'll start by analyzing this task and getting GPU specs to understand the hardware constraints.
call → get_gpu_specs
callget_gpu_specs
{}
result ← get_gpu_specs (ok)
okget_gpu_specs
GPU specs for NVIDIA H100 80GB HBM3:
total memory (runtime): 79.1 GB
spec entry: H100
GPU Architecture: Hopper
GPU Memory: 80GB
Memory Bandwidth: 3.35 TB/s
FP64 TFLOPS: 34
FP64 Tensor Core TFLOPS: 67
FP32 TFLOPS: 67
TF32 Tensor Core TFLOPS: 989 with sparsity
BFLOAT16 Tensore Core TFLOPS: 1979 with sparsity
FP16 Tensor Core TFLOPS: 1979 with sparsity
FP8 Tensor Core TFLOPS: 3958 with sparsity
INT8 Tensor Core TOPS: 3958 with sparsity
Register File Size: 64K 32-bit registers per SM
Maximum number of registers per thread: 255
Maximum number of thread blocks per SM: 32
Shared memory capacity per SM: 228 KB
Maximum shared memory per thread block: 227 KB
turn 1 · 1 tool call(s) · 621.5s turn · 17.9s LLM · tokens in=2,020 · out=1,787 · total=3,807 · 8,238 chars (click to expand)
turn 1621.5s turn · 17.9s LLM · 603.6s tools · tokens in=2,020 · out=1,787 · total=3,807
reasoning
Now I understand the GPU. Let me write a fused GroupNorm + SiLU kernel. The key operations are:
1. GroupNorm: Normalize each channel group, then optionally apply affine transformation (weight * x + bias)
2. SiLU: x * sigmoid(x) = x / (1 + exp(-x))
For GroupNorm with G groups and C channels:
- Each group has C/G channels
- For each sample n and each group g, we compute:
- mean = (1/(C/G * H * W)) * sum over channels in group and spatial dims
- var = (1/(C/G * H * W)) * sum of (x - mean)^2
- x_norm = (x - mean) / sqrt(var + eps)
- If affine: y = weight[c] * x_norm + bias[c]
Let me write an efficient kernel. The typical approach is:
- Each thread block handles one (batch, group) pair
- Use parallel reduction for mean/var computation
- Use shared memory for intermediate results
Let me write the kernel:
assistantNow I'll write a fused GroupNorm + SiLU CUDA kernel. The key is to efficiently compute the group normalization statistics and apply the SiLU activation in a single pass.
call → compile_kernel
callcompile_kernel
{
"kernel_code": "import torch\nimport torch.nn as nn\nimport torch.utils.cpp_extension as cpp_extension\n\nCUDA_SOURCE = r\"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n// Fused GroupNorm + SiLU kernel\n// Each thread block processes one (batch, group) pair\n// Threads within block cooperatively compute mean, variance, then apply normalization + SiLU\n\ntemplate <int BLOCK_SIZE>\n__global__ void fused_groupnorm_silu_kernel(\n const float* __restrict__ input,\n float* __restrict__ output,\n const float* __restrict__ weight,\n const float* __restrict__ bias,\n int N, int C, int H, int W, int G, int channels_per_group, float eps\n) {\n // Each block handles one (batch_item, group) pair\n int batch_idx = blockIdx.x / G;\n int group_idx = blockIdx.x % G;\n \n int group_start_channel = group_idx * channels_per_group;\n int group_size = channels_per_group * H * W;\n \n // Index for this thread's element within the group\n int tid = threadIdx.x;\n int total_elements = group_size;\n \n // Pointers to the start of this batch and group in input/output\n int batch_offset = batch_idx * C * H * W;\n int group_offset = group_start_channel * H * W;\n const float* __restrict__ input_ptr = input + batch_offset + group_offset;\n float* __restrict__ output_ptr = output + batch_offset + group_offset;\n \n // Shared memory for reduction\n __shared__ float s_mean[BLOCK_SIZE];\n __shared__ float s_var[BLOCK_SIZE];\n \n // Compute mean using parallel reduction\n float local_sum = 0.0f;\n float local_sq_sum = 0.0f;\n \n // Grid-stride loop to process all elements in the group\n for (int i = tid; i < total_elements; i += BLOCK_SIZE) {\n float val = input_ptr[i];\n local_sum += val;\n local_sq_sum += val * val;\n }\n \n s_mean[tid] = local_sum;\n s_var[tid] = local_sq_sum;\n __syncthreads();\n \n // Parallel reduction in shared memory\n for (int stride = BLOCK_SIZE / 2; stride > 0; stride >>= 1) {\n if (tid < stride) {\n s_mean[tid] += s_mean[tid + stride];\n s_var[tid] += s_var[tid + stride];\n }\n __syncthreads();\n }\n \n // Compute mean and variance\n float mean = 0.0f;\n float inv_std = 0.0f;\n if (tid == 0) {\n mean = s_mean[0] / total_elements;\n float variance = s_var[0] / total_elements - mean * mean;\n inv_std = 1.0f / sqrtf(variance + eps);\n s_mean[0] = mean;\n s_var[0] = inv_std;\n }\n __syncthreads();\n \n mean = s_mean[0];\n inv_std = s_var[0];\n \n // Apply normalization, affine, and SiLU\n for (int i = tid; i < total_elements; i += BLOCK_SIZE) {\n int c_local = i / (H * W);\n int c_global = group_start_channel + c_local;\n \n float val = input_ptr[i];\n float norm_val = (val - mean) * inv_std;\n \n // Apply affine transformation if weight/bias are provided\n if (weight != nullptr && bias != nullptr) {\n norm_val = weight[c_global] * norm_val + bias[c_global];\n }\n \n // Apply SiLU: x * sigmoid(x) = x / (1 + exp(-x))\n float silu_val = norm_val / (1.0f + expf(-norm_val));\n \n output_ptr[i] = silu_val;\n }\n}\n\nat::Tensor fused_groupnorm_silu_cuda(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor bias,\n int num_groups,\n double eps\n) {\n int N = input.size(0);\n int C = input.size(1);\n int H = input.size(2);\n int W = input.size(3);\n int channels_per_group = C / num_groups;\n \n auto output = at::empty_like(input);\n \n int num_blocks = N * num_groups;\n int block_size = 256;\n \n const float* input_ptr = input.data_ptr<float>();\n float* output_ptr = output.data_ptr<float>();\n const float* weight_ptr = weight.defined() ? weight.data_ptr<float>() : nullptr;\n const float* bias_ptr = bias.defined() ? bias.data_ptr<float>() : nullptr;\n \n fused_groupnorm_silu_kernel<256><<<num_blocks, block_size>>>(\n input_ptr, output_ptr, weight_ptr, bias_ptr,\n N, C, H, W, num_groups, channels_per_group, static_cast<float>(eps)\n );\n \n return output;\n}\n\"\"\"\n\nCPP_SOURCE = r\"\"\"\nat::Tensor fused_groupnorm_silu_cuda(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor bias,\n int num_groups,\n double eps\n);\n\"\"\"\n\n# Compile the extension\nfused_gn_silu = cpp_extension.load_inline(\n name='fused_gn_silu',\n cpp_sources=CPP_SOURCE,\n cuda_sources=CUDA_SOURCE,\n functions=['fused_groupnorm_silu_cuda'],\n extra_cuda_cflags=['-O3'],\n with_cuda=True,\n)\n\nimport torch\nimport torch.nn as nn\n\nclass ModelNew(nn.Module):\n \"\"\"\n Fused GroupNorm + SiLU using custom CUDA kernel.\n \"\"\"\n \n def __init__(\n self,\n num_channels: int,\n num_groups: int,\n eps: float = 1e-5,\n affine: bool = True,\n ):\n super().__init__()\n assert num_channels % num_groups == 0\n self.num_channels = num_channels\n self.num_groups = num_groups\n self.eps = eps\n self.affine = affine\n \n if affine:\n self.weight = nn.Parameter(torch.ones(num_channels))\n self.bias = nn.Parameter(torch.zeros(num_channels))\n else:\n self.register_parameter('weight', None)\n self.register_parameter('bias', None)\n \n def forward(self, x: torch.Tensor) -> torch.Tensor:\n \"\"\"\n Args:\n x: (N, C, H, W) feature map\n Returns:\n Same shape as x with GroupNorm + SiLU applied\n \"\"\"\n # Ensure contiguous input\n if not x.is_contiguous():\n x = x.contiguous()\n \n return fused_gn_silu.fused_groupnorm_silu_cuda(\n x,\n self.weight if self.weight is not None else torch.empty(0),\n self.bias if self.bias is not None else torch.empty(0),\n self.num_groups,\n self.eps\n )"
}
result ← compile_kernel (ok)
okcompile_kernel
compile_kernel PASSED: kernel compiled without errors.
turn 2 · 1 tool call(s) · 662.3s turn · 13.9s LLM · tokens in=3,595 · out=1,539 · total=5,134 · 7,318 chars (click to expand)
turn 2662.3s turn · 13.9s LLM · 648.4s tools · tokens in=3,595 · out=1,539 · total=5,134
reasoning
Good, it compiled. Now let me check correctness.
call → run_correctness
callrun_correctness
{
"kernel_code": "import torch\nimport torch.nn as nn\nimport torch.utils.cpp_extension as cpp_extension\n\nCUDA_SOURCE = r\"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n// Fused GroupNorm + SiLU kernel\n// Each thread block processes one (batch, group) pair\n// Threads within block cooperatively compute mean, variance, then apply normalization + SiLU\n\ntemplate <int BLOCK_SIZE>\n__global__ void fused_groupnorm_silu_kernel(\n const float* __restrict__ input,\n float* __restrict__ output,\n const float* __restrict__ weight,\n const float* __restrict__ bias,\n int N, int C, int H, int W, int G, int channels_per_group, float eps\n) {\n // Each block handles one (batch_item, group) pair\n int batch_idx = blockIdx.x / G;\n int group_idx = blockIdx.x % G;\n \n int group_start_channel = group_idx * channels_per_group;\n int group_size = channels_per_group * H * W;\n \n // Index for this thread's element within the group\n int tid = threadIdx.x;\n int total_elements = group_size;\n \n // Pointers to the start of this batch and group in input/output\n int batch_offset = batch_idx * C * H * W;\n int group_offset = group_start_channel * H * W;\n const float* __restrict__ input_ptr = input + batch_offset + group_offset;\n float* __restrict__ output_ptr = output + batch_offset + group_offset;\n \n // Shared memory for reduction\n __shared__ float s_mean[BLOCK_SIZE];\n __shared__ float s_var[BLOCK_SIZE];\n \n // Compute mean using parallel reduction\n float local_sum = 0.0f;\n float local_sq_sum = 0.0f;\n \n // Grid-stride loop to process all elements in the group\n for (int i = tid; i < total_elements; i += BLOCK_SIZE) {\n float val = input_ptr[i];\n local_sum += val;\n local_sq_sum += val * val;\n }\n \n s_mean[tid] = local_sum;\n s_var[tid] = local_sq_sum;\n __syncthreads();\n \n // Parallel reduction in shared memory\n for (int stride = BLOCK_SIZE / 2; stride > 0; stride >>= 1) {\n if (tid < stride) {\n s_mean[tid] += s_mean[tid + stride];\n s_var[tid] += s_var[tid + stride];\n }\n __syncthreads();\n }\n \n // Compute mean and variance\n float mean = 0.0f;\n float inv_std = 0.0f;\n if (tid == 0) {\n mean = s_mean[0] / total_elements;\n float variance = s_var[0] / total_elements - mean * mean;\n inv_std = 1.0f / sqrtf(variance + eps);\n s_mean[0] = mean;\n s_var[0] = inv_std;\n }\n __syncthreads();\n \n mean = s_mean[0];\n inv_std = s_var[0];\n \n // Apply normalization, affine, and SiLU\n for (int i = tid; i < total_elements; i += BLOCK_SIZE) {\n int c_local = i / (H * W);\n int c_global = group_start_channel + c_local;\n \n float val = input_ptr[i];\n float norm_val = (val - mean) * inv_std;\n \n // Apply affine transformation if weight/bias are provided\n if (weight != nullptr && bias != nullptr) {\n norm_val = weight[c_global] * norm_val + bias[c_global];\n }\n \n // Apply SiLU: x * sigmoid(x) = x / (1 + exp(-x))\n float silu_val = norm_val / (1.0f + expf(-norm_val));\n \n output_ptr[i] = silu_val;\n }\n}\n\nat::Tensor fused_groupnorm_silu_cuda(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor bias,\n int num_groups,\n double eps\n) {\n int N = input.size(0);\n int C = input.size(1);\n int H = input.size(2);\n int W = input.size(3);\n int channels_per_group = C / num_groups;\n \n auto output = at::empty_like(input);\n \n int num_blocks = N * num_groups;\n int block_size = 256;\n \n const float* input_ptr = input.data_ptr<float>();\n float* output_ptr = output.data_ptr<float>();\n const float* weight_ptr = weight.defined() ? weight.data_ptr<float>() : nullptr;\n const float* bias_ptr = bias.defined() ? bias.data_ptr<float>() : nullptr;\n \n fused_groupnorm_silu_kernel<256><<<num_blocks, block_size>>>(\n input_ptr, output_ptr, weight_ptr, bias_ptr,\n N, C, H, W, num_groups, channels_per_group, static_cast<float>(eps)\n );\n \n return output;\n}\n\"\"\"\n\nCPP_SOURCE = r\"\"\"\nat::Tensor fused_groupnorm_silu_cuda(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor bias,\n int num_groups,\n double eps\n);\n\"\"\"\n\n# Compile the extension\nfused_gn_silu = cpp_extension.load_inline(\n name='fused_gn_silu',\n cpp_sources=CPP_SOURCE,\n cuda_sources=CUDA_SOURCE,\n functions=['fused_groupnorm_silu_cuda'],\n extra_cuda_cflags=['-O3'],\n with_cuda=True,\n)\n\nimport torch\nimport torch.nn as nn\n\nclass ModelNew(nn.Module):\n \"\"\"\n Fused GroupNorm + SiLU using custom CUDA kernel.\n \"\"\"\n \n def __init__(\n self,\n num_channels: int,\n num_groups: int,\n eps: float = 1e-5,\n affine: bool = True,\n ):\n super().__init__()\n assert num_channels % num_groups == 0\n self.num_channels = num_channels\n self.num_groups = num_groups\n self.eps = eps\n self.affine = affine\n \n if affine:\n self.weight = nn.Parameter(torch.ones(num_channels))\n self.bias = nn.Parameter(torch.zeros(num_channels))\n else:\n self.register_parameter('weight', None)\n self.register_parameter('bias', None)\n \n def forward(self, x: torch.Tensor) -> torch.Tensor:\n \"\"\"\n Args:\n x: (N, C, H, W) feature map\n Returns:\n Same shape as x with GroupNorm + SiLU applied\n \"\"\"\n # Ensure contiguous input\n if not x.is_contiguous():\n x = x.contiguous()\n \n return fused_gn_silu.fused_groupnorm_silu_cuda(\n x,\n self.weight if self.weight is not None else torch.empty(0),\n self.bias if self.bias is not None else torch.empty(0),\n self.num_groups,\n self.eps\n )"
}
result ← run_correctness (ok)
okrun_correctness
run_correctness PASSED: (5 / 5) trials all matched the reference.
Numerical precision: max_abs_err=1.43e-06 mean_abs_err=2.54e-08 max_rel_err=1.25e-02
turn 3 · 1 tool call(s) · 14.5s turn · 14.5s LLM · tokens in=5,180 · out=1,541 · total=6,721 · 7,234 chars (click to expand)
turn 314.5s turn · 14.5s LLM · 0.0s tools · tokens in=5,180 · out=1,541 · total=6,721
reasoning
The kernel passes correctness. Let me run a static check before submitting.
call → static_check
callstatic_check
{
"kernel_code": "import torch\nimport torch.nn as nn\nimport torch.utils.cpp_extension as cpp_extension\n\nCUDA_SOURCE = r\"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n// Fused GroupNorm + SiLU kernel\n// Each thread block processes one (batch, group) pair\n// Threads within block cooperatively compute mean, variance, then apply normalization + SiLU\n\ntemplate <int BLOCK_SIZE>\n__global__ void fused_groupnorm_silu_kernel(\n const float* __restrict__ input,\n float* __restrict__ output,\n const float* __restrict__ weight,\n const float* __restrict__ bias,\n int N, int C, int H, int W, int G, int channels_per_group, float eps\n) {\n // Each block handles one (batch_item, group) pair\n int batch_idx = blockIdx.x / G;\n int group_idx = blockIdx.x % G;\n \n int group_start_channel = group_idx * channels_per_group;\n int group_size = channels_per_group * H * W;\n \n // Index for this thread's element within the group\n int tid = threadIdx.x;\n int total_elements = group_size;\n \n // Pointers to the start of this batch and group in input/output\n int batch_offset = batch_idx * C * H * W;\n int group_offset = group_start_channel * H * W;\n const float* __restrict__ input_ptr = input + batch_offset + group_offset;\n float* __restrict__ output_ptr = output + batch_offset + group_offset;\n \n // Shared memory for reduction\n __shared__ float s_mean[BLOCK_SIZE];\n __shared__ float s_var[BLOCK_SIZE];\n \n // Compute mean using parallel reduction\n float local_sum = 0.0f;\n float local_sq_sum = 0.0f;\n \n // Grid-stride loop to process all elements in the group\n for (int i = tid; i < total_elements; i += BLOCK_SIZE) {\n float val = input_ptr[i];\n local_sum += val;\n local_sq_sum += val * val;\n }\n \n s_mean[tid] = local_sum;\n s_var[tid] = local_sq_sum;\n __syncthreads();\n \n // Parallel reduction in shared memory\n for (int stride = BLOCK_SIZE / 2; stride > 0; stride >>= 1) {\n if (tid < stride) {\n s_mean[tid] += s_mean[tid + stride];\n s_var[tid] += s_var[tid + stride];\n }\n __syncthreads();\n }\n \n // Compute mean and variance\n float mean = 0.0f;\n float inv_std = 0.0f;\n if (tid == 0) {\n mean = s_mean[0] / total_elements;\n float variance = s_var[0] / total_elements - mean * mean;\n inv_std = 1.0f / sqrtf(variance + eps);\n s_mean[0] = mean;\n s_var[0] = inv_std;\n }\n __syncthreads();\n \n mean = s_mean[0];\n inv_std = s_var[0];\n \n // Apply normalization, affine, and SiLU\n for (int i = tid; i < total_elements; i += BLOCK_SIZE) {\n int c_local = i / (H * W);\n int c_global = group_start_channel + c_local;\n \n float val = input_ptr[i];\n float norm_val = (val - mean) * inv_std;\n \n // Apply affine transformation if weight/bias are provided\n if (weight != nullptr && bias != nullptr) {\n norm_val = weight[c_global] * norm_val + bias[c_global];\n }\n \n // Apply SiLU: x * sigmoid(x) = x / (1 + exp(-x))\n float silu_val = norm_val / (1.0f + expf(-norm_val));\n \n output_ptr[i] = silu_val;\n }\n}\n\nat::Tensor fused_groupnorm_silu_cuda(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor bias,\n int num_groups,\n double eps\n) {\n int N = input.size(0);\n int C = input.size(1);\n int H = input.size(2);\n int W = input.size(3);\n int channels_per_group = C / num_groups;\n \n auto output = at::empty_like(input);\n \n int num_blocks = N * num_groups;\n int block_size = 256;\n \n const float* input_ptr = input.data_ptr<float>();\n float* output_ptr = output.data_ptr<float>();\n const float* weight_ptr = weight.defined() ? weight.data_ptr<float>() : nullptr;\n const float* bias_ptr = bias.defined() ? bias.data_ptr<float>() : nullptr;\n \n fused_groupnorm_silu_kernel<256><<<num_blocks, block_size>>>(\n input_ptr, output_ptr, weight_ptr, bias_ptr,\n N, C, H, W, num_groups, channels_per_group, static_cast<float>(eps)\n );\n \n return output;\n}\n\"\"\"\n\nCPP_SOURCE = r\"\"\"\nat::Tensor fused_groupnorm_silu_cuda(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor bias,\n int num_groups,\n double eps\n);\n\"\"\"\n\n# Compile the extension\nfused_gn_silu = cpp_extension.load_inline(\n name='fused_gn_silu',\n cpp_sources=CPP_SOURCE,\n cuda_sources=CUDA_SOURCE,\n functions=['fused_groupnorm_silu_cuda'],\n extra_cuda_cflags=['-O3'],\n with_cuda=True,\n)\n\nimport torch\nimport torch.nn as nn\n\nclass ModelNew(nn.Module):\n \"\"\"\n Fused GroupNorm + SiLU using custom CUDA kernel.\n \"\"\"\n \n def __init__(\n self,\n num_channels: int,\n num_groups: int,\n eps: float = 1e-5,\n affine: bool = True,\n ):\n super().__init__()\n assert num_channels % num_groups == 0\n self.num_channels = num_channels\n self.num_groups = num_groups\n self.eps = eps\n self.affine = affine\n \n if affine:\n self.weight = nn.Parameter(torch.ones(num_channels))\n self.bias = nn.Parameter(torch.zeros(num_channels))\n else:\n self.register_parameter('weight', None)\n self.register_parameter('bias', None)\n \n def forward(self, x: torch.Tensor) -> torch.Tensor:\n \"\"\"\n Args:\n x: (N, C, H, W) feature map\n Returns:\n Same shape as x with GroupNorm + SiLU applied\n \"\"\"\n # Ensure contiguous input\n if not x.is_contiguous():\n x = x.contiguous()\n \n return fused_gn_silu.fused_groupnorm_silu_cuda(\n x,\n self.weight if self.weight is not None else torch.empty(0),\n self.bias if self.bias is not None else torch.empty(0),\n self.num_groups,\n self.eps\n )"
}
result ← static_check (ok)
okstatic_check
static_check PASSED: no violations or warnings detected.
turn 4 · FINAL · 1 tool call(s) · 202.6s turn · 15.1s LLM · tokens in=6,721 · out=1,556 · total=8,277 · 7,683 chars (click to expand)
turn 4202.6s turn · 15.1s LLM · 187.5s tools · tokens in=6,721 · out=1,556 · total=8,277 · FINAL
reasoning
The kernel passes both correctness and static checks. Let me submit it now.
assistantThe kernel passes all checks. Let me submit it for final evaluation.
call → submit_kernel
callsubmit_kernel
{
"kernel_code": "import torch\nimport torch.nn as nn\nimport torch.utils.cpp_extension as cpp_extension\n\nCUDA_SOURCE = r\"\"\"\n#include <torch/extension.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <cmath>\n\n// Fused GroupNorm + SiLU kernel\n// Each thread block processes one (batch, group) pair\n// Threads within block cooperatively compute mean, variance, then apply normalization + SiLU\n\ntemplate <int BLOCK_SIZE>\n__global__ void fused_groupnorm_silu_kernel(\n const float* __restrict__ input,\n float* __restrict__ output,\n const float* __restrict__ weight,\n const float* __restrict__ bias,\n int N, int C, int H, int W, int G, int channels_per_group, float eps\n) {\n // Each block handles one (batch_item, group) pair\n int batch_idx = blockIdx.x / G;\n int group_idx = blockIdx.x % G;\n \n int group_start_channel = group_idx * channels_per_group;\n int group_size = channels_per_group * H * W;\n \n // Index for this thread's element within the group\n int tid = threadIdx.x;\n int total_elements = group_size;\n \n // Pointers to the start of this batch and group in input/output\n int batch_offset = batch_idx * C * H * W;\n int group_offset = group_start_channel * H * W;\n const float* __restrict__ input_ptr = input + batch_offset + group_offset;\n float* __restrict__ output_ptr = output + batch_offset + group_offset;\n \n // Shared memory for reduction\n __shared__ float s_mean[BLOCK_SIZE];\n __shared__ float s_var[BLOCK_SIZE];\n \n // Compute mean using parallel reduction\n float local_sum = 0.0f;\n float local_sq_sum = 0.0f;\n \n // Grid-stride loop to process all elements in the group\n for (int i = tid; i < total_elements; i += BLOCK_SIZE) {\n float val = input_ptr[i];\n local_sum += val;\n local_sq_sum += val * val;\n }\n \n s_mean[tid] = local_sum;\n s_var[tid] = local_sq_sum;\n __syncthreads();\n \n // Parallel reduction in shared memory\n for (int stride = BLOCK_SIZE / 2; stride > 0; stride >>= 1) {\n if (tid < stride) {\n s_mean[tid] += s_mean[tid + stride];\n s_var[tid] += s_var[tid + stride];\n }\n __syncthreads();\n }\n \n // Compute mean and variance\n float mean = 0.0f;\n float inv_std = 0.0f;\n if (tid == 0) {\n mean = s_mean[0] / total_elements;\n float variance = s_var[0] / total_elements - mean * mean;\n inv_std = 1.0f / sqrtf(variance + eps);\n s_mean[0] = mean;\n s_var[0] = inv_std;\n }\n __syncthreads();\n \n mean = s_mean[0];\n inv_std = s_var[0];\n \n // Apply normalization, affine, and SiLU\n for (int i = tid; i < total_elements; i += BLOCK_SIZE) {\n int c_local = i / (H * W);\n int c_global = group_start_channel + c_local;\n \n float val = input_ptr[i];\n float norm_val = (val - mean) * inv_std;\n \n // Apply affine transformation if weight/bias are provided\n if (weight != nullptr && bias != nullptr) {\n norm_val = weight[c_global] * norm_val + bias[c_global];\n }\n \n // Apply SiLU: x * sigmoid(x) = x / (1 + exp(-x))\n float silu_val = norm_val / (1.0f + expf(-norm_val));\n \n output_ptr[i] = silu_val;\n }\n}\n\nat::Tensor fused_groupnorm_silu_cuda(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor bias,\n int num_groups,\n double eps\n) {\n int N = input.size(0);\n int C = input.size(1);\n int H = input.size(2);\n int W = input.size(3);\n int channels_per_group = C / num_groups;\n \n auto output = at::empty_like(input);\n \n int num_blocks = N * num_groups;\n int block_size = 256;\n \n const float* input_ptr = input.data_ptr<float>();\n float* output_ptr = output.data_ptr<float>();\n const float* weight_ptr = weight.defined() ? weight.data_ptr<float>() : nullptr;\n const float* bias_ptr = bias.defined() ? bias.data_ptr<float>() : nullptr;\n \n fused_groupnorm_silu_kernel<256><<<num_blocks, block_size>>>(\n input_ptr, output_ptr, weight_ptr, bias_ptr,\n N, C, H, W, num_groups, channels_per_group, static_cast<float>(eps)\n );\n \n return output;\n}\n\"\"\"\n\nCPP_SOURCE = r\"\"\"\nat::Tensor fused_groupnorm_silu_cuda(\n at::Tensor input,\n at::Tensor weight,\n at::Tensor bias,\n int num_groups,\n double eps\n);\n\"\"\"\n\n# Compile the extension\nfused_gn_silu = cpp_extension.load_inline(\n name='fused_gn_silu',\n cpp_sources=CPP_SOURCE,\n cuda_sources=CUDA_SOURCE,\n functions=['fused_groupnorm_silu_cuda'],\n extra_cuda_cflags=['-O3'],\n with_cuda=True,\n)\n\nimport torch\nimport torch.nn as nn\n\nclass ModelNew(nn.Module):\n \"\"\"\n Fused GroupNorm + SiLU using custom CUDA kernel.\n \"\"\"\n \n def __init__(\n self,\n num_channels: int,\n num_groups: int,\n eps: float = 1e-5,\n affine: bool = True,\n ):\n super().__init__()\n assert num_channels % num_groups == 0\n self.num_channels = num_channels\n self.num_groups = num_groups\n self.eps = eps\n self.affine = affine\n \n if affine:\n self.weight = nn.Parameter(torch.ones(num_channels))\n self.bias = nn.Parameter(torch.zeros(num_channels))\n else:\n self.register_parameter('weight', None)\n self.register_parameter('bias', None)\n \n def forward(self, x: torch.Tensor) -> torch.Tensor:\n \"\"\"\n Args:\n x: (N, C, H, W) feature map\n Returns:\n Same shape as x with GroupNorm + SiLU applied\n \"\"\"\n # Ensure contiguous input\n if not x.is_contiguous():\n x = x.contiguous()\n \n return fused_gn_silu.fused_groupnorm_silu_cuda(\n x,\n self.weight if self.weight is not None else torch.empty(0),\n self.bias if self.bias is not None else torch.empty(0),\n self.num_groups,\n self.eps\n )"
}
result ← submit_kernel (ok)
oksubmit_kernel
submit_kernel PASSED: (5 / 5) correctness trials all passed.
Kernel runtime: 0.01 μs
Runtime stats: mean=0.01μs median=?μs std=0.00μs
Numerical precision: max_abs_err=1.43e-06 mean_abs_err=2.54e-08 max_rel_err=1.25e-02
Memory: 42.0 MB peak (ref: 46.5 MB, ratio: 0.9032x)
Kernel launches: 2 (ref: 12, fusion ratio: 6.0)
SOL score: 0.2198
final submitted kernel (201 lines, 5,951 chars) — full source, untruncated
import torch
import torch.nn as nn
import torch.utils.cpp_extension as cpp_extension
CUDA_SOURCE = r"""
#include <torch/extension.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <cmath>
// Fused GroupNorm + SiLU kernel
// Each thread block processes one (batch, group) pair
// Threads within block cooperatively compute mean, variance, then apply normalization + SiLU
template <int BLOCK_SIZE>
__global__ void fused_groupnorm_silu_kernel(
const float* __restrict__ input,
float* __restrict__ output,
const float* __restrict__ weight,
const float* __restrict__ bias,
int N, int C, int H, int W, int G, int channels_per_group, float eps
) {
// Each block handles one (batch_item, group) pair
int batch_idx = blockIdx.x / G;
int group_idx = blockIdx.x % G;
int group_start_channel = group_idx * channels_per_group;
int group_size = channels_per_group * H * W;
// Index for this thread's element within the group
int tid = threadIdx.x;
int total_elements = group_size;
// Pointers to the start of this batch and group in input/output
int batch_offset = batch_idx * C * H * W;
int group_offset = group_start_channel * H * W;
const float* __restrict__ input_ptr = input + batch_offset + group_offset;
float* __restrict__ output_ptr = output + batch_offset + group_offset;
// Shared memory for reduction
__shared__ float s_mean[BLOCK_SIZE];
__shared__ float s_var[BLOCK_SIZE];
// Compute mean using parallel reduction
float local_sum = 0.0f;
float local_sq_sum = 0.0f;
// Grid-stride loop to process all elements in the group
for (int i = tid; i < total_elements; i += BLOCK_SIZE) {
float val = input_ptr[i];
local_sum += val;
local_sq_sum += val * val;
}
s_mean[tid] = local_sum;
s_var[tid] = local_sq_sum;
__syncthreads();
// Parallel reduction in shared memory
for (int stride = BLOCK_SIZE / 2; stride > 0; stride >>= 1) {
if (tid < stride) {
s_mean[tid] += s_mean[tid + stride];
s_var[tid] += s_var[tid + stride];
}
__syncthreads();
}
// Compute mean and variance
float mean = 0.0f;
float inv_std = 0.0f;
if (tid == 0) {
mean = s_mean[0] / total_elements;
float variance = s_var[0] / total_elements - mean * mean;
inv_std = 1.0f / sqrtf(variance + eps);
s_mean[0] = mean;
s_var[0] = inv_std;
}
__syncthreads();
mean = s_mean[0];
inv_std = s_var[0];
// Apply normalization, affine, and SiLU
for (int i = tid; i < total_elements; i += BLOCK_SIZE) {
int c_local = i / (H * W);
int c_global = group_start_channel + c_local;
float val = input_ptr[i];
float norm_val = (val - mean) * inv_std;
// Apply affine transformation if weight/bias are provided
if (weight != nullptr && bias != nullptr) {
norm_val = weight[c_global] * norm_val + bias[c_global];
}
// Apply SiLU: x * sigmoid(x) = x / (1 + exp(-x))
float silu_val = norm_val / (1.0f + expf(-norm_val));
output_ptr[i] = silu_val;
}
}
at::Tensor fused_groupnorm_silu_cuda(
at::Tensor input,
at::Tensor weight,
at::Tensor bias,
int num_groups,
double eps
) {
int N = input.size(0);
int C = input.size(1);
int H = input.size(2);
int W = input.size(3);
int channels_per_group = C / num_groups;
auto output = at::empty_like(input);
int num_blocks = N * num_groups;
int block_size = 256;
const float* input_ptr = input.data_ptr<float>();
float* output_ptr = output.data_ptr<float>();
const float* weight_ptr = weight.defined() ? weight.data_ptr<float>() : nullptr;
const float* bias_ptr = bias.defined() ? bias.data_ptr<float>() : nullptr;
fused_groupnorm_silu_kernel<256><<<num_blocks, block_size>>>(
input_ptr, output_ptr, weight_ptr, bias_ptr,
N, C, H, W, num_groups, channels_per_group, static_cast<float>(eps)
);
return output;
}
"""
CPP_SOURCE = r"""
at::Tensor fused_groupnorm_silu_cuda(
at::Tensor input,
at::Tensor weight,
at::Tensor bias,
int num_groups,
double eps
);
"""
# Compile the extension
fused_gn_silu = cpp_extension.load_inline(
name='fused_gn_silu',
cpp_sources=CPP_SOURCE,
cuda_sources=CUDA_SOURCE,
functions=['fused_groupnorm_silu_cuda'],
extra_cuda_cflags=['-O3'],
with_cuda=True,
)
import torch
import torch.nn as nn
class ModelNew(nn.Module):
"""
Fused GroupNorm + SiLU using custom CUDA kernel.
"""
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.num_channels = num_channels
self.num_groups = num_groups
self.eps = eps
self.affine = affine
if affine:
self.weight = nn.Parameter(torch.ones(num_channels))
self.bias = nn.Parameter(torch.zeros(num_channels))
else:
self.register_parameter('weight', None)
self.register_parameter('bias', None)
def forward(self, x: torch.Tensor) -> torch.Tensor:
"""
Args:
x: (N, C, H, W) feature map
Returns:
Same shape as x with GroupNorm + SiLU applied
"""
# Ensure contiguous input
if not x.is_contiguous():
x = x.contiguous()
return fused_gn_silu.fused_groupnorm_silu_cuda(
x,
self.weight if self.weight is not None else torch.empty(0),
self.bias if self.bias is not None else torch.empty(0),
self.num_groups,
self.eps
)