gpt-5.5-priority · original · l123_single_gpt
correct 11_ConvTranspose2d_BatchNorm_Tanh_MaxPool_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
2.67 μs
ref runtime
2.70 μs
speedup
1.01x
started
2026-05-05T00:40:52Z
timingkernelbench.timing
kernel runtime
2.67 μs
ref runtime
2.70 μs
speedup
1.01x
kernel mean / median / std
2.67 / — / 0.02 μs
ref mean / median / std
2.70 / — / 0.61 μs
numerical precisioneval_kernel_against_ref
max abs error
3.81e-06
mean abs error
2.00e-07
max rel error
7.80
mean rel error
2.34e-06
gpu memoryextended_metrics.measure_memory
kernel peak
882.57 MB
ref peak
882.57 MB
kernel / ref
1.00x
kernel launches / fusiontorch.profiler
kernel launches
13
ref launches
32
fusion ratio (ref / kernel)
2.46x
total cuda time
5633 μs
energyNVML (pynvml)
kernel mJ / run
921.86
ref mJ / run
3533.88
ratio (ref / kernel)
3.83x
avg power
345.11 W
ref avg power
1351.30 W
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
1.000
dram util %
100.0%
compute util %
84.1%
bottleneck
memory-bound
dominant pipe
tensor-core
arithmetic intensity
—
ridge point
295.22
roofline / occupancynsight_hardware_counters
dram bandwidth
13888.42 GB/s
dram util %
100.0%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
573.9%
bottleneck
memory-bound
dominant pipe %
84.1%
arithmetic intensity
—
ridge point
295.22
L1 hit %
—
L2 hit %
507.8%
ld sectors / req
46.17
st sectors / req
—
regs / thread
414
smem / block
254592 B
block size
2305
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
13161.4%
short_scoreboard
737.8%
mio_throttle
294.6%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn

class Model(nn.Module):
    """
    Model that performs a transposed convolution, batch normalization, tanh activation, max pooling, and group normalization.
    """
    def __init__(self, in_channels, out_channels, kernel_size, stride, padding, groups, num_groups):
        super(Model, self).__init__()
        self.conv_transpose = nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride=stride, padding=padding)
        self.batch_norm = nn.BatchNorm2d(out_channels)
        self.tanh = nn.Tanh()
        self.max_pool = nn.MaxPool2d(kernel_size=2, stride=2)
        self.group_norm = nn.GroupNorm(num_groups=num_groups, num_channels=out_channels)

    def forward(self, x):
        x = self.conv_transpose(x)
        x = self.batch_norm(x)
        x = self.tanh(x)
        x = self.max_pool(x)
        x = self.group_norm(x)
        return x

batch_size = 512
in_channels  = 64  
out_channels = 128  
height = width = 2048  
kernel_size  = 5
stride       = 1  
padding      = 1
groups       = 8
num_groups   = 8
height, width = 32, 32

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

def get_init_inputs():
    return [in_channels, out_channels, kernel_size, stride, padding, groups, num_groups]
