```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>

torch::Tensor transpose_conv3d_weight_cuda(
    torch::Tensor weight, int64_t groups);
"""

_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>

template <typename scalar_t>
__global__ void transform_weight(
    const scalar_t* __restrict__ source,
    scalar_t* __restrict__ destination,
    int64_t count,
    int64_t input_channels_per_group,
    int64_t output_channels_per_group,
    int64_t kernel_volume)
{
    for (int64_t index =
             static_cast<int64_t>(blockIdx.x) * blockDim.x + threadIdx.x;
         index < count;
         index += static_cast<int64_t>(blockDim.x) * gridDim.x) {
        // Destination storage: [out_channel, depth, height, width, in_channel].
        const int64_t input_local = index % input_channels_per_group;
        const int64_t position = index / input_channels_per_group;
        const int64_t kernel_index = position % kernel_volume;
        const int64_t output_channel = position / kernel_volume;
        const int64_t group = output_channel / output_channels_per_group;
        const int64_t output_local =
            output_channel % output_channels_per_group;
        const int64_t input_channel =
            group * input_channels_per_group + input_local;

        const int64_t source_index =
            (input_channel * output_channels_per_group + output_local)
                * kernel_volume
            + kernel_volume - 1 - kernel_index;

        destination[index] = source[source_index];
    }
}

torch::Tensor transpose_conv3d_weight_cuda(
    torch::Tensor weight, int64_t groups)
{
    TORCH_CHECK(weight.is_cuda(), "weight must be a CUDA tensor");
    TORCH_CHECK(weight.dim() == 5, "weight must have five dimensions");
    TORCH_CHECK(weight.is_contiguous(), "weight must be contiguous");
    TORCH_CHECK(groups > 0 && weight.size(0) % groups == 0,
                "invalid groups");

    const c10::cuda::CUDAGuard device_guard(weight.device());

    const int64_t input_channels_per_group = weight.size(0) / groups;
    const int64_t output_channels_per_group = weight.size(1);
    const int64_t output_channels = output_channels_per_group * groups;
    const int64_t kd = weight.size(2);
    const int64_t kh = weight.size(3);
    const int64_t kw = weight.size(4);

    auto storage = torch::empty(
        {output_channels, kd, kh, kw, input_channels_per_group},
        weight.options());
    auto transformed = storage.permute({0, 4, 1, 2, 3});

    const int64_t count = weight.numel();
    if (count == 0) {
        return transformed;
    }

    constexpr int threads = 256;
    const int64_t required_blocks = (count + threads - 1) / threads;
    const int blocks =
        static_cast<int>(required_blocks < 4096 ? required_blocks : 4096);
    const auto stream = at::cuda::getCurrentCUDAStream();

    AT_DISPATCH_FLOATING_TYPES_AND2(
        at::ScalarType::Half,
        at::ScalarType::BFloat16,
        weight.scalar_type(),
        "transpose_conv3d_weight_cuda",
        [&] {
            transform_weight<scalar_t><<<blocks, threads, 0, stream>>>(
                weight.data_ptr<scalar_t>(),
                storage.data_ptr<scalar_t>(),
                count,
                input_channels_per_group,
                output_channels_per_group,
                kd * kh * kw);
        });

    C10_CUDA_KERNEL_LAUNCH_CHECK();
    return transformed;
}
"""

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


class _TransformWeight(torch.autograd.Function):
    @staticmethod
    def forward(ctx, weight, groups):
        ctx.groups = groups
        ctx.original_shape = tuple(weight.shape)
        return _extension.transpose_conv3d_weight_cuda(
            weight.contiguous(), groups
        )

    @staticmethod
    def backward(ctx, grad_output):
        ic, oc_per_group, kd, kh, kw = ctx.original_shape
        groups = ctx.groups
        grad_weight = (
            grad_output.reshape(
                groups, oc_per_group, ic // groups, kd, kh, kw
            )
            .permute(0, 2, 1, 3, 4, 5)
            .flip((3, 4, 5))
            .reshape(ctx.original_shape)
        )
        return grad_weight, None


class ModelNew(nn.Module):
    def __init__(
        self,
        in_channels: int,
        out_channels: int,
        kernel_size: tuple,
        stride: tuple = (1, 1, 1),
        padding: tuple = (0, 0, 0),
        output_padding: tuple = (0, 0, 0),
        groups: int = 1,
        bias: bool = False,
    ):
        super().__init__()
        self.conv_transpose3d = nn.ConvTranspose3d(
            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_transpose3d
        forward_padding = tuple(
            k - 1 - p for k, p in zip(conv.kernel_size, conv.padding)
        )

        if (
            not x.is_cuda
            or x.ndim != 5
            or conv.stride != (1, 1, 1)
            or conv.output_padding != (0, 0, 0)
            or any(p < 0 for p in forward_padding)
            or x.dtype not in (
                torch.float16,
                torch.bfloat16,
                torch.float32,
                torch.float64,
            )
        ):
            return conv(x)

        # Unit-stride transposed convolution is an ordinary convolution
        # with exchanged channel axes and spatially reversed weights.
        weight = _TransformWeight.apply(conv.weight, conv.groups)
        x = x.contiguous(memory_format=torch.channels_last_3d)

        return F.conv3d(
            x,
            weight,
            conv.bias,
            stride=1,
            padding=forward_padding,
            dilation=1,
            groups=conv.groups,
        )
```