gpt-5.5-priority · original · l123_single_gpt
correct 20_MobileNetV2.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
4.69 μs
ref runtime
4.64 μs
speedup
0.99x
started
2026-05-05T01:20:33Z
timingkernelbench.timing
kernel runtime
4.69 μs
ref runtime
4.64 μs
speedup
0.99x
kernel mean / median / std
4.69 / — / 3.42 μs
ref mean / median / std
4.64 / — / 2.18 μ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
165.55 MB
ref peak
165.55 MB
kernel / ref
1.00x
kernel launches / fusiontorch.profiler
kernel launches
963
ref launches
963
fusion ratio (ref / kernel)
1.00x
total cuda time
23338 μs
energyNVML (pynvml)
kernel mJ / run
969.88
ref mJ / run
968.98
ratio (ref / kernel)
1.00x
avg power
296.45 W
ref avg power
291.67 W
SOL (speed-of-light)heuristic_from_speedup
SOL score (0–1)
0.099
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 Model(nn.Module):
    def __init__(self, num_classes=1000):
        """
        MobileNetV2 architecture implementation in PyTorch.

        :param num_classes: The number of output classes. Default is 1000.
        """
        super(Model, self).__init__()
        
        def _make_divisible(v, divisor, min_value=None):
            """
            This function ensures that the number of channels is divisible by the divisor.
            """
            if min_value is None:
                min_value = divisor
            new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
            # Make sure that round down does not go down by more than 10%.
            if new_v < 0.9 * v:
                new_v += divisor
            return new_v

        def _inverted_residual_block(inp, oup, stride, expand_ratio):
            """
            Inverted Residual Block for MobileNetV2.
            """
            hidden_dim = int(inp * expand_ratio)
            use_res_connect = stride == 1 and inp == oup

            layers = []
            if expand_ratio != 1:
                # Pointwise convolution
                layers.append(nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False))
                layers.append(nn.BatchNorm2d(hidden_dim))
                layers.append(nn.ReLU6(inplace=True))

            layers.extend([
                # Depthwise convolution
                nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),
                nn.BatchNorm2d(hidden_dim),
                nn.ReLU6(inplace=True),
                # Pointwise linear convolution
                nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
                nn.BatchNorm2d(oup),
            ])

            if use_res_connect:
                return nn.Sequential(*layers), True
            else:
                return nn.Sequential(*layers), False

        # MobileNetV2 architecture
        input_channel = 32
        last_channel = 1280
        inverted_residual_setting = [
            # t, c, n, s
            [1, 16, 1, 1],
            [6, 24, 2, 2],
            [6, 32, 3, 2],
            [6, 64, 4, 2],
            [6, 96, 3, 1],
            [6, 160, 3, 2],
            [6, 320, 1, 1],
        ]

        # Building first layer
        features = [nn.Conv2d(3, input_channel, 3, 2, 1, bias=False),
                    nn.BatchNorm2d(input_channel),
                    nn.ReLU6(inplace=True)]

        # Building inverted residual blocks
        for t, c, n, s in inverted_residual_setting:
            output_channel = _make_divisible(c, 8)
            for i in range(n):
                stride = s if i == 0 else 1
                features.append(_inverted_residual_block(input_channel, output_channel, stride, expand_ratio=t)[0])
                input_channel = output_channel

        # Building last several layers
        features.append(nn.Conv2d(input_channel, last_channel, 1, 1, 0, bias=False))
        features.append(nn.BatchNorm2d(last_channel))
        features.append(nn.ReLU6(inplace=True))

        # Final layer
        features.append(nn.AdaptiveAvgPool2d((1, 1)))

        self.features = nn.Sequential(*features)

        # Linear layer
        self.classifier = nn.Sequential(
            nn.Dropout(0.0),
            nn.Linear(last_channel, num_classes),
        )

        # Weight initialization
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    nn.init.zeros_(m.bias)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.ones_(m.weight)
                nn.init.zeros_(m.bias)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.zeros_(m.bias)

    def forward(self, x):
        """
        Forward pass of the MobileNetV2 model.

        :param x: The input tensor, shape (batch_size, 3, 224, 224)
        :return: The output tensor, shape (batch_size, num_classes)
        """
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x