turn 0 · FINAL · 1 tool call(s) · 66.6s turn · 66.6s LLM · 13,786 chars (click to expand)
turn 066.6s turn · 66.6s LLM · 0.0s tools · FINAL
reasoning
**Considering implementation options** I'm thinking about implementing an eval path and possibly extending the training call with batch statistics. I wonder if detection will not have a fallback in this scenario. It's important to think through these details carefully to ensure everything functions smoothly. Let's explore the implications and the best approach to take with this plan. I want to make sure everything aligns well and produces the desired outcomes! **Assessing value reduction** I’m considering whether reducing a block of float values will suffice for summing around 300k values. There’s also the matter of tolerance after nonlinear adjustments, which might need attention. I guess I should compare this process with batch normalization (BN) training to see how it stacks up. That could provide valuable insights on efficiency and accuracy. I'll have to double-check my assumptions to ensure everything aligns well!
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.cpp_extension import load_inline\n\n_cpp_src = r\"\"\"\n#include <torch/extension.h>\n\ntorch::Tensor fused_post(torch::Tensor conv,\n torch::Tensor bn_weight,\n torch::Tensor bn_bias,\n torch::Tensor running_mean,\n torch::Tensor running_var,\n torch::Tensor gn_weight,\n torch::Tensor gn_bias,\n bool bn_training,\n double bn_eps,\n int64_t num_groups,\n double gn_eps);\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 <vector>\n\n__inline__ __device__ float warp_reduce_sum(float v) {\n for (int offset = 16; offset > 0; offset >>= 1)\n v += __shfl_down_sync(0xffffffff, v, offset);\n return v;\n}\n\n__inline__ __device__ float block_reduce_sum(float v) {\n static __shared__ float shared[32];\n int lane = threadIdx.x & 31;\n int wid = threadIdx.x >> 5;\n v = warp_reduce_sum(v);\n if (lane == 0) shared[wid] = v;\n __syncthreads();\n v = (threadIdx.x < (blockDim.x >> 5)) ? shared[lane] : 0.0f;\n if (wid == 0) v = warp_reduce_sum(v);\n return v;\n}\n\n__global__ void bn_stats_kernel(const float* __restrict__ x, float* __restrict__ mean, float* __restrict__ invstd,\n int N, int C, int H, int W, float eps) {\n int c = blockIdx.x;\n int HW = H * W;\n int M = N * HW;\n float sum = 0.0f;\n float sum2 = 0.0f;\n for (int i = threadIdx.x; i < M; i += blockDim.x) {\n int n = i / HW;\n int hw = i - n * HW;\n float v = x[((long long)n * C + c) * HW + hw];\n sum += v;\n sum2 = fmaf(v, v, sum2);\n }\n sum = block_reduce_sum(sum);\n sum2 = block_reduce_sum(sum2);\n if (threadIdx.x == 0) {\n float m = sum / (float)M;\n float var = sum2 / (float)M - m * m;\n var = var < 0.0f ? 0.0f : var;\n mean[c] = m;\n invstd[c] = rsqrtf(var + eps);\n }\n}\n\n__global__ void pool_bn_tanh_kernel(const float* __restrict__ x,\n const float* __restrict__ bn_weight,\n const float* __restrict__ bn_bias,\n const float* __restrict__ mean,\n const float* __restrict__ invstd,\n float* __restrict__ pooled,\n int total, int C, int H, int W, int PH, int PW) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int stride = blockDim.x * gridDim.x;\n int PHW = PH * PW;\n int HW = H * W;\n for (; idx < total; idx += stride) {\n int t = idx;\n int pw = t % PW; t /= PW;\n int ph = t % PH; t /= PH;\n int c = t % C;\n int n = t / C;\n int h0 = ph * 2;\n int w0 = pw * 2;\n float a = bn_weight[c] * invstd[c];\n float b = bn_bias[c] - mean[c] * a;\n long long base = ((long long)n * C + c) * HW + h0 * W + w0;\n float v0 = tanhf(fmaf(x[base], a, b));\n float v1 = tanhf(fmaf(x[base + 1], a, b));\n float v2 = tanhf(fmaf(x[base + W], a, b));\n float v3 = tanhf(fmaf(x[base + W + 1], a, b));\n float m = fmaxf(fmaxf(v0, v1), fmaxf(v2, v3));\n pooled[idx] = m;\n }\n}\n\n__global__ void group_stats_kernel(const float* __restrict__ pooled, float* __restrict__ gmean, float* __restrict__ grstd,\n int N, int C, int PH, int PW, int G, float eps) {\n int ng = blockIdx.x;\n int n = ng / G;\n int g = ng - n * G;\n int cpg = C / G;\n int PHW = PH * PW;\n int M = cpg * PHW;\n int c0 = g * cpg;\n float sum = 0.0f;\n float sum2 = 0.0f;\n long long nbase = (long long)n * C * PHW;\n for (int i = threadIdx.x; i < M; i += blockDim.x) {\n int lc = i / PHW;\n int hw = i - lc * PHW;\n float v = pooled[nbase + (long long)(c0 + lc) * PHW + hw];\n sum += v;\n sum2 = fmaf(v, v, sum2);\n }\n sum = block_reduce_sum(sum);\n sum2 = block_reduce_sum(sum2);\n if (threadIdx.x == 0) {\n float m = sum / (float)M;\n float var = sum2 / (float)M - m * m;\n var = var < 0.0f ? 0.0f : var;\n gmean[ng] = m;\n grstd[ng] = rsqrtf(var + eps);\n }\n}\n\n__global__ void group_apply_kernel(const float* __restrict__ pooled,\n const float* __restrict__ gmean,\n const float* __restrict__ grstd,\n const float* __restrict__ gn_weight,\n const float* __restrict__ gn_bias,\n float* __restrict__ out,\n int total, int C, int PH, int PW, int G) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int stride = blockDim.x * gridDim.x;\n int PHW = PH * PW;\n int cpg = C / G;\n for (; idx < total; idx += stride) {\n int t = idx;\n int hw = t % PHW; (void)hw;\n t /= PHW;\n int c = t % C;\n int n = t / C;\n int g = c / cpg;\n int ng = n * G + g;\n float v = (pooled[idx] - gmean[ng]) * grstd[ng];\n out[idx] = fmaf(v, gn_weight[c], gn_bias[c]);\n }\n}\n\ntorch::Tensor fused_post(torch::Tensor conv,\n torch::Tensor bn_weight,\n torch::Tensor bn_bias,\n torch::Tensor running_mean,\n torch::Tensor running_var,\n torch::Tensor gn_weight,\n torch::Tensor gn_bias,\n bool bn_training,\n double bn_eps_d,\n int64_t num_groups,\n double gn_eps_d) {\n TORCH_CHECK(conv.is_cuda(), \"conv must be CUDA\");\n TORCH_CHECK(conv.scalar_type() == at::kFloat, \"only float32 supported\");\n conv = conv.contiguous();\n int N = (int)conv.size(0);\n int C = (int)conv.size(1);\n int H = (int)conv.size(2);\n int W = (int)conv.size(3);\n int PH = (H - 2) / 2 + 1;\n int PW = (W - 2) / 2 + 1;\n float bn_eps = (float)bn_eps_d;\n float gn_eps = (float)gn_eps_d;\n auto opts = conv.options();\n auto mean = torch::empty({C}, opts);\n auto invstd = torch::empty({C}, opts);\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n if (bn_training) {\n bn_stats_kernel<<<C, 256, 0, stream>>>(conv.data_ptr<float>(), mean.data_ptr<float>(), invstd.data_ptr<float>(), N, C, H, W, bn_eps);\n } else {\n // invstd from running variance; mean is running mean. Small one-block-per-channel kernel via bn_stats_kernel not suitable,\n // so use a simple lambda-like kernel implemented below by reusing group_apply? no: launch dedicated local kernel unavailable.\n // Fill using a tiny generic transform kernel defined inline as a separate launch below.\n int threads = 128;\n int blocks = (C + threads - 1) / threads;\n // use cuda kernel declared in this translation unit\n // placeholder replaced by actual kernel call\n \n // Direct copy/compute with at operations would add overhead; kernel below.\n extern void launch_bn_eval_params(const float*, const float*, float*, float*, int, float, cudaStream_t);\n launch_bn_eval_params(running_mean.data_ptr<float>(), running_var.data_ptr<float>(), mean.data_ptr<float>(), invstd.data_ptr<float>(), C, bn_eps, stream);\n }\n auto pooled = torch::empty({N, C, PH, PW}, opts);\n int total = N * C * PH * PW;\n int threads = 256;\n int blocks = (total + threads - 1) / threads;\n if (blocks > 65535) blocks = 65535;\n pool_bn_tanh_kernel<<<blocks, threads, 0, stream>>>(conv.data_ptr<float>(), bn_weight.data_ptr<float>(), bn_bias.data_ptr<float>(), mean.data_ptr<float>(), invstd.data_ptr<float>(), pooled.data_ptr<float>(), total, C, H, W, PH, PW);\n int G = (int)num_groups;\n auto gmean = torch::empty({N, G}, opts);\n auto grstd = torch::empty({N, G}, opts);\n group_stats_kernel<<<N * G, 256, 0, stream>>>(pooled.data_ptr<float>(), gmean.data_ptr<float>(), grstd.data_ptr<float>(), N, C, PH, PW, G, gn_eps);\n auto out = torch::empty({N, C, PH, PW}, opts);\n group_apply_kernel<<<blocks, threads, 0, stream>>>(pooled.data_ptr<float>(), gmean.data_ptr<float>(), grstd.data_ptr<float>(), gn_weight.data_ptr<float>(), gn_bias.data_ptr<float>(), out.data_ptr<float>(), total, C, PH, PW, G);\n return out;\n}\n\n__global__ void bn_eval_params_kernel(const float* __restrict__ running_mean,\n const float* __restrict__ running_var,\n float* __restrict__ mean,\n float* __restrict__ invstd,\n int C, float eps) {\n int c = blockIdx.x * blockDim.x + threadIdx.x;\n if (c < C) {\n mean[c] = running_mean[c];\n invstd[c] = rsqrtf(running_var[c] + eps);\n }\n}\n\nvoid launch_bn_eval_params(const float* rm, const float* rv, float* mean, float* invstd, int C, float eps, cudaStream_t stream) {\n int threads = 128;\n int blocks = (C + threads - 1) / threads;\n bn_eval_params_kernel<<<blocks, threads, 0, stream>>>(rm, rv, mean, invstd, C, eps);\n}\n\"\"\"\n\n_ext = load_inline(\n name=\"fused_deconv_bn_tanh_pool_gn_ext_v1\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"fused_post\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\"],\n with_cuda=True,\n verbose=False,\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, in_channels, out_channels, kernel_size, stride, padding, groups, num_groups):\n super(ModelNew, self).__init__()\n self.conv_transpose = nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride=stride, padding=padding)\n self.batch_norm = nn.BatchNorm2d(out_channels)\n self.tanh = nn.Tanh()\n self.max_pool = nn.MaxPool2d(kernel_size=2, stride=2)\n self.group_norm = nn.GroupNorm(num_groups=num_groups, num_channels=out_channels)\n self.num_groups = num_groups\n\n def forward(self, x):\n y = F.conv_transpose2d(x, self.conv_transpose.weight, self.conv_transpose.bias,\n stride=self.conv_transpose.stride, padding=self.conv_transpose.padding,\n output_padding=self.conv_transpose.output_padding, groups=self.conv_transpose.groups,\n dilation=self.conv_transpose.dilation)\n return _ext.fused_post(y,\n self.batch_norm.weight, self.batch_norm.bias,\n self.batch_norm.running_mean, self.batch_norm.running_var,\n self.group_norm.weight, self.group_norm.bias,\n bool(self.batch_norm.training), float(self.batch_norm.eps),\n int(self.group_norm.num_groups), float(self.group_norm.eps))\n" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 2.67 μs Runtime stats: mean=2.67μs median=?μs std=0.02μs Numerical precision: max_abs_err=3.81e-06 mean_abs_err=2.00e-07 max_rel_err=7.80e+00 Memory: 882.57 MB peak (ref: 882.57 MB, ratio: 1.0x) Kernel launches: 13 (ref: 32, fusion ratio: 2.4615) Energy: 921.86 mJ/run (ref: 3533.88 mJ, ratio: 3.8334x) SOL score: 1.0
final submitted kernel (264 lines, 11,135 chars) — full source, untruncated
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.cpp_extension import load_inline

