gpt-5.5-priority · original · l123_single_gpt
compile_fail 32_Conv2d_Scaling_Min.py
model
gpt-5.5-priority
turns / max
1 / 1
tool calls
1
tokens (total)
0
wall clock
—
truncated
no
backend
cuda fp32
variant
original
runtime
—
ref runtime
-1.00 μs
speedup
—
started
2026-05-05T00:40:31Z
compile error: Error building extension 'fused_conv_min_ext_v1': [1/3] /opt/conda/bin/x86_64-conda-linux-gnu-c++ -MMD -MF main.o.d -DTORCH_EXTENSION_NAME=fused_conv_min_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -fPIC -std=c++17 -O3 -c /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_2_problem_32_cache/k_77238cd0165e/f
timingkernelbench.timing
kernel runtime
—
ref runtime
—
speedup
—
kernel mean / median / std
—
ref mean / median / std
—
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn

class Model(nn.Module):
    """
    Model that performs a convolution, scales the output, and then applies a minimum operation.
    """
    def __init__(self, in_channels, out_channels, kernel_size, scale_factor):
        super(Model, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size)
        self.scale_factor = scale_factor

    def forward(self, x):
        """
        Args:
            x (torch.Tensor): Input tensor of shape (batch_size, in_channels, height, width).
        Returns:
            torch.Tensor: Output tensor of shape (batch_size, out_channels, height, width).
        """
        x = self.conv(x)
        x = x * self.scale_factor
        x = torch.min(x, dim=1, keepdim=True)[0]  # Minimum along channel dimension
        return x

batch_size = 64
in_channels = 64
out_channels = 128
height = width = 256
kernel_size = 3
scale_factor = 2.0

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

def get_init_inputs():
    return [in_channels, out_channels, kernel_size, scale_factor]
