gpt-5.5-priority · original · l123_single_gpt
incorrect 94_Gemm_BiasAdd_Hardtanh_Mish_GroupNorm.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
—
ref runtime
2.96 μs
speedup
—
started
2026-05-05T00:12:44Z
correctness issue: Output mismatch
timingkernelbench.timing
kernel runtime
—
ref runtime
2.96 μs
speedup
—
kernel mean / median / std
—
ref mean / median / std
2.96 / — / 0.00 μs
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.878
dram util %
12.2%
compute util %
87.8%
bottleneck
compute-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
409.52 GB/s
dram util %
12.2%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
26.9%
bottleneck
compute-bound
dominant pipe %
87.8%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
142.2%
ld sectors / req
8.00
st sectors / req
—
regs / thread
272
smem / block
35840 B
block size
160
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
593.8%
short_scoreboard
233.0%
mio_throttle
2.8%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn

class Model(nn.Module):
    """
    A model that performs a GEMM, BiasAdd, Hardtanh, Mish, and GroupNorm operations in sequence.
    """
    def __init__(self, in_features, out_features, bias_shape, num_groups):
        super(Model, self).__init__()
        self.gemm = nn.Linear(in_features, out_features)
        self.bias = nn.Parameter(torch.randn(bias_shape))
        self.hardtanh = nn.Hardtanh()
        self.mish = nn.Mish()
        self.groupnorm = nn.GroupNorm(num_groups=num_groups, num_channels=out_features)

    def forward(self, x):
        """
        Args:
            x (torch.Tensor): Input tensor of shape (batch_size, in_features).
        Returns:
            torch.Tensor: Output tensor of shape (batch_size, out_features).
        """
        x = self.gemm(x)
        x = x + self.bias
        x = self.hardtanh(x)
        x = self.mish(x)
        x = self.groupnorm(x)
        return x


batch_size = 1024
in_features = 8192
out_features = 8192
bias_shape = (out_features,)
num_groups = 256

def get_inputs():
    return [torch.rand(batch_size, in_features)]

def get_init_inputs():
    return [in_features, out_features, bias_shape, num_groups]
