DeanoCalver's picture
Initial DotCache Arena Space upload
751ad26 verified
Raw
History Blame Contribute Delete
5.7 kB
from __future__ import annotations
from math import ceil
import numpy as np
from .m0_affine import pad_last_dim
def _quantize_lut_flat_values(
flat_values: np.ndarray,
*,
levels: int,
refine_steps: int,
preconditioner: str,
precondition_strength: float,
) -> tuple[np.ndarray, np.ndarray]:
fit_values = flat_values.astype(np.float32, copy=False)
restore_mean = np.float32(0.0)
restore_scale = np.float32(1.0)
if preconditioner == "tanh":
restore_mean = np.float32(np.mean(flat_values, dtype=np.float64))
centered = flat_values - restore_mean
restore_scale = np.float32(np.std(centered, dtype=np.float64))
if restore_scale < np.float32(1e-6):
restore_scale = np.float32(1.0)
fit_values = np.tanh(centered / (restore_scale * np.float32(precondition_strength))).astype(np.float32, copy=False)
elif preconditioner != "none":
raise ValueError("unsupported preconditioner")
lut = np.quantile(fit_values, np.linspace(0.0, 1.0, num=levels, dtype=np.float32)).astype(np.float32)
if levels > 1:
for _ in range(refine_steps):
boundaries = (lut[:-1] + lut[1:]) * np.float32(0.5)
codes = np.searchsorted(boundaries, fit_values, side="left").astype(np.int32)
counts = np.bincount(codes, minlength=levels)
sums = np.bincount(codes, weights=fit_values.astype(np.float64, copy=False), minlength=levels)
updated = lut.copy()
valid = counts > 0
updated[valid] = (sums[valid] / counts[valid]).astype(np.float32, copy=False)
if np.allclose(updated, lut, atol=1e-6, rtol=0.0):
lut = updated
break
lut = updated
boundaries = (lut[:-1] + lut[1:]) * np.float32(0.5)
codes = np.searchsorted(boundaries, fit_values, side="left").astype(np.uint8, copy=False)
else:
codes = np.zeros_like(fit_values, dtype=np.uint8)
if preconditioner == "tanh":
lut = np.clip(lut, -0.999, 0.999)
lut = (
np.arctanh(lut).astype(np.float32) * np.float32(restore_scale * precondition_strength)
+ np.float32(restore_mean)
)
return codes, lut
def _quantize_lut_segment_matrix(
segment_values: np.ndarray,
*,
levels: int,
refine_steps: int,
preconditioner: str,
precondition_strength: float,
) -> tuple[np.ndarray, np.ndarray]:
group_count = int(segment_values.shape[0])
token_count = int(segment_values.shape[1])
group_size = int(segment_values.shape[2])
codes = np.zeros((group_count, token_count * group_size), dtype=np.uint8)
lut = np.zeros((group_count, levels), dtype=np.float32)
flat_values = segment_values.reshape(group_count, token_count * group_size)
for group_index in range(group_count):
group_codes, group_lut = _quantize_lut_flat_values(
flat_values[group_index],
levels=levels,
refine_steps=refine_steps,
preconditioner=preconditioner,
precondition_strength=precondition_strength,
)
codes[group_index] = group_codes
lut[group_index] = group_lut
return codes.reshape(segment_values.shape), lut
def quantize_tensor_lut(
values: np.ndarray,
*,
group_size: int,
bits: int,
segment_count: int = 1,
refine_steps: int = 6,
preconditioner: str = "none",
precondition_strength: float = 2.0,
) -> tuple[np.ndarray, np.ndarray, int]:
array = np.asarray(values, dtype=np.float32)
if array.ndim != 2:
raise ValueError("values must have shape [token_count, head_dim]")
token_count, head_dim = array.shape
segment_count = max(1, min(int(segment_count), token_count))
num_groups = ceil(head_dim / group_size)
padded_head_dim = num_groups * group_size
padded = pad_last_dim(array, padded_head_dim)
grouped = padded.reshape(token_count, num_groups, group_size)
levels = 1 << bits
codebooks = np.zeros((num_groups, segment_count, levels), dtype=np.float32)
codes = np.zeros((token_count, num_groups, group_size), dtype=np.uint8)
segment_slices = np.array_split(np.arange(token_count, dtype=np.int32), segment_count)
grouped_by_group = np.transpose(grouped, (1, 0, 2))
for segment_index, token_indices in enumerate(segment_slices):
segment_values = grouped_by_group[:, token_indices, :]
segment_codes, segment_lut = _quantize_lut_segment_matrix(
segment_values,
levels=levels,
refine_steps=refine_steps,
preconditioner=preconditioner,
precondition_strength=precondition_strength,
)
codes[token_indices] = np.transpose(np.clip(segment_codes, 0, levels - 1), (1, 0, 2))
codebooks[:, segment_index] = segment_lut
return codes, codebooks, padded_head_dim
def dequantize_group_lut(codes: np.ndarray, *, codebook: np.ndarray) -> np.ndarray:
code_array = np.asarray(codes, dtype=np.int64)
lut = np.asarray(codebook, dtype=np.float32)
if lut.ndim == 1:
return lut[code_array]
if lut.ndim == 2 and code_array.ndim == 2:
token_count = code_array.shape[0]
segment_count = lut.shape[0]
if segment_count == 1:
return lut[0][code_array]
segment_ids = (np.arange(token_count, dtype=np.int64) * segment_count) // max(token_count, 1)
return lut[segment_ids[:, None], code_array]
if lut.ndim == 2 and code_array.ndim == 1 and lut.shape[0] == code_array.shape[0]:
return lut[np.arange(lut.shape[0]), code_array]
raise ValueError("unsupported codebook shape for LUT decode")