turn 0 · FINAL · 1 tool call(s) · 61.1s turn · 61.1s LLM · 14,368 chars (click to expand)
turn 061.1s turn · 61.1s LLM · 0.0s tools · FINAL
reasoning
**Exploring convolution efficiency** I need to craft a complete Python implementation while understanding shapes in a convolutional context. With a Conv2D layer set to no padding and stride one, the output shape comes out to (64, 128, 254, 254), and reducing over channels gives (64, 1, 254, 254). It aims to implement a fused convolution where I must use weights and biases, and also optimize performance given large input sizes. Overall, I'm trying to ensure efficiency without excessive operations, which seem too heavy! **Optimizing convolution with CUDA** I need to find a way to custom fuse my operations without writing out all 128 channels while still computing everything. It seems like leveraging cuDNN might be necessary, but custom CUDA is the focus. The challenge will be to beat PyTorch; I'm wondering if a reformulation of how I compute the minimum over output channels from the convolution could help. Calling a custom CUDA kernel is essential, and I'm considering if using CUTLASS could optimize the convolution effectively. My goal is to write an efficient kernel while managing compile times! **Tiling for efficient computation** I need to focus on outputting only the minimum while ensuring all the necessary calculations are still performed. I can use tiling to manage the spatial and channel dimensions since the batch and spatial dimensions are independent. For each pixel and for each batch element, my plan is to compute the 128 output channel accumulations. I’m considering using one block per pixel to handle this efficiently. It seems like a good way to optimize the process! **Exploring weight management** I’m thinking about how to handle load weights for a thread when there's no coalescing. Should I consider pre-transforming weights in the constructor? It feels tricky since I can’t know the weights after the state dict is possibly copied post-initialization. Maybe I could create transformed weights during the forward pass? However, I worry about the cost being too high. There's a lot to think through here! **Exploring input coalescing** I'm thinking about input coalescing and how, for a given resource (r), all lanes should be using the same address. It seems like a warp uniform load could work by broadcasting a constant output for each pixel. The way I see it, there's a lot of focus on making sure data is accessed efficiently. I wonder if there are best practices for implementing this in different scenarios. This feels like a technical challenge worth diving into!
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\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 <float.h>\n\n// Convert PyTorch conv weight layout [OC,C,3,3] to [C*3*3,OC].\n// The transformed matrix is tiny (128*64*9 floats) and stays resident in cache\n// during the fused convolution/min kernel.\n__global__ void transform_w_3x3_128_kernel(const float* __restrict__ w,\n float* __restrict__ wt) {\n int idx = blockIdx.x * blockDim.x + threadIdx.x;\n const int total = 128 * 64 * 9;\n if (idx >= total) return;\n int rem = idx;\n int kw = rem % 3; rem /= 3;\n int kh = rem % 3; rem /= 3;\n int c = rem % 64; rem /= 64;\n int oc = rem;\n int r = (c * 9 + kh * 3 + kw) * 128 + oc;\n wt[r] = w[idx];\n}\n\n__device__ __forceinline__ float warp_min(float v) {\n unsigned mask = 0xffffffffu;\n v = fminf(v, __shfl_down_sync(mask, v, 16));\n v = fminf(v, __shfl_down_sync(mask, v, 8));\n v = fminf(v, __shfl_down_sync(mask, v, 4));\n v = fminf(v, __shfl_down_sync(mask, v, 2));\n v = fminf(v, __shfl_down_sync(mask, v, 1));\n return v;\n}\n\n// One warp computes one output pixel. Each lane computes four output channels\n// (lane, lane+32, lane+64, lane+96); the warp then reduces the 128 channel\n// values to the required channel-minimum. The convolution, scale and reduction\n// are fully fused, avoiding the 64*128*254*254 intermediate tensor and the\n// separate multiply/min kernels in the reference.\n__global__ __launch_bounds__(256, 2)\nvoid conv3x3_min_scale_64_128_kernel(const float* __restrict__ x,\n const float* __restrict__ wt,\n const float* __restrict__ bias,\n float* __restrict__ out,\n int N, int H, int W,\n float scale) {\n const int lane = threadIdx.x & 31;\n const int warp_in_block = threadIdx.x >> 5;\n const int warps_per_block = blockDim.x >> 5;\n const int OH = H - 2;\n const int OW = W - 2;\n const int64_t num_pix = (int64_t)N * OH * OW;\n int64_t pix = (int64_t)blockIdx.x * warps_per_block + warp_in_block;\n if (pix >= num_pix) return;\n\n int ow = (int)(pix % OW);\n int t = (int)(pix / OW);\n int oh = t % OH;\n int n = t / OH;\n\n float a0 = __ldg(bias + lane);\n float a1 = __ldg(bias + lane + 32);\n float a2 = __ldg(bias + lane + 64);\n float a3 = __ldg(bias + lane + 96);\n\n // Base pointer for this image. Input is NCHW contiguous.\n const int img_stride = 64 * H * W;\n const float* xN = x + (int64_t)n * img_stride;\n\n #pragma unroll 1\n for (int c = 0; c < 64; ++c) {\n const float* xc = xN + (int64_t)c * H * W + oh * W + ow;\n int wr = c * 9 * 128 + lane;\n\n float xv;\n xv = __ldg(xc + 0);\n a0 = fmaf(xv, __ldg(wt + wr + 0), a0);\n a1 = fmaf(xv, __ldg(wt + wr + 32), a1);\n a2 = fmaf(xv, __ldg(wt + wr + 64), a2);\n a3 = fmaf(xv, __ldg(wt + wr + 96), a3);\n\n xv = __ldg(xc + 1); wr += 128;\n a0 = fmaf(xv, __ldg(wt + wr + 0), a0);\n a1 = fmaf(xv, __ldg(wt + wr + 32), a1);\n a2 = fmaf(xv, __ldg(wt + wr + 64), a2);\n a3 = fmaf(xv, __ldg(wt + wr + 96), a3);\n\n xv = __ldg(xc + 2); wr += 128;\n a0 = fmaf(xv, __ldg(wt + wr + 0), a0);\n a1 = fmaf(xv, __ldg(wt + wr + 32), a1);\n a2 = fmaf(xv, __ldg(wt + wr + 64), a2);\n a3 = fmaf(xv, __ldg(wt + wr + 96), a3);\n\n xc += W; wr += 128;\n xv = __ldg(xc + 0);\n a0 = fmaf(xv, __ldg(wt + wr + 0), a0);\n a1 = fmaf(xv, __ldg(wt + wr + 32), a1);\n a2 = fmaf(xv, __ldg(wt + wr + 64), a2);\n a3 = fmaf(xv, __ldg(wt + wr + 96), a3);\n\n xv = __ldg(xc + 1); wr += 128;\n a0 = fmaf(xv, __ldg(wt + wr + 0), a0);\n a1 = fmaf(xv, __ldg(wt + wr + 32), a1);\n a2 = fmaf(xv, __ldg(wt + wr + 64), a2);\n a3 = fmaf(xv, __ldg(wt + wr + 96), a3);\n\n xv = __ldg(xc + 2); wr += 128;\n a0 = fmaf(xv, __ldg(wt + wr + 0), a0);\n a1 = fmaf(xv, __ldg(wt + wr + 32), a1);\n a2 = fmaf(xv, __ldg(wt + wr + 64), a2);\n a3 = fmaf(xv, __ldg(wt + wr + 96), a3);\n\n xc += W; wr += 128;\n xv = __ldg(xc + 0);\n a0 = fmaf(xv, __ldg(wt + wr + 0), a0);\n a1 = fmaf(xv, __ldg(wt + wr + 32), a1);\n a2 = fmaf(xv, __ldg(wt + wr + 64), a2);\n a3 = fmaf(xv, __ldg(wt + wr + 96), a3);\n\n xv = __ldg(xc + 1); wr += 128;\n a0 = fmaf(xv, __ldg(wt + wr + 0), a0);\n a1 = fmaf(xv, __ldg(wt + wr + 32), a1);\n a2 = fmaf(xv, __ldg(wt + wr + 64), a2);\n a3 = fmaf(xv, __ldg(wt + wr + 96), a3);\n\n xv = __ldg(xc + 2); wr += 128;\n a0 = fmaf(xv, __ldg(wt + wr + 0), a0);\n a1 = fmaf(xv, __ldg(wt + wr + 32), a1);\n a2 = fmaf(xv, __ldg(wt + wr + 64), a2);\n a3 = fmaf(xv, __ldg(wt + wr + 96), a3);\n }\n\n a0 *= scale; a1 *= scale; a2 *= scale; a3 *= scale;\n float m = fminf(fminf(a0, a1), fminf(a2, a3));\n m = warp_min(m);\n if (lane == 0) out[pix] = m;\n}\n\ntorch::Tensor fused_conv_min_forward(torch::Tensor x, torch::Tensor weight,\n torch::Tensor bias, double scale) {\n TORCH_CHECK(x.is_cuda() && weight.is_cuda() && bias.is_cuda(), \"all tensors must be CUDA tensors\");\n TORCH_CHECK(x.scalar_type() == torch::kFloat32, \"only float32 is supported\");\n TORCH_CHECK(weight.scalar_type() == torch::kFloat32 && bias.scalar_type() == torch::kFloat32, \"only float32 weights are supported\");\n TORCH_CHECK(x.is_contiguous() && weight.is_contiguous() && bias.is_contiguous(), \"tensors must be contiguous\");\n TORCH_CHECK(x.dim() == 4 && weight.dim() == 4, \"bad tensor rank\");\n TORCH_CHECK(x.size(1) == 64 && weight.size(0) == 128 && weight.size(1) == 64 && weight.size(2) == 3 && weight.size(3) == 3,\n \"this optimized kernel expects C=64, OC=128, K=3\");\n\n const int N = (int)x.size(0);\n const int H = (int)x.size(2);\n const int W = (int)x.size(3);\n auto wt = torch::empty({64 * 9, 128}, x.options());\n auto out = torch::empty({N, 1, H - 2, W - 2}, x.options());\n\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n transform_w_3x3_128_kernel<<<(128*64*9 + 255) / 256, 256, 0, stream>>>(\n weight.data_ptr<float>(), wt.data_ptr<float>());\n\n const int threads = 256;\n const int warps = threads / 32;\n int64_t num_pix = (int64_t)N * (H - 2) * (W - 2);\n int blocks = (int)((num_pix + warps - 1) / warps);\n conv3x3_min_scale_64_128_kernel<<<blocks, threads, 0, stream>>>(\n x.data_ptr<float>(), wt.data_ptr<float>(), bias.data_ptr<float>(), out.data_ptr<float>(),\n N, H, W, (float)scale);\n return out;\n}\n'''\n\n_ext = load_inline(\n name='fused_conv_min_ext_v1',\n cpp_sources='',\n cuda_sources=_cuda_src,\n functions=['fused_conv_min_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, in_channels, out_channels, kernel_size, scale_factor):\n super(ModelNew, self).__init__()\n self.conv = nn.Conv2d(in_channels, out_channels, kernel_size)\n self.scale_factor = float(scale_factor)\n\n def forward(self, x):\n return _ext.fused_conv_min_forward(x.contiguous(), self.conv.weight.contiguous(), self.conv.bias.contiguous(), self.scale_factor)\n" }
result ← submit_kernel (fail)
failsubmit_kernel submit_kernel FAILED: kernel did not compile. Error building extension 'fused_conv_min_ext_v1': [1/3] /opt/conda/bin/x86_64-conda-linux-gnu-c++ -MMD -MF main.o.d -DTORCH_EXTENSION_NAME=fused_conv_min_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -fPIC -std=c++17 -O3 -c /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_2_problem_32_cache/k_77238cd0165e/fused_conv_min_ext_v1/main.cpp -o main.o FAILED: [code=1] main.o /opt/conda/bin/x86_64-conda-linux-gnu-c++ -MMD -MF main.o.d -DTORCH_EXTENSION_NAME=fused_conv_min_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -fPIC -std=c++17 -O3 -c /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_2_problem_32_cache/k_77238cd0165e/fused_conv_min_ext_v1/main.cpp -o main.o /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_2_problem_32_cache/k_77238cd0165e/fused_conv_min_ext_v1/main.cpp: In function 'void pybind11_init_fused_conv_min_ext_v1(pybind11::module_&)': /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_2_problem_32_cache/k_77238cd0165e/fused_conv_min_ext_v1/main.cpp:4:61: error: 'fused_conv_min_forward' was not declared in this scope 4 | m.def("fused_conv_min_forward", torch::wrap_pybind_function(fused_conv_min_forward), "fused_conv_min_forward"); | ^~~~~~~~~~~~~~~~~~~~~~ [2/3] /usr/local/cuda/bin/nvcc -MD -MF cuda.cuda.o.d -ccbin /opt/conda/bin/x86_64-conda-linux-gnu-cc -DTORCH_EXTENSION_NAME=fused_conv_min_ext_v1 -DTORCH_API_INCLUDE_EXTENSION_H -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include -isystem /scratch/abaner/KernelBench/.venv/lib/python3.10/site-packages/torch/include/torch/csrc/api/include -isystem /usr/local/cuda/include -isystem /opt/conda/include/python3.10 -D__CUDA_NO_HALF_OPERATORS__ -D__CUDA_NO_HALF_CONVERSIONS__ -D__CUDA_NO_BFLOAT16_CONVERSIONS__ -D__CUDA_NO_HALF2_OPERATORS__ --expt-relaxed-constexpr -gencode=arch=compute_90,code=compute_90 -gencode=arch=compute_90,code=sm_90 --compiler-options '-fPIC' -O3 --use_fast_math -std=c++17 -c /scratch/abaner/KernelBench/runs/l123_single_gpt/original/gpt-5.5-priority/level_2_problem_32_cache/k_77238cd0165e/fused_conv_min_ext_v1/cuda.cu -o cuda.cuda.o nvcc warning : incompatible redefinition for option 'compiler-bindir', the last value of this option was used ninja: build stopped: subcommand failed.
final submitted kernel (192 lines, 7,596 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

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

// Convert PyTorch conv weight layout [OC,C,3,3] to [C*3*3,OC].
// The transformed matrix is tiny (128*64*9 floats) and stays resident in cache
// during the fused convolution/min kernel.
__global__ void transform_w_3x3_128_kernel(const float* __restrict__ w,
                                           float* __restrict__ wt) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    const int total = 128 * 64 * 9;
    if (idx >= total) return;
    int rem = idx;
    int kw = rem % 3; rem /= 3;
    int kh = rem % 3; rem /= 3;
    int c  = rem % 64; rem /= 64;
    int oc = rem;
    int r = (c * 9 + kh * 3 + kw) * 128 + oc;
    wt[r] = w[idx];
}