turn 0 · FINAL · 1 tool call(s) · 46.1s turn · 46.1s LLM · 9,337 chars (click to expand)
turn 046.1s turn · 46.1s LLM · 0.0s tools · FINAL
reasoning
**Exploring C++ extension wrappers** I’m considering if a C++ extension wrapper can allocate `gemm` by using `at::linear(input, weight, linear_bias)` and then launch a post kernel. It looks like `at::linear` applies the linear bias, but the kernel adds `self.bias`. I wonder if it makes more sense to use `at::matmul` and then add both within the kernel. There’s definitely a lot to think about in terms of efficiency and how these elements interact. **Exploring function binding** I’m wondering if functions can only bind functions within cpp_sources? It seems there might be some limitations or specific contexts where this applies. It’s interesting to think about how function binding works, especially with types and scopes in C++. I’ll need to delve deeper into this to clarify any misconceptions I have. This seems like a good opportunity to learn more about how binding is structured and its implications in programming.
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n_cpp_src = r'''\n#include <torch/extension.h>\n#include <ATen/ATen.h>\n#include <ATen/ops/linear.h>\n\nvoid postprocess_launch(torch::Tensor y, torch::Tensor bias, torch::Tensor gn_weight, torch::Tensor gn_bias, int64_t num_groups);\n\ntorch::Tensor fused_forward(torch::Tensor x,\n torch::Tensor weight,\n torch::Tensor lin_bias,\n torch::Tensor bias,\n torch::Tensor gn_weight,\n torch::Tensor gn_bias,\n int64_t num_groups) {\n // Use the same cuBLAS/ATen GEMM path as PyTorch for numerical parity, then\n // fuse all remaining elementwise ops and per-(N,group) normalization below.\n auto y = at::linear(x, weight, lin_bias);\n postprocess_launch(y, bias, gn_weight, gn_bias, num_groups);\n return y;\n}\n'''\n\n_cuda_src = r'''\n#include <ATen/ATen.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <c10/cuda/CUDAException.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <stdint.h>\n\nstatic __device__ __forceinline__ float warp_sum(float v) {\n #pragma unroll\n for (int offset = 16; offset > 0; offset >>= 1) {\n v += __shfl_down_sync(0xffffffff, v, offset);\n }\n return v;\n}\n\n__global__ void postprocess_gn32_kernel(float* __restrict__ y,\n const float* __restrict__ bias,\n const float* __restrict__ gn_weight,\n const float* __restrict__ gn_bias,\n int N, int C, int G) {\n // One warp computes one group of one batch item. For the benchmark\n // C=8192,G=256, so group size is exactly 32.\n const int n = blockIdx.y;\n const int g = blockIdx.x;\n const int lane = threadIdx.x;\n const int group_size = C / G;\n const int c = g * group_size + lane;\n\n float v = 0.0f;\n if (lane < group_size) {\n const int idx = n * C + c;\n v = y[idx] + bias[c];\n // Hardtanh default range [-1, 1]\n v = fminf(1.0f, fmaxf(-1.0f, v));\n // Mish: x * tanh(softplus(x)); x is already in [-1,1].\n v = v * tanhf(log1pf(expf(v)));\n }\n\n float sum = warp_sum(v);\n float sum2 = warp_sum(v * v);\n float mean = sum * (1.0f / (float)group_size);\n float var = sum2 * (1.0f / (float)group_size) - mean * mean;\n var = fmaxf(var, 0.0f);\n float inv_std = rsqrtf(var + 1.0e-5f);\n\n if (lane < group_size) {\n y[n * C + c] = (v - mean) * inv_std * gn_weight[c] + gn_bias[c];\n }\n}\n\n// A generic-but-still-warp based version for safety if constructor arguments vary.\n__global__ void postprocess_gn_generic_kernel(float* __restrict__ y,\n const float* __restrict__ bias,\n const float* __restrict__ gn_weight,\n const float* __restrict__ gn_bias,\n int N, int C, int G) {\n const int n = blockIdx.y;\n const int g = blockIdx.x;\n const int tid = threadIdx.x;\n const int group_size = C / G;\n const int base = n * C + g * group_size;\n\n float sum = 0.0f;\n float sum2 = 0.0f;\n for (int i = tid; i < group_size; i += blockDim.x) {\n int c = g * group_size + i;\n float v = y[base + i] + bias[c];\n v = fminf(1.0f, fmaxf(-1.0f, v));\n v = v * tanhf(log1pf(expf(v)));\n sum += v;\n sum2 += v * v;\n }\n __shared__ float smem[64];\n // reduce within block (blockDim <= 256 expected)\n for (int offset = 16; offset > 0; offset >>= 1) {\n sum += __shfl_down_sync(0xffffffff, sum, offset);\n sum2 += __shfl_down_sync(0xffffffff, sum2, offset);\n }\n int warp = tid >> 5;\n int lane = tid & 31;\n if (lane == 0) { smem[warp] = sum; smem[warp + 32] = sum2; }\n __syncthreads();\n if (warp == 0) {\n sum = (lane < ((blockDim.x + 31) >> 5)) ? smem[lane] : 0.0f;\n sum2 = (lane < ((blockDim.x + 31) >> 5)) ? smem[lane + 32] : 0.0f;\n for (int offset = 16; offset > 0; offset >>= 1) {\n sum += __shfl_down_sync(0xffffffff, sum, offset);\n sum2 += __shfl_down_sync(0xffffffff, sum2, offset);\n }\n if (lane == 0) { smem[0] = sum; smem[1] = sum2; }\n }\n __syncthreads();\n float mean = smem[0] / (float)group_size;\n float var = smem[1] / (float)group_size - mean * mean;\n float inv_std = rsqrtf(fmaxf(var, 0.0f) + 1.0e-5f);\n for (int i = tid; i < group_size; i += blockDim.x) {\n int c = g * group_size + i;\n float v = y[base + i] + bias[c];\n v = fminf(1.0f, fmaxf(-1.0f, v));\n v = v * tanhf(log1pf(expf(v)));\n y[base + i] = (v - mean) * inv_std * gn_weight[c] + gn_bias[c];\n }\n}\n\nvoid postprocess_launch(torch::Tensor y, torch::Tensor bias, torch::Tensor gn_weight, torch::Tensor gn_bias, int64_t num_groups) {\n const int N = (int)y.size(0);\n const int C = (int)y.size(1);\n const int G = (int)num_groups;\n dim3 grid(G, N, 1);\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n if (C / G == 32) {\n postprocess_gn32_kernel<<<grid, 32, 0, stream>>>(\n y.data_ptr<float>(), bias.data_ptr<float>(), gn_weight.data_ptr<float>(), gn_bias.data_ptr<float>(), N, C, G);\n } else {\n int gs = C / G;\n int threads = 32;\n if (gs > 32) threads = 64;\n if (gs > 64) threads = 128;\n if (gs > 128) threads = 256;\n postprocess_gn_generic_kernel<<<grid, threads, 0, stream>>>(\n y.data_ptr<float>(), bias.data_ptr<float>(), gn_weight.data_ptr<float>(), gn_bias.data_ptr<float>(), N, C, G);\n }\n C10_CUDA_KERNEL_LAUNCH_CHECK();\n}\n'''\n\n_ext = load_inline(\n name='fused_linear_mish_groupnorm_ext_v1',\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=['fused_forward'],\n with_cuda=True,\n extra_cflags=['-O3'],\n extra_cuda_cflags=['-O3', '--use_fast_math'],\n verbose=False,\n)\n\n\nclass ModelNew(nn.Module):\n \"\"\"\n Fused implementation of: Linear -> add bias -> Hardtanh -> Mish -> GroupNorm.\n \"\"\"\n def __init__(self, in_features, out_features, bias_shape, num_groups):\n super(ModelNew, self).__init__()\n self.gemm = nn.Linear(in_features, out_features)\n self.bias = nn.Parameter(torch.randn(bias_shape))\n self.hardtanh = nn.Hardtanh()\n self.mish = nn.Mish()\n self.groupnorm = nn.GroupNorm(num_groups=num_groups, num_channels=out_features)\n\n def forward(self, x):\n return _ext.fused_forward(\n x,\n self.gemm.weight,\n self.gemm.bias,\n self.bias,\n self.groupnorm.weight,\n self.groupnorm.bias,\n self.groupnorm.num_groups,\n )\n" }
result ← submit_kernel (fail)
failsubmit_kernel submit_kernel FAILED: correctness check did not pass ((0 / 5) trials). correctness_issue: Output mismatch max_difference: ['4.035159', '4.038151', '4.015758', '4.008082', '4.072634'] avg_difference: ['0.229454', '0.229427', '0.229516', '0.229401', '0.229382']
final submitted kernel (188 lines, 6,891 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>
#include <ATen/ATen.h>
#include <ATen/ops/linear.h>

void postprocess_launch(torch::Tensor y, torch::Tensor bias, torch::Tensor gn_weight, torch::Tensor gn_bias, int64_t num_groups);

torch::Tensor fused_forward(torch::Tensor x,
                           torch::Tensor weight,
                           torch::Tensor lin_bias,
                           torch::Tensor bias,
                           torch::Tensor gn_weight,
                           torch::Tensor gn_bias,
                           int64_t num_groups) {
    // Use the same cuBLAS/ATen GEMM path as PyTorch for numerical parity, then
    // fuse all remaining elementwise ops and per-(N,group) normalization below.
    auto y = at::linear(x, weight, lin_bias);
    postprocess_launch(y, bias, gn_weight, gn_bias, num_groups);
    return y;
}
'''

_cuda_src = r'''
#include <ATen/ATen.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAException.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <stdint.h>

static __device__ __forceinline__ float warp_sum(float v) {
    #pragma unroll
    for (int offset = 16; offset > 0; offset >>= 1) {
        v += __shfl_down_sync(0xffffffff, v, offset);
    }
    return v;
}

__global__ void postprocess_gn32_kernel(float* __restrict__ y,
                                        const float* __restrict__ bias,
                                        const float* __restrict__ gn_weight,
                                        const float* __restrict__ gn_bias,
                                        int N, int C, int G) {
    // One warp computes one group of one batch item.  For the benchmark
    // C=8192,G=256, so group size is exactly 32.
    const int n = blockIdx.y;
    const int g = blockIdx.x;
    const int lane = threadIdx.x;
    const int group_size = C / G;
    const int c = g * group_size + lane;

    float v = 0.0f;
    if (lane < group_size) {
        const int idx = n * C + c;
        v = y[idx] + bias[c];
        // Hardtanh default range [-1, 1]
        v = fminf(1.0f, fmaxf(-1.0f, v));
        // Mish: x * tanh(softplus(x)); x is already in [-1,1].
        v = v * tanhf(log1pf(expf(v)));
    }

    float sum = warp_sum(v);
    float sum2 = warp_sum(v * v);
    float mean = sum * (1.0f / (float)group_size);
    float var = sum2 * (1.0f / (float)group_size) - mean * mean;
    var = fmaxf(var, 0.0f);
    float inv_std = rsqrtf(var + 1.0e-5f);

    if (lane < group_size) {
        y[n * C + c] = (v - mean) * inv_std * gn_weight[c] + gn_bias[c];
    }
}

// A generic-but-still-warp based version for safety if constructor arguments vary.
__global__ void postprocess_gn_generic_kernel(float* __restrict__ y,
                                              const float* __restrict__ bias,
                                              const float* __restrict__ gn_weight,
                                              const float* __restrict__ gn_bias,
                                              int N, int C, int G) {
    const int n = blockIdx.y;
    const int g = blockIdx.x;
    const int tid = threadIdx.x;
    const int group_size = C / G;
    const int base = n * C + g * group_size;

    float sum = 0.0f;
    float sum2 = 0.0f;
    for (int i = tid; i < group_size; i += blockDim.x) {
        int c = g * group_size + i;
        float v = y[base + i] + bias[c];
        v = fminf(1.0f, fmaxf(-1.0f, v));
        v = v * tanhf(log1pf(expf(v)));
        sum += v;
        sum2 += v * v;
    }
    __shared__ float smem[64];
    // reduce within block (blockDim <= 256 expected)
    for (int offset = 16; offset > 0; offset >>= 1) {
        sum += __shfl_down_sync(0xffffffff, sum, offset);
        sum2 += __shfl_down_sync(0xffffffff, sum2, offset);
    }
    int warp = tid >> 5;
    int lane = tid & 31;
    if (lane == 0) { smem[warp] = sum; smem[warp + 32] = sum2; }
    __syncthreads();
    if (warp == 0) {
        sum = (lane < ((blockDim.x + 31) >> 5)) ? smem[lane] : 0.0f;
        sum2 = (lane < ((blockDim.x + 31) >> 5)) ? smem[lane + 32] : 0.0f;
        for (int offset = 16; offset > 0; offset >>= 1) {
            sum += __shfl_down_sync(0xffffffff, sum, offset);
            sum2 += __shfl_down_sync(0xffffffff, sum2, offset);
        }
        if (lane == 0) { smem[0] = sum; smem[1] = sum2; }
    }
    __syncthreads();
    float mean = smem[0] / (float)group_size;
    float var = smem[1] / (float)group_size - mean * mean;
    float inv_std = rsqrtf(fmaxf(var, 0.0f) + 1.0e-5f);
    for (int i = tid; i < group_size; i += blockDim.x) {
        int c = g * group_size + i;
        float v = y[base + i] + bias[c];
        v = fminf(1.0f, fmaxf(-1.0f, v));
        v = v * tanhf(log1pf(expf(v)));
        y[base + i] = (v - mean) * inv_std * gn_weight[c] + gn_bias[c];
    }
}

void postprocess_launch(torch::Tensor y, torch::Tensor bias, torch::Tensor gn_weight, torch::Tensor gn_bias, int64_t num_groups) {
    const int N = (int)y.size(0);
    const int C = (int)y.size(1);
    const int G = (int)num_groups;
    dim3 grid(G, N, 1);
    cudaStream_t stream = at::cuda::getCurrentCUDAStream();
    if (C / G == 32) {
        postprocess_gn32_kernel<<<grid, 32, 0, stream>>>(
            y.data_ptr<float>(), bias.data_ptr<float>(), gn_weight.data_ptr<float>(), gn_bias.data_ptr<float>(), N, C, G);
    } else {
        int gs = C / G;
        int threads = 32;
        if (gs > 32) threads = 64;
        if (gs > 64) threads = 128;
        if (gs > 128) threads = 256;
        postprocess_gn_generic_kernel<<<grid, threads, 0, stream>>>(
            y.data_ptr<float>(), bias.data_ptr<float>(), gn_weight.data_ptr<float>(), gn_bias.data_ptr<float>(), N, C, G);
    }
    C10_CUDA_KERNEL_LAUNCH_CHECK();
}
'''

_ext = load_inline(
    name='fused_linear_mish_groupnorm_ext_v1',
    cpp_sources=_cpp_src,
    cuda_sources=_cuda_src,
    functions=['fused_forward'],
    with_cuda=True,
    extra_cflags=['-O3'],
    extra_cuda_cflags=['-O3', '--use_fast_math'],
    verbose=False,
)


class ModelNew(nn.Module):
    """
    Fused implementation of: Linear -> add bias -> Hardtanh -> Mish -> GroupNorm.
    """
    def __init__(self, in_features, out_features, bias_shape, num_groups):
        super(ModelNew, self).__init__()
        self.gemm = nn.Linear(in_features, out_features)
        self.bias = nn.Parameter(torch.randn(bias_shape))
        self.hardtanh = nn.Hardtanh()
        self.mish = nn.Mish()
        self.groupnorm = nn.GroupNorm(num_groups=num_groups, num_channels=out_features)

    def forward(self, x):
        return _ext.fused_forward(
            x,
            self.gemm.weight,
            self.gemm.bias,
            self.bias,
            self.groupnorm.weight,
            self.groupnorm.bias,
            self.groupnorm.num_groups,
        )