gpt-5.5-priority · original · l123_single_gpt
correct 77_ConvTranspose3d_Scale_BatchNorm_GlobalAvgPool.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
232.00 μs
ref runtime
5.45 μs
speedup
0.02x
started
2026-05-05T00:02:45Z
timingkernelbench.timing
kernel runtime
232.00 μs
ref runtime
5.45 μs
speedup
0.02x
kernel mean / median / std
232.00 / — / 0.28 μs
ref mean / median / std
5.45 / — / 0.14 μs
numerical precisioneval_kernel_against_ref
max abs error
7.48e-06
mean abs error
1.43e-06
max rel error
2.92
mean rel error
0.00
gpu memoryextended_metrics.measure_memory
kernel peak
104.87 MB
ref peak
577.73 MB
kernel / ref
0.18x
kernel launches / fusiontorch.profiler
kernel launches
8
ref launches
21
fusion ratio (ref / kernel)
2.62x
total cuda time
79 μs
energyNVML (pynvml)
kernel mJ / run
113074.90
ref mJ / run
5042.18
ratio (ref / kernel)
0.04x
avg power
487.08 W
ref avg power
929.46 W
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.718
dram util %
71.8%
compute util %
48.6%
bottleneck
memory-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
2406.03 GB/s
dram util %
71.8%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
331.9%
bottleneck
memory-bound
dominant pipe %
48.6%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
395.8%
ld sectors / req
29.74
st sectors / req
—
regs / thread
155
smem / block
9728 B
block size
1280
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
8834.0%
short_scoreboard
1432.6%
mio_throttle
319.3%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn

class Model(nn.Module):
    """
    Model that performs a 3D transposed convolution, scales the output, applies batch normalization, 
    and then performs global average pooling. 
    """
    def __init__(self, in_channels, out_channels, kernel_size, scale_factor, eps=1e-5, momentum=0.1):
        super(Model, self).__init__()
        self.conv_transpose = nn.ConvTranspose3d(in_channels, out_channels, kernel_size)
        self.scale_factor = scale_factor
        self.batch_norm = nn.BatchNorm3d(out_channels, eps=eps, momentum=momentum)
        self.global_avg_pool = nn.AdaptiveAvgPool3d((1, 1, 1))

    def forward(self, x):
        x = self.conv_transpose(x)
        x = x * self.scale_factor
        x = self.batch_norm(x)
        x = self.global_avg_pool(x)
        return x

batch_size = 16
in_channels = 64
out_channels = 128
depth, height, width = 16, 32, 32
kernel_size = 5
scale_factor = 2.0

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

def get_init_inputs():
    return [in_channels, out_channels, kernel_size, scale_factor]
