gpt-5.5-priority · original · l123_single_gpt
correct 83_conv_depthwise_2D_square_input_asymmetric_kernel.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
0.38 μs
ref runtime
1.45 μs
speedup
3.85x
started
2026-05-05T01:18:37Z
timingkernelbench.timing
kernel runtime
0.38 μs
ref runtime
1.45 μs
speedup
3.85x
kernel mean / median / std
0.38 / — / 0.05 μs
ref mean / median / std
1.45 / — / 0.03 μs
numerical precisioneval_kernel_against_ref
max abs error
5.96e-08
mean abs error
5.16e-09
max rel error
6.58
mean rel error
1.68e-07
gpu memoryextended_metrics.measure_memory
kernel peak
1055.00 MB
ref peak
1055.00 MB
kernel / ref
1.00x
kernel launches / fusiontorch.profiler
kernel launches
1
ref launches
6
fusion ratio (ref / kernel)
6.00x
total cuda time
22 μs
SOL (speed-of-light)nsight_hardware_counters
SOL score (0–1)
0.879
dram util %
87.9%
compute util %
6.3%
bottleneck
memory-bound
dominant pipe
FP32
arithmetic intensity
—
ridge point
20.00
roofline / occupancynsight_hardware_counters
dram bandwidth
2945.78 GB/s
dram util %
87.9%
fp32 tflops
—
fp32 util %
—
fp16 tflops
—
occupancy %
87.1%
bottleneck
memory-bound
dominant pipe %
6.3%
arithmetic intensity
—
ridge point
20.00
L1 hit %
—
L2 hit %
55.5%
ld sectors / req
11.07
st sectors / req
—
regs / thread
32
smem / block
1024 B
block size
128
peak bw
3350.00 GB/s
peak fp32 tflops
67.00
peak fp16 tflops
1979.00
warp stalls (top 5)nsight
long_scoreboard
6353.8%
mio_throttle
1314.6%
short_scoreboard
589.2%
reference PyTorch source (the prompt the agent sees)
import torch
import torch.nn as nn

class Model(nn.Module):
    """
    Performs a depthwise 2D convolution with a square input and an asymmetric kernel.

    Args:
        in_channels (int): Number of channels in the input tensor.
        kernel_size (int): Size of the convolution kernel.
        stride (int, optional): Stride of the convolution. Defaults to 1.
        padding (int, optional): Padding applied to the input. Defaults to 0.
        dilation (int, optional): Spacing between kernel elements. Defaults to 1.
        bias (bool, optional): If `True`, adds a learnable bias to the output. Defaults to `False`.
    """
    def __init__(self, in_channels: int, kernel_size: int, stride: int = 1, padding: int = 0, dilation: int = 1, bias: bool = False):
        super(Model, self).__init__()
        self.conv2d = nn.Conv2d(in_channels, in_channels, kernel_size=(kernel_size, 1), stride=stride, padding=padding, dilation=dilation, groups=in_channels, bias=bias)
        
    def forward(self, x: torch.Tensor) -> torch.Tensor:
        """
        Performs the depthwise 2D convolution.

        Args:
            x (torch.Tensor): Input tensor of shape (batch_size, in_channels, height, width).

        Returns:
            torch.Tensor: Output tensor of shape (batch_size, in_channels, height_out, width_out).
        """
        return self.conv2d(x)

# Test code
batch_size = 64
in_channels = 8
kernel_size = 3
width = 512
height = 512
stride = 1
padding = 0
dilation = 1

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

def get_init_inputs():
    return [in_channels, kernel_size, stride, padding, dilation]