_cpp_src = r"""
#include <torch/extension.h>

torch::Tensor fused_post(torch::Tensor conv,
                         torch::Tensor bn_weight,
                         torch::Tensor bn_bias,
                         torch::Tensor running_mean,
                         torch::Tensor running_var,
                         torch::Tensor gn_weight,
                         torch::Tensor gn_bias,
                         bool bn_training,
                         double bn_eps,
                         int64_t num_groups,
                         double gn_eps);
"""

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

__inline__ __device__ float warp_reduce_sum(float v) {
    for (int offset = 16; offset > 0; offset >>= 1)
        v += __shfl_down_sync(0xffffffff, v, offset);
    return v;
}

__inline__ __device__ float block_reduce_sum(float v) {
    static __shared__ float shared[32];
    int lane = threadIdx.x & 31;
    int wid = threadIdx.x >> 5;
    v = warp_reduce_sum(v);
    if (lane == 0) shared[wid] = v;
    __syncthreads();
    v = (threadIdx.x < (blockDim.x >> 5)) ? shared[lane] : 0.0f;
    if (wid == 0) v = warp_reduce_sum(v);
    return v;
}

__global__ void bn_stats_kernel(const float* __restrict__ x, float* __restrict__ mean, float* __restrict__ invstd,
                                int N, int C, int H, int W, float eps) {
    int c = blockIdx.x;
    int HW = H * W;
    int M = N * HW;
    float sum = 0.0f;
    float sum2 = 0.0f;
    for (int i = threadIdx.x; i < M; i += blockDim.x) {
        int n = i / HW;
        int hw = i - n * HW;
        float v = x[((long long)n * C + c) * HW + hw];
        sum += v;
        sum2 = fmaf(v, v, sum2);
    }
    sum = block_reduce_sum(sum);
    sum2 = block_reduce_sum(sum2);
    if (threadIdx.x == 0) {
        float m = sum / (float)M;
        float var = sum2 / (float)M - m * m;
        var = var < 0.0f ? 0.0f : var;
        mean[c] = m;
        invstd[c] = rsqrtf(var + eps);
    }
}