turn 0 · FINAL · 1 tool call(s) · 263.1s turn · 263.1s LLM · 15,756 chars (click to expand)
turn 0263.1s turn · 263.1s LLM · 0.0s tools · FINAL
reasoning
**Evaluating model output** I’m figuring out the evaluation output formula: average equals gamma times a calculation involving scale, average convolution, running mean, and some other factors. It seems simpler with only spatial sums, but I wonder if default module training means it doesn’t evaluate. I need it to be robust — should I check if it’s in training mode during the forward pass? Also, I’m considering implementing calculations for ConvTranspose3d with specific parameters for output dimensions. **Evaluating quadratic form calculations** I'm considering how to compute the sum of squares of transposed convolutions. It seems like it involves using a quadratic form that includes input autocorrelations and kernel correlations. But then I wonder if there's a simpler approach. I think it might help to derive this step by step. Should I focus on each operation per batch? Also, I need to think about how to train the batch normalization mean and variance across N and spatial dimensions. **Evaluating Batch Normalization** I'm considering how to approximate training BatchNorm with a global average. It seems like the output involves a scale factor, where the numerator might be small. I need to ensure variance is positive, and if I'm referencing running statistics, the training output won't match the evaluation. I think the harness probably calls the evaluation phase, but many inference speed tasks with BatchNorm rely on eval, although that's not always guaranteed. **Resolving compilation concerns** I’m thinking about a compilation issue with loading inline auto-binding bool parameters. First, do I need to include the CUDA runtime? That seems important. Also, should I use AT_CUDA_CHECK? I know these functions can be tricky, so I want to make sure I'm approaching this correctly. I could double-check the documentation to clarify what’s needed. It’s all about ensuring everything compiles smoothly!
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 fused_forward(torch::Tensor x,\n torch::Tensor weight,\n torch::Tensor bias,\n torch::Tensor bn_weight,\n torch::Tensor bn_bias,\n torch::Tensor running_mean,\n torch::Tensor running_var,\n double scale,\n double eps,\n bool training);\n\"\"\"\n\n_cuda_src = r\"\"\"\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n#include <algorithm>\n\n__global__ void input_sum_kernel(const float* __restrict__ x, float* __restrict__ in_sum,\n int N, int Cin, int D, int H, int W) {\n extern __shared__ float sdata[];\n int idx = blockIdx.x;\n int n = idx / Cin;\n int c = idx - n * Cin;\n int S = D * H * W;\n float acc = 0.0f;\n const float* base = x + ((n * Cin + c) * S);\n for (int i = threadIdx.x; i < S; i += blockDim.x) acc += base[i];\n sdata[threadIdx.x] = acc;\n __syncthreads();\n for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {\n if (threadIdx.x < stride) sdata[threadIdx.x] += sdata[threadIdx.x + stride];\n __syncthreads();\n }\n if (threadIdx.x == 0) in_sum[idx] = sdata[0];\n}\n\n__global__ void weight_sum_kernel(const float* __restrict__ w, float* __restrict__ w_sum,\n int Cin, int Cout, int Kd, int Kh, int Kw) {\n extern __shared__ float sdata[];\n int idx = blockIdx.x;\n int ic = idx / Cout;\n int oc = idx - ic * Cout;\n int K = Kd * Kh * Kw;\n const float* base = w + ((ic * Cout + oc) * K);\n float acc = 0.0f;\n for (int i = threadIdx.x; i < K; i += blockDim.x) acc += base[i];\n sdata[threadIdx.x] = acc;\n __syncthreads();\n for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {\n if (threadIdx.x < stride) sdata[threadIdx.x] += sdata[threadIdx.x + stride];\n __syncthreads();\n }\n if (threadIdx.x == 0) w_sum[idx] = sdata[0];\n}\n\n__global__ void eval_out_kernel(const float* __restrict__ in_sum,\n const float* __restrict__ w_sum,\n const float* __restrict__ bias,\n const float* __restrict__ gamma,\n const float* __restrict__ beta,\n const float* __restrict__ rmean,\n const float* __restrict__ rvar,\n float* __restrict__ out,\n int N, int Cin, int Cout, int Vout,\n float scale, float eps) {\n extern __shared__ float sdata[];\n int bid = blockIdx.x;\n int n = bid / Cout;\n int oc = bid - n * Cout;\n float acc = 0.0f;\n for (int ic = threadIdx.x; ic < Cin; ic += blockDim.x) {\n acc += in_sum[n * Cin + ic] * w_sum[ic * Cout + oc];\n }\n sdata[threadIdx.x] = acc;\n __syncthreads();\n for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {\n if (threadIdx.x < stride) sdata[threadIdx.x] += sdata[threadIdx.x + stride];\n __syncthreads();\n }\n if (threadIdx.x == 0) {\n float conv_avg = (sdata[0] + bias[oc] * (float)Vout) / (float)Vout;\n float v = conv_avg * scale;\n float y = (v - rmean[oc]) * rsqrtf(rvar[oc] + eps) * gamma[oc] + beta[oc];\n out[n * Cout + oc] = y;\n }\n}\n\n__global__ void conv_sum_kernel(const float* __restrict__ in_sum,\n const float* __restrict__ w_sum,\n const float* __restrict__ bias,\n float* __restrict__ conv_sum,\n int N, int Cin, int Cout, int Vout) {\n extern __shared__ float sdata[];\n int bid = blockIdx.x;\n int n = bid / Cout;\n int oc = bid - n * Cout;\n float acc = 0.0f;\n for (int ic = threadIdx.x; ic < Cin; ic += blockDim.x) {\n acc += in_sum[n * Cin + ic] * w_sum[ic * Cout + oc];\n }\n sdata[threadIdx.x] = acc;\n __syncthreads();\n for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {\n if (threadIdx.x < stride) sdata[threadIdx.x] += sdata[threadIdx.x + stride];\n __syncthreads();\n }\n if (threadIdx.x == 0) conv_sum[n * Cout + oc] = sdata[0] + bias[oc] * (float)Vout;\n}\n\n__global__ void sumsq_kernel(const float* __restrict__ x,\n const float* __restrict__ w,\n const float* __restrict__ bias,\n float* __restrict__ sumsq,\n int N, int Cin, int Cout,\n int D, int H, int Wd,\n int Kd, int Kh, int Kw,\n int OD, int OH, int OW,\n float scale, int blocks_per_oc) {\n int global_bid = blockIdx.x;\n int oc = global_bid / blocks_per_oc;\n int local_bid = global_bid - oc * blocks_per_oc;\n int M = N * OD * OH * OW;\n float acc2 = 0.0f;\n int stride = blockDim.x * blocks_per_oc;\n for (int m = local_bid * blockDim.x + threadIdx.x; m < M; m += stride) {\n int t = m;\n int ow = t % OW; t /= OW;\n int oh = t % OH; t /= OH;\n int od = t % OD; t /= OD;\n int n = t;\n float val = bias[oc];\n int kd0 = max(0, od - (D - 1));\n int kd1 = min(Kd - 1, od);\n int kh0 = max(0, oh - (H - 1));\n int kh1 = min(Kh - 1, oh);\n int kw0 = max(0, ow - (Wd - 1));\n int kw1 = min(Kw - 1, ow);\n for (int ic = 0; ic < Cin; ++ic) {\n for (int kd = kd0; kd <= kd1; ++kd) {\n int id = od - kd;\n for (int kh = kh0; kh <= kh1; ++kh) {\n int ih = oh - kh;\n const float* xb = x + (((n * Cin + ic) * D + id) * H + ih) * Wd;\n const float* wb = w + (((ic * Cout + oc) * Kd + kd) * Kh + kh) * Kw;\n for (int kw = kw0; kw <= kw1; ++kw) {\n val += xb[ow - kw] * wb[kw];\n }\n }\n }\n }\n float sv = val * scale;\n acc2 += sv * sv;\n }\n extern __shared__ float sdata[];\n sdata[threadIdx.x] = acc2;\n __syncthreads();\n for (int s = blockDim.x >> 1; s > 0; s >>= 1) {\n if (threadIdx.x < s) sdata[threadIdx.x] += sdata[threadIdx.x + s];\n __syncthreads();\n }\n if (threadIdx.x == 0) atomicAdd(sumsq + oc, sdata[0]);\n}\n\n__global__ void train_out_kernel(const float* __restrict__ conv_sum,\n const float* __restrict__ sumsq,\n const float* __restrict__ gamma,\n const float* __restrict__ beta,\n float* __restrict__ out,\n int N, int Cout, int Vout,\n float scale, float eps) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = N * Cout;\n if (idx >= total) return;\n int n = idx / Cout;\n int oc = idx - n * Cout;\n float tsum = 0.0f;\n for (int i = 0; i < N; ++i) tsum += conv_sum[i * Cout + oc];\n float invM = 1.0f / (float)(N * Vout);\n float mean = tsum * scale * invM;\n float var = sumsq[oc] * invM - mean * mean;\n if (var < 0.0f) var = 0.0f;\n float avg = conv_sum[n * Cout + oc] * scale / (float)Vout;\n out[idx] = (avg - mean) * rsqrtf(var + eps) * gamma[oc] + beta[oc];\n}\n\ntorch::Tensor fused_forward(torch::Tensor x,\n torch::Tensor weight,\n torch::Tensor bias,\n torch::Tensor bn_weight,\n torch::Tensor bn_bias,\n torch::Tensor running_mean,\n torch::Tensor running_var,\n double scale_d,\n double eps_d,\n bool training) {\n TORCH_CHECK(x.is_cuda(), \"x must be CUDA\");\n TORCH_CHECK(x.scalar_type() == at::kFloat, \"only float32 supported\");\n int N = (int)x.size(0);\n int Cin = (int)x.size(1);\n int D = (int)x.size(2);\n int H = (int)x.size(3);\n int Wd = (int)x.size(4);\n int Cout = (int)weight.size(1);\n int Kd = (int)weight.size(2);\n int Kh = (int)weight.size(3);\n int Kw = (int)weight.size(4);\n int OD = D + Kd - 1;\n int OH = H + Kh - 1;\n int OW = Wd + Kw - 1;\n int Vout = OD * OH * OW;\n auto opts = x.options();\n auto in_sum = torch::empty({N, Cin}, opts);\n auto w_sum = torch::empty({Cin, Cout}, opts);\n auto out = torch::empty({N, Cout, 1, 1, 1}, opts);\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n const int T = 256;\n input_sum_kernel<<<N * Cin, T, T * sizeof(float), stream>>>(x.data_ptr<float>(), in_sum.data_ptr<float>(), N, Cin, D, H, Wd);\n weight_sum_kernel<<<Cin * Cout, T, T * sizeof(float), stream>>>(weight.data_ptr<float>(), w_sum.data_ptr<float>(), Cin, Cout, Kd, Kh, Kw);\n float scale = (float)scale_d;\n float eps = (float)eps_d;\n if (!training) {\n eval_out_kernel<<<N * Cout, T, T * sizeof(float), stream>>>(\n in_sum.data_ptr<float>(), w_sum.data_ptr<float>(), bias.data_ptr<float>(),\n bn_weight.data_ptr<float>(), bn_bias.data_ptr<float>(), running_mean.data_ptr<float>(), running_var.data_ptr<float>(),\n out.data_ptr<float>(), N, Cin, Cout, Vout, scale, eps);\n } else {\n auto conv_sum = torch::empty({N, Cout}, opts);\n conv_sum_kernel<<<N * Cout, T, T * sizeof(float), stream>>>(\n in_sum.data_ptr<float>(), w_sum.data_ptr<float>(), bias.data_ptr<float>(), conv_sum.data_ptr<float>(), N, Cin, Cout, Vout);\n auto sumsq = torch::zeros({Cout}, opts);\n int blocks_per_oc = 256;\n sumsq_kernel<<<Cout * blocks_per_oc, 128, 128 * sizeof(float), stream>>>(\n x.data_ptr<float>(), weight.data_ptr<float>(), bias.data_ptr<float>(), sumsq.data_ptr<float>(),\n N, Cin, Cout, D, H, Wd, Kd, Kh, Kw, OD, OH, OW, scale, blocks_per_oc);\n int total = N * Cout;\n train_out_kernel<<<(total + 255) / 256, 256, 0, stream>>>(\n conv_sum.data_ptr<float>(), sumsq.data_ptr<float>(), bn_weight.data_ptr<float>(), bn_bias.data_ptr<float>(),\n out.data_ptr<float>(), N, Cout, Vout, scale, eps);\n }\n return out;\n}\n\"\"\"\n\n_ext = load_inline(\n name=\"fused_deconv_bn_gap_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\", \"--use_fast_math\"],\n verbose=False,\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, in_channels, out_channels, kernel_size, scale_factor, eps=1e-5, momentum=0.1):\n super(ModelNew, self).__init__()\n self.conv_transpose = nn.ConvTranspose3d(in_channels, out_channels, kernel_size)\n self.scale_factor = scale_factor\n self.batch_norm = nn.BatchNorm3d(out_channels, eps=eps, momentum=momentum)\n self.global_avg_pool = nn.AdaptiveAvgPool3d((1, 1, 1))\n\n def forward(self, x):\n # The benchmark input and parameters are contiguous CUDA fp32 tensors. Keep a safe\n # contiguous view for the custom address arithmetic.\n if not x.is_contiguous():\n x = x.contiguous()\n return _ext.fused_forward(\n x,\n self.conv_transpose.weight,\n self.conv_transpose.bias,\n self.batch_norm.weight,\n self.batch_norm.bias,\n self.batch_norm.running_mean,\n self.batch_norm.running_var,\n float(self.scale_factor),\n float(self.batch_norm.eps),\n bool(self.training),\n )\n" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 232.00 μs Runtime stats: mean=232.00μs median=?μs std=0.28μs Numerical precision: max_abs_err=7.48e-06 mean_abs_err=1.43e-06 max_rel_err=2.92e+00 Memory: 104.87 MB peak (ref: 577.73 MB, ratio: 0.1815x) Kernel launches: 8 (ref: 21, fusion ratio: 2.625) Energy: 113074.9 mJ/run (ref: 5042.18 mJ, ratio: 0.0446x) SOL score: 0.7182
final submitted kernel (288 lines, 11,942 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 x,
                            torch::Tensor weight,
                            torch::Tensor bias,
                            torch::Tensor bn_weight,
                            torch::Tensor bn_bias,
                            torch::Tensor running_mean,
                            torch::Tensor running_var,
                            double scale,
                            double eps,
                            bool training);
