```python
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.cpp_extension import load_inline


_cpp_source = r"""
#include <torch/extension.h>
#include <vector>

std::vector<torch::Tensor> prepare_conv_cuda(
    torch::Tensor input,
    torch::Tensor weight);
"""

_cuda_source = r"""
#include <torch/extension.h>
#include <ATen/cuda/CUDAContext.h>
#include <c10/cuda/CUDAGuard.h>
#include <c10/cuda/CUDAException.h>
#include <cuda_runtime.h>
#include <vector>

constexpr int CHANNELS = 64;
constexpr int TILE = 64;
constexpr int THREADS = 256;

// Transpose tiles of the [channels, spatial] matrix into NHWC storage.
__global__ __launch_bounds__(THREADS)
void nchw_to_nhwc(
    const float* __restrict__ input,
    float* __restrict__ output,
    int64_t spatial)
{
    __shared__ float tile[TILE][TILE + 1];

    const int tid = threadIdx.x;
    const int64_t position_base = int64_t(blockIdx.x) * TILE;
    const int64_t batch_base = int64_t(blockIdx.y) * CHANNELS * spatial;

    #pragma unroll
    for (int i = tid; i < TILE * TILE; i += THREADS) {
        const int channel = i / TILE;
        const int position = i % TILE;
        const int64_t p = position_base + position;
        tile[channel][position] =
            p < spatial ? input[batch_base + channel * spatial + p] : 0.0f;
    }

    __syncthreads();

    #pragma unroll
    for (int i = tid; i < TILE * TILE; i += THREADS) {
        const int position = i / TILE;
        const int channel = i % TILE;
        const int64_t p = position_base + position;
        if (p < spatial) {
            output[batch_base + p * CHANNELS + channel] =
                tile[channel][position];
        }
    }
}

// A stride-one transposed convolution is a forward convolution with
// exchanged input/output channels and spatially reversed filters.
// Produce the forward filter directly in channels-last storage.
__global__ void prepare_weight(
    const float* __restrict__ input,
    float* __restrict__ output)
{
    const int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i >= CHANNELS * CHANNELS * 9) {
        return;
    }

    const int ci = i % CHANNELS;
    const int kw = (i / CHANNELS) % 3;
    const int kh = (i / (CHANNELS * 3)) % 3;
    const int co = i / (CHANNELS * 9);

    output[i] = input[
        (ci * CHANNELS + co) * 9 + (2 - kh) * 3 + (2 - kw)];
}

std::vector<torch::Tensor> prepare_conv_cuda(
    torch::Tensor input,
    torch::Tensor weight)
{
    TORCH_CHECK(input.is_cuda() && weight.is_cuda(),
                "Expected CUDA tensors");
    TORCH_CHECK(input.device() == weight.device(),
                "Input and weight must be on the same device");
    TORCH_CHECK(input.scalar_type() == at::kFloat &&
                weight.scalar_type() == at::kFloat,
                "Expected float32 tensors");
    TORCH_CHECK(input.dim() == 4 && input.size(1) == CHANNELS,
                "Expected an input with 64 channels");
    TORCH_CHECK(input.is_contiguous() ||
                input.is_contiguous(at::MemoryFormat::ChannelsLast),
                "Unsupported input layout");
    TORCH_CHECK(weight.is_contiguous() &&
                weight.dim() == 4 &&
                weight.size(0) == CHANNELS &&
                weight.size(1) == CHANNELS &&
                weight.size(2) == 3 &&
                weight.size(3) == 3,
                "Expected contiguous [64, 64, 3, 3] weights");
    TORCH_CHECK(input.size(0) > 0 && input.size(0) <= 65535 &&
                input.size(2) > 0 && input.size(3) > 0,
                "Unsupported input dimensions");

    c10::cuda::CUDAGuard guard(input.device());
    cudaStream_t stream = at::cuda::getCurrentCUDAStream();

    torch::Tensor converted_input;
    if (input.is_contiguous(at::MemoryFormat::ChannelsLast)) {
        converted_input = input;
    } else {
        converted_input = torch::empty(
            input.sizes(),
            input.options().memory_format(at::MemoryFormat::ChannelsLast));

        const int64_t spatial = input.size(2) * input.size(3);
        TORCH_CHECK((spatial + TILE - 1) / TILE <= 2147483647LL,
                    "Input is too large");

        const dim3 grid(
            static_cast<unsigned>((spatial + TILE - 1) / TILE),
            static_cast<unsigned>(input.size(0)));

        nchw_to_nhwc<<<grid, THREADS, 0, stream>>>(
            input.data_ptr<float>(),
            converted_input.data_ptr<float>(),
            spatial);
        C10_CUDA_KERNEL_LAUNCH_CHECK();
    }

    auto converted_weight = torch::empty(
        {CHANNELS, CHANNELS, 3, 3},
        weight.options().memory_format(at::MemoryFormat::ChannelsLast));

    prepare_weight<<<
        (CHANNELS * CHANNELS * 9 + THREADS - 1) / THREADS,
        THREADS, 0, stream>>>(
            weight.data_ptr<float>(),
            converted_weight.data_ptr<float>());
    C10_CUDA_KERNEL_LAUNCH_CHECK();

    return {converted_input, converted_weight};
}
"""

_extension = load_inline(
    name="transpose_conv64_forward_nhwc_v5",
    cpp_sources=_cpp_source,
    cuda_sources=_cuda_source,
    functions=["prepare_conv_cuda"],
    extra_cflags=["-O3"],
    extra_cuda_cflags=["-O3"],
    verbose=False,
)


class _TransposedConv(torch.autograd.Function):
    @staticmethod
    def forward(ctx, x, weight, bias):
        ctx.save_for_backward(x, weight)
        ctx.has_bias = bias is not None

        converted_x, converted_weight = _extension.prepare_conv_cuda(
            x, weight
        )
        return F.conv2d(
            converted_x,
            converted_weight,
            bias,
            stride=1,
            padding=2,
        )

    @staticmethod
    def backward(ctx, grad_output):
        x, weight = ctx.saved_tensors
        grad_x = grad_weight = grad_bias = None

        if ctx.needs_input_grad[0]:
            grad_x = F.conv2d(
                grad_output, weight, stride=1, padding=0
            )

        if ctx.needs_input_grad[1]:
            grad_weight = torch.nn.grad.conv2d_weight(
                grad_output,
                weight.shape,
                x,
                stride=1,
                padding=0,
                dilation=1,
                groups=1,
            )

        if ctx.has_bias and ctx.needs_input_grad[2]:
            grad_bias = grad_output.sum(dim=(0, 2, 3))

        return grad_x, grad_weight, grad_bias


class ModelNew(nn.Module):
    def __init__(
        self,
        in_channels: int,
        out_channels: int,
        kernel_size: int,
        stride: int = 1,
        padding: int = 0,
        output_padding: int = 0,
        groups: int = 1,
        bias: bool = False,
    ):
        super().__init__()
        self.conv_transpose2d = nn.ConvTranspose2d(
            in_channels,
            out_channels,
            kernel_size,
            stride=stride,
            padding=padding,
            output_padding=output_padding,
            groups=groups,
            bias=bias,
        )

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

        if (
            x.is_cuda
            and x.ndim == 4
            and x.dtype == torch.float32
            and (
                x.is_contiguous()
                or x.is_contiguous(memory_format=torch.channels_last)
            )
            and 0 < x.shape[0] <= 65535
            and x.shape[1] == 64
            and x.shape[2] > 0
            and x.shape[3] > 0
            and x.shape[2] * x.shape[3] <= 2147483647
            and conv.in_channels == 64
            and conv.out_channels == 64
            and conv.kernel_size == (3, 3)
            and conv.stride == (1, 1)
            and conv.padding == (0, 0)
            and conv.output_padding == (0, 0)
            and conv.dilation == (1, 1)
            and conv.groups == 1
            and conv.weight.device == x.device
            and conv.weight.dtype == torch.float32
            and conv.weight.is_contiguous()
            and (
                conv.bias is None
                or (
                    conv.bias.device == x.device
                    and conv.bias.dtype == torch.float32
                    and conv.bias.is_contiguous()
                )
            )
            and not torch.is_autocast_enabled()
        ):
            return _TransposedConv.apply(x, conv.weight, conv.bias)

        return conv(x)
```