__global__ void pool_bn_tanh_kernel(const float* __restrict__ x,
                                    const float* __restrict__ bn_weight,
                                    const float* __restrict__ bn_bias,
                                    const float* __restrict__ mean,
                                    const float* __restrict__ invstd,
                                    float* __restrict__ pooled,
                                    int total, int C, int H, int W, int PH, int PW) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    int stride = blockDim.x * gridDim.x;
    int PHW = PH * PW;
    int HW = H * W;
    for (; idx < total; idx += stride) {
        int t = idx;
        int pw = t % PW; t /= PW;
        int ph = t % PH; t /= PH;
        int c = t % C;
        int n = t / C;
        int h0 = ph * 2;
        int w0 = pw * 2;
        float a = bn_weight[c] * invstd[c];
        float b = bn_bias[c] - mean[c] * a;
        long long base = ((long long)n * C + c) * HW + h0 * W + w0;
        float v0 = tanhf(fmaf(x[base], a, b));
        float v1 = tanhf(fmaf(x[base + 1], a, b));
        float v2 = tanhf(fmaf(x[base + W], a, b));
        float v3 = tanhf(fmaf(x[base + W + 1], a, b));
        float m = fmaxf(fmaxf(v0, v1), fmaxf(v2, v3));
        pooled[idx] = m;
    }
}