"""

_cuda_src = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <cuda.h>
#include <cuda_runtime.h>
#include <algorithm>

__global__ void input_sum_kernel(const float* __restrict__ x, float* __restrict__ in_sum,
                                 int N, int Cin, int D, int H, int W) {
    extern __shared__ float sdata[];
    int idx = blockIdx.x;
    int n = idx / Cin;
    int c = idx - n * Cin;
    int S = D * H * W;
    float acc = 0.0f;
    const float* base = x + ((n * Cin + c) * S);
    for (int i = threadIdx.x; i < S; i += blockDim.x) acc += base[i];
    sdata[threadIdx.x] = acc;
    __syncthreads();
    for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
        if (threadIdx.x < stride) sdata[threadIdx.x] += sdata[threadIdx.x + stride];
        __syncthreads();
    }
    if (threadIdx.x == 0) in_sum[idx] = sdata[0];
}

__global__ void weight_sum_kernel(const float* __restrict__ w, float* __restrict__ w_sum,
                                  int Cin, int Cout, int Kd, int Kh, int Kw) {
    extern __shared__ float sdata[];
    int idx = blockIdx.x;
    int ic = idx / Cout;
    int oc = idx - ic * Cout;
    int K = Kd * Kh * Kw;
    const float* base = w + ((ic * Cout + oc) * K);
    float acc = 0.0f;
    for (int i = threadIdx.x; i < K; i += blockDim.x) acc += base[i];
    sdata[threadIdx.x] = acc;
    __syncthreads();
    for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
        if (threadIdx.x < stride) sdata[threadIdx.x] += sdata[threadIdx.x + stride];
        __syncthreads();
    }
    if (threadIdx.x == 0) w_sum[idx] = sdata[0];
}

__global__ void eval_out_kernel(const float* __restrict__ in_sum,
                                const float* __restrict__ w_sum,
                                const float* __restrict__ bias,
                                const float* __restrict__ gamma,
                                const float* __restrict__ beta,
                                const float* __restrict__ rmean,
                                const float* __restrict__ rvar,
                                float* __restrict__ out,
                                int N, int Cin, int Cout, int Vout,
                                float scale, float eps) {
    extern __shared__ float sdata[];
    int bid = blockIdx.x;
    int n = bid / Cout;
    int oc = bid - n * Cout;
    float acc = 0.0f;
    for (int ic = threadIdx.x; ic < Cin; ic += blockDim.x) {
        acc += in_sum[n * Cin + ic] * w_sum[ic * Cout + oc];
    }
    sdata[threadIdx.x] = acc;
    __syncthreads();
    for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
        if (threadIdx.x < stride) sdata[threadIdx.x] += sdata[threadIdx.x + stride];
        __syncthreads();
    }
    if (threadIdx.x == 0) {
        float conv_avg = (sdata[0] + bias[oc] * (float)Vout) / (float)Vout;
        float v = conv_avg * scale;
        float y = (v - rmean[oc]) * rsqrtf(rvar[oc] + eps) * gamma[oc] + beta[oc];
        out[n * Cout + oc] = y;
    }
}

__global__ void conv_sum_kernel(const float* __restrict__ in_sum,
                                const float* __restrict__ w_sum,
                                const float* __restrict__ bias,
                                float* __restrict__ conv_sum,
                                int N, int Cin, int Cout, int Vout) {
    extern __shared__ float sdata[];
    int bid = blockIdx.x;
    int n = bid / Cout;
    int oc = bid - n * Cout;
    float acc = 0.0f;
    for (int ic = threadIdx.x; ic < Cin; ic += blockDim.x) {
        acc += in_sum[n * Cin + ic] * w_sum[ic * Cout + oc];
    }
    sdata[threadIdx.x] = acc;
    __syncthreads();
    for (int stride = blockDim.x >> 1; stride > 0; stride >>= 1) {
        if (threadIdx.x < stride) sdata[threadIdx.x] += sdata[threadIdx.x + stride];
        __syncthreads();
    }
    if (threadIdx.x == 0) conv_sum[n * Cout + oc] = sdata[0] + bias[oc] * (float)Vout;
}

__global__ void sumsq_kernel(const float* __restrict__ x,
                             const float* __restrict__ w,
                             const float* __restrict__ bias,
                             float* __restrict__ sumsq,
                             int N, int Cin, int Cout,
                             int D, int H, int Wd,
                             int Kd, int Kh, int Kw,
                             int OD, int OH, int OW,
                             float scale, int blocks_per_oc) {
    int global_bid = blockIdx.x;
    int oc = global_bid / blocks_per_oc;
    int local_bid = global_bid - oc * blocks_per_oc;
    int M = N * OD * OH * OW;
    float acc2 = 0.0f;
    int stride = blockDim.x * blocks_per_oc;
    for (int m = local_bid * blockDim.x + threadIdx.x; m < M; m += stride) {
        int t = m;
        int ow = t % OW; t /= OW;
        int oh = t % OH; t /= OH;
        int od = t % OD; t /= OD;
        int n = t;
        float val = bias[oc];
        int kd0 = max(0, od - (D - 1));
        int kd1 = min(Kd - 1, od);
        int kh0 = max(0, oh - (H - 1));
        int kh1 = min(Kh - 1, oh);
        int kw0 = max(0, ow - (Wd - 1));
        int kw1 = min(Kw - 1, ow);
        for (int ic = 0; ic < Cin; ++ic) {
            for (int kd = kd0; kd <= kd1; ++kd) {
                int id = od - kd;
                for (int kh = kh0; kh <= kh1; ++kh) {
                    int ih = oh - kh;
                    const float* xb = x + (((n * Cin + ic) * D + id) * H + ih) * Wd;
                    const float* wb = w + (((ic * Cout + oc) * Kd + kd) * Kh + kh) * Kw;
                    for (int kw = kw0; kw <= kw1; ++kw) {
                        val += xb[ow - kw] * wb[kw];
                    }
                }
            }
        }
        float sv = val * scale;
        acc2 += sv * sv;
    }
    extern __shared__ float sdata[];
    sdata[threadIdx.x] = acc2;
    __syncthreads();
    for (int s = blockDim.x >> 1; s > 0; s >>= 1) {
        if (threadIdx.x < s) sdata[threadIdx.x] += sdata[threadIdx.x + s];
        __syncthreads();
    }
    if (threadIdx.x == 0) atomicAdd(sumsq + oc, sdata[0]);
}

__global__ void train_out_kernel(const float* __restrict__ conv_sum,
                                 const float* __restrict__ sumsq,
                                 const float* __restrict__ gamma,
                                 const float* __restrict__ beta,
                                 float* __restrict__ out,
                                 int N, int Cout, int Vout,
                                 float scale, float eps) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    int total = N * Cout;
    if (idx >= total) return;
    int n = idx / Cout;
    int oc = idx - n * Cout;
    float tsum = 0.0f;
    for (int i = 0; i < N; ++i) tsum += conv_sum[i * Cout + oc];
    float invM = 1.0f / (float)(N * Vout);
    float mean = tsum * scale * invM;
    float var = sumsq[oc] * invM - mean * mean;
    if (var < 0.0f) var = 0.0f;
    float avg = conv_sum[n * Cout + oc] * scale / (float)Vout;
    out[idx] = (avg - mean) * rsqrtf(var + eps) * gamma[oc] + beta[oc];
}

torch::Tensor fused_forward(torch::Tensor x,
                            torch::Tensor weight,
                            torch::Tensor bias,
                            torch::Tensor bn_weight,
                            torch::Tensor bn_bias,
                            torch::Tensor running_mean,
                            torch::Tensor running_var,
                            double scale_d,
                            double eps_d,
                            bool training) {
    TORCH_CHECK(x.is_cuda(), "x must be CUDA");
    TORCH_CHECK(x.scalar_type() == at::kFloat, "only float32 supported");
    int N = (int)x.size(0);
    int Cin = (int)x.size(1);
    int D = (int)x.size(2);
    int H = (int)x.size(3);
    int Wd = (int)x.size(4);
    int Cout = (int)weight.size(1);
    int Kd = (int)weight.size(2);
    int Kh = (int)weight.size(3);
    int Kw = (int)weight.size(4);
    int OD = D + Kd - 1;
    int OH = H + Kh - 1;
    int OW = Wd + Kw - 1;
    int Vout = OD * OH * OW;
    auto opts = x.options();
    auto in_sum = torch::empty({N, Cin}, opts);
    auto w_sum = torch::empty({Cin, Cout}, opts);
    auto out = torch::empty({N, Cout, 1, 1, 1}, opts);
    cudaStream_t stream = at::cuda::getCurrentCUDAStream();
    const int T = 256;
    input_sum_kernel<<<N * Cin, T, T * sizeof(float), stream>>>(x.data_ptr<float>(), in_sum.data_ptr<float>(), N, Cin, D, H, Wd);
    weight_sum_kernel<<<Cin * Cout, T, T * sizeof(float), stream>>>(weight.data_ptr<float>(), w_sum.data_ptr<float>(), Cin, Cout, Kd, Kh, Kw);
    float scale = (float)scale_d;
    float eps = (float)eps_d;
    if (!training) {
        eval_out_kernel<<<N * Cout, T, T * sizeof(float), stream>>>(
            in_sum.data_ptr<float>(), w_sum.data_ptr<float>(), bias.data_ptr<float>(),
            bn_weight.data_ptr<float>(), bn_bias.data_ptr<float>(), running_mean.data_ptr<float>(), running_var.data_ptr<float>(),
            out.data_ptr<float>(), N, Cin, Cout, Vout, scale, eps);
    } else {
        auto conv_sum = torch::empty({N, Cout}, opts);
        conv_sum_kernel<<<N * Cout, T, T * sizeof(float), stream>>>(
            in_sum.data_ptr<float>(), w_sum.data_ptr<float>(), bias.data_ptr<float>(), conv_sum.data_ptr<float>(), N, Cin, Cout, Vout);
        auto sumsq = torch::zeros({Cout}, opts);
        int blocks_per_oc = 256;
        sumsq_kernel<<<Cout * blocks_per_oc, 128, 128 * sizeof(float), stream>>>(
            x.data_ptr<float>(), weight.data_ptr<float>(), bias.data_ptr<float>(), sumsq.data_ptr<float>(),
            N, Cin, Cout, D, H, Wd, Kd, Kh, Kw, OD, OH, OW, scale, blocks_per_oc);
        int total = N * Cout;
        train_out_kernel<<<(total + 255) / 256, 256, 0, stream>>>(
            conv_sum.data_ptr<float>(), sumsq.data_ptr<float>(), bn_weight.data_ptr<float>(), bn_bias.data_ptr<float>(),
            out.data_ptr<float>(), N, Cout, Vout, scale, eps);
    }
    return out;
}
"""

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

class ModelNew(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, scale_factor, eps=1e-5, momentum=0.1):
        super(ModelNew, self).__init__()
        self.conv_transpose = nn.ConvTranspose3d(in_channels, out_channels, kernel_size)
        self.scale_factor = scale_factor
        self.batch_norm = nn.BatchNorm3d(out_channels, eps=eps, momentum=momentum)
        self.global_avg_pool = nn.AdaptiveAvgPool3d((1, 1, 1))

    def forward(self, x):
        # The benchmark input and parameters are contiguous CUDA fp32 tensors.  Keep a safe
        # contiguous view for the custom address arithmetic.
        if not x.is_contiguous():
            x = x.contiguous()
        return _ext.fused_forward(
            x,
            self.conv_transpose.weight,
            self.conv_transpose.bias,
            self.batch_norm.weight,
            self.batch_norm.bias,
            self.batch_norm.running_mean,
            self.batch_norm.running_var,
            float(self.scale_factor),
            float(self.batch_norm.eps),
            bool(self.training),
        )