gpt-5.5-priority · original · l123_single_gpt
incorrect 13_DenseNet121TransitionLayer.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
7.46 μs
speedup
—
started
2026-05-05T00:14:09Z
correctness issue: Output mismatch
timingkernelbench.timing
kernel runtime
—
ref runtime
7.46 μs
speedup
—
kernel mean / median / std
—
ref mean / median / std
7.46 / — / 1.02 μs
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.576
dram util %
57.6%
compute util %
36.2%
bottleneck
memory-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
1930.24 GB/s
dram util %
57.6%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
158.3%
bottleneck
memory-bound
dominant pipe %
36.2%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
207.6%
ld sectors / req
16.85
st sectors / req
—
regs / thread
144
smem / block
18432 B
block size
1152
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
2964.1%
mio_throttle
2645.8%
short_scoreboard
1448.0%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
import torch.nn.functional as F

class Model(nn.Module):
    def __init__(self, num_input_features: int, num_output_features: int):
        """
        :param num_input_features: The number of input feature maps
        :param num_output_features: The number of output feature maps
        """
        super(Model, self).__init__()
        self.transition = nn.Sequential(
            nn.BatchNorm2d(num_input_features),
            nn.ReLU(inplace=True),
            nn.Conv2d(num_input_features, num_output_features, kernel_size=1, bias=False),
            nn.AvgPool2d(kernel_size=2, stride=2)
        )

    def forward(self, x):
        """
        :param x: Input tensor of shape (batch_size, num_input_features, height, width)
        :return: Downsampled tensor with reduced number of feature maps
        """
        return self.transition(x)

batch_size = 128
num_input_features = 32
num_output_features = 64
height, width = 256, 256

def get_inputs():
    return [torch.rand(batch_size, num_input_features, height, width)]

def get_init_inputs():
    return [num_input_features, num_output_features]