__device__ __forceinline__ float warp_min(float v) {
    unsigned mask = 0xffffffffu;
    v = fminf(v, __shfl_down_sync(mask, v, 16));
    v = fminf(v, __shfl_down_sync(mask, v, 8));
    v = fminf(v, __shfl_down_sync(mask, v, 4));
    v = fminf(v, __shfl_down_sync(mask, v, 2));
    v = fminf(v, __shfl_down_sync(mask, v, 1));
    return v;
}

// One warp computes one output pixel.  Each lane computes four output channels
// (lane, lane+32, lane+64, lane+96); the warp then reduces the 128 channel
// values to the required channel-minimum.  The convolution, scale and reduction
// are fully fused, avoiding the 64*128*254*254 intermediate tensor and the
// separate multiply/min kernels in the reference.
__global__ __launch_bounds__(256, 2)
void conv3x3_min_scale_64_128_kernel(const float* __restrict__ x,
                                      const float* __restrict__ wt,
                                      const float* __restrict__ bias,
                                      float* __restrict__ out,
                                      int N, int H, int W,
                                      float scale) {
    const int lane = threadIdx.x & 31;
    const int warp_in_block = threadIdx.x >> 5;
    const int warps_per_block = blockDim.x >> 5;
    const int OH = H - 2;
    const int OW = W - 2;
    const int64_t num_pix = (int64_t)N * OH * OW;
    int64_t pix = (int64_t)blockIdx.x * warps_per_block + warp_in_block;
    if (pix >= num_pix) return;

    int ow = (int)(pix % OW);
    int t = (int)(pix / OW);
    int oh = t % OH;
    int n  = t / OH;

    float a0 = __ldg(bias + lane);
    float a1 = __ldg(bias + lane + 32);
    float a2 = __ldg(bias + lane + 64);
    float a3 = __ldg(bias + lane + 96);

    // Base pointer for this image. Input is NCHW contiguous.
    const int img_stride = 64 * H * W;
    const float* xN = x + (int64_t)n * img_stride;

    #pragma unroll 1
    for (int c = 0; c < 64; ++c) {
        const float* xc = xN + (int64_t)c * H * W + oh * W + ow;
        int wr = c * 9 * 128 + lane;

        float xv;
        xv = __ldg(xc + 0);
        a0 = fmaf(xv, __ldg(wt + wr +   0), a0);
        a1 = fmaf(xv, __ldg(wt + wr +  32), a1);
        a2 = fmaf(xv, __ldg(wt + wr +  64), a2);
        a3 = fmaf(xv, __ldg(wt + wr +  96), a3);

        xv = __ldg(xc + 1); wr += 128;
        a0 = fmaf(xv, __ldg(wt + wr +   0), a0);
        a1 = fmaf(xv, __ldg(wt + wr +  32), a1);
        a2 = fmaf(xv, __ldg(wt + wr +  64), a2);
        a3 = fmaf(xv, __ldg(wt + wr +  96), a3);

        xv = __ldg(xc + 2); wr += 128;
        a0 = fmaf(xv, __ldg(wt + wr +   0), a0);
        a1 = fmaf(xv, __ldg(wt + wr +  32), a1);
        a2 = fmaf(xv, __ldg(wt + wr +  64), a2);
        a3 = fmaf(xv, __ldg(wt + wr +  96), a3);

        xc += W; wr += 128;
        xv = __ldg(xc + 0);
        a0 = fmaf(xv, __ldg(wt + wr +   0), a0);
        a1 = fmaf(xv, __ldg(wt + wr +  32), a1);
        a2 = fmaf(xv, __ldg(wt + wr +  64), a2);
        a3 = fmaf(xv, __ldg(wt + wr +  96), a3);

        xv = __ldg(xc + 1); wr += 128;
        a0 = fmaf(xv, __ldg(wt + wr +   0), a0);
        a1 = fmaf(xv, __ldg(wt + wr +  32), a1);
        a2 = fmaf(xv, __ldg(wt + wr +  64), a2);
        a3 = fmaf(xv, __ldg(wt + wr +  96), a3);

        xv = __ldg(xc + 2); wr += 128;
        a0 = fmaf(xv, __ldg(wt + wr +   0), a0);
        a1 = fmaf(xv, __ldg(wt + wr +  32), a1);
        a2 = fmaf(xv, __ldg(wt + wr +  64), a2);
        a3 = fmaf(xv, __ldg(wt + wr +  96), a3);

        xc += W; wr += 128;
        xv = __ldg(xc + 0);
        a0 = fmaf(xv, __ldg(wt + wr +   0), a0);
        a1 = fmaf(xv, __ldg(wt + wr +  32), a1);
        a2 = fmaf(xv, __ldg(wt + wr +  64), a2);
        a3 = fmaf(xv, __ldg(wt + wr +  96), a3);

        xv = __ldg(xc + 1); wr += 128;
        a0 = fmaf(xv, __ldg(wt + wr +   0), a0);
        a1 = fmaf(xv, __ldg(wt + wr +  32), a1);
        a2 = fmaf(xv, __ldg(wt + wr +  64), a2);
        a3 = fmaf(xv, __ldg(wt + wr +  96), a3);

        xv = __ldg(xc + 2); wr += 128;
        a0 = fmaf(xv, __ldg(wt + wr +   0), a0);
        a1 = fmaf(xv, __ldg(wt + wr +  32), a1);
        a2 = fmaf(xv, __ldg(wt + wr +  64), a2);
        a3 = fmaf(xv, __ldg(wt + wr +  96), a3);
    }

    a0 *= scale; a1 *= scale; a2 *= scale; a3 *= scale;
    float m = fminf(fminf(a0, a1), fminf(a2, a3));
    m = warp_min(m);
    if (lane == 0) out[pix] = m;
}