turn 0 · FINAL · 1 tool call(s) · 79.1s turn · 79.1s LLM · 11,391 chars (click to expand)
turn 079.1s turn · 79.1s LLM · 0.0s tools · FINAL
reasoning
**Considering performance in PyTorch** I need to focus on speed, especially since I'm working with PyTorch. The convolution functions might be optimized using cuDNN, which usually helps performance. However, I'm wondering if depthwise convolutions could be slower than expected. It's crucial to analyze and compare their performance closely to find the best balance. I'll dig into potential optimizations later to ensure I'm using the best techniques for my needs. **Evaluating device compatibility** I need to consider that a device mismatch would occur unless the model is on CUDA. It seems like evaluation is likely going to happen on CUDA as well. Once I'm sure about that compatibility issue, I suppose the next step would be to confirm and then submit. It's a straightforward consideration, but I want to make sure everything aligns before taking that next step!
call → submit_kernel
callsubmit_kernel { "kernel_code": "import torch\nimport torch.nn as nn\nfrom torch.utils.cpp_extension import load_inline\n\n_cpp_src = r'''\n#include <torch/extension.h>\n\ntorch::Tensor vertical_conv_forward(torch::Tensor x,\n torch::Tensor weight,\n torch::Tensor bias,\n int64_t stride,\n int64_t padding,\n int64_t dilation);\n'''\n\n_cuda_src = r'''\n#include <torch/extension.h>\n#include <ATen/cuda/CUDAContext.h>\n#include <c10/cuda/CUDAGuard.h>\n#include <cuda.h>\n#include <cuda_runtime.h>\n\n__global__ void dw_vertical_k3_s1p0_vec4_kernel(const float* __restrict__ x,\n const float* __restrict__ w,\n const float* __restrict__ bias,\n float* __restrict__ out,\n int N, int C, int H, int W,\n int Hout, int Wout,\n bool has_bias) {\n int ow = (blockIdx.x * blockDim.x + threadIdx.x) * 4;\n int oh0 = blockIdx.y * 4;\n int nc = blockIdx.z;\n if (ow >= Wout || oh0 >= Hout) return;\n\n int c = nc % C;\n float w0 = w[c * 3 + 0];\n float w1 = w[c * 3 + 1];\n float w2 = w[c * 3 + 2];\n float bv = has_bias ? bias[c] : 0.0f;\n\n const float* in = x + ((int64_t)nc * H) * W;\n float* o = out + ((int64_t)nc * Hout) * Wout;\n\n // Fast path for the overwhelmingly common full 4-column by 4-row tile.\n if (ow + 3 < Wout && oh0 + 3 < Hout) {\n const float4 r0 = *reinterpret_cast<const float4*>(in + (int64_t)(oh0 + 0) * W + ow);\n const float4 r1 = *reinterpret_cast<const float4*>(in + (int64_t)(oh0 + 1) * W + ow);\n const float4 r2 = *reinterpret_cast<const float4*>(in + (int64_t)(oh0 + 2) * W + ow);\n const float4 r3 = *reinterpret_cast<const float4*>(in + (int64_t)(oh0 + 3) * W + ow);\n const float4 r4 = *reinterpret_cast<const float4*>(in + (int64_t)(oh0 + 4) * W + ow);\n const float4 r5 = *reinterpret_cast<const float4*>(in + (int64_t)(oh0 + 5) * W + ow);\n\n float4 y0, y1, y2, y3;\n y0.x = r0.x*w0 + r1.x*w1 + r2.x*w2 + bv;\n y0.y = r0.y*w0 + r1.y*w1 + r2.y*w2 + bv;\n y0.z = r0.z*w0 + r1.z*w1 + r2.z*w2 + bv;\n y0.w = r0.w*w0 + r1.w*w1 + r2.w*w2 + bv;\n\n y1.x = r1.x*w0 + r2.x*w1 + r3.x*w2 + bv;\n y1.y = r1.y*w0 + r2.y*w1 + r3.y*w2 + bv;\n y1.z = r1.z*w0 + r2.z*w1 + r3.z*w2 + bv;\n y1.w = r1.w*w0 + r2.w*w1 + r3.w*w2 + bv;\n\n y2.x = r2.x*w0 + r3.x*w1 + r4.x*w2 + bv;\n y2.y = r2.y*w0 + r3.y*w1 + r4.y*w2 + bv;\n y2.z = r2.z*w0 + r3.z*w1 + r4.z*w2 + bv;\n y2.w = r2.w*w0 + r3.w*w1 + r4.w*w2 + bv;\n\n y3.x = r3.x*w0 + r4.x*w1 + r5.x*w2 + bv;\n y3.y = r3.y*w0 + r4.y*w1 + r5.y*w2 + bv;\n y3.z = r3.z*w0 + r4.z*w1 + r5.z*w2 + bv;\n y3.w = r3.w*w0 + r4.w*w1 + r5.w*w2 + bv;\n\n *reinterpret_cast<float4*>(o + (int64_t)(oh0 + 0) * Wout + ow) = y0;\n *reinterpret_cast<float4*>(o + (int64_t)(oh0 + 1) * Wout + ow) = y1;\n *reinterpret_cast<float4*>(o + (int64_t)(oh0 + 2) * Wout + ow) = y2;\n *reinterpret_cast<float4*>(o + (int64_t)(oh0 + 3) * Wout + ow) = y3;\n } else {\n // Boundary tiles (only the last row tile for the benchmark shape).\n #pragma unroll\n for (int rr = 0; rr < 4; ++rr) {\n int oh = oh0 + rr;\n if (oh < Hout) {\n #pragma unroll\n for (int jj = 0; jj < 4; ++jj) {\n int col = ow + jj;\n if (col < Wout) {\n float v = in[(int64_t)(oh + 0) * W + col] * w0\n + in[(int64_t)(oh + 1) * W + col] * w1\n + in[(int64_t)(oh + 2) * W + col] * w2\n + bv;\n o[(int64_t)oh * Wout + col] = v;\n }\n }\n }\n }\n }\n}\n\n__global__ void dw_vertical_generic_kernel(const float* __restrict__ x,\n const float* __restrict__ w,\n const float* __restrict__ bias,\n float* __restrict__ out,\n int N, int C, int H, int W,\n int Hout, int Wout, int K,\n int stride, int padding, int dilation,\n bool has_bias,\n int64_t total) {\n int64_t idx = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;\n if (idx >= total) return;\n\n int ow = idx % Wout;\n int64_t t = idx / Wout;\n int oh = t % Hout;\n t /= Hout;\n int c = t % C;\n int n = t / C;\n\n int ih0 = oh * stride - padding;\n int iw = ow * stride - padding;\n float sum = 0.0f;\n if ((unsigned)iw < (unsigned)W) {\n for (int k = 0; k < K; ++k) {\n int ih = ih0 + k * dilation;\n if ((unsigned)ih < (unsigned)H) {\n sum += x[((int64_t)n * C + c) * H * W + (int64_t)ih * W + iw] * w[c * K + k];\n }\n }\n }\n if (has_bias) sum += bias[c];\n out[idx] = sum;\n}\n\ntorch::Tensor vertical_conv_forward(torch::Tensor x,\n torch::Tensor weight,\n torch::Tensor bias,\n int64_t stride64,\n int64_t padding64,\n int64_t dilation64) {\n TORCH_CHECK(x.is_cuda(), \"x must be CUDA\");\n TORCH_CHECK(weight.is_cuda(), \"weight must be CUDA\");\n TORCH_CHECK(x.scalar_type() == torch::kFloat32, \"only float32 is supported\");\n TORCH_CHECK(weight.scalar_type() == torch::kFloat32, \"only float32 weights are supported\");\n c10::cuda::CUDAGuard device_guard(x.device());\n\n auto xc = x.contiguous();\n auto wc = weight.contiguous();\n auto bc = bias.numel() ? bias.contiguous() : bias;\n\n int N = (int)xc.size(0);\n int C = (int)xc.size(1);\n int H = (int)xc.size(2);\n int W = (int)xc.size(3);\n int K = (int)wc.size(2);\n int stride = (int)stride64;\n int padding = (int)padding64;\n int dilation = (int)dilation64;\n\n int Hout = (H + 2 * padding - dilation * (K - 1) - 1) / stride + 1;\n int Wout = (W + 2 * padding - 1) / stride + 1;\n auto out = torch::empty({N, C, Hout, Wout}, xc.options());\n\n bool has_bias = (bc.numel() > 0);\n const float* bptr = has_bias ? bc.data_ptr<float>() : nullptr;\n cudaStream_t stream = at::cuda::getCurrentCUDAStream();\n\n if (K == 3 && stride == 1 && padding == 0 && dilation == 1 && Wout == W) {\n dim3 block(128);\n dim3 grid((Wout + (int)block.x * 4 - 1) / ((int)block.x * 4),\n (Hout + 3) / 4,\n N * C);\n dw_vertical_k3_s1p0_vec4_kernel<<<grid, block, 0, stream>>>(\n xc.data_ptr<float>(), wc.data_ptr<float>(), bptr, out.data_ptr<float>(),\n N, C, H, W, Hout, Wout, has_bias);\n } else {\n int64_t total = (int64_t)N * C * Hout * Wout;\n dim3 block(256);\n dim3 grid((unsigned int)((total + block.x - 1) / block.x));\n dw_vertical_generic_kernel<<<grid, block, 0, stream>>>(\n xc.data_ptr<float>(), wc.data_ptr<float>(), bptr, out.data_ptr<float>(),\n N, C, H, W, Hout, Wout, K, stride, padding, dilation, has_bias, total);\n }\n return out;\n}\n'''\n\n_ext = load_inline(\n name=\"dw_vertical_conv_inline_ext_v1\",\n cpp_sources=_cpp_src,\n cuda_sources=_cuda_src,\n functions=[\"vertical_conv_forward\"],\n extra_cflags=[\"-O3\"],\n extra_cuda_cflags=[\"-O3\"],\n verbose=False,\n)\n\nclass ModelNew(nn.Module):\n def __init__(self, in_channels: int, kernel_size: int, stride: int = 1, padding: int = 0, dilation: int = 1, bias: bool = False):\n super(ModelNew, self).__init__()\n self.conv2d = nn.Conv2d(in_channels, in_channels, kernel_size=(kernel_size, 1), stride=stride, padding=padding, dilation=dilation, groups=in_channels, bias=bias)\n self._stride_i = stride if isinstance(stride, int) else stride[0]\n self._padding_i = padding if isinstance(padding, int) else padding[0]\n self._dilation_i = dilation if isinstance(dilation, int) else dilation[0]\n self.register_buffer(\"_empty_bias\", torch.empty(0), persistent=False)\n\n def forward(self, x: torch.Tensor) -> torch.Tensor:\n b = self.conv2d.bias\n if b is None:\n b = self._empty_bias\n if b.device != x.device or b.dtype != x.dtype:\n b = x.new_empty((0,))\n return _ext.vertical_conv_forward(x, self.conv2d.weight, b, self._stride_i, self._padding_i, self._dilation_i)\n" }
result ← submit_kernel (ok)
oksubmit_kernel submit_kernel PASSED: (5 / 5) correctness trials all passed. Kernel runtime: 0.38 μs Runtime stats: mean=0.38μs median=?μs std=0.05μs Numerical precision: max_abs_err=5.96e-08 mean_abs_err=5.16e-09 max_rel_err=6.58e+00 Memory: 1055.0 MB peak (ref: 1055.0 MB, ratio: 1.0x) Kernel launches: 1 (ref: 6, fusion ratio: 6.0) SOL score: 0.8793
final submitted kernel (213 lines, 8,928 chars) — full source, untruncated
import torch
import torch.nn as nn
from torch.utils.cpp_extension import load_inline

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

