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

_cpp_source = r"""
#include <torch/extension.h>
torch::Tensor hinge_loss_cuda(torch::Tensor predictions, torch::Tensor targets);
"""

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

struct BroadcastInfo {
    int ndim;
    int64_t sizes[64];
    int64_t prediction_strides[64];
    int64_t target_strides[64];
};

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

    #pragma unroll
    for (int offset = 16; offset > 0; offset >>= 1)
        value += __shfl_down_sync(0xffffffff, value, offset);

    if (lane == 0)
        warp_sums[warp] = value;

    __syncthreads();

    if (warp == 0) {
        value = lane < 8 ? warp_sums[lane] : 0.0f;
        #pragma unroll
        for (int offset = 16; offset > 0; offset >>= 1)
            value += __shfl_down_sync(0xffffffff, value, offset);
    }
    return value;
}

template<int Mode>
__global__ void hinge_reduce(
    const float* __restrict__ predictions,
    const float* __restrict__ targets,
    float* __restrict__ partials,
    int64_t count,
    int64_t target_period,
    BroadcastInfo info
) {
    float sum = 0.0f;
    const int64_t step = static_cast<int64_t>(blockDim.x) * gridDim.x;

    for (int64_t i = static_cast<int64_t>(blockIdx.x) * blockDim.x
                       + threadIdx.x;
         i < count;
         i += step) {
        int64_t prediction_offset = i;
        int64_t target_offset;

        if (Mode == 0) {
            target_offset = i;
        } else if (Mode == 1) {
            target_offset = i % target_period;
        } else {
            int64_t remaining = i;
            prediction_offset = 0;
            target_offset = 0;
            for (int d = info.ndim - 1; d >= 0; --d) {
                const int64_t coordinate = remaining % info.sizes[d];
                remaining /= info.sizes[d];
                prediction_offset += coordinate * info.prediction_strides[d];
                target_offset += coordinate * info.target_strides[d];
            }
        }

        const float product = __fmul_rn(
            predictions[prediction_offset], targets[target_offset]);
        const float margin = __fsub_rn(1.0f, product);
        sum += margin < 0.0f ? 0.0f : margin;
    }

    sum = block_sum(sum);
    if (threadIdx.x == 0)
        partials[blockIdx.x] =
            gridDim.x == 1 ? sum / static_cast<float>(count) : sum;
}

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

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

torch::Tensor hinge_loss_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 float32 dtype");

    c10::cuda::CUDAGuard guard(predictions.device());

    // Match PyTorch's broadcasting rules without materializing expanded data.
    auto expanded = at::broadcast_tensors({predictions, targets});
    const auto& p = expanded[0];
    const auto& t = expanded[1];
    const int64_t count = p.numel();

    if (count == 0) {
        return torch::full(
            {}, std::numeric_limits<float>::quiet_NaN(),
            predictions.options());
    }

    BroadcastInfo info{};
    TORCH_CHECK(p.dim() <= 64, "At most 64 dimensions are supported");
    info.ndim = static_cast<int>(p.dim());
    for (int d = 0; d < info.ndim; ++d) {
        info.sizes[d] = p.size(d);
        info.prediction_strides[d] = p.stride(d);
        info.target_strides[d] = t.stride(d);
    }

    int mode = 2;
    int64_t target_period = 1;
    if (p.is_contiguous() && t.is_contiguous()) {
        mode = 0;
    } else if (p.is_contiguous() && targets.is_contiguous() &&
               (targets.numel() == 1 ||
                (targets.dim() == 1 && p.dim() > 0 &&
                 targets.numel() == p.size(p.dim() - 1)))) {
        mode = 1;
        target_period = targets.numel();
    }

    constexpr int threads = 256;
    const int blocks = static_cast<int>(
        std::min<int64_t>(1024, (count + 1023) / 1024));

    auto output = torch::empty({}, predictions.options());
    auto partials = blocks == 1
        ? output
        : torch::empty({blocks}, predictions.options());

    const auto stream = at::cuda::getCurrentCUDAStream();

    if (mode == 0) {
        hinge_reduce<0><<<blocks, threads, 0, stream>>>(
            p.data_ptr<float>(), t.data_ptr<float>(),
            partials.data_ptr<float>(), count, target_period, info);
    } else if (mode == 1) {
        hinge_reduce<1><<<blocks, threads, 0, stream>>>(
            p.data_ptr<float>(), t.data_ptr<float>(),
            partials.data_ptr<float>(), count, target_period, info);
    } else {
        hinge_reduce<2><<<blocks, threads, 0, stream>>>(
            p.data_ptr<float>(), t.data_ptr<float>(),
            partials.data_ptr<float>(), count, target_period, info);
    }
    C10_CUDA_KERNEL_LAUNCH_CHECK();

    if (blocks > 1) {
        finish_reduce<<<1, threads, 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=_cpp_source,
    cuda_sources=_cuda_source,
    functions=["hinge_loss_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_loss_cuda(predictions, targets)
```