Write a custom CUDA kernel to optimize torch.fake_quantize_per_channel_affine.
The mathematical operation is:
out = (clamp(round(x / scale[c] + zero_point[c]), quant_min, quant_max) - zero_point[c]) * scale[c]
Here, c is the channel index derived from the input element's position and the quantization axis.
Specific Constraints:
The zero_point input tensor is of type torch.float32. The kernel must handle floating-point zero points directly without implicit integer casting logic in the arithmetic.
The scale and zero_point tensors are 1D arrays corresponding to the channel dimension.
Optimization Strategy: Fused Kernel with On-the-Fly Indexing
Fused Computation: Implement the entire logic (division, addition, rounding, clamping, subtraction, multiplication) in a single CUDA kernel.
Implicit Broadcasting: Do not broadcast scale and zero_point in memory. Instead, compute the channel_index for every element inside the kernel based on its global index and the stride of the quantization axis.
Formula: c = (global_index / inner_stride) % num_channels.
Vectorized Access: Use float4 (128-bit) load/store instructions for the input x and output tensors to maximize global memory throughput.
Fast Math: Use CUDA intrinsics rintf (round to nearest even), fminf, fmaxf for high-performance arithmetic.
Here's an example to show you the syntax of inline embedding custom CUDA operators in torch: The example given architecture is:   
  
```python
import torch
import torch.nn as nn

BATCH_SIZE = 64
CHANNELS = 64
HEIGHT = 64
WIDTH = 64
SHAPE = (BATCH_SIZE, CHANNELS, HEIGHT, WIDTH)

AXIS = 1
Q_MIN = -128
Q_MAX = 127

class Model(nn.Module):
    def __init__(self, axis, q_min, q_max):
        super(Model, self).__init__()
        self.axis = axis
        self.q_min = q_min
        self.q_max = q_max

    def forward(self, x, scale, zero_point):
        return torch.fake_quantize_per_channel_affine(
            x, scale, zero_point, self.axis, self.q_min, self.q_max
        )

def get_inputs():
    x = torch.randn(SHAPE, dtype=torch.float32)
    # Per-channel parameters
    scale = torch.rand((CHANNELS,), dtype=torch.float32)
    zero_point = torch.randint(0, 10, (CHANNELS,), dtype=torch.int32)
    
    return [x.contiguous(), scale.contiguous(), zero_point.contiguous()]

def get_init_inputs():
    return [AXIS, Q_MIN, Q_MAX]