turn 0 · FINAL · 1 tool call(s) · 107.1s turn · 107.1s LLM · 17,059 chars (click to expand)
turn 0107.1s turn · 107.1s LLM · 0.0s tools · FINAL
reasoning
**Evaluating training stats** I'm considering the use of self.training for a call extension that involves running statistics. I think I need to set some defaults for BatchNorm, like mean=0, variance=1, weight=1, and bias=0. It seems crucial to implement the right statistics for training using PyTorch functions for mean and variance calculations. I should also evaluate the risks involved with using torch operations, ensuring correctness while optimizing performance. It feels like finding a balance between robustness and speed is really important here! **Implementing batch normalization** I’m looking into implementing an extension that supports training for batch normalization (BN). This would require calculating mean and variance through a two-stage reduction process. I’ll need to reduce per channel over a significant number of elements, creating partial arrays to conduct the final reductions. Updating the running statistics is important, especially if the same model is reused repeatedly. Although it may slow things down, I'm factoring in that, with careful implementation, it may still be faster than previous methods. Let's ensure everything is necessary! **Allocating statistics tensors** I’m thinking about how to allocate stats tensors for mean and invstd based on whether training is true or not. If it’s training, I’ll need to allocate those tensors and update the running statistics with reduce kernels. But during evaluation, I might use the running statistics to compute the scale inside a fused kernel. I wonder if I can pass the running_mean and invstd tensor to keep everything consistent. Allocating those properly will be crucial for performance! **Considering PyTorch and accuracy** I’m thinking about how PyTorch uses cuDNN for batch normalization (BN) and the potential internal precision issues involved. Our mean error is around 1e-5. I should consider using double accumulation during reduction to get better accuracy, but I wonder if the reference uses something different. For random BN, the normalized output is very sensitive, so using double precision could help improve statistics, but that might slow things down. I also need to think about how to handle partial tensors. **Evaluating Torch operations** I'm thinking about Torch's exact calculations and its tolerance of 1e-4. The order of ReLU and convolution operations differs; in general, convolution is done after pooling, but our method averages activations first. This could introduce small differences, though potentially under 1e-4. I wonder about fp32 convolution and cuDNN's use of TF32 by default. It seems PyTorch's Conv2d defaults may allow tf32, which might differ from our intended fp32 precision. Lastly, I need to ensure proper declarations in cpp_sources for CUDA functions. **Understanding CUDA and pybind** I'm considering how CUDA sources are compiled separately but can include host functions. I think using load_inline with cuda_sources can define C++ functions and includes torch/extension. The main cpp requires a function declaration for pybind. It seems load_inline concatenates cpp_sources with generated pybind, while cuda_sources stay separate. If a function is defined in cuda sources without declaration in cpp, the binding won't compile. I need to make sure the host wrapper is included in cpp_sources, while kernels remain in cuda_sources. This includes function signatures and parameters to return tensors properly.
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\ntorch::Tensor trans_forward_cuda(torch::Tensor x,\n torch::Tensor bn_weight,\n torch::Tensor bn_bias,\n torch::Tensor running_mean,\n torch::Tensor running_var,\n torch::Tensor conv_weight,\n bool training,\n double momentum,\n double eps);\n\ntorch::Tensor trans_forward(torch::Tensor x,\n torch::Tensor bn_weight,\n torch::Tensor bn_bias,\n torch::Tensor running_mean,\n torch::Tensor running_var,\n torch::Tensor conv_weight,\n bool training,\n double momentum,\n double eps) {\n return trans_forward_cuda(x, bn_weight, bn_bias, running_mean, running_var,\n conv_weight, training, momentum, eps);\n}\n'''\n\n_cuda_src = r'''\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <c10/cuda/CUDAGuard.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n#define SP_TILE 16\n#define CO_TILE 32\n#define CIN_FIXED 32\n#define BPC 256\n\n__global__ void eval_scale_shift_kernel(const float* __restrict__ gamma,\n const float* __restrict__ beta,\n const float* __restrict__ rmean,\n const float* __restrict__ rvar,\n float* __restrict__ scale,\n float* __restrict__ shift,\n float eps, int C) {\n int c = blockIdx.x * blockDim.x + threadIdx.x;\n if (c < C) {\n float sc = gamma[c] * rsqrtf(rvar[c] + eps);\n scale[c] = sc;\n shift[c] = beta[c] - rmean[c] * sc;\n }\n}\n\n__global__ void reduce_sum_sumsq_kernel(const float* __restrict__ x,\n double* __restrict__ psum,\n double* __restrict__ psq,\n int N, int C, int H, int W) {\n int c = blockIdx.x;\n int b = blockIdx.y;\n long long HW = (long long)H * W;\n long long M = (long long)N * HW;\n double sum = 0.0;\n double sq = 0.0;\n for (long long m = (long long)b * blockDim.x + threadIdx.x; m < M; m += (long long)BPC * blockDim.x) {\n long long n = m / HW;\n long long hw = m - n * HW;\n float v = x[n * (long long)C * HW + c * HW + hw];\n sum += (double)v;\n sq += (double)v * (double)v;\n }\n __shared__ double ssum[256];\n __shared__ double ssq[256];\n ssum[threadIdx.x] = sum;\n ssq[threadIdx.x] = sq;\n __syncthreads();\n for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {\n if (threadIdx.x < stride) {\n ssum[threadIdx.x] += ssum[threadIdx.x + stride];\n ssq[threadIdx.x] += ssq[threadIdx.x + stride];\n }\n __syncthreads();\n }\n if (threadIdx.x == 0) {\n psum[c * BPC + b] = ssum[0];\n psq[c * BPC + b] = ssq[0];\n }\n}\n\n__global__ void train_scale_shift_kernel(const double* __restrict__ psum,\n const double* __restrict__ psq,\n const float* __restrict__ gamma,\n const float* __restrict__ beta,\n float* __restrict__ running_mean,\n float* __restrict__ running_var,\n float* __restrict__ scale,\n float* __restrict__ shift,\n int N, int H, int W,\n float momentum, float eps, int C) {\n int c = blockIdx.x;\n __shared__ double ssum[256];\n __shared__ double ssq[256];\n double a = (threadIdx.x < BPC) ? psum[c * BPC + threadIdx.x] : 0.0;\n double b = (threadIdx.x < BPC) ? psq[c * BPC + threadIdx.x] : 0.0;\n ssum[threadIdx.x] = a;\n ssq[threadIdx.x] = b;\n __syncthreads();\n for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {\n if (threadIdx.x < stride) {\n ssum[threadIdx.x] += ssum[threadIdx.x + stride];\n ssq[threadIdx.x] += ssq[threadIdx.x + stride];\n }\n __syncthreads();\n }\n if (threadIdx.x == 0) {\n double M = (double)N * (double)H * (double)W;\n double mean = ssum[0] / M;\n double var = ssq[0] / M - mean * mean;\n if (var < 0.0) var = 0.0;\n float inv = rsqrtf((float)var + eps);\n float sc = gamma[c] * inv;\n scale[c] = sc;\n shift[c] = beta[c] - (float)mean * sc;\n // PyTorch BatchNorm running_var uses the unbiased variance estimate.\n float unb = (float)(var * (M / (M - 1.0)));\n running_mean[c] = running_mean[c] * (1.0f - momentum) + momentum * (float)mean;\n running_var[c] = running_var[c] * (1.0f - momentum) + momentum * unb;\n }\n}\n\n__global__ void fused_pool_conv_kernel(const float* __restrict__ x,\n const float* __restrict__ scale,\n const float* __restrict__ shift,\n const float* __restrict__ w,\n float* __restrict__ out,\n int N, int H, int W, int Cout) {\n __shared__ float act[SP_TILE * CIN_FIXED];\n __shared__ float wt[CO_TILE * CIN_FIXED];\n\n const int tx = threadIdx.x; // spatial row within tile: 0..15\n const int ty = threadIdx.y; // output channel within tile: 0..31\n const int tid = ty * SP_TILE + tx;\n\n const int OH = H >> 1;\n const int OW = W >> 1;\n const int OHW = OH * OW;\n const int S = N * OHW;\n const int spatial_base = blockIdx.x * SP_TILE;\n const int co_base = blockIdx.y * CO_TILE;\n\n // Load pooled BN+ReLU activations. Layout is act[spatial][ci].\n if (tid < SP_TILE * CIN_FIXED) {\n int ci = tid / SP_TILE;\n int sr = tid - ci * SP_TILE;\n int s = spatial_base + sr;\n float av = 0.0f;\n if (s < S) {\n int n = s / OHW;\n int r = s - n * OHW;\n int oh = r / OW;\n int ow = r - oh * OW;\n long long base = ((long long)n * CIN_FIXED + ci) * (long long)H * W + (long long)(oh << 1) * W + (ow << 1);\n float sc = scale[ci];\n float sh = shift[ci];\n float v0 = fmaf(sc, x[base], sh); v0 = v0 > 0.0f ? v0 : 0.0f;\n float v1 = fmaf(sc, x[base + 1], sh); v1 = v1 > 0.0f ? v1 : 0.0f;\n float v2 = fmaf(sc, x[base + W], sh); v2 = v2 > 0.0f ? v2 : 0.0f;\n float v3 = fmaf(sc, x[base + W + 1], sh); v3 = v3 > 0.0f ? v3 : 0.0f;\n av = (v0 + v1 + v2 + v3) * 0.25f;\n }\n act[sr * CIN_FIXED + ci] = av;\n }\n\n // Load a 32x32 weight tile, w is [Cout, 32, 1, 1].\n for (int k = tid; k < CO_TILE * CIN_FIXED; k += SP_TILE * CO_TILE) {\n int lco = k / CIN_FIXED;\n int ci = k - lco * CIN_FIXED;\n int co = co_base + lco;\n wt[lco * CIN_FIXED + ci] = (co < Cout) ? w[co * CIN_FIXED + ci] : 0.0f;\n }\n __syncthreads();\n\n int s = spatial_base + tx;\n int co = co_base + ty;\n if (s < S && co < Cout) {\n float sum = 0.0f;\n #pragma unroll\n for (int ci = 0; ci < CIN_FIXED; ++ci) {\n sum = fmaf(act[tx * CIN_FIXED + ci], wt[ty * CIN_FIXED + ci], sum);\n }\n int n = s / OHW;\n int r = s - n * OHW;\n out[((long long)n * Cout + co) * (long long)OHW + r] = sum;\n }\n}\n\ntorch::Tensor trans_forward_cuda(torch::Tensor x,\n torch::Tensor bn_weight,\n torch::Tensor bn_bias,\n torch::Tensor running_mean,\n torch::Tensor running_var,\n torch::Tensor conv_weight,\n bool training,\n double momentum,\n double eps) {\n TORCH_CHECK(x.is_cuda(), \"x must be CUDA\");\n TORCH_CHECK(x.scalar_type() == torch::kFloat32, \"only float32 supported\");\n TORCH_CHECK(x.is_contiguous(), \"x must be contiguous NCHW\");\n TORCH_CHECK(conv_weight.is_contiguous(), \"conv weight must be contiguous\");\n int N = (int)x.size(0);\n int C = (int)x.size(1);\n int H = (int)x.size(2);\n int W = (int)x.size(3);\n int Cout = (int)conv_weight.size(0);\n TORCH_CHECK(C == CIN_FIXED, \"this optimized kernel expects 32 input channels\");\n TORCH_CHECK((H % 2) == 0 && (W % 2) == 0, \"H and W must be even\");\n\n c10::cuda::CUDAGuard device_guard(x.device());\n auto out = torch::empty({N, Cout, H / 2, W / 2}, x.options());\n auto scale = torch::empty({C}, x.options());\n auto shift = torch::empty({C}, x.options());\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n\n if (training) {\n auto dopt = x.options().dtype(torch::kFloat64);\n auto psum = torch::empty({C, BPC}, dopt);\n auto psq = torch::empty({C, BPC}, dopt);\n dim3 rg(C, BPC);\n reduce_sum_sumsq_kernel<<<rg, 256, 0, stream>>>(x.data_ptr<float>(), psum.data_ptr<double>(), psq.data_ptr<double>(), N, C, H, W);\n train_scale_shift_kernel<<<C, 256, 0, stream>>>(psum.data_ptr<double>(), psq.data_ptr<double>(),\n bn_weight.data_ptr<float>(), bn_bias.data_ptr<float>(), running_mean.data_ptr<float>(), running_var.data_ptr<float>(),\n scale.data_ptr<float>(), shift.data_ptr<float>(), N, H, W, (float)momentum, (float)eps, C);\n } else {\n eval_scale_shift_kernel<<<1, 64, 0, stream>>>(bn_weight.data_ptr<float>(), bn_bias.data_ptr<float>(),\n running_mean.data_ptr<float>(), running_var.data_ptr<float>(), scale.data_ptr<float>(), shift.data_ptr<float>(), (float)eps, C);\n }\n\n int OH = H / 2, OW = W / 2;\n int S = N * OH * OW;\n dim3 block(SP_TILE, CO_TILE);\n dim3 grid((S + SP_TILE - 1) / SP_TILE, (Cout + CO_TILE - 1) / CO_TILE);\n fused_pool_conv_kernel<<<grid, block, 0, stream>>>(x.data_ptr<float>(), scale.data_ptr<float>(), shift.data_ptr<float>(),\n conv_weight.data_ptr<float>(), out.data_ptr<float>(), N, H, W, Cout);\n C10_CUDA_KERNEL_LAUNCH_CHECK();\n return out;\n}\n'''\n\n_ext = load_inline(\n name=\"transition_fused_pool_conv_ext\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"trans_forward\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\"],\n verbose=False,\n)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, num_input_features: int, num_output_features: int):\n super(ModelNew, self).__init__()\n self.transition = nn.Sequential(\n nn.BatchNorm2d(num_input_features),\n nn.ReLU(inplace=True),\n nn.Conv2d(num_input_features, num_output_features, kernel_size=1, bias=False),\n nn.AvgPool2d(kernel_size=2, stride=2),\n )\n\n def forward(self, x):\n bn = self.transition[0]\n conv = self.transition[2]\n if self.training and bn.track_running_stats:\n bn.num_batches_tracked.add_(1)\n mom = 0.1 if bn.momentum is None else float(bn.momentum)\n return _ext.trans_forward(x, bn.weight, bn.bias, bn.running_mean, bn.running_var,\n conv.weight, bool(self.training), mom, float(bn.eps))\n" }
result ← submit_kernel (fail)
failsubmit_kernel submit_kernel FAILED: correctness check did not pass ((0 / 5) trials). correctness_issue: Output mismatch max_difference: ['0.000479', '0.000486', '0.000500', '0.000544', '0.000506'] avg_difference: ['0.000059', '0.000059', '0.000059', '0.000059', '0.000059']
final submitted kernel (282 lines, 11,782 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 trans_forward_cuda(torch::Tensor x,
                                 torch::Tensor bn_weight,
                                 torch::Tensor bn_bias,
                                 torch::Tensor running_mean,
                                 torch::Tensor running_var,
                                 torch::Tensor conv_weight,
                                 bool training,
                                 double momentum,
                                 double eps);

torch::Tensor trans_forward(torch::Tensor x,
                            torch::Tensor bn_weight,
                            torch::Tensor bn_bias,
                            torch::Tensor running_mean,
                            torch::Tensor running_var,
                            torch::Tensor conv_weight,
                            bool training,
                            double momentum,
                            double eps) {
    return trans_forward_cuda(x, bn_weight, bn_bias, running_mean, running_var,
                              conv_weight, training, momentum, eps);
}
'''

_cuda_src = r'''
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <cuda.h>
#include <cuda_runtime.h>

#define SP_TILE 16
#define CO_TILE 32
#define CIN_FIXED 32
#define BPC 256

__global__ void eval_scale_shift_kernel(const float* __restrict__ gamma,
                                        const float* __restrict__ beta,
                                        const float* __restrict__ rmean,
                                        const float* __restrict__ rvar,
                                        float* __restrict__ scale,
                                        float* __restrict__ shift,
                                        float eps, int C) {
    int c = blockIdx.x * blockDim.x + threadIdx.x;
    if (c < C) {
        float sc = gamma[c] * rsqrtf(rvar[c] + eps);
        scale[c] = sc;
        shift[c] = beta[c] - rmean[c] * sc;
    }
}

__global__ void reduce_sum_sumsq_kernel(const float* __restrict__ x,
                                        double* __restrict__ psum,
                                        double* __restrict__ psq,
                                        int N, int C, int H, int W) {
    int c = blockIdx.x;
    int b = blockIdx.y;
    long long HW = (long long)H * W;
    long long M = (long long)N * HW;
    double sum = 0.0;
    double sq = 0.0;
    for (long long m = (long long)b * blockDim.x + threadIdx.x; m < M; m += (long long)BPC * blockDim.x) {
        long long n = m / HW;
        long long hw = m - n * HW;
        float v = x[n * (long long)C * HW + c * HW + hw];
        sum += (double)v;
        sq += (double)v * (double)v;
    }
    __shared__ double ssum[256];
    __shared__ double ssq[256];
    ssum[threadIdx.x] = sum;
    ssq[threadIdx.x] = sq;
    __syncthreads();
    for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
        if (threadIdx.x < stride) {
            ssum[threadIdx.x] += ssum[threadIdx.x + stride];
            ssq[threadIdx.x] += ssq[threadIdx.x + stride];
        }
        __syncthreads();
    }
    if (threadIdx.x == 0) {
        psum[c * BPC + b] = ssum[0];
        psq[c * BPC + b] = ssq[0];
    }
}

