2024-05-13 18:10:51 +00:00
|
|
|
from __future__ import annotations
|
2024-08-08 17:33:09 +00:00
|
|
|
from abc import ABC, abstractmethod
|
|
|
|
from typing import Any, Callable, Sequence
|
2024-05-13 18:10:51 +00:00
|
|
|
|
|
|
|
from numpy.typing import DTypeLike
|
|
|
|
|
2024-08-09 03:11:42 +00:00
|
|
|
from .constants import GGML_QUANT_SIZES, GGMLQuantizationType, QK_K
|
2024-05-13 18:10:51 +00:00
|
|
|
from .lazy import LazyNumpyTensor
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
|
2024-08-08 17:33:09 +00:00
|
|
|
def quant_shape_to_byte_shape(shape: Sequence[int], quant_type: GGMLQuantizationType) -> tuple[int, ...]:
|
2024-05-25 01:11:48 +00:00
|
|
|
block_size, type_size = GGML_QUANT_SIZES[quant_type]
|
|
|
|
if shape[-1] % block_size != 0:
|
|
|
|
raise ValueError(f"Quantized tensor row size ({shape[-1]}) is not a multiple of {quant_type.name} block size ({block_size})")
|
|
|
|
return (*shape[:-1], shape[-1] // block_size * type_size)
|
|
|
|
|
|
|
|
|
2024-08-08 17:33:09 +00:00
|
|
|
def quant_shape_from_byte_shape(shape: Sequence[int], quant_type: GGMLQuantizationType) -> tuple[int, ...]:
|
2024-05-25 01:11:48 +00:00
|
|
|
block_size, type_size = GGML_QUANT_SIZES[quant_type]
|
|
|
|
if shape[-1] % type_size != 0:
|
|
|
|
raise ValueError(f"Quantized tensor bytes per row ({shape[-1]}) is not a multiple of {quant_type.name} type size ({type_size})")
|
|
|
|
return (*shape[:-1], shape[-1] // type_size * block_size)
|
|
|
|
|
|
|
|
|
2024-05-13 18:10:51 +00:00
|
|
|
# This is faster than np.vectorize and np.apply_along_axis because it works on more than one row at a time
|
2024-08-08 17:33:09 +00:00
|
|
|
def _apply_over_grouped_rows(func: Callable[[np.ndarray], np.ndarray], arr: np.ndarray, otype: DTypeLike, oshape: tuple[int, ...]) -> np.ndarray:
|
2024-05-13 18:10:51 +00:00
|
|
|
rows = arr.reshape((-1, arr.shape[-1]))
|
|
|
|
osize = 1
|
|
|
|
for dim in oshape:
|
|
|
|
osize *= dim
|
|
|
|
out = np.empty(shape=osize, dtype=otype)
|
|
|
|
# compute over groups of 16 rows (arbitrary, but seems good for performance)
|
2024-07-15 18:50:47 +00:00
|
|
|
n_groups = (rows.shape[0] // 16) or 1
|
2024-05-13 18:10:51 +00:00
|
|
|
np.concatenate([func(group).ravel() for group in np.array_split(rows, n_groups)], axis=0, out=out)
|
|
|
|
return out.reshape(oshape)
|
|
|
|
|
|
|
|
|
|
|
|
# round away from zero
|
|
|
|
# ref: https://stackoverflow.com/a/59143326/22827863
|
|
|
|
def np_roundf(n: np.ndarray) -> np.ndarray:
|
|
|
|
a = abs(n)
|
|
|
|
floored = np.floor(a)
|
|
|
|
b = floored + np.floor(2 * (a - floored))
|
|
|
|
return np.sign(n) * b
|
|
|
|
|
|
|
|
|
2024-08-08 17:33:09 +00:00
|
|
|
class QuantError(Exception): ...
|
2024-05-13 18:10:51 +00:00
|
|
|
|
|
|
|
|
2024-08-08 17:33:09 +00:00
|
|
|
_type_traits: dict[GGMLQuantizationType, type[__Quant]] = {}
|
2024-05-13 18:10:51 +00:00
|
|
|
|
|
|
|
|
2024-08-08 17:33:09 +00:00
|
|
|
def quantize(data: np.ndarray, qtype: GGMLQuantizationType) -> np.ndarray:
|
|
|
|
if qtype == GGMLQuantizationType.F32:
|
|
|
|
return data.astype(np.float32, copy=False)
|
|
|
|
elif qtype == GGMLQuantizationType.F16:
|
|
|
|
return data.astype(np.float16, copy=False)
|
|
|
|
elif (q := _type_traits.get(qtype)) is not None:
|
|
|
|
return q.quantize(data)
|
|
|
|
else:
|
|
|
|
raise NotImplementedError(f"Quantization for {qtype.name} is not yet implemented")
|
2024-05-13 18:10:51 +00:00
|
|
|
|
|
|
|
|
2024-08-08 17:33:09 +00:00
|
|
|
def dequantize(data: np.ndarray, qtype: GGMLQuantizationType) -> np.ndarray:
|
2024-08-09 03:11:42 +00:00
|
|
|
if qtype == GGMLQuantizationType.F32:
|
|
|
|
return data.view(np.float32)
|
|
|
|
elif qtype == GGMLQuantizationType.F16:
|
|
|
|
return data.view(np.float16).astype(np.float32)
|
2024-08-08 17:33:09 +00:00
|
|
|
elif (q := _type_traits.get(qtype)) is not None:
|
|
|
|
return q.dequantize(data)
|
2024-05-13 18:10:51 +00:00
|
|
|
else:
|
2024-08-08 17:33:09 +00:00
|
|
|
raise NotImplementedError(f"Dequantization for {qtype.name} is not yet implemented")
|
|
|
|
|
|
|
|
|
|
|
|
class __Quant(ABC):
|
|
|
|
qtype: GGMLQuantizationType
|
|
|
|
block_size: int
|
|
|
|
type_size: int
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
return TypeError("Quant conversion classes can't have instances")
|
|
|
|
|
|
|
|
def __init_subclass__(cls, qtype: GGMLQuantizationType) -> None:
|
|
|
|
cls.qtype = qtype
|
|
|
|
cls.block_size, cls.type_size = GGML_QUANT_SIZES[qtype]
|
|
|
|
cls.__quantize_lazy = LazyNumpyTensor._wrap_fn(
|
|
|
|
cls.__quantize_array,
|
|
|
|
meta_noop=(np.uint8, cls.__shape_to_bytes)
|
|
|
|
)
|
|
|
|
cls.__dequantize_lazy = LazyNumpyTensor._wrap_fn(
|
|
|
|
cls.__dequantize_array,
|
|
|
|
meta_noop=(np.float32, cls.__shape_from_bytes)
|
|
|
|
)
|
|
|
|
assert qtype not in _type_traits
|
|
|
|
_type_traits[qtype] = cls
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
@abstractmethod
|
|
|
|
def quantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
@abstractmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
raise NotImplementedError
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def quantize_rows(cls, rows: np.ndarray) -> np.ndarray:
|
|
|
|
rows = rows.astype(np.float32, copy=False)
|
|
|
|
shape = rows.shape
|
|
|
|
n_blocks = rows.size // cls.block_size
|
|
|
|
blocks = rows.reshape((n_blocks, cls.block_size))
|
|
|
|
blocks = cls.quantize_blocks(blocks)
|
|
|
|
assert blocks.dtype == np.uint8
|
|
|
|
assert blocks.shape[-1] == cls.type_size
|
|
|
|
return blocks.reshape(cls.__shape_to_bytes(shape))
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dequantize_rows(cls, rows: np.ndarray) -> np.ndarray:
|
|
|
|
rows = rows.view(np.uint8)
|
|
|
|
shape = rows.shape
|
|
|
|
n_blocks = rows.size // cls.type_size
|
|
|
|
blocks = rows.reshape((n_blocks, cls.type_size))
|
|
|
|
blocks = cls.dequantize_blocks(blocks)
|
|
|
|
assert blocks.dtype == np.float32
|
|
|
|
assert blocks.shape[-1] == cls.block_size
|
|
|
|
return blocks.reshape(cls.__shape_from_bytes(shape))
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __shape_to_bytes(cls, shape: Sequence[int]):
|
|
|
|
return quant_shape_to_byte_shape(shape, cls.qtype)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __shape_from_bytes(cls, shape: Sequence[int]):
|
|
|
|
return quant_shape_from_byte_shape(shape, cls.qtype)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __quantize_array(cls, array: np.ndarray) -> np.ndarray:
|
|
|
|
return _apply_over_grouped_rows(cls.quantize_rows, arr=array, otype=np.uint8, oshape=cls.__shape_to_bytes(array.shape))
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __dequantize_array(cls, array: np.ndarray) -> np.ndarray:
|
|
|
|
return _apply_over_grouped_rows(cls.dequantize_rows, arr=array, otype=np.float32, oshape=cls.__shape_from_bytes(array.shape))
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __quantize_lazy(cls, lazy_tensor: LazyNumpyTensor, /) -> Any:
|
|
|
|
pass
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __dequantize_lazy(cls, lazy_tensor: LazyNumpyTensor, /) -> Any:
|
|
|
|
pass
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def can_quantize(cls, tensor: np.ndarray | LazyNumpyTensor) -> bool:
|
|
|
|
return tensor.shape[-1] % cls.block_size == 0
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def quantize(cls, tensor: np.ndarray | LazyNumpyTensor) -> np.ndarray:
|
|
|
|
if not cls.can_quantize(tensor):
|
|
|
|
raise QuantError(f"Can't quantize tensor with shape {tensor.shape} to {cls.qtype.name}")
|
|
|
|
if isinstance(tensor, LazyNumpyTensor):
|
|
|
|
return cls.__quantize_lazy(tensor)
|
|
|
|
else:
|
|
|
|
return cls.__quantize_array(tensor)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dequantize(cls, tensor: np.ndarray | LazyNumpyTensor) -> np.ndarray:
|
|
|
|
if isinstance(tensor, LazyNumpyTensor):
|
|
|
|
return cls.__dequantize_lazy(tensor)
|
|
|
|
else:
|
|
|
|
return cls.__dequantize_array(tensor)
|
|
|
|
|
|
|
|
|
|
|
|
class BF16(__Quant, qtype=GGMLQuantizationType.BF16):
|
|
|
|
@classmethod
|
|
|
|
# same as ggml_compute_fp32_to_bf16 in ggml-impl.h
|
|
|
|
def quantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n = blocks.view(np.uint32)
|
|
|
|
# force nan to quiet
|
|
|
|
n = np.where((n & 0x7fffffff) > 0x7f800000, (n & np.uint32(0xffff0000)) | np.uint32(64 << 16), n)
|
|
|
|
# round to nearest even
|
|
|
|
n = (np.uint64(n) + (0x7fff + ((n >> 16) & 1))) >> 16
|
|
|
|
return n.astype(np.uint16).view(np.uint8)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
return (blocks.view(np.int16).astype(np.int32) << 16).view(np.float32)
|
|
|
|
|
|
|
|
|
2024-08-09 03:11:42 +00:00
|
|
|
class Q4_0(__Quant, qtype=GGMLQuantizationType.Q4_0):
|
|
|
|
@classmethod
|
|
|
|
def quantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
imax = abs(blocks).argmax(axis=-1, keepdims=True)
|
|
|
|
max = np.take_along_axis(blocks, imax, axis=-1)
|
|
|
|
|
|
|
|
d = max / -8
|
|
|
|
with np.errstate(divide="ignore"):
|
|
|
|
id = np.where(d == 0, 0, 1 / d)
|
|
|
|
# FIXME: Q4_0's reference rounding is cursed and depends on FMA
|
|
|
|
qs = np.trunc((np.float64(blocks) * np.float64(id)) + np.float64(8.5), dtype=np.float32).astype(np.uint8).clip(0, 15)
|
|
|
|
|
|
|
|
qs = qs.reshape((n_blocks, 2, cls.block_size // 2))
|
|
|
|
qs = qs[..., 0, :] | (qs[..., 1, :] << np.uint8(4))
|
|
|
|
|
|
|
|
d = d.astype(np.float16).view(np.uint8)
|
|
|
|
|
|
|
|
return np.concatenate([d, qs], axis=-1)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
d, qs = np.hsplit(blocks, [2])
|
|
|
|
|
|
|
|
d = d.view(np.float16).astype(np.float32)
|
|
|
|
|
|
|
|
qs = qs.reshape((n_blocks, -1, 1, cls.block_size // 2)) >> np.array([0, 4], dtype=np.uint8).reshape((1, 1, 2, 1))
|
|
|
|
qs = (qs & np.uint8(0x0F)).reshape((n_blocks, -1)).astype(np.int8) - np.int8(8)
|
|
|
|
|
|
|
|
return (d * qs.astype(np.float32))
|
|
|
|
|
|
|
|
|
|
|
|
class Q4_1(__Quant, qtype=GGMLQuantizationType.Q4_1):
|
|
|
|
@classmethod
|
|
|
|
def quantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
max = blocks.max(axis=-1, keepdims=True)
|
|
|
|
min = blocks.min(axis=-1, keepdims=True)
|
|
|
|
|
|
|
|
d = (max - min) / 15
|
|
|
|
with np.errstate(divide="ignore"):
|
|
|
|
id = np.where(d == 0, 0, 1 / d)
|
|
|
|
qs = np.trunc((blocks - min) * id + np.float32(0.5), dtype=np.float32).astype(np.uint8).clip(0, 15)
|
|
|
|
|
|
|
|
qs = qs.reshape((n_blocks, 2, cls.block_size // 2))
|
|
|
|
qs = qs[..., 0, :] | (qs[..., 1, :] << np.uint8(4))
|
|
|
|
|
|
|
|
d = d.astype(np.float16).view(np.uint8)
|
|
|
|
m = min.astype(np.float16).view(np.uint8)
|
|
|
|
|
|
|
|
return np.concatenate([d, m, qs], axis=-1)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
d, rest = np.hsplit(blocks, [2])
|
|
|
|
m, qs = np.hsplit(rest, [2])
|
|
|
|
|
|
|
|
d = d.view(np.float16).astype(np.float32)
|
|
|
|
m = m.view(np.float16).astype(np.float32)
|
|
|
|
|
|
|
|
qs = qs.reshape((n_blocks, -1, 1, cls.block_size // 2)) >> np.array([0, 4], dtype=np.uint8).reshape((1, 1, 2, 1))
|
|
|
|
qs = (qs & np.uint8(0x0F)).reshape((n_blocks, -1)).astype(np.float32)
|
|
|
|
|
|
|
|
return (d * qs) + m
|
|
|
|
|
|
|
|
|
|
|
|
class Q5_0(__Quant, qtype=GGMLQuantizationType.Q5_0):
|
|
|
|
@classmethod
|
|
|
|
def quantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
imax = abs(blocks).argmax(axis=-1, keepdims=True)
|
|
|
|
max = np.take_along_axis(blocks, imax, axis=-1)
|
|
|
|
|
|
|
|
d = max / -16
|
|
|
|
with np.errstate(divide="ignore"):
|
|
|
|
id = np.where(d == 0, 0, 1 / d)
|
|
|
|
# FIXME: Q5_0's reference rounding is cursed and depends on FMA
|
|
|
|
q = np.trunc((np.float64(blocks) * np.float64(id)) + np.float64(16.5), dtype=np.float32).astype(np.uint8).clip(0, 31)
|
|
|
|
|
|
|
|
qs = q.reshape((n_blocks, 2, cls.block_size // 2))
|
|
|
|
qs = (qs[..., 0, :] & np.uint8(0x0F)) | (qs[..., 1, :] << np.uint8(4))
|
|
|
|
|
|
|
|
qh = np.packbits(q.reshape((n_blocks, 1, 32)) >> np.uint8(4), axis=-1, bitorder="little").reshape(n_blocks, 4)
|
|
|
|
|
|
|
|
d = d.astype(np.float16).view(np.uint8)
|
|
|
|
|
|
|
|
return np.concatenate([d, qh, qs], axis=-1)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
d, rest = np.hsplit(blocks, [2])
|
|
|
|
qh, qs = np.hsplit(rest, [4])
|
|
|
|
|
|
|
|
d = d.view(np.float16).astype(np.float32)
|
|
|
|
qh = qh.view(np.uint32)
|
|
|
|
|
|
|
|
qh = qh.reshape((n_blocks, 1)) >> np.array([i for i in range(32)], dtype=np.uint32).reshape((1, 32))
|
|
|
|
ql = qs.reshape((n_blocks, -1, 1, cls.block_size // 2)) >> np.array([0, 4], dtype=np.uint8).reshape((1, 1, 2, 1))
|
|
|
|
qh = (qh & np.uint32(0x01)).astype(np.uint8)
|
|
|
|
ql = (ql & np.uint8(0x0F)).reshape((n_blocks, -1))
|
|
|
|
|
|
|
|
qs = (ql | (qh << np.uint8(4))).astype(np.int8) - np.int8(16)
|
|
|
|
|
|
|
|
return (d * qs.astype(np.float32))
|
|
|
|
|
|
|
|
|
|
|
|
class Q5_1(__Quant, qtype=GGMLQuantizationType.Q5_1):
|
|
|
|
@classmethod
|
|
|
|
def quantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
max = blocks.max(axis=-1, keepdims=True)
|
|
|
|
min = blocks.min(axis=-1, keepdims=True)
|
|
|
|
|
|
|
|
d = (max - min) / 31
|
|
|
|
with np.errstate(divide="ignore"):
|
|
|
|
id = np.where(d == 0, 0, 1 / d)
|
|
|
|
q = np.trunc((blocks - min) * id + np.float32(0.5), dtype=np.float32).astype(np.uint8).clip(0, 31)
|
|
|
|
|
|
|
|
qs = q.reshape((n_blocks, 2, cls.block_size // 2))
|
|
|
|
qs = (qs[..., 0, :] & np.uint8(0x0F)) | (qs[..., 1, :] << np.uint8(4))
|
|
|
|
|
|
|
|
qh = np.packbits(q.reshape((n_blocks, 1, 32)) >> np.uint8(4), axis=-1, bitorder="little").reshape(n_blocks, 4)
|
|
|
|
|
|
|
|
d = d.astype(np.float16).view(np.uint8)
|
|
|
|
m = min.astype(np.float16).view(np.uint8)
|
|
|
|
|
|
|
|
return np.concatenate([d, m, qh, qs], axis=-1)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
d, rest = np.hsplit(blocks, [2])
|
|
|
|
m, rest = np.hsplit(rest, [2])
|
|
|
|
qh, qs = np.hsplit(rest, [4])
|
|
|
|
|
|
|
|
d = d.view(np.float16).astype(np.float32)
|
|
|
|
m = m.view(np.float16).astype(np.float32)
|
|
|
|
qh = qh.view(np.uint32)
|
|
|
|
|
|
|
|
qh = qh.reshape((n_blocks, 1)) >> np.array([i for i in range(32)], dtype=np.uint32).reshape((1, 32))
|
|
|
|
ql = qs.reshape((n_blocks, -1, 1, cls.block_size // 2)) >> np.array([0, 4], dtype=np.uint8).reshape((1, 1, 2, 1))
|
|
|
|
qh = (qh & np.uint32(0x01)).astype(np.uint8)
|
|
|
|
ql = (ql & np.uint8(0x0F)).reshape((n_blocks, -1))
|
|
|
|
|
|
|
|
qs = (ql | (qh << np.uint8(4))).astype(np.float32)
|
|
|
|
|
|
|
|
return (d * qs) + m
|
|
|
|
|
|
|
|
|
2024-08-08 17:33:09 +00:00
|
|
|
class Q8_0(__Quant, qtype=GGMLQuantizationType.Q8_0):
|
|
|
|
@classmethod
|
|
|
|
# Implementation of Q8_0 with bit-exact same results as reference implementation in ggml-quants.c
|
|
|
|
def quantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
|
|
|
|
d = abs(blocks).max(axis=1, keepdims=True) / 127
|
|
|
|
with np.errstate(divide="ignore"):
|
|
|
|
id = np.where(d == 0, 0, 1 / d)
|
|
|
|
qs = np_roundf(blocks * id)
|
|
|
|
|
|
|
|
# (n_blocks, 2)
|
|
|
|
d = d.astype(np.float16).view(np.uint8)
|
|
|
|
# (n_blocks, block_size)
|
|
|
|
qs = qs.astype(np.int8).view(np.uint8)
|
|
|
|
|
|
|
|
return np.concatenate([d, qs], axis=1)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
d, x = np.split(blocks, [2], axis=1)
|
|
|
|
d = d.view(np.float16).astype(np.float32)
|
|
|
|
x = x.view(np.int8).astype(np.float32)
|
|
|
|
|
|
|
|
return (x * d)
|
2024-08-09 03:11:42 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Q2_K(__Quant, qtype=GGMLQuantizationType.Q2_K):
|
|
|
|
@classmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
scales, rest = np.hsplit(blocks, [QK_K // 16])
|
|
|
|
qs, rest = np.hsplit(rest, [QK_K // 4])
|
|
|
|
d, dmin = np.hsplit(rest, [2])
|
|
|
|
|
|
|
|
d = d.view(np.float16).astype(np.float32)
|
|
|
|
dmin = dmin.view(np.float16).astype(np.float32)
|
|
|
|
|
|
|
|
# (n_blocks, 16, 1)
|
|
|
|
dl = (d * (scales & 0xF).astype(np.float32)).reshape((n_blocks, QK_K // 16, 1))
|
|
|
|
ml = (dmin * (scales >> 4).astype(np.float32)).reshape((n_blocks, QK_K // 16, 1))
|
|
|
|
|
|
|
|
shift = np.array([0, 2, 4, 6], dtype=np.uint8).reshape((1, 1, 4, 1))
|
|
|
|
|
|
|
|
qs = (qs.reshape((n_blocks, -1, 1, 32)) >> shift) & np.uint8(3)
|
|
|
|
|
|
|
|
qs = qs.reshape((n_blocks, QK_K // 16, 16)).astype(np.float32)
|
|
|
|
|
|
|
|
qs = dl * qs - ml
|
|
|
|
|
|
|
|
return qs.reshape((n_blocks, -1))
|
|
|
|
|
|
|
|
|
|
|
|
class Q3_K(__Quant, qtype=GGMLQuantizationType.Q3_K):
|
|
|
|
@classmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
hmask, rest = np.hsplit(blocks, [QK_K // 8])
|
|
|
|
qs, rest = np.hsplit(rest, [QK_K // 4])
|
|
|
|
scales, d = np.hsplit(rest, [12])
|
|
|
|
|
|
|
|
d = d.view(np.float16).astype(np.float32)
|
|
|
|
|
|
|
|
# The scales are packed at 6-bit each in this pattern:
|
|
|
|
# 0: IIIIAAAA
|
|
|
|
# 1: JJJJBBBB
|
|
|
|
# 2: KKKKCCCC
|
|
|
|
# 3: LLLLDDDD
|
|
|
|
# 4: MMMMEEEE
|
|
|
|
# 5: NNNNFFFF
|
|
|
|
# 6: OOOOGGGG
|
|
|
|
# 7: PPPPHHHH
|
|
|
|
# 8: MMIIEEAA
|
|
|
|
# 9: NNJJFFBB
|
|
|
|
# 10: OOKKGGCC
|
|
|
|
# 11: PPLLHHDD
|
|
|
|
lscales, hscales = np.hsplit(scales, [8])
|
|
|
|
lscales = lscales.reshape((n_blocks, 1, 8)) >> np.array([0, 4], dtype=np.uint8).reshape((1, 2, 1))
|
|
|
|
lscales = lscales.reshape((n_blocks, 16))
|
|
|
|
hscales = hscales.reshape((n_blocks, 1, 4)) >> np.array([0, 2, 4, 6], dtype=np.uint8).reshape((1, 4, 1))
|
|
|
|
hscales = hscales.reshape((n_blocks, 16))
|
|
|
|
scales = (lscales & np.uint8(0x0F)) | ((hscales & np.uint8(0x03)) << np.uint8(4))
|
|
|
|
scales = (scales.astype(np.int8) - np.int8(32)).astype(np.float32)
|
|
|
|
|
|
|
|
dl = (d * scales).reshape((n_blocks, 16, 1))
|
|
|
|
|
|
|
|
ql = qs.reshape((n_blocks, -1, 1, 32)) >> np.array([0, 2, 4, 6], dtype=np.uint8).reshape((1, 1, 4, 1))
|
|
|
|
qh = hmask.reshape(n_blocks, -1, 1, 32) >> np.array([i for i in range(8)], dtype=np.uint8).reshape((1, 1, 8, 1))
|
|
|
|
ql = ql.reshape((n_blocks, 16, QK_K // 16)) & np.uint8(3)
|
|
|
|
qh = (qh.reshape((n_blocks, 16, QK_K // 16)) & np.uint8(1))
|
|
|
|
qh = qh ^ np.uint8(1) # strangely, the offset is zero when the bitmask is 1
|
|
|
|
q = (ql.astype(np.int8) - (qh << np.uint8(2)).astype(np.int8)).astype(np.float32)
|
|
|
|
|
|
|
|
return (dl * q).reshape((n_blocks, QK_K))
|
|
|
|
|
|
|
|
|
|
|
|
class Q4_K(__Quant, qtype=GGMLQuantizationType.Q4_K):
|
|
|
|
K_SCALE_SIZE = 12
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def get_scale_min(scales: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
|
|
|
|
n_blocks = scales.shape[0]
|
|
|
|
scales = scales.view(np.uint8)
|
|
|
|
### Unpacking the following: ###
|
|
|
|
# 0 EEAAAAAA
|
|
|
|
# 1 FFBBBBBB
|
|
|
|
# 2 GGCCCCCC
|
|
|
|
# 3 HHDDDDDD
|
|
|
|
# 4 eeaaaaaa
|
|
|
|
# 5 ffbbbbbb
|
|
|
|
# 6 ggcccccc
|
|
|
|
# 7 hhdddddd
|
|
|
|
# 8 eeeeEEEE
|
|
|
|
# 9 ffffFFFF
|
|
|
|
# 10 ggggGGGG
|
|
|
|
# 11 hhhhHHHH
|
|
|
|
scales = scales.reshape((n_blocks, 3, 4))
|
|
|
|
d, m, m_d = np.split(scales, 3, axis=-2)
|
|
|
|
|
|
|
|
sc = np.concatenate([d & 0x3F, (m_d & 0x0F) | ((d >> 2) & 0x30)], axis=-1)
|
|
|
|
min = np.concatenate([m & 0x3F, (m_d >> 4) | ((m >> 2) & 0x30)], axis=-1)
|
|
|
|
|
|
|
|
return (sc.reshape((n_blocks, 8)), min.reshape((n_blocks, 8)))
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
d, rest = np.hsplit(blocks, [2])
|
|
|
|
dmin, rest = np.hsplit(rest, [2])
|
|
|
|
scales, qs = np.hsplit(rest, [cls.K_SCALE_SIZE])
|
|
|
|
|
|
|
|
d = d.view(np.float16).astype(np.float32)
|
|
|
|
dmin = dmin.view(np.float16).astype(np.float32)
|
|
|
|
|
|
|
|
sc, m = Q4_K.get_scale_min(scales)
|
|
|
|
|
|
|
|
d = (d * sc.astype(np.float32)).reshape((n_blocks, -1, 1))
|
|
|
|
dm = (dmin * m.astype(np.float32)).reshape((n_blocks, -1, 1))
|
|
|
|
|
|
|
|
qs = qs.reshape((n_blocks, -1, 1, 32)) >> np.array([0, 4], dtype=np.uint8).reshape((1, 1, 2, 1))
|
|
|
|
qs = (qs & np.uint8(0x0F)).reshape((n_blocks, -1, 32)).astype(np.float32)
|
|
|
|
|
|
|
|
return (d * qs - dm).reshape((n_blocks, QK_K))
|
|
|
|
|
|
|
|
|
|
|
|
class Q5_K(__Quant, qtype=GGMLQuantizationType.Q5_K):
|
|
|
|
@classmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
d, rest = np.hsplit(blocks, [2])
|
|
|
|
dmin, rest = np.hsplit(rest, [2])
|
|
|
|
scales, rest = np.hsplit(rest, [Q4_K.K_SCALE_SIZE])
|
|
|
|
qh, qs = np.hsplit(rest, [QK_K // 8])
|
|
|
|
|
|
|
|
d = d.view(np.float16).astype(np.float32)
|
|
|
|
dmin = dmin.view(np.float16).astype(np.float32)
|
|
|
|
|
|
|
|
sc, m = Q4_K.get_scale_min(scales)
|
|
|
|
|
|
|
|
d = (d * sc.astype(np.float32)).reshape((n_blocks, -1, 1))
|
|
|
|
dm = (dmin * m.astype(np.float32)).reshape((n_blocks, -1, 1))
|
|
|
|
|
|
|
|
ql = qs.reshape((n_blocks, -1, 1, 32)) >> np.array([0, 4], dtype=np.uint8).reshape((1, 1, 2, 1))
|
|
|
|
qh = qh.reshape((n_blocks, -1, 1, 32)) >> np.array([i for i in range(8)], dtype=np.uint8).reshape((1, 1, 8, 1))
|
|
|
|
ql = (ql & np.uint8(0x0F)).reshape((n_blocks, -1, 32))
|
|
|
|
qh = (qh & np.uint8(0x01)).reshape((n_blocks, -1, 32))
|
|
|
|
q = (ql | (qh << np.uint8(4))).astype(np.float32)
|
|
|
|
|
|
|
|
return (d * q - dm).reshape((n_blocks, QK_K))
|
|
|
|
|
|
|
|
|
|
|
|
class Q6_K(__Quant, qtype=GGMLQuantizationType.Q6_K):
|
|
|
|
@classmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
ql, rest = np.hsplit(blocks, [QK_K // 2])
|
|
|
|
qh, rest = np.hsplit(rest, [QK_K // 4])
|
|
|
|
scales, d = np.hsplit(rest, [QK_K // 16])
|
|
|
|
|
|
|
|
scales = scales.view(np.int8).astype(np.float32)
|
|
|
|
d = d.view(np.float16).astype(np.float32)
|
|
|
|
d = (d * scales).reshape((n_blocks, QK_K // 16, 1))
|
|
|
|
|
|
|
|
ql = ql.reshape((n_blocks, -1, 1, 64)) >> np.array([0, 4], dtype=np.uint8).reshape((1, 1, 2, 1))
|
|
|
|
ql = (ql & np.uint8(0x0F)).reshape((n_blocks, -1, 32))
|
|
|
|
qh = qh.reshape((n_blocks, -1, 1, 32)) >> np.array([0, 2, 4, 6], dtype=np.uint8).reshape((1, 1, 4, 1))
|
|
|
|
qh = (qh & np.uint8(0x03)).reshape((n_blocks, -1, 32))
|
|
|
|
q = (ql | (qh << np.uint8(4))).astype(np.int8) - np.int8(32)
|
|
|
|
q = q.reshape((n_blocks, QK_K // 16, -1)).astype(np.float32)
|
|
|
|
|
|
|
|
return (d * q).reshape((n_blocks, QK_K))
|
|
|
|
|
|
|
|
|
|
|
|
class IQ4_NL(__Quant, qtype=GGMLQuantizationType.IQ4_NL):
|
|
|
|
QK4_NL = 32
|
|
|
|
|
|
|
|
kvalues = (-127, -104, -83, -65, -49, -35, -22, -10, 1, 13, 25, 38, 53, 69, 89, 113)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
d, qs = np.hsplit(blocks, [2])
|
|
|
|
|
|
|
|
d = d.view(np.float16).astype(np.float32)
|
|
|
|
|
|
|
|
qs = qs.reshape((n_blocks, -1, 1, cls.QK4_NL // 2)) >> np.array([0, 4], dtype=np.uint8).reshape((1, 1, 2, 1))
|
|
|
|
|
|
|
|
qs = (qs & np.uint8(0x0F)).reshape((n_blocks, -1, 1))
|
|
|
|
|
|
|
|
kvalues = np.array(cls.kvalues, dtype=np.int8).reshape(1, 1, 16)
|
|
|
|
qs = np.take_along_axis(kvalues, qs, axis=-1).astype(np.float32).reshape((n_blocks, -1))
|
|
|
|
|
|
|
|
return (d * qs)
|
|
|
|
|
|
|
|
|
|
|
|
class IQ4_XS(__Quant, qtype=GGMLQuantizationType.IQ4_XS):
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
|
|
|
n_blocks = blocks.shape[0]
|
|
|
|
|
|
|
|
d, rest = np.hsplit(blocks, [2])
|
|
|
|
scales_h, rest = np.hsplit(rest, [2])
|
|
|
|
scales_l, qs = np.hsplit(rest, [QK_K // 64])
|
|
|
|
|
|
|
|
d = d.view(np.float16).astype(np.float32)
|
|
|
|
scales_h = scales_h.view(np.uint16)
|
|
|
|
|
|
|
|
scales_l = scales_l.reshape((n_blocks, -1, 1)) >> np.array([0, 4], dtype=np.uint8).reshape((1, 1, 2))
|
|
|
|
scales_h = scales_h.reshape((n_blocks, 1, -1)) >> np.array([2 * i for i in range(QK_K // 32)], dtype=np.uint16).reshape((1, -1, 1))
|
|
|
|
scales_l = scales_l.reshape((n_blocks, -1)) & np.uint8(0x0F)
|
|
|
|
scales_h = scales_h.reshape((n_blocks, -1)).astype(np.uint8) & np.uint8(0x03)
|
|
|
|
|
|
|
|
scales = (scales_l | (scales_h << np.uint8(4))).astype(np.int8) - np.int8(32)
|
|
|
|
dl = (d * scales.astype(np.float32)).reshape((n_blocks, -1, 1))
|
|
|
|
|
|
|
|
qs = qs.reshape((n_blocks, -1, 1, 16)) >> np.array([0, 4], dtype=np.uint8).reshape((1, 1, 2, 1))
|
|
|
|
qs = qs.reshape((n_blocks, -1, 32, 1)) & np.uint8(0x0F)
|
|
|
|
|
|
|
|
kvalues = np.array(IQ4_NL.kvalues, dtype=np.int8).reshape((1, 1, 1, -1))
|
|
|
|
qs = np.take_along_axis(kvalues, qs, axis=-1).astype(np.float32).reshape((n_blocks, -1, 32))
|
|
|
|
|
|
|
|
return (dl * qs).reshape((n_blocks, -1))
|