__global__ void group_stats_kernel(const float* __restrict__ pooled, float* __restrict__ gmean, float* __restrict__ grstd,
                                   int N, int C, int PH, int PW, int G, float eps) {
    int ng = blockIdx.x;
    int n = ng / G;
    int g = ng - n * G;
    int cpg = C / G;
    int PHW = PH * PW;
    int M = cpg * PHW;
    int c0 = g * cpg;
    float sum = 0.0f;
    float sum2 = 0.0f;
    long long nbase = (long long)n * C * PHW;
    for (int i = threadIdx.x; i < M; i += blockDim.x) {
        int lc = i / PHW;
        int hw = i - lc * PHW;
        float v = pooled[nbase + (long long)(c0 + lc) * PHW + hw];
        sum += v;
        sum2 = fmaf(v, v, sum2);
    }
    sum = block_reduce_sum(sum);
    sum2 = block_reduce_sum(sum2);
    if (threadIdx.x == 0) {
        float m = sum / (float)M;
        float var = sum2 / (float)M - m * m;
        var = var < 0.0f ? 0.0f : var;
        gmean[ng] = m;
        grstd[ng] = rsqrtf(var + eps);
    }
}

__global__ void group_apply_kernel(const float* __restrict__ pooled,
                                   const float* __restrict__ gmean,
                                   const float* __restrict__ grstd,
                                   const float* __restrict__ gn_weight,
                                   const float* __restrict__ gn_bias,
                                   float* __restrict__ out,
                                   int total, int C, int PH, int PW, int G) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    int stride = blockDim.x * gridDim.x;
    int PHW = PH * PW;
    int cpg = C / G;
    for (; idx < total; idx += stride) {
        int t = idx;
        int hw = t % PHW; (void)hw;
        t /= PHW;
        int c = t % C;
        int n = t / C;
        int g = c / cpg;
        int ng = n * G + g;
        float v = (pooled[idx] - gmean[ng]) * grstd[ng];
        out[idx] = fmaf(v, gn_weight[c], gn_bias[c]);
    }
}

