```python
import torch
import torch.nn as nn
import triton
import triton.language as tl


@triton.jit
def _asymmetric_conv_kernel(
    X, W, Bias, Y,
    C: tl.constexpr,
    H: tl.constexpr,
    IW: tl.constexpr,
    OC: tl.constexpr,
    KH: tl.constexpr,
    KW: tl.constexpr,
    OH: tl.constexpr,
    OW: tl.constexpr,
    SH: tl.constexpr,
    SW: tl.constexpr,
    PH: tl.constexpr,
    PW: tl.constexpr,
    DH: tl.constexpr,
    DW: tl.constexpr,
    GROUPS: tl.constexpr,
    HAS_BIAS: tl.constexpr,
    BM: tl.constexpr,
    BN: tl.constexpr,
    BK: tl.constexpr,
):
    spatial = OH * OW
    channels_per_group = C // GROUPS
    outputs_per_group = OC // GROUPS
    reduction = channels_per_group * KH * KW

    batch_group = tl.program_id(2)
    batch = batch_group // GROUPS
    group = batch_group % GROUPS

    oc = tl.program_id(1) * BM + tl.arange(0, BM)
    positions = tl.program_id(0) * BN + tl.arange(0, BN)
    rk = tl.arange(0, BK)

    input_h = (positions // OW) * SH - PH
    input_w = (positions % OW) * SW - PW
    global_oc = group * outputs_per_group + oc

    input_base = (
        batch.to(tl.int64) * C + group * channels_per_group
    ) * H * IW
    weight_base = global_oc.to(tl.int64)[:, None] * reduction

    accumulator = tl.zeros((BM, BN), dtype=tl.float32)

    for block in range(tl.cdiv(reduction, BK)):
        k = block * BK + rk
        ic = k // (KH * KW)
        kh = (k // KW) % KH
        kw = k % KW

        weights = tl.load(
            W + weight_base + k[None, :],
            mask=(oc[:, None] < outputs_per_group)
            & (k[None, :] < reduction),
            other=0.0,
        )

        ih = input_h[None, :] + kh[:, None] * DH
        iw = input_w[None, :] + kw[:, None] * DW
        input_offsets = (
            input_base
            + ic[:, None].to(tl.int64) * H * IW
            + ih * IW
            + iw
        )
        values = tl.load(
            X + input_offsets,
            mask=(k[:, None] < reduction)
            & (positions[None, :] < spatial)
            & (ih >= 0)
            & (ih < H)
            & (iw >= 0)
            & (iw < IW),
            other=0.0,
        )

        accumulator = tl.dot(
            weights,
            values,
            accumulator,
            input_precision="tf32x3",
        )

    if HAS_BIAS:
        bias = tl.load(
            Bias + global_oc,
            mask=oc < outputs_per_group,
            other=0.0,
        )
        accumulator += bias[:, None]

    output_offsets = (
        (batch.to(tl.int64) * OC + global_oc[:, None]) * spatial
        + positions[None, :]
    )
    tl.store(
        Y + output_offsets,
        accumulator,
        mask=(oc[:, None] < outputs_per_group)
        & (positions[None, :] < spatial),
    )


class ModelNew(nn.Module):
    def __init__(
        self,
        in_channels: int,
        out_channels: int,
        kernel_size: tuple,
        stride: int = 1,
        padding: int = 0,
        dilation: int = 1,
        groups: int = 1,
        bias: bool = False,
    ):
        super().__init__()
        self.conv2d = nn.Conv2d(
            in_channels,
            out_channels,
            kernel_size,
            stride=stride,
            padding=padding,
            dilation=dilation,
            groups=groups,
            bias=bias,
        )

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        conv = self.conv2d
        weight = conv.weight

        if (
            not x.is_cuda
            or x.dtype != torch.float32
            or weight.dtype != torch.float32
            or weight.device != x.device
            or x.ndim != 4
            or isinstance(conv.padding, str)
            or torch.is_autocast_enabled()
        ):
            return conv(x)

        if torch.cuda.get_device_capability(x.device)[0] < 8:
            return conv(x)

        batch, channels, height, width = x.shape
        if channels != conv.in_channels or batch * conv.groups > 65535:
            return conv(x)

        kh, kw = conv.kernel_size
        sh, sw = conv.stride
        ph, pw = conv.padding
        dh, dw = conv.dilation

        oh = (height + 2 * ph - dh * (kh - 1) - 1) // sh + 1
        ow = (width + 2 * pw - dw * (kw - 1) - 1) // sw + 1
        if oh <= 0 or ow <= 0:
            return conv(x)

        output = torch.empty(
            (batch, conv.out_channels, oh, ow),
            device=x.device,
            dtype=x.dtype,
        )
        if batch == 0:
            return output

        x = x.contiguous()
        weight = weight.contiguous()
        bias = conv.bias
        if bias is not None:
            bias = bias.contiguous()

        bm, bn, bk = 64, 128, 32
        grid = (
            triton.cdiv(oh * ow, bn),
            triton.cdiv(conv.out_channels // conv.groups, bm),
            batch * conv.groups,
        )

        with torch.cuda.device(x.device):
            _asymmetric_conv_kernel[grid](
                x,
                weight,
                bias if bias is not None else weight,
                output,
                C=channels,
                H=height,
                IW=width,
                OC=conv.out_channels,
                KH=kh,
                KW=kw,
                OH=oh,
                OW=ow,
                SH=sh,
                SW=sw,
                PH=ph,
                PW=pw,
                DH=dh,
                DW=dw,
                GROUPS=conv.groups,
                HAS_BIAS=bias is not None,
                BM=bm,
                BN=bn,
                BK=bk,
                num_warps=8,
                num_stages=2,
            )

        return output
```