Source code for auto_adpq.class_format
"""Dataclasses for Auto_AdpQ configuration and quantized weights."""
from dataclasses import dataclass
from typing import Optional, Tuple, Union
import numpy as np
from pydantic import BaseModel
[docs]
class AutoAdpQConfig(BaseModel):
"""Configuration for Auto_AdpQ.
Attributes:
group_size (int): Number of elements in a group for group-wise
quantization. Must be between 1 and 65535 (inclusive).
n_iters (int): Maximum number of iterations for outlier detection.
alpha (float): Target fraction (0..1) of entries considered outliers.
device (str): Device string (e.g. "cpu" or "cuda"). Informational.
q_bit (int): Quantization bitwidth (e.g. 4 for 4-bit quantization).
data_packing (bool): If True, multiple quantized values are packed
into 32-bit integers; otherwise plain int8 arrays are used.
symmetrical_quantization (bool): If True, use symmetric quantization
(no zeropoint). If False, use asymmetric quantization with
zeropoints.
target_layers (Optional[Tuple[str, ...]]): Tuple of layer names to
quantize. If None, all linear layers are quantized.
Raises:
ValueError: If `group_size` or `n_iters` are out of valid ranges.
"""
group_size: int = 128
n_iters: int = 100
alpha: float = 0.08
device: str = "cpu"
q_bit: int = 4
data_packing: bool = True
symmetrical_quantization: bool = True
target_layers: Optional[Tuple[str, ...]] = (
"q_proj",
"k_proj",
"v_proj",
"o_proj",
"up_proj",
"down_proj",
"gate_proj",
)
def __init__(self, **kwargs):
"""Init ADPQ config.
Raises:
ValueError: if the group_size is not between 1 and 65536.
ValueError: if n_iters is not positive.
"""
super().__init__(**kwargs)
if self.group_size <= 0:
raise ValueError("group_size must be a positive integer.")
if self.n_iters <= 0:
raise ValueError("n_iters must be a positive integer.")
if self.group_size > 2**16:
raise ValueError("group_size too large, must be less than 65536.")
def __eq__(self, value) -> bool:
"""Check equality for the object.
Args:
value (AutoAdpQConfig): Another AutoAdpQConfig instance to compare.
Returns:
bool: True if all configuration attributes are equal, False otherwise.
"""
return (
self.group_size == value.group_size
and self.n_iters == value.n_iters
and self.alpha == value.alpha
and self.device == value.device
and self.q_bit == value.q_bit
and self.data_packing == value.data_packing
and self.symmetrical_quantization == value.symmetrical_quantization
)
[docs]
@dataclass(frozen=True) # frozen=True makes it immutable (optional but safer)
class AdpQQuantizedWeights:
"""Container for AdpQ quantization outputs.
Attributes:
original_shape (Optional[tuple[int, ...]]): Original shape of the
matrix passed to `AdpQ_quantize`. Used to reshape reconstructed
output back to original shape.
group_num (int): Number of groups after reshaping to (-1, group_size).
scale (Union[list[float], np.ndarray]): Per-group scale values. In
practice an array of shape (group_num, 2) where second column is
for outliers.
zeropoint (Optional[Union[list[float], np.ndarray]]): Per-group
zeropoints (None when symmetric quantization is used).
quantized_vector (Union[list[list[int]], np.ndarray]): Quantized
integer vectors for each group (group_num x group_size).
outlier_indices (Union[list[list[int]], np.ndarray]): Per-group list
of outlier indices or sentinel values.
Raises:
ValueError: If lengths of lists do not match `group_num`.
TODO: Currently, there is a major overhead when creating a new object
to validate the field. Since it is used internally only, we could ditch
the Pydantic module but would need to ensure proper dump and load function.
"""
group_num: int
scale: Union[list[float], np.ndarray]
quantized_vector: Union[list[list[int]], np.ndarray]
outlier_indices: Union[list[list[int]], np.ndarray]
original_shape: Optional[Tuple[int, ...]] = None
zeropoint: Optional[Union[list[float], np.ndarray]] = None
# Optional: If you really need that check, use __post_init__
# This runs faster than Pydantic validators because there is no pydantic overhead
def __post_init__(self):
"""Post init.
Check for the right size of the values.
Raises:
ValueError: if mismatched dimensions are found. Groups must match
group_num.
TODO: sometimes when loading from npz, I get zeropoint as np.array(None)
"""
# Only run this if you suspect bugs in your generation logic
if (
len(self.scale) != self.group_num
or len(self.quantized_vector) != self.group_num
or len(self.outlier_indices) != self.group_num
):
raise ValueError("Dimensions mismatch")
if self.zeropoint is not None:
# Meaning it is an array which is not none, can have np.array(None)
if type(self.zeropoint) is np.ndarray:
if self.zeropoint.ndim != 0 and len(self.zeropoint) != self.group_num:
raise ValueError("Dimensions mismatch for zeropoint")
elif len(self.zeropoint) != self.group_num:
raise ValueError("Dimensions mismatch for zeropoint")