torch::Tensor fused_conv_min_forward(torch::Tensor x, torch::Tensor weight,
                                     torch::Tensor bias, double scale) {
    TORCH_CHECK(x.is_cuda() && weight.is_cuda() && bias.is_cuda(), "all tensors must be CUDA tensors");
    TORCH_CHECK(x.scalar_type() == torch::kFloat32, "only float32 is supported");
    TORCH_CHECK(weight.scalar_type() == torch::kFloat32 && bias.scalar_type() == torch::kFloat32, "only float32 weights are supported");
    TORCH_CHECK(x.is_contiguous() && weight.is_contiguous() && bias.is_contiguous(), "tensors must be contiguous");
    TORCH_CHECK(x.dim() == 4 && weight.dim() == 4, "bad tensor rank");
    TORCH_CHECK(x.size(1) == 64 && weight.size(0) == 128 && weight.size(1) == 64 && weight.size(2) == 3 && weight.size(3) == 3,
                "this optimized kernel expects C=64, OC=128, K=3");

    const int N = (int)x.size(0);
    const int H = (int)x.size(2);
    const int W = (int)x.size(3);
    auto wt = torch::empty({64 * 9, 128}, x.options());
    auto out = torch::empty({N, 1, H - 2, W - 2}, x.options());

    cudaStream_t stream = at::cuda::getCurrentCUDAStream();
    transform_w_3x3_128_kernel<<<(128*64*9 + 255) / 256, 256, 0, stream>>>(
        weight.data_ptr<float>(), wt.data_ptr<float>());

    const int threads = 256;
    const int warps = threads / 32;
    int64_t num_pix = (int64_t)N * (H - 2) * (W - 2);
    int blocks = (int)((num_pix + warps - 1) / warps);
    conv3x3_min_scale_64_128_kernel<<<blocks, threads, 0, stream>>>(
        x.data_ptr<float>(), wt.data_ptr<float>(), bias.data_ptr<float>(), out.data_ptr<float>(),
        N, H, W, (float)scale);
    return out;
}
'''

_ext = load_inline(
    name='fused_conv_min_ext_v1',
    cpp_sources='',
    cuda_sources=_cuda_src,
    functions=['fused_conv_min_forward'],
    extra_cuda_cflags=['-O3', '--use_fast_math'],
    extra_cflags=['-O3'],
    verbose=False,
)

class ModelNew(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, scale_factor):
        super(ModelNew, self).__init__()
        self.conv = nn.Conv2d(in_channels, out_channels, kernel_size)
        self.scale_factor = float(scale_factor)

    def forward(self, x):
        return _ext.fused_conv_min_forward(x.contiguous(), self.conv.weight.contiguous(), self.conv.bias.contiguous(), self.scale_factor)