gpt-5.5-priority · original · l123_single_gpt
correct 15_DenseNet121.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
7.29 μs
ref runtime
7.48 μs
speedup
1.03x
started
2026-05-05T00:54:07Z
timingkernelbench.timing
kernel runtime
7.29 μs
ref runtime
7.48 μs
speedup
1.03x
kernel mean / median / std
7.29 / — / 0.46 μs
ref mean / median / std
7.48 / — / 0.39 μs
numerical precisioneval_kernel_against_ref
max abs error
0.00
mean abs error
0.00
max rel error
0.00
mean rel error
0.00
gpu memoryextended_metrics.measure_memory
kernel peak
214.38 MB
ref peak
229.69 MB
kernel / ref
0.93x
kernel launches / fusiontorch.profiler
kernel launches
1196
ref launches
1196
fusion ratio (ref / kernel)
1.00x
total cuda time
38066 μs
energyNVML (pynvml)
kernel mJ / run
1681.54
ref mJ / run
720.44
ratio (ref / kernel)
0.43x
avg power
253.69 W
ref avg power
113.52 W
SOL (speed-of-light)heuristic_from_speedup
SOL score (0–1)
0.103
dram util %
—
compute util %
—
bottleneck
unknown
dominant pipe
—
arithmetic intensity
—
ridge point
—
roofline / occupancyheuristic
dram bandwidth
—
dram util %
—
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
—
bottleneck
—
dominant pipe %
—
arithmetic intensity
—
ridge point
—
L1 hit %
—
L2 hit %
—
ld sectors / req
—
st sectors / req
—
regs / thread
—
smem / block
—
block size
—
peak bw
—
peak fp32 tflops
—
peak fp16 tflops
—
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn
import torch.nn.functional as F

class DenseBlock(nn.Module):
    def __init__(self, num_layers: int, num_input_features: int, growth_rate: int):
        """
        :param num_layers: The number of layers in the dense block
        :param num_input_features: The number of input feature maps
        :param growth_rate: The growth rate for the dense block (new features added per layer)
        """
        super(DenseBlock, self).__init__()
        layers = []
        for i in range(num_layers):
            layers.append(self._make_layer(num_input_features + i * growth_rate, growth_rate))
        self.layers = nn.ModuleList(layers)

    def _make_layer(self, in_features: int, growth_rate: int):
        """
        Creates a single layer with BatchNorm, ReLU, Conv2D, and Dropout.
        """
        return nn.Sequential(
            nn.BatchNorm2d(in_features),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_features, growth_rate, kernel_size=3, padding=1, bias=False),
            nn.Dropout(0.0)
        )

    def forward(self, x):
        """
        :param x: Input tensor of shape (batch_size, num_input_features, height, width)
        :return: Concatenated output tensor with shape (batch_size, num_output_features, height, width)
        """
        features = [x]
        for layer in self.layers:
            new_feature = layer(x)
            features.append(new_feature)
            x = torch.cat(features, 1)  # Concatenate along channel axis
        return x