torch::Tensor fused_post(torch::Tensor conv,
                         torch::Tensor bn_weight,
                         torch::Tensor bn_bias,
                         torch::Tensor running_mean,
                         torch::Tensor running_var,
                         torch::Tensor gn_weight,
                         torch::Tensor gn_bias,
                         bool bn_training,
                         double bn_eps_d,
                         int64_t num_groups,
                         double gn_eps_d) {
    TORCH_CHECK(conv.is_cuda(), "conv must be CUDA");
    TORCH_CHECK(conv.scalar_type() == at::kFloat, "only float32 supported");
    conv = conv.contiguous();
    int N = (int)conv.size(0);
    int C = (int)conv.size(1);
    int H = (int)conv.size(2);
    int W = (int)conv.size(3);
    int PH = (H - 2) / 2 + 1;
    int PW = (W - 2) / 2 + 1;
    float bn_eps = (float)bn_eps_d;
    float gn_eps = (float)gn_eps_d;
    auto opts = conv.options();
    auto mean = torch::empty({C}, opts);
    auto invstd = torch::empty({C}, opts);
    cudaStream_t stream = at::cuda::getCurrentCUDAStream();
    if (bn_training) {
        bn_stats_kernel<<<C, 256, 0, stream>>>(conv.data_ptr<float>(), mean.data_ptr<float>(), invstd.data_ptr<float>(), N, C, H, W, bn_eps);
    } else {
        // invstd from running variance; mean is running mean. Small one-block-per-channel kernel via bn_stats_kernel not suitable,
        // so use a simple lambda-like kernel implemented below by reusing group_apply? no: launch dedicated local kernel unavailable.
        // Fill using a tiny generic transform kernel defined inline as a separate launch below.
        int threads = 128;
        int blocks = (C + threads - 1) / threads;
        // use cuda kernel declared in this translation unit
        // placeholder replaced by actual kernel call
        
        // Direct copy/compute with at operations would add overhead; kernel below.
        extern void launch_bn_eval_params(const float*, const float*, float*, float*, int, float, cudaStream_t);
        launch_bn_eval_params(running_mean.data_ptr<float>(), running_var.data_ptr<float>(), mean.data_ptr<float>(), invstd.data_ptr<float>(), C, bn_eps, stream);
    }
    auto pooled = torch::empty({N, C, PH, PW}, opts);
    int total = N * C * PH * PW;
    int threads = 256;
    int blocks = (total + threads - 1) / threads;
    if (blocks > 65535) blocks = 65535;
    pool_bn_tanh_kernel<<<blocks, threads, 0, stream>>>(conv.data_ptr<float>(), bn_weight.data_ptr<float>(), bn_bias.data_ptr<float>(), mean.data_ptr<float>(), invstd.data_ptr<float>(), pooled.data_ptr<float>(), total, C, H, W, PH, PW);
    int G = (int)num_groups;
    auto gmean = torch::empty({N, G}, opts);
    auto grstd = torch::empty({N, G}, opts);
    group_stats_kernel<<<N * G, 256, 0, stream>>>(pooled.data_ptr<float>(), gmean.data_ptr<float>(), grstd.data_ptr<float>(), N, C, PH, PW, G, gn_eps);
    auto out = torch::empty({N, C, PH, PW}, opts);
    group_apply_kernel<<<blocks, threads, 0, stream>>>(pooled.data_ptr<float>(), gmean.data_ptr<float>(), grstd.data_ptr<float>(), gn_weight.data_ptr<float>(), gn_bias.data_ptr<float>(), out.data_ptr<float>(), total, C, PH, PW, G);
    return out;
}