# Test code
batch_size = 10
num_classes = 1000

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

def get_init_inputs():
    return [num_classes]
turn 0 · FINAL · 1 tool call(s) · 82.8s turn · 82.8s LLM · 12,818 chars (click to expand)
turn 082.8s turn · 82.8s LLM · 0.0s tools · FINAL
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 first_conv_bn_relu6_forward(torch::Tensor x, torch::Tensor w, torch::Tensor gamma, torch::Tensor beta, torch::Tensor mean, torch::Tensor var, double eps);\ntorch::Tensor dw_conv_bn_relu6_forward(torch::Tensor x, torch::Tensor w, torch::Tensor gamma, torch::Tensor beta, torch::Tensor mean, torch::Tensor var, int64_t stride, double eps);\ntorch::Tensor avgpool2d_forward(torch::Tensor x);\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\nstatic inline int div_up_int64(int64_t a, int b) { return (int)((a + b - 1) / b); }\n\n__global__ void first_conv_bn_relu6_kernel(\n const float* __restrict__ x,\n const float* __restrict__ w,\n const float* __restrict__ gamma,\n const float* __restrict__ beta,\n const float* __restrict__ mean,\n const float* __restrict__ var,\n float* __restrict__ y,\n int N, int H, int W, int OH, int OW, float eps)\n{\n int64_t idx = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;\n int64_t total = (int64_t)N * 32 * OH * OW;\n if (idx >= total) return;\n\n int ow = idx % OW;\n int t = idx / OW;\n int oh = t % OH;\n t /= OH;\n int oc = t % 32;\n int n = t / 32;\n\n int ih0 = oh * 2 - 1;\n int iw0 = ow * 2 - 1;\n float acc = 0.0f;\n #pragma unroll\n for (int ic = 0; ic < 3; ++ic) {\n #pragma unroll\n for (int kh = 0; kh < 3; ++kh) {\n int ih = ih0 + kh;\n if ((unsigned)ih >= (unsigned)H) continue;\n #pragma unroll\n for (int kw = 0; kw < 3; ++kw) {\n int iw = iw0 + kw;\n if ((unsigned)iw < (unsigned)W) {\n float xv = x[((n * 3 + ic) * H + ih) * W + iw];\n float wv = w[((oc * 3 + ic) * 3 + kh) * 3 + kw];\n acc = fmaf(xv, wv, acc);\n }\n }\n }\n }\n float scale = gamma[oc] * rsqrtf(var[oc] + eps);\n float out = fmaf(acc - mean[oc], scale, beta[oc]);\n out = fminf(fmaxf(out, 0.0f), 6.0f);\n y[idx] = out;\n}\n\n__global__ void dw_conv_bn_relu6_kernel(\n const float* __restrict__ x,\n const float* __restrict__ w,\n const float* __restrict__ gamma,\n const float* __restrict__ beta,\n const float* __restrict__ mean,\n const float* __restrict__ var,\n float* __restrict__ y,\n int N, int C, int H, int W, int OH, int OW, int stride, float eps)\n{\n int64_t idx = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;\n int64_t total = (int64_t)N * C * OH * OW;\n if (idx >= total) return;\n\n int ow = idx % OW;\n int t = idx / OW;\n int oh = t % OH;\n t /= OH;\n int c = t % C;\n int n = t / C;\n\n int ih0 = oh * stride - 1;\n int iw0 = ow * stride - 1;\n float acc = 0.0f;\n const float* wp = w + c * 9;\n #pragma unroll\n for (int kh = 0; kh < 3; ++kh) {\n int ih = ih0 + kh;\n if ((unsigned)ih >= (unsigned)H) continue;\n #pragma unroll\n for (int kw = 0; kw < 3; ++kw) {\n int iw = iw0 + kw;\n if ((unsigned)iw < (unsigned)W) {\n float xv = x[((n * C + c) * H + ih) * W + iw];\n float wv = wp[kh * 3 + kw];\n acc = fmaf(xv, wv, acc);\n }\n }\n }\n float scale = gamma[c] * rsqrtf(var[c] + eps);\n float out = fmaf(acc - mean[c], scale, beta[c]);\n out = fminf(fmaxf(out, 0.0f), 6.0f);\n y[idx] = out;\n}\n\n__global__ void avgpool2d_kernel(const float* __restrict__ x, float* __restrict__ y,\n int N, int C, int H, int W)\n{\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n int total = N * C;\n if (idx >= total) return;\n int c = idx % C;\n int n = idx / C;\n const float* xp = x + ((n * C + c) * H) * W;\n float s = 0.0f;\n int HW = H * W;\n for (int i = 0; i < HW; ++i) s += xp[i];\n y[idx] = s / (float)HW;\n}\n\ntorch::Tensor first_conv_bn_relu6_forward(torch::Tensor x, torch::Tensor w, torch::Tensor gamma, torch::Tensor beta, torch::Tensor mean, torch::Tensor var, double eps) {\n x = x.contiguous();\n w = w.contiguous();\n int N = (int)x.size(0);\n int H = (int)x.size(2);\n int W = (int)x.size(3);\n int OH = (H + 1) / 2;\n int OW = (W + 1) / 2;\n auto y = torch::empty({N, 32, OH, OW}, x.options());\n int threads = 256;\n int64_t total = (int64_t)N * 32 * OH * OW;\n first_conv_bn_relu6_kernel<<<div_up_int64(total, threads), threads, 0, at::cuda::getCurrentCUDAStream()>>>(\n x.data_ptr<float>(), w.data_ptr<float>(), gamma.data_ptr<float>(), beta.data_ptr<float>(), mean.data_ptr<float>(), var.data_ptr<float>(), y.data_ptr<float>(), N, H, W, OH, OW, (float)eps);\n return y;\n}\n\ntorch::Tensor dw_conv_bn_relu6_forward(torch::Tensor x, torch::Tensor w, torch::Tensor gamma, torch::Tensor beta, torch::Tensor mean, torch::Tensor var, int64_t stride, double eps) {\n x = x.contiguous();\n w = w.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 s = (int)stride;\n int OH = (H + 2 - 3) / s + 1;\n int OW = (W + 2 - 3) / s + 1;\n auto y = torch::empty({N, C, OH, OW}, x.options());\n int threads = 256;\n int64_t total = (int64_t)N * C * OH * OW;\n dw_conv_bn_relu6_kernel<<<div_up_int64(total, threads), threads, 0, at::cuda::getCurrentCUDAStream()>>>(\n x.data_ptr<float>(), w.data_ptr<float>(), gamma.data_ptr<float>(), beta.data_ptr<float>(), mean.data_ptr<float>(), var.data_ptr<float>(), y.data_ptr<float>(), N, C, H, W, OH, OW, s, (float)eps);\n return y;\n}\n\ntorch::Tensor avgpool2d_forward(torch::Tensor x) {\n x = x.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 auto y = torch::empty({N, C}, x.options());\n int threads = 256;\n int total = N * C;\n avgpool2d_kernel<<<div_up_int64(total, threads), threads, 0, at::cuda::getCurrentCUDAStream()>>>(x.data_ptr<float>(), y.data_ptr<float>(), N, C, H, W);\n return y;\n}\n\"\"\"\n\n_mobilenet_ext = load_inline(\n name=\"mobilenetv2_fused_depthwise_ext_v1\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"first_conv_bn_relu6_forward\", \"dw_conv_bn_relu6_forward\", \"avgpool2d_forward\"],\n extra_cuda_cflags=[\"-O3\", \"--use_fast_math\"],\n extra_cflags=[\"-O3\"],\n verbose=False,\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, num_classes=1000):\n super(ModelNew, self).__init__()\n def _make_divisible(v, divisor, min_value=None):\n if min_value is None:\n min_value = divisor\n new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)\n if new_v < 0.9 * v:\n new_v += divisor\n return new_v\n def _inverted_residual_block(inp, oup, stride, expand_ratio):\n hidden_dim = int(inp * expand_ratio)\n layers = []\n if expand_ratio != 1:\n layers.append(nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False))\n layers.append(nn.BatchNorm2d(hidden_dim))\n layers.append(nn.ReLU6(inplace=True))\n layers.extend([\n nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),\n nn.BatchNorm2d(hidden_dim),\n nn.ReLU6(inplace=True),\n nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),\n nn.BatchNorm2d(oup),\n ])\n return nn.Sequential(*layers), (stride == 1 and inp == oup)\n\n input_channel = 32\n last_channel = 1280\n inverted_residual_setting = [[1,16,1,1],[6,24,2,2],[6,32,3,2],[6,64,4,2],[6,96,3,1],[6,160,3,2],[6,320,1,1]]\n features = [nn.Conv2d(3, input_channel, 3, 2, 1, bias=False), nn.BatchNorm2d(input_channel), nn.ReLU6(inplace=True)]\n for t, c, n, s in inverted_residual_setting:\n output_channel = _make_divisible(c, 8)\n for i in range(n):\n stride = s if i == 0 else 1\n features.append(_inverted_residual_block(input_channel, output_channel, stride, expand_ratio=t)[0])\n input_channel = output_channel\n features.append(nn.Conv2d(input_channel, last_channel, 1, 1, 0, bias=False))\n features.append(nn.BatchNorm2d(last_channel))\n features.append(nn.ReLU6(inplace=True))\n features.append(nn.AdaptiveAvgPool2d((1, 1)))\n self.features = nn.Sequential(*features)\n self.classifier = nn.Sequential(nn.Dropout(0.0), nn.Linear(last_channel, num_classes))\n for m in self.modules():\n if isinstance(m, nn.Conv2d):\n nn.init.kaiming_normal_(m.weight, mode='fan_out')\n if m.bias is not None:\n nn.init.zeros_(m.bias)\n elif isinstance(m, nn.BatchNorm2d):\n nn.init.ones_(m.weight)\n nn.init.zeros_(m.bias)\n elif isinstance(m, nn.Linear):\n nn.init.normal_(m.weight, 0, 0.01)\n nn.init.zeros_(m.bias)\n\n def _forward_inner_seq_eval(self, seq, x):\n i = 0\n L = len(seq)\n while i < L:\n m = seq[i]\n # Fuse depthwise 3x3 conv + following BatchNorm + ReLU6 in eval mode.\n if (i + 2 < L and isinstance(m, nn.Conv2d) and m.groups == m.in_channels and\n m.in_channels == m.out_channels and m.kernel_size == (3, 3) and\n m.padding == (1, 1) and isinstance(seq[i+1], nn.BatchNorm2d) and\n isinstance(seq[i+2], nn.ReLU6) and x.is_cuda and x.dtype is torch.float32):\n bn = seq[i+1]\n x = _mobilenet_ext.dw_conv_bn_relu6_forward(\n x, m.weight, bn.weight, bn.bias, bn.running_mean, bn.running_var,\n int(m.stride[0]), float(bn.eps))\n i += 3\n else:\n x = m(x)\n i += 1\n return x\n\n def forward(self, x):\n # Training-mode BatchNorm uses batch statistics; keep the exact PyTorch path there.\n if self.training or (not x.is_cuda) or x.dtype is not torch.float32:\n x = self.features(x)\n x = x.view(x.size(0), -1)\n return self.classifier(x)\n\n # First conv + BN + ReLU6 fused.\n conv0 = self.features[0]\n bn0 = self.features[1]\n x = _mobilenet_ext.first_conv_bn_relu6_forward(\n x, conv0.weight, bn0.weight, bn0.bias, bn0.running_mean, bn0.running_var, float(bn0.eps))\n\n # Run all layers except the final adaptive average pool; depthwise conv blocks are fused.\n for j in range(3, len(self.features) - 1):\n layer = self.features[j]\n if isinstance(layer, nn.Sequential):\n x = self._forward_inner_seq_eval(layer, x)\n else:\n x = layer(x)\n\n x = _mobilenet_ext.avgpool2d_forward(x)\n # Dropout probability is zero in the reference, so this is exactly the classifier's Linear.\n lin = self.classifier[1]\n return F.linear(x, lin.weight, lin.bias)\n" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 4.69 μs Runtime stats: mean=4.69μs median=?μs std=3.42μs Numerical precision: max_abs_err=0.00e+00 mean_abs_err=0.00e+00 max_rel_err=0.00e+00 Memory: 165.55 MB peak (ref: 165.55 MB, ratio: 1.0x) Kernel launches: 963 (ref: 963, fusion ratio: 1.0) Energy: 969.88 mJ/run (ref: 968.98 mJ, ratio: 0.9991x) SOL score: 0.0989
final submitted kernel (287 lines, 11,234 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 first_conv_bn_relu6_forward(torch::Tensor x, torch::Tensor w, torch::Tensor gamma, torch::Tensor beta, torch::Tensor mean, torch::Tensor var, double eps);
torch::Tensor dw_conv_bn_relu6_forward(torch::Tensor x, torch::Tensor w, torch::Tensor gamma, torch::Tensor beta, torch::Tensor mean, torch::Tensor var, int64_t stride, double eps);
torch::Tensor avgpool2d_forward(torch::Tensor x);
"""

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

static inline int div_up_int64(int64_t a, int b) { return (int)((a + b - 1) / b); }

__global__ void first_conv_bn_relu6_kernel(
    const float* __restrict__ x,
    const float* __restrict__ w,
    const float* __restrict__ gamma,
    const float* __restrict__ beta,
    const float* __restrict__ mean,
    const float* __restrict__ var,
    float* __restrict__ y,
    int N, int H, int W, int OH, int OW, float eps)
{
    int64_t idx = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;
    int64_t total = (int64_t)N * 32 * OH * OW;
    if (idx >= total) return;

    int ow = idx % OW;
    int t = idx / OW;
    int oh = t % OH;
    t /= OH;
    int oc = t % 32;
    int n = t / 32;

    int ih0 = oh * 2 - 1;
    int iw0 = ow * 2 - 1;
    float acc = 0.0f;
    #pragma unroll
    for (int ic = 0; ic < 3; ++ic) {
        #pragma unroll
        for (int kh = 0; kh < 3; ++kh) {
            int ih = ih0 + kh;
            if ((unsigned)ih >= (unsigned)H) continue;
            #pragma unroll
            for (int kw = 0; kw < 3; ++kw) {
                int iw = iw0 + kw;
                if ((unsigned)iw < (unsigned)W) {
                    float xv = x[((n * 3 + ic) * H + ih) * W + iw];
                    float wv = w[((oc * 3 + ic) * 3 + kh) * 3 + kw];
                    acc = fmaf(xv, wv, acc);
                }
            }
        }
    }
    float scale = gamma[oc] * rsqrtf(var[oc] + eps);
    float out = fmaf(acc - mean[oc], scale, beta[oc]);
    out = fminf(fmaxf(out, 0.0f), 6.0f);
    y[idx] = out;
}

__global__ void dw_conv_bn_relu6_kernel(
    const float* __restrict__ x,
    const float* __restrict__ w,
    const float* __restrict__ gamma,
    const float* __restrict__ beta,
    const float* __restrict__ mean,
    const float* __restrict__ var,
    float* __restrict__ y,
    int N, int C, int H, int W, int OH, int OW, int stride, float eps)
{
    int64_t idx = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;
    int64_t total = (int64_t)N * C * OH * OW;
    if (idx >= total) return;

    int ow = idx % OW;
    int t = idx / OW;
    int oh = t % OH;
    t /= OH;
    int c = t % C;
    int n = t / C;

    int ih0 = oh * stride - 1;
    int iw0 = ow * stride - 1;
    float acc = 0.0f;
    const float* wp = w + c * 9;
    #pragma unroll
    for (int kh = 0; kh < 3; ++kh) {
        int ih = ih0 + kh;
        if ((unsigned)ih >= (unsigned)H) continue;
        #pragma unroll
        for (int kw = 0; kw < 3; ++kw) {
            int iw = iw0 + kw;
            if ((unsigned)iw < (unsigned)W) {
                float xv = x[((n * C + c) * H + ih) * W + iw];
                float wv = wp[kh * 3 + kw];
                acc = fmaf(xv, wv, acc);
            }
        }
    }
    float scale = gamma[c] * rsqrtf(var[c] + eps);
    float out = fmaf(acc - mean[c], scale, beta[c]);
    out = fminf(fmaxf(out, 0.0f), 6.0f);
    y[idx] = out;
}

__global__ void avgpool2d_kernel(const float* __restrict__ x, float* __restrict__ y,
                                 int N, int C, int H, int W)
{
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    int total = N * C;
    if (idx >= total) return;
    int c = idx % C;
    int n = idx / C;
    const float* xp = x + ((n * C + c) * H) * W;
    float s = 0.0f;
    int HW = H * W;
    for (int i = 0; i < HW; ++i) s += xp[i];
    y[idx] = s / (float)HW;
}

torch::Tensor first_conv_bn_relu6_forward(torch::Tensor x, torch::Tensor w, torch::Tensor gamma, torch::Tensor beta, torch::Tensor mean, torch::Tensor var, double eps) {
    x = x.contiguous();
    w = w.contiguous();
    int N = (int)x.size(0);
    int H = (int)x.size(2);
    int W = (int)x.size(3);
    int OH = (H + 1) / 2;
    int OW = (W + 1) / 2;
    auto y = torch::empty({N, 32, OH, OW}, x.options());
    int threads = 256;
    int64_t total = (int64_t)N * 32 * OH * OW;
    first_conv_bn_relu6_kernel<<<div_up_int64(total, threads), threads, 0, at::cuda::getCurrentCUDAStream()>>>(
        x.data_ptr<float>(), w.data_ptr<float>(), gamma.data_ptr<float>(), beta.data_ptr<float>(), mean.data_ptr<float>(), var.data_ptr<float>(), y.data_ptr<float>(), N, H, W, OH, OW, (float)eps);
    return y;
}

torch::Tensor dw_conv_bn_relu6_forward(torch::Tensor x, torch::Tensor w, torch::Tensor gamma, torch::Tensor beta, torch::Tensor mean, torch::Tensor var, int64_t stride, double eps) {
    x = x.contiguous();
    w = w.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 s = (int)stride;
    int OH = (H + 2 - 3) / s + 1;
    int OW = (W + 2 - 3) / s + 1;
    auto y = torch::empty({N, C, OH, OW}, x.options());
    int threads = 256;
    int64_t total = (int64_t)N * C * OH * OW;
    dw_conv_bn_relu6_kernel<<<div_up_int64(total, threads), threads, 0, at::cuda::getCurrentCUDAStream()>>>(
        x.data_ptr<float>(), w.data_ptr<float>(), gamma.data_ptr<float>(), beta.data_ptr<float>(), mean.data_ptr<float>(), var.data_ptr<float>(), y.data_ptr<float>(), N, C, H, W, OH, OW, s, (float)eps);
    return y;
}

torch::Tensor avgpool2d_forward(torch::Tensor x) {
    x = x.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);
    auto y = torch::empty({N, C}, x.options());
    int threads = 256;
    int total = N * C;
    avgpool2d_kernel<<<div_up_int64(total, threads), threads, 0, at::cuda::getCurrentCUDAStream()>>>(x.data_ptr<float>(), y.data_ptr<float>(), N, C, H, W);
    return y;
}
"""

_mobilenet_ext = load_inline(
    name="mobilenetv2_fused_depthwise_ext_v1",
    cpp_sources=_cpp_src,
    cuda_sources=_cuda_src,
    functions=["first_conv_bn_relu6_forward", "dw_conv_bn_relu6_forward", "avgpool2d_forward"],
    extra_cuda_cflags=["-O3", "--use_fast_math"],
    extra_cflags=["-O3"],
    verbose=False,
)

class ModelNew(nn.Module):
    def __init__(self, num_classes=1000):
        super(ModelNew, self).__init__()
        def _make_divisible(v, divisor, min_value=None):
            if min_value is None:
                min_value = divisor
            new_v = max(min_value, int(v + divisor / 2) // divisor * divisor)
            if new_v < 0.9 * v:
                new_v += divisor
            return new_v
        def _inverted_residual_block(inp, oup, stride, expand_ratio):
            hidden_dim = int(inp * expand_ratio)
            layers = []
            if expand_ratio != 1:
                layers.append(nn.Conv2d(inp, hidden_dim, 1, 1, 0, bias=False))
                layers.append(nn.BatchNorm2d(hidden_dim))
                layers.append(nn.ReLU6(inplace=True))
            layers.extend([
                nn.Conv2d(hidden_dim, hidden_dim, 3, stride, 1, groups=hidden_dim, bias=False),
                nn.BatchNorm2d(hidden_dim),
                nn.ReLU6(inplace=True),
                nn.Conv2d(hidden_dim, oup, 1, 1, 0, bias=False),
                nn.BatchNorm2d(oup),
            ])
            return nn.Sequential(*layers), (stride == 1 and inp == oup)

        input_channel = 32
        last_channel = 1280
        inverted_residual_setting = [[1,16,1,1],[6,24,2,2],[6,32,3,2],[6,64,4,2],[6,96,3,1],[6,160,3,2],[6,320,1,1]]
        features = [nn.Conv2d(3, input_channel, 3, 2, 1, bias=False), nn.BatchNorm2d(input_channel), nn.ReLU6(inplace=True)]
        for t, c, n, s in inverted_residual_setting:
            output_channel = _make_divisible(c, 8)
            for i in range(n):
                stride = s if i == 0 else 1
                features.append(_inverted_residual_block(input_channel, output_channel, stride, expand_ratio=t)[0])
                input_channel = output_channel
        features.append(nn.Conv2d(input_channel, last_channel, 1, 1, 0, bias=False))
        features.append(nn.BatchNorm2d(last_channel))
        features.append(nn.ReLU6(inplace=True))
        features.append(nn.AdaptiveAvgPool2d((1, 1)))
        self.features = nn.Sequential(*features)
        self.classifier = nn.Sequential(nn.Dropout(0.0), nn.Linear(last_channel, num_classes))
        for m in self.modules():
            if isinstance(m, nn.Conv2d):
                nn.init.kaiming_normal_(m.weight, mode='fan_out')
                if m.bias is not None:
                    nn.init.zeros_(m.bias)
            elif isinstance(m, nn.BatchNorm2d):
                nn.init.ones_(m.weight)
                nn.init.zeros_(m.bias)
            elif isinstance(m, nn.Linear):
                nn.init.normal_(m.weight, 0, 0.01)
                nn.init.zeros_(m.bias)

    def _forward_inner_seq_eval(self, seq, x):
        i = 0
        L = len(seq)
        while i < L:
            m = seq[i]
            # Fuse depthwise 3x3 conv + following BatchNorm + ReLU6 in eval mode.
            if (i + 2 < L and isinstance(m, nn.Conv2d) and m.groups == m.in_channels and
                m.in_channels == m.out_channels and m.kernel_size == (3, 3) and
                m.padding == (1, 1) and isinstance(seq[i+1], nn.BatchNorm2d) and
                isinstance(seq[i+2], nn.ReLU6) and x.is_cuda and x.dtype is torch.float32):
                bn = seq[i+1]
                x = _mobilenet_ext.dw_conv_bn_relu6_forward(
                    x, m.weight, bn.weight, bn.bias, bn.running_mean, bn.running_var,
                    int(m.stride[0]), float(bn.eps))
                i += 3
            else:
                x = m(x)
                i += 1
        return x

    def forward(self, x):
        # Training-mode BatchNorm uses batch statistics; keep the exact PyTorch path there.
        if self.training or (not x.is_cuda) or x.dtype is not torch.float32:
            x = self.features(x)
            x = x.view(x.size(0), -1)
            return self.classifier(x)

        # First conv + BN + ReLU6 fused.
        conv0 = self.features[0]
        bn0 = self.features[1]
        x = _mobilenet_ext.first_conv_bn_relu6_forward(
            x, conv0.weight, bn0.weight, bn0.bias, bn0.running_mean, bn0.running_var, float(bn0.eps))

        # Run all layers except the final adaptive average pool; depthwise conv blocks are fused.
        for j in range(3, len(self.features) - 1):
            layer = self.features[j]
            if isinstance(layer, nn.Sequential):
                x = self._forward_inner_seq_eval(layer, x)
            else:
                x = layer(x)

        x = _mobilenet_ext.avgpool2d_forward(x)
        # Dropout probability is zero in the reference, so this is exactly the classifier's Linear.
        lin = self.classifier[1]
        return F.linear(x, lin.weight, lin.bias)