class TransitionLayer(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(TransitionLayer, 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)

class Model(nn.Module):
    def __init__(self, growth_rate: int = 32, num_classes: int = 1000):
        """
        :param growth_rate: The growth rate of the DenseNet (new features added per layer)
        :param num_classes: The number of output classes for classification
        """
        super(Model, self).__init__()

        # Initial convolution and pooling
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        )

        # Each dense block is followed by a transition layer, except the last one
        num_features = 64
        block_layers = [6, 12, 24, 16]  # Corresponding layers in DenseNet121

        self.dense_blocks = nn.ModuleList()
        self.transition_layers = nn.ModuleList()

        for i, num_layers in enumerate(block_layers):
            block = DenseBlock(num_layers=num_layers, num_input_features=num_features, growth_rate=growth_rate)
            self.dense_blocks.append(block)
            num_features = num_features + num_layers * growth_rate

            if i != len(block_layers) - 1:
                transition = TransitionLayer(num_input_features=num_features, num_output_features=num_features // 2)
                self.transition_layers.append(transition)
                num_features = num_features // 2

        # Final batch norm and classifier
        self.final_bn = nn.BatchNorm2d(num_features)
        self.classifier = nn.Linear(num_features, num_classes)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """
        :param x: Input tensor of shape (batch_size, 3, height, width)
        :return: Output tensor of shape (batch_size, num_classes)
        """
        x = self.features(x)

        for i, block in enumerate(self.dense_blocks):
            x = block(x)
            if i != len(self.dense_blocks) - 1:
                x = self.transition_layers[i](x)

        x = self.final_bn(x)
        x = F.relu(x, inplace=True)
        x = F.adaptive_avg_pool2d(x, (1, 1)).view(x.size(0), -1)
        x = self.classifier(x)
        return x

# Testing the DenseNet121 model
batch_size = 10
num_classes = 10
height, width = 224, 224  # Standard input size for DenseNet

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

def get_init_inputs():
    return [32, num_classes]
turn 0 · FINAL · 1 tool call(s) · 56.9s turn · 56.9s LLM · 13,873 chars (click to expand)
turn 056.9s turn · 56.9s LLM · 0.0s tools · FINAL
reasoning
**Exploring DenseBlock implementation** I'm considering an alternative way to implement DenseBlock using torch.cat at each step. It seems that just custom kernels for Batch Normalization and ReLU might not be quite sufficient. The final average pool followed by a linear fused operation appears to offer speed, but a classifier with 1024x10 for a batch size of 10 doesn't seem adequate. I'll need to explore whether evaluation can accept a custom CUDA extension that calls cuDNN, but that's not straightforward. Implementing special convolutions directly for a batch of 10 with input 224 could be tricky, especially since DenseNet has many convolutional layers, making direct convolution likely slower than cuDNN. However, I should consider if optimizing for PyTorch's overhead from numerous small convolutions and concatenations could lead to better performance by creating custom direct or fused operations for 3x3 kernels. **Building class structure** I'm focusing on building a class with the same submodules- the state dictionary keys need to align with the reference. If I define `self.features` as sequential, then `dense_blocks` should be a ModuleList of DenseBlock modules with consistent layer names. That seems good. Now, I'm wondering if the dense block layers should include Dropout. If I set Dropout to 0, does it have no effect, and is it the same in both eval and training modes? **Evaluating BatchNorm settings** I'm considering BatchNorm training and its importance. It seems like I should reference the model's default training mode unless explicitly set to evaluate. I’m pondering how inputs are initialized randomly and how weights are copied. BatchNorm works differently in training mode since it uses batch statistics, while running stats update. I need to ensure that I manage the training mode correctly, especially when referencing benchmarks. It's a bit tricky to determine if the model should be in eval mode for inference. **Optimizing BatchNorm and custom layers** I’m working on BatchNorm (BN) over dimensions N, H, W, needing to calculate mean and variance per channel before pooling and linear layers. It seems manageable, though it might require a two-pass method. If using PyTorch's final_bn during training, I should consider custom ReLU layers. Using final_fused should only happen when the model isn’t training for efficiency. I also want to ensure my custom concatenation in autograd is efficient and clean. I'll use appropriate checks for CUDA float contiguous implementations too. **Calculating BatchNorm and FC layers** I'm thinking about how to calculate BatchNorm for each index over total elements, defined as C times H times W. For each index, I can find the channel using c = idx/HW. I then need to compute the batch normalization using the value, mean, inverse standard deviation, weight, and bias for the channel. After applying ReLU, I multiply by the fully connected weights and sum everything up. Finally, I'll divide by HW and add in the fully connected bias. It's a bit of a process, but I think it’ll work! **Considering potential issues** I'm looking at the name "densenet_opt_ext" and wondering if there might be name collisions. That could cause some problems, right? I think I'll avoid using `--use_fast_math` to prevent any compile-time failures, especially for accuracy. I'll also be sure to use `-lineinfo` and include extra CUDA flags like `['-O3']`. It seems I need to submit everything through the tool, so I’ll get on that!
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\n_cpp_src = r\"\"\"\n#include <torch/extension.h>\n\ntorch::Tensor cat2_cuda(torch::Tensor a, torch::Tensor b);\ntorch::Tensor final_eval_cuda(torch::Tensor x,\n torch::Tensor bn_weight,\n torch::Tensor bn_bias,\n torch::Tensor bn_mean,\n torch::Tensor bn_var,\n torch::Tensor fc_weight,\n torch::Tensor fc_bias,\n double 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 <math.h>\n\n__global__ void cat2_kernel(const float* __restrict__ a,\n const float* __restrict__ b,\n float* __restrict__ out,\n long long total,\n int C1, int C2, int HW) {\n long long t = (long long)blockIdx.x * blockDim.x + threadIdx.x;\n long long stride = (long long)blockDim.x * gridDim.x;\n int C = C1 + C2;\n for (; t < total; t += stride) {\n int inner = (int)(t % ((long long)C * HW));\n int c = inner / HW;\n int hw = inner - c * HW;\n long long n = t / ((long long)C * HW);\n if (c < C1) {\n out[t] = a[(n * (long long)C1 + c) * HW + hw];\n } else {\n out[t] = b[(n * (long long)C2 + (c - C1)) * HW + hw];\n }\n }\n}\n\ntorch::Tensor cat2_cuda(torch::Tensor a, torch::Tensor b) {\n const int N = (int)a.size(0);\n const int C1 = (int)a.size(1);\n const int H = (int)a.size(2);\n const int W = (int)a.size(3);\n const int C2 = (int)b.size(1);\n auto out = torch::empty({N, C1 + C2, H, W}, a.options());\n long long total = (long long)N * (C1 + C2) * H * W;\n const int threads = 256;\n int blocks = (int)((total + threads - 1) / threads);\n if (blocks > 4096) blocks = 4096;\n cat2_kernel<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>(\n a.data_ptr<float>(), b.data_ptr<float>(), out.data_ptr<float>(), total, C1, C2, H * W);\n return out;\n}\n\n__global__ void final_eval_kernel(const float* __restrict__ x,\n const float* __restrict__ bn_w,\n const float* __restrict__ bn_b,\n const float* __restrict__ bn_m,\n const float* __restrict__ bn_v,\n const float* __restrict__ fc_w,\n const float* __restrict__ fc_b,\n float* __restrict__ out,\n int C, int HW, int K, float eps) {\n // one block computes one (batch, class) output\n int nk = blockIdx.x;\n int n = nk / K;\n int k = nk - n * K;\n\n float acc = 0.0f;\n int total = C * HW;\n for (int idx = threadIdx.x; idx < total; idx += blockDim.x) {\n int c = idx / HW;\n float v = x[(n * (long long)C * HW) + idx];\n float y = (v - bn_m[c]) * rsqrtf(bn_v[c] + eps) * bn_w[c] + bn_b[c];\n y = y > 0.0f ? y : 0.0f;\n acc += y * fc_w[k * (long long)C + c];\n }\n\n __shared__ float smem[256];\n smem[threadIdx.x] = acc;\n __syncthreads();\n for (int s = blockDim.x >> 1; s > 0; s >>= 1) {\n if (threadIdx.x < s) smem[threadIdx.x] += smem[threadIdx.x + s];\n __syncthreads();\n }\n if (threadIdx.x == 0) {\n out[n * K + k] = smem[0] / (float)HW + fc_b[k];\n }\n}\n\ntorch::Tensor final_eval_cuda(torch::Tensor x,\n torch::Tensor bn_weight,\n torch::Tensor bn_bias,\n torch::Tensor bn_mean,\n torch::Tensor bn_var,\n torch::Tensor fc_weight,\n torch::Tensor fc_bias,\n double eps) {\n const int N = (int)x.size(0);\n const int C = (int)x.size(1);\n const int HW = (int)(x.size(2) * x.size(3));\n const int K = (int)fc_weight.size(0);\n auto out = torch::empty({N, K}, x.options());\n final_eval_kernel<<<N * K, 256, 0, at::cuda::getCurrentCUDAStream()>>>(\n x.data_ptr<float>(), bn_weight.data_ptr<float>(), bn_bias.data_ptr<float>(),\n bn_mean.data_ptr<float>(), bn_var.data_ptr<float>(), fc_weight.data_ptr<float>(),\n fc_bias.data_ptr<float>(), out.data_ptr<float>(), C, HW, K, (float)eps);\n return out;\n}\n\"\"\"\n\n_ext = load_inline(\n name=\"densenet121_cat_final_ext\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"cat2_cuda\", \"final_eval_cuda\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\"],\n with_cuda=True,\n verbose=False,\n)\n\n\nclass _DenseBlock(nn.Module):\n def __init__(self, num_layers: int, num_input_features: int, growth_rate: int):\n super(_DenseBlock, self).__init__()\n layers = []\n for i in range(num_layers):\n layers.append(self._make_layer(num_input_features + i * growth_rate, growth_rate))\n self.layers = nn.ModuleList(layers)\n\n def _make_layer(self, in_features: int, growth_rate: int):\n return nn.Sequential(\n nn.BatchNorm2d(in_features),\n nn.ReLU(inplace=True),\n nn.Conv2d(in_features, growth_rate, kernel_size=3, padding=1, bias=False),\n nn.Dropout(0.0),\n )\n\n def forward(self, x):\n for layer in self.layers:\n new_feature = layer(x)\n if x.is_cuda and x.dtype is torch.float32 and new_feature.is_contiguous() and x.is_contiguous():\n x = _ext.cat2_cuda(x, new_feature)\n else:\n x = torch.cat((x, new_feature), 1)\n return x\n\n\nclass _TransitionLayer(nn.Module):\n def __init__(self, num_input_features: int, num_output_features: int):\n super(_TransitionLayer, 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 return self.transition(x)\n\n\nclass ModelNew(nn.Module):\n def __init__(self, growth_rate: int = 32, num_classes: int = 1000):\n super(ModelNew, self).__init__()\n self.features = nn.Sequential(\n nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False),\n nn.BatchNorm2d(64),\n nn.ReLU(inplace=True),\n nn.MaxPool2d(kernel_size=3, stride=2, padding=1),\n )\n\n num_features = 64\n block_layers = [6, 12, 24, 16]\n self.dense_blocks = nn.ModuleList()\n self.transition_layers = nn.ModuleList()\n for i, num_layers in enumerate(block_layers):\n block = _DenseBlock(num_layers=num_layers, num_input_features=num_features, growth_rate=growth_rate)\n self.dense_blocks.append(block)\n num_features = num_features + num_layers * growth_rate\n if i != len(block_layers) - 1:\n transition = _TransitionLayer(num_input_features=num_features, num_output_features=num_features // 2)\n self.transition_layers.append(transition)\n num_features = num_features // 2\n\n self.final_bn = nn.BatchNorm2d(num_features)\n self.classifier = nn.Linear(num_features, num_classes)\n\n def forward(self, x: torch.Tensor) -> torch.Tensor:\n x = self.features(x)\n for i, block in enumerate(self.dense_blocks):\n x = block(x)\n if i != len(self.dense_blocks) - 1:\n x = self.transition_layers[i](x)\n\n # In eval/inference mode fuse final BN + ReLU + global average pool + linear.\n # In training mode BatchNorm must use batch statistics and update buffers, so keep\n # the exact PyTorch path for correctness.\n if (not self.training and x.is_cuda and x.dtype is torch.float32 and x.is_contiguous()\n and self.classifier.bias is not None):\n return _ext.final_eval_cuda(\n x,\n self.final_bn.weight,\n self.final_bn.bias,\n self.final_bn.running_mean,\n self.final_bn.running_var,\n self.classifier.weight,\n self.classifier.bias,\n self.final_bn.eps,\n )\n\n x = self.final_bn(x)\n x = F.relu(x, inplace=True)\n x = F.adaptive_avg_pool2d(x, (1, 1)).view(x.size(0), -1)\n x = self.classifier(x)\n return x\n" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 7.29 μs Runtime stats: mean=7.29μs median=?μs std=0.46μs Numerical precision: max_abs_err=0.00e+00 mean_abs_err=0.00e+00 max_rel_err=0.00e+00 Memory: 214.38 MB peak (ref: 229.69 MB, ratio: 0.9333x) Kernel launches: 1196 (ref: 1196, fusion ratio: 1.0) Energy: 1681.54 mJ/run (ref: 720.44 mJ, ratio: 0.4284x) SOL score: 0.1026
final submitted kernel (228 lines, 8,747 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 cat2_cuda(torch::Tensor a, torch::Tensor b);
torch::Tensor final_eval_cuda(torch::Tensor x,
                              torch::Tensor bn_weight,
                              torch::Tensor bn_bias,
                              torch::Tensor bn_mean,
                              torch::Tensor bn_var,
                              torch::Tensor fc_weight,
                              torch::Tensor fc_bias,
                              double eps);
"""

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

__global__ void cat2_kernel(const float* __restrict__ a,
                            const float* __restrict__ b,
                            float* __restrict__ out,
                            long long total,
                            int C1, int C2, int HW) {
    long long t = (long long)blockIdx.x * blockDim.x + threadIdx.x;
    long long stride = (long long)blockDim.x * gridDim.x;
    int C = C1 + C2;
    for (; t < total; t += stride) {
        int inner = (int)(t % ((long long)C * HW));
        int c = inner / HW;
        int hw = inner - c * HW;
        long long n = t / ((long long)C * HW);
        if (c < C1) {
            out[t] = a[(n * (long long)C1 + c) * HW + hw];
        } else {
            out[t] = b[(n * (long long)C2 + (c - C1)) * HW + hw];
        }
    }
}

torch::Tensor cat2_cuda(torch::Tensor a, torch::Tensor b) {
    const int N = (int)a.size(0);
    const int C1 = (int)a.size(1);
    const int H = (int)a.size(2);
    const int W = (int)a.size(3);
    const int C2 = (int)b.size(1);
    auto out = torch::empty({N, C1 + C2, H, W}, a.options());
    long long total = (long long)N * (C1 + C2) * H * W;
    const int threads = 256;
    int blocks = (int)((total + threads - 1) / threads);
    if (blocks > 4096) blocks = 4096;
    cat2_kernel<<<blocks, threads, 0, at::cuda::getCurrentCUDAStream()>>>(
        a.data_ptr<float>(), b.data_ptr<float>(), out.data_ptr<float>(), total, C1, C2, H * W);
    return out;
}

__global__ void final_eval_kernel(const float* __restrict__ x,
                                  const float* __restrict__ bn_w,
                                  const float* __restrict__ bn_b,
                                  const float* __restrict__ bn_m,
                                  const float* __restrict__ bn_v,
                                  const float* __restrict__ fc_w,
                                  const float* __restrict__ fc_b,
                                  float* __restrict__ out,
                                  int C, int HW, int K, float eps) {
    // one block computes one (batch, class) output
    int nk = blockIdx.x;
    int n = nk / K;
    int k = nk - n * K;

    float acc = 0.0f;
    int total = C * HW;
    for (int idx = threadIdx.x; idx < total; idx += blockDim.x) {
        int c = idx / HW;
        float v = x[(n * (long long)C * HW) + idx];
        float y = (v - bn_m[c]) * rsqrtf(bn_v[c] + eps) * bn_w[c] + bn_b[c];
        y = y > 0.0f ? y : 0.0f;
        acc += y * fc_w[k * (long long)C + c];
    }

    __shared__ float smem[256];
    smem[threadIdx.x] = acc;
    __syncthreads();
    for (int s = blockDim.x >> 1; s > 0; s >>= 1) {
        if (threadIdx.x < s) smem[threadIdx.x] += smem[threadIdx.x + s];
        __syncthreads();
    }
    if (threadIdx.x == 0) {
        out[n * K + k] = smem[0] / (float)HW + fc_b[k];
    }
}

torch::Tensor final_eval_cuda(torch::Tensor x,
                              torch::Tensor bn_weight,
                              torch::Tensor bn_bias,
                              torch::Tensor bn_mean,
                              torch::Tensor bn_var,
                              torch::Tensor fc_weight,
                              torch::Tensor fc_bias,
                              double eps) {
    const int N = (int)x.size(0);
    const int C = (int)x.size(1);
    const int HW = (int)(x.size(2) * x.size(3));
    const int K = (int)fc_weight.size(0);
    auto out = torch::empty({N, K}, x.options());
    final_eval_kernel<<<N * K, 256, 0, at::cuda::getCurrentCUDAStream()>>>(
        x.data_ptr<float>(), bn_weight.data_ptr<float>(), bn_bias.data_ptr<float>(),
        bn_mean.data_ptr<float>(), bn_var.data_ptr<float>(), fc_weight.data_ptr<float>(),
        fc_bias.data_ptr<float>(), out.data_ptr<float>(), C, HW, K, (float)eps);
    return out;
}
"""

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


class _DenseBlock(nn.Module):
    def __init__(self, num_layers: int, num_input_features: int, growth_rate: int):
        super(_DenseBlock, self).__init__()
        layers = []
        for i in range(num_layers):
            layers.append(self._make_layer(num_input_features + i * growth_rate, growth_rate))
        self.layers = nn.ModuleList(layers)

    def _make_layer(self, in_features: int, growth_rate: int):
        return nn.Sequential(
            nn.BatchNorm2d(in_features),
            nn.ReLU(inplace=True),
            nn.Conv2d(in_features, growth_rate, kernel_size=3, padding=1, bias=False),
            nn.Dropout(0.0),
        )

    def forward(self, x):
        for layer in self.layers:
            new_feature = layer(x)
            if x.is_cuda and x.dtype is torch.float32 and new_feature.is_contiguous() and x.is_contiguous():
                x = _ext.cat2_cuda(x, new_feature)
            else:
                x = torch.cat((x, new_feature), 1)
        return x


class _TransitionLayer(nn.Module):
    def __init__(self, num_input_features: int, num_output_features: int):
        super(_TransitionLayer, 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):
        return self.transition(x)


class ModelNew(nn.Module):
    def __init__(self, growth_rate: int = 32, num_classes: int = 1000):
        super(ModelNew, self).__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2, padding=1),
        )

        num_features = 64
        block_layers = [6, 12, 24, 16]
        self.dense_blocks = nn.ModuleList()
        self.transition_layers = nn.ModuleList()
        for i, num_layers in enumerate(block_layers):
            block = _DenseBlock(num_layers=num_layers, num_input_features=num_features, growth_rate=growth_rate)
            self.dense_blocks.append(block)
            num_features = num_features + num_layers * growth_rate
            if i != len(block_layers) - 1:
                transition = _TransitionLayer(num_input_features=num_features, num_output_features=num_features // 2)
                self.transition_layers.append(transition)
                num_features = num_features // 2

        self.final_bn = nn.BatchNorm2d(num_features)
        self.classifier = nn.Linear(num_features, num_classes)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        x = self.features(x)
        for i, block in enumerate(self.dense_blocks):
            x = block(x)
            if i != len(self.dense_blocks) - 1:
                x = self.transition_layers[i](x)

        # In eval/inference mode fuse final BN + ReLU + global average pool + linear.
        # In training mode BatchNorm must use batch statistics and update buffers, so keep
        # the exact PyTorch path for correctness.
        if (not self.training and x.is_cuda and x.dtype is torch.float32 and x.is_contiguous()
                and self.classifier.bias is not None):
            return _ext.final_eval_cuda(
                x,
                self.final_bn.weight,
                self.final_bn.bias,
                self.final_bn.running_mean,
                self.final_bn.running_var,
                self.classifier.weight,
                self.classifier.bias,
                self.final_bn.eps,
            )

        x = self.final_bn(x)
        x = F.relu(x, inplace=True)
        x = F.adaptive_avg_pool2d(x, (1, 1)).view(x.size(0), -1)
        x = self.classifier(x)
        return x