__global__ void bn_eval_params_kernel(const float* __restrict__ running_mean,
                                      const float* __restrict__ running_var,
                                      float* __restrict__ mean,
                                      float* __restrict__ invstd,
                                      int C, float eps) {
    int c = blockIdx.x * blockDim.x + threadIdx.x;
    if (c < C) {
        mean[c] = running_mean[c];
        invstd[c] = rsqrtf(running_var[c] + eps);
    }
}

void launch_bn_eval_params(const float* rm, const float* rv, float* mean, float* invstd, int C, float eps, cudaStream_t stream) {
    int threads = 128;
    int blocks = (C + threads - 1) / threads;
    bn_eval_params_kernel<<<blocks, threads, 0, stream>>>(rm, rv, mean, invstd, C, eps);
}
"""

_ext = load_inline(
    name="fused_deconv_bn_tanh_pool_gn_ext_v1",
    cpp_sources=_cpp_src,
    cuda_sources=_cuda_src,
    functions=["fused_post"],
    extra_cflags=["-O3"],
    extra_cuda_cflags=["-O3"],
    with_cuda=True,
    verbose=False,
)

class ModelNew(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride, padding, groups, num_groups):
        super(ModelNew, self).__init__()
        self.conv_transpose = nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride=stride, padding=padding)
        self.batch_norm = nn.BatchNorm2d(out_channels)
        self.tanh = nn.Tanh()
        self.max_pool = nn.MaxPool2d(kernel_size=2, stride=2)
        self.group_norm = nn.GroupNorm(num_groups=num_groups, num_channels=out_channels)
        self.num_groups = num_groups

    def forward(self, x):
        y = F.conv_transpose2d(x, self.conv_transpose.weight, self.conv_transpose.bias,
                               stride=self.conv_transpose.stride, padding=self.conv_transpose.padding,
                               output_padding=self.conv_transpose.output_padding, groups=self.conv_transpose.groups,
                               dilation=self.conv_transpose.dilation)
        return _ext.fused_post(y,
                               self.batch_norm.weight, self.batch_norm.bias,
                               self.batch_norm.running_mean, self.batch_norm.running_var,
                               self.group_norm.weight, self.group_norm.bias,
                               bool(self.batch_norm.training), float(self.batch_norm.eps),
                               int(self.group_norm.num_groups), float(self.group_norm.eps))