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

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

struct Layout {
    int ndim;
    int contiguous;
    int64_t sizes[64];
    int64_t strides[64];
};

static Layout make_layout(const torch::Tensor& x) {
    Layout result{};
    result.ndim = x.dim();
    result.contiguous = x.is_contiguous();
    for (int d = 0; d < result.ndim; ++d) {
        result.sizes[d] = x.size(d);
        result.strides[d] = x.stride(d);
    }
    return result;
}

__device__ __forceinline__ int64_t offset(int64_t index, const Layout& layout) {
    if (layout.contiguous) return index;
    int64_t result = 0;
    for (int d = layout.ndim - 1; d >= 0; --d) {
        result += (index % layout.sizes[d]) * layout.strides[d];
        index /= layout.sizes[d];
    }
    return result;
}

__device__ __forceinline__ float block_sum(float value) {
    __shared__ float sums[8];
    const int lane = threadIdx.x & 31;
    const int warp = threadIdx.x >> 5;

    for (int delta = 16; delta > 0; delta >>= 1)
        value += __shfl_down_sync(0xffffffff, value, delta);

    if (lane == 0) sums[warp] = value;
    __syncthreads();

    value = threadIdx.x < 8 ? sums[lane] : 0.0f;
    if (warp == 0) {
        for (int delta = 16; delta > 0; delta >>= 1)
            value += __shfl_down_sync(0xffffffff, value, delta);
    }
    return value;
}

__global__ void hinge_reduce(
    const float* __restrict__ predictions,
    const float* __restrict__ targets,
    float* __restrict__ output,
    int64_t count,
    Layout prediction_layout,
    Layout target_layout,
    bool final_output
) {
    float sum = 0.0f;
    const int64_t step = int64_t(blockDim.x) * gridDim.x;

    for (int64_t i = int64_t(blockIdx.x) * blockDim.x + threadIdx.x;
         i < count; i += step) {
        const float p = predictions[offset(i, prediction_layout)];
        const float t = targets[offset(i, target_layout)];
        const float margin = __fsub_rn(1.0f, __fmul_rn(p, t));
        sum += margin < 0.0f ? 0.0f : margin;
    }

    sum = block_sum(sum);
    if (threadIdx.x == 0) {
        output[blockIdx.x] = final_output
            ? (count == 0 ? nanf("") : sum / static_cast<float>(count))
            : sum;
    }
}

__global__ void finish_reduce(
    const float* __restrict__ partials,
    float* __restrict__ output,
    int blocks,
    int64_t count
) {
    float sum = 0.0f;
    for (int i = threadIdx.x; i < blocks; i += blockDim.x)
        sum += partials[i];

    sum = block_sum(sum);
    if (threadIdx.x == 0)
        output[0] = sum / static_cast<float>(count);
}

torch::Tensor hinge_cuda(torch::Tensor predictions, torch::Tensor targets) {
    TORCH_CHECK(predictions.is_cuda() && targets.is_cuda(),
                "Inputs must be CUDA tensors");
    TORCH_CHECK(predictions.device() == targets.device(),
                "Inputs must be on the same device");
    TORCH_CHECK(predictions.scalar_type() == at::kFloat &&
                targets.scalar_type() == at::kFloat,
                "Inputs must have dtype float32");
    TORCH_CHECK(predictions.dim() <= 64 && targets.dim() <= 64,
                "At most 64 dimensions are supported");

    const c10::cuda::CUDAGuard guard(predictions.device());
    auto expanded = at::broadcast_tensors({predictions, targets});
    const auto& p = expanded[0];
    const auto& t = expanded[1];

    const int64_t count = p.numel();
    auto output = torch::empty({}, predictions.options());
    const auto stream = at::cuda::getCurrentCUDAStream();

    const int blocks = count <= 4096
        ? 1
        : static_cast<int>(std::min<int64_t>(1024, (count + 1023) / 1024));

    if (blocks == 1) {
        hinge_reduce<<<1, 256, 0, stream>>>(
            p.data_ptr<float>(), t.data_ptr<float>(), output.data_ptr<float>(),
            count, make_layout(p), make_layout(t), true);
        C10_CUDA_KERNEL_LAUNCH_CHECK();
    } else {
        auto partials = torch::empty({blocks}, predictions.options());
        hinge_reduce<<<blocks, 256, 0, stream>>>(
            p.data_ptr<float>(), t.data_ptr<float>(), partials.data_ptr<float>(),
            count, make_layout(p), make_layout(t), false);
        C10_CUDA_KERNEL_LAUNCH_CHECK();

        finish_reduce<<<1, 256, 0, stream>>>(
            partials.data_ptr<float>(), output.data_ptr<float>(), blocks, count);
        C10_CUDA_KERNEL_LAUNCH_CHECK();
    }

    return output;
}
"""

_hinge_extension = load_inline(
    name="fused_hinge_loss_cuda",
    cpp_sources=(
        "torch::Tensor hinge_cuda(torch::Tensor predictions, "
        "torch::Tensor targets);"
    ),
    cuda_sources=_cuda_source,
    functions=["hinge_cuda"],
    extra_cflags=["-O3"],
    extra_cuda_cflags=["-O3"],
    verbose=False,
)


class ModelNew(nn.Module):
    def __init__(self):
        super().__init__()

    def forward(self, predictions, targets):
        return _hinge_extension.hinge_cuda(predictions, targets)
```