def quantize_per_channel(
    t: torch.Tensor, dtype, scales=None, zero_points=None, sym_quant=False
):
    r"""
    Quantize a weight tensor of Linear modules per channel.
    Assume the tensor shape is [output channel, input channel],
    each output channel has its own quantization parameters.

    Args:
        input: The tensor to be quantized
        dtype: data type of the quantized tensor, int8, int4 or nf4
        scales: Scales for quantization. If None, find by min/max.
        zero_points: zero points for quantization. If None, find by min/max.
        sym_quant: symmetric or asymmetric quantization

    Returns:
        A tuple of
        - The quantized tensor
        - Scales
        - Zero points
    """
    assert t.ndim == 2
    assert dtype in (WoqWeightDtype.INT8, WoqWeightDtype.INT4, WoqWeightDtype.NF4)
    assert not (
        dtype == WoqWeightDtype.NF4 and not sym_quant
    ), "NF4 must be symmetric quant"

    def get_qparams(scales, zps):
        if scales is not None and (sym_quant or zps is not None):
            return scales, zps
        eps = torch.tensor([torch.finfo(torch.float32).eps])
        zeros = torch.zeros(t.shape[0], dtype=t.dtype, device=t.device)
        mins = torch.minimum(t.min(dim=1)[0], zeros)
        maxs = torch.maximum(t.max(dim=1)[0], zeros)
        zps = None
        if dtype == WoqWeightDtype.INT8:
            if sym_quant:
                scales = torch.maximum(torch.abs(maxs), torch.abs(mins)) / 127
                scales = torch.max(scales, eps)
            else:
                scales = (maxs - mins) / 255
                scales = torch.max(scales, eps)
                zps = -torch.round(mins / scales)
                zps -= 128
        elif dtype == WoqWeightDtype.INT4:
            if sym_quant:
                scales = torch.maximum(torch.abs(maxs), torch.abs(mins)) / 7
                scales = torch.max(scales, eps)
            else:
                scales = (maxs - mins) / 15
                scales = torch.max(scales, eps)
                zps = -torch.round(mins / scales)
        else:  # NF4
            scales = torch.maximum(torch.abs(maxs), torch.abs(mins))
            scales = torch.max(scales, eps)
        return scales, zps

    scales, zps = get_qparams(scales, zero_points)
    inv_scales = 1 / scales.unsqueeze(1)
    if dtype == WoqWeightDtype.INT8:
        qmin = -128
        qmax = 127
        qt = torch.clamp(
            torch.round(t * inv_scales) + (zps.unsqueeze(1) if zps is not None else 0),
            min=qmin,
            max=qmax,
        ).to(torch.int8)
    elif dtype == WoqWeightDtype.INT4:
        qmin = 0
        qmax = 15
        # for sym_quant, shift to 0-15 for storage
        qt = torch.clamp(
            torch.round(t * inv_scales) + (zps.unsqueeze(1) if not sym_quant else 8),
            min=qmin,
            max=qmax,
        ).to(torch.uint8)
    else:  # NF4
        qt = map_float_tensor_to_nf4(t * inv_scales)
    if is_4bit(dtype):
        if qt.size(-1) % 2:
            qt = torch.nn.functional.pad(qt, (0, 1), value=0)
        qt = qt[:, 1::2].bitwise_left_shift(4).bitwise_or_(qt[:, ::2].bitwise_and(0xF))
    return qt.contiguous(), scales, zps

    Quantize a weight tensor of Linear modules per channel.
    Assume the tensor shape is [output channel, input channel],
    each output channel has its own quantization parameters.

    Args:
        input: The tensor to be quantized
        dtype: data type of the quantized tensor, int8, int4 or nf4
        scales: Scales for quantization. If None, find by min/max.
        zero_points: zero points for quantization. If None, find by min/max.
        sym_quant: symmetric or asymmetric quantization

    Returns:
        A tuple of
        - The quantized tensor
        - Scales
        - Zero points
    
intel_extension_for_pytorch/quantization/_quantize_utils.py