__global__ void train_scale_shift_kernel(const double* __restrict__ psum,
                                         const double* __restrict__ psq,
                                         const float* __restrict__ gamma,
                                         const float* __restrict__ beta,
                                         float* __restrict__ running_mean,
                                         float* __restrict__ running_var,
                                         float* __restrict__ scale,
                                         float* __restrict__ shift,
                                         int N, int H, int W,
                                         float momentum, float eps, int C) {
    int c = blockIdx.x;
    __shared__ double ssum[256];
    __shared__ double ssq[256];
    double a = (threadIdx.x < BPC) ? psum[c * BPC + threadIdx.x] : 0.0;
    double b = (threadIdx.x < BPC) ? psq[c * BPC + threadIdx.x] : 0.0;
    ssum[threadIdx.x] = a;
    ssq[threadIdx.x] = b;
    __syncthreads();
    for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
        if (threadIdx.x < stride) {
            ssum[threadIdx.x] += ssum[threadIdx.x + stride];
            ssq[threadIdx.x] += ssq[threadIdx.x + stride];
        }
        __syncthreads();
    }
    if (threadIdx.x == 0) {
        double M = (double)N * (double)H * (double)W;
        double mean = ssum[0] / M;
        double var = ssq[0] / M - mean * mean;
        if (var < 0.0) var = 0.0;
        float inv = rsqrtf((float)var + eps);
        float sc = gamma[c] * inv;
        scale[c] = sc;
        shift[c] = beta[c] - (float)mean * sc;
        // PyTorch BatchNorm running_var uses the unbiased variance estimate.
        float unb = (float)(var * (M / (M - 1.0)));
        running_mean[c] = running_mean[c] * (1.0f - momentum) + momentum * (float)mean;
        running_var[c] = running_var[c] * (1.0f - momentum) + momentum * unb;
    }
}