torch::Tensor vertical_conv_forward(torch::Tensor x,
                                    torch::Tensor weight,
                                    torch::Tensor bias,
                                    int64_t stride,
                                    int64_t padding,
                                    int64_t dilation);
'''

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

__global__ void dw_vertical_k3_s1p0_vec4_kernel(const float* __restrict__ x,
                                                const float* __restrict__ w,
                                                const float* __restrict__ bias,
                                                float* __restrict__ out,
                                                int N, int C, int H, int W,
                                                int Hout, int Wout,
                                                bool has_bias) {
    int ow = (blockIdx.x * blockDim.x + threadIdx.x) * 4;
    int oh0 = blockIdx.y * 4;
    int nc = blockIdx.z;
    if (ow >= Wout || oh0 >= Hout) return;

    int c = nc % C;
    float w0 = w[c * 3 + 0];
    float w1 = w[c * 3 + 1];
    float w2 = w[c * 3 + 2];
    float bv = has_bias ? bias[c] : 0.0f;

    const float* in = x + ((int64_t)nc * H) * W;
    float* o = out + ((int64_t)nc * Hout) * Wout;

    // Fast path for the overwhelmingly common full 4-column by 4-row tile.
    if (ow + 3 < Wout && oh0 + 3 < Hout) {
        const float4 r0 = *reinterpret_cast<const float4*>(in + (int64_t)(oh0 + 0) * W + ow);
        const float4 r1 = *reinterpret_cast<const float4*>(in + (int64_t)(oh0 + 1) * W + ow);
        const float4 r2 = *reinterpret_cast<const float4*>(in + (int64_t)(oh0 + 2) * W + ow);
        const float4 r3 = *reinterpret_cast<const float4*>(in + (int64_t)(oh0 + 3) * W + ow);
        const float4 r4 = *reinterpret_cast<const float4*>(in + (int64_t)(oh0 + 4) * W + ow);
        const float4 r5 = *reinterpret_cast<const float4*>(in + (int64_t)(oh0 + 5) * W + ow);

        float4 y0, y1, y2, y3;
        y0.x = r0.x*w0 + r1.x*w1 + r2.x*w2 + bv;
        y0.y = r0.y*w0 + r1.y*w1 + r2.y*w2 + bv;
        y0.z = r0.z*w0 + r1.z*w1 + r2.z*w2 + bv;
        y0.w = r0.w*w0 + r1.w*w1 + r2.w*w2 + bv;

        y1.x = r1.x*w0 + r2.x*w1 + r3.x*w2 + bv;
        y1.y = r1.y*w0 + r2.y*w1 + r3.y*w2 + bv;
        y1.z = r1.z*w0 + r2.z*w1 + r3.z*w2 + bv;
        y1.w = r1.w*w0 + r2.w*w1 + r3.w*w2 + bv;

        y2.x = r2.x*w0 + r3.x*w1 + r4.x*w2 + bv;
        y2.y = r2.y*w0 + r3.y*w1 + r4.y*w2 + bv;
        y2.z = r2.z*w0 + r3.z*w1 + r4.z*w2 + bv;
        y2.w = r2.w*w0 + r3.w*w1 + r4.w*w2 + bv;

        y3.x = r3.x*w0 + r4.x*w1 + r5.x*w2 + bv;
        y3.y = r3.y*w0 + r4.y*w1 + r5.y*w2 + bv;
        y3.z = r3.z*w0 + r4.z*w1 + r5.z*w2 + bv;
        y3.w = r3.w*w0 + r4.w*w1 + r5.w*w2 + bv;

        *reinterpret_cast<float4*>(o + (int64_t)(oh0 + 0) * Wout + ow) = y0;
        *reinterpret_cast<float4*>(o + (int64_t)(oh0 + 1) * Wout + ow) = y1;
        *reinterpret_cast<float4*>(o + (int64_t)(oh0 + 2) * Wout + ow) = y2;
        *reinterpret_cast<float4*>(o + (int64_t)(oh0 + 3) * Wout + ow) = y3;
    } else {
        // Boundary tiles (only the last row tile for the benchmark shape).
        #pragma unroll
        for (int rr = 0; rr < 4; ++rr) {
            int oh = oh0 + rr;
            if (oh < Hout) {
                #pragma unroll
                for (int jj = 0; jj < 4; ++jj) {
                    int col = ow + jj;
                    if (col < Wout) {
                        float v = in[(int64_t)(oh + 0) * W + col] * w0
                                + in[(int64_t)(oh + 1) * W + col] * w1
                                + in[(int64_t)(oh + 2) * W + col] * w2
                                + bv;
                        o[(int64_t)oh * Wout + col] = v;
                    }
                }
            }
        }
    }
}

__global__ void dw_vertical_generic_kernel(const float* __restrict__ x,
                                           const float* __restrict__ w,
                                           const float* __restrict__ bias,
                                           float* __restrict__ out,
                                           int N, int C, int H, int W,
                                           int Hout, int Wout, int K,
                                           int stride, int padding, int dilation,
                                           bool has_bias,
                                           int64_t total) {
    int64_t idx = (int64_t)blockIdx.x * blockDim.x + threadIdx.x;
    if (idx >= total) return;

    int ow = idx % Wout;
    int64_t t = idx / Wout;
    int oh = t % Hout;
    t /= Hout;
    int c = t % C;
    int n = t / C;

    int ih0 = oh * stride - padding;
    int iw = ow * stride - padding;
    float sum = 0.0f;
    if ((unsigned)iw < (unsigned)W) {
        for (int k = 0; k < K; ++k) {
            int ih = ih0 + k * dilation;
            if ((unsigned)ih < (unsigned)H) {
                sum += x[((int64_t)n * C + c) * H * W + (int64_t)ih * W + iw] * w[c * K + k];
            }
        }
    }
    if (has_bias) sum += bias[c];
    out[idx] = sum;
}

torch::Tensor vertical_conv_forward(torch::Tensor x,
                                    torch::Tensor weight,
                                    torch::Tensor bias,
                                    int64_t stride64,
                                    int64_t padding64,
                                    int64_t dilation64) {
    TORCH_CHECK(x.is_cuda(), "x must be CUDA");
    TORCH_CHECK(weight.is_cuda(), "weight must be CUDA");
    TORCH_CHECK(x.scalar_type() == torch::kFloat32, "only float32 is supported");
    TORCH_CHECK(weight.scalar_type() == torch::kFloat32, "only float32 weights are supported");
    c10::cuda::CUDAGuard device_guard(x.device());

    auto xc = x.contiguous();
    auto wc = weight.contiguous();
    auto bc = bias.numel() ? bias.contiguous() : bias;

    int N = (int)xc.size(0);
    int C = (int)xc.size(1);
    int H = (int)xc.size(2);
    int W = (int)xc.size(3);
    int K = (int)wc.size(2);
    int stride = (int)stride64;
    int padding = (int)padding64;
    int dilation = (int)dilation64;

    int Hout = (H + 2 * padding - dilation * (K - 1) - 1) / stride + 1;
    int Wout = (W + 2 * padding - 1) / stride + 1;
    auto out = torch::empty({N, C, Hout, Wout}, xc.options());

    bool has_bias = (bc.numel() > 0);
    const float* bptr = has_bias ? bc.data_ptr<float>() : nullptr;
    cudaStream_t stream = at::cuda::getCurrentCUDAStream();

    if (K == 3 && stride == 1 && padding == 0 && dilation == 1 && Wout == W) {
        dim3 block(128);
        dim3 grid((Wout + (int)block.x * 4 - 1) / ((int)block.x * 4),
                  (Hout + 3) / 4,
                  N * C);
        dw_vertical_k3_s1p0_vec4_kernel<<<grid, block, 0, stream>>>(
            xc.data_ptr<float>(), wc.data_ptr<float>(), bptr, out.data_ptr<float>(),
            N, C, H, W, Hout, Wout, has_bias);
    } else {
        int64_t total = (int64_t)N * C * Hout * Wout;
        dim3 block(256);
        dim3 grid((unsigned int)((total + block.x - 1) / block.x));
        dw_vertical_generic_kernel<<<grid, block, 0, stream>>>(
            xc.data_ptr<float>(), wc.data_ptr<float>(), bptr, out.data_ptr<float>(),
            N, C, H, W, Hout, Wout, K, stride, padding, dilation, has_bias, total);
    }
    return out;
}
'''

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

class ModelNew(nn.Module):
    def __init__(self, in_channels: int, kernel_size: int, stride: int = 1, padding: int = 0, dilation: int = 1, bias: bool = False):
        super(ModelNew, self).__init__()
        self.conv2d = nn.Conv2d(in_channels, in_channels, kernel_size=(kernel_size, 1), stride=stride, padding=padding, dilation=dilation, groups=in_channels, bias=bias)
        self._stride_i = stride if isinstance(stride, int) else stride[0]
        self._padding_i = padding if isinstance(padding, int) else padding[0]
        self._dilation_i = dilation if isinstance(dilation, int) else dilation[0]
        self.register_buffer("_empty_bias", torch.empty(0), persistent=False)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        b = self.conv2d.bias
        if b is None:
            b = self._empty_bias
            if b.device != x.device or b.dtype != x.dtype:
                b = x.new_empty((0,))
        return _ext.vertical_conv_forward(x, self.conv2d.weight, b, self._stride_i, self._padding_i, self._dilation_i)