__global__ void fused_pool_conv_kernel(const float* __restrict__ x,
                                       const float* __restrict__ scale,
                                       const float* __restrict__ shift,
                                       const float* __restrict__ w,
                                       float* __restrict__ out,
                                       int N, int H, int W, int Cout) {
    __shared__ float act[SP_TILE * CIN_FIXED];
    __shared__ float wt[CO_TILE * CIN_FIXED];

    const int tx = threadIdx.x; // spatial row within tile: 0..15
    const int ty = threadIdx.y; // output channel within tile: 0..31
    const int tid = ty * SP_TILE + tx;

    const int OH = H >> 1;
    const int OW = W >> 1;
    const int OHW = OH * OW;
    const int S = N * OHW;
    const int spatial_base = blockIdx.x * SP_TILE;
    const int co_base = blockIdx.y * CO_TILE;

    // Load pooled BN+ReLU activations. Layout is act[spatial][ci].
    if (tid < SP_TILE * CIN_FIXED) {
        int ci = tid / SP_TILE;
        int sr = tid - ci * SP_TILE;
        int s = spatial_base + sr;
        float av = 0.0f;
        if (s < S) {
            int n = s / OHW;
            int r = s - n * OHW;
            int oh = r / OW;
            int ow = r - oh * OW;
            long long base = ((long long)n * CIN_FIXED + ci) * (long long)H * W + (long long)(oh << 1) * W + (ow << 1);
            float sc = scale[ci];
            float sh = shift[ci];
            float v0 = fmaf(sc, x[base], sh);          v0 = v0 > 0.0f ? v0 : 0.0f;
            float v1 = fmaf(sc, x[base + 1], sh);      v1 = v1 > 0.0f ? v1 : 0.0f;
            float v2 = fmaf(sc, x[base + W], sh);      v2 = v2 > 0.0f ? v2 : 0.0f;
            float v3 = fmaf(sc, x[base + W + 1], sh);  v3 = v3 > 0.0f ? v3 : 0.0f;
            av = (v0 + v1 + v2 + v3) * 0.25f;
        }
        act[sr * CIN_FIXED + ci] = av;
    }

    // Load a 32x32 weight tile, w is [Cout, 32, 1, 1].
    for (int k = tid; k < CO_TILE * CIN_FIXED; k += SP_TILE * CO_TILE) {
        int lco = k / CIN_FIXED;
        int ci = k - lco * CIN_FIXED;
        int co = co_base + lco;
        wt[lco * CIN_FIXED + ci] = (co < Cout) ? w[co * CIN_FIXED + ci] : 0.0f;
    }
    __syncthreads();

    int s = spatial_base + tx;
    int co = co_base + ty;
    if (s < S && co < Cout) {
        float sum = 0.0f;
        #pragma unroll
        for (int ci = 0; ci < CIN_FIXED; ++ci) {
            sum = fmaf(act[tx * CIN_FIXED + ci], wt[ty * CIN_FIXED + ci], sum);
        }
        int n = s / OHW;
        int r = s - n * OHW;
        out[((long long)n * Cout + co) * (long long)OHW + r] = sum;
    }
}

torch::Tensor trans_forward_cuda(torch::Tensor x,
                                 torch::Tensor bn_weight,
                                 torch::Tensor bn_bias,
                                 torch::Tensor running_mean,
                                 torch::Tensor running_var,
                                 torch::Tensor conv_weight,
                                 bool training,
                                 double momentum,
                                 double eps) {
    TORCH_CHECK(x.is_cuda(), "x must be CUDA");
    TORCH_CHECK(x.scalar_type() == torch::kFloat32, "only float32 supported");
    TORCH_CHECK(x.is_contiguous(), "x must be contiguous NCHW");
    TORCH_CHECK(conv_weight.is_contiguous(), "conv weight must be contiguous");
    int N = (int)x.size(0);
    int C = (int)x.size(1);
    int H = (int)x.size(2);
    int W = (int)x.size(3);
    int Cout = (int)conv_weight.size(0);
    TORCH_CHECK(C == CIN_FIXED, "this optimized kernel expects 32 input channels");
    TORCH_CHECK((H % 2) == 0 && (W % 2) == 0, "H and W must be even");

    c10::cuda::CUDAGuard device_guard(x.device());
    auto out = torch::empty({N, Cout, H / 2, W / 2}, x.options());
    auto scale = torch::empty({C}, x.options());
    auto shift = torch::empty({C}, x.options());
    cudaStream_t stream = at::cuda::getCurrentCUDAStream();

    if (training) {
        auto dopt = x.options().dtype(torch::kFloat64);
        auto psum = torch::empty({C, BPC}, dopt);
        auto psq = torch::empty({C, BPC}, dopt);
        dim3 rg(C, BPC);
        reduce_sum_sumsq_kernel<<<rg, 256, 0, stream>>>(x.data_ptr<float>(), psum.data_ptr<double>(), psq.data_ptr<double>(), N, C, H, W);
        train_scale_shift_kernel<<<C, 256, 0, stream>>>(psum.data_ptr<double>(), psq.data_ptr<double>(),
            bn_weight.data_ptr<float>(), bn_bias.data_ptr<float>(), running_mean.data_ptr<float>(), running_var.data_ptr<float>(),
            scale.data_ptr<float>(), shift.data_ptr<float>(), N, H, W, (float)momentum, (float)eps, C);
    } else {
        eval_scale_shift_kernel<<<1, 64, 0, stream>>>(bn_weight.data_ptr<float>(), bn_bias.data_ptr<float>(),
            running_mean.data_ptr<float>(), running_var.data_ptr<float>(), scale.data_ptr<float>(), shift.data_ptr<float>(), (float)eps, C);
    }

    int OH = H / 2, OW = W / 2;
    int S = N * OH * OW;
    dim3 block(SP_TILE, CO_TILE);
    dim3 grid((S + SP_TILE - 1) / SP_TILE, (Cout + CO_TILE - 1) / CO_TILE);
    fused_pool_conv_kernel<<<grid, block, 0, stream>>>(x.data_ptr<float>(), scale.data_ptr<float>(), shift.data_ptr<float>(),
        conv_weight.data_ptr<float>(), out.data_ptr<float>(), N, H, W, Cout);
    C10_CUDA_KERNEL_LAUNCH_CHECK();
    return out;
}
'''

_ext = load_inline(
    name="transition_fused_pool_conv_ext",
    cpp_sources=_cpp_src,
    cuda_sources=_cuda_src,
    functions=["trans_forward"],
    extra_cflags=["-O3"],
    extra_cuda_cflags=["-O3"],
    verbose=False,
)


class ModelNew(nn.Module):
    def __init__(self, num_input_features: int, num_output_features: int):
        super(ModelNew, self).__init__()
        self.transition = nn.Sequential(
            nn.BatchNorm2d(num_input_features),
            nn.ReLU(inplace=True),
            nn.Conv2d(num_input_features, num_output_features, kernel_size=1, bias=False),
            nn.AvgPool2d(kernel_size=2, stride=2),
        )

    def forward(self, x):
        bn = self.transition[0]
        conv = self.transition[2]
        if self.training and bn.track_running_stats:
            bn.num_batches_tracked.add_(1)
        mom = 0.1 if bn.momentum is None else float(bn.momentum)
        return _ext.trans_forward(x, bn.weight, bn.bias, bn.running_mean, bn.running_var,
                                  conv.weight, bool(self.training), mom, float(bn.eps))