Download q2k.py from CompressedGemma/HPC-Quantize-V2: direct link, hf CLI and curl.
- Browser
- Download file 9.25 kB
-
https://huggingface.co/CompressedGemma/HPC-Quantize-V2/resolve/main/q2k.py
- Command line
-
hf download hf://CompressedGemma/HPC-Quantize-V2/q2k.py
-
curl -L -o q2k.py https://huggingface.co/CompressedGemma/HPC-Quantize-V2/resolve/main/q2k.py
9.25 kB
| """Bit-exact Q2_K block representation, packing and reconstruction. | |
| Layout (84 bytes per 256 weights):: | |
| scales[16] uint8 low nibble = Ls[j], high nibble = Lm[j] | |
| qs[64] uint8 four 2-bit codes per byte | |
| d fp16 super-block scale | |
| dmin fp16 super-block minimum scale | |
| The decoder reconstructs:: | |
| x_hat[16*j + k] = d * Ls[j] * L[16*j + k] - dmin * Lm[j] | |
| with ``L in {0,1,2,3}`` and ``Ls, Lm in {0..15}``. | |
| """ | |
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| import numpy as np | |
| from .fp16 import from_fp16_bits, nearest_int, to_fp16_bits | |
| QK_K = 256 | |
| NS = QK_K // 16 | |
| NQ = 4 | |
| class Q2KBlock: | |
| scales: np.ndarray | |
| qs: np.ndarray | |
| d: np.uint16 | |
| dmin: np.uint16 | |
| def d_float(self) -> np.float32: | |
| return np.float32(from_fp16_bits(np.uint16(self.d))) | |
| def dmin_float(self) -> np.float32: | |
| return np.float32(from_fp16_bits(np.uint16(self.dmin))) | |
| def unpack_scales(self) -> tuple[np.ndarray, np.ndarray]: | |
| scales = np.asarray(self.scales, dtype=np.uint8) | |
| return scales & 0x0F, scales >> 4 | |
| def dequantize(self) -> np.ndarray: | |
| ls, lm = self.unpack_scales() | |
| return dequantize_params( | |
| self.d_float, self.dmin_float, ls, lm, unpack_codes(self.qs) | |
| ) | |
| def error(self, x: np.ndarray) -> float: | |
| e = np.asarray(x, dtype=np.float64) - self.dequantize().astype(np.float64) | |
| return float(np.dot(e, e)) | |
| def unpack_codes(qs: np.ndarray) -> np.ndarray: | |
| qs = np.asarray(qs, dtype=np.uint8) | |
| codes = np.empty(QK_K, dtype=np.uint8) | |
| for base in (0, 128): | |
| q = qs[base // 4 : base // 4 + 32] | |
| for shift_level in range(4): | |
| lo = base + 32 * shift_level | |
| codes[lo : lo + 32] = (q >> np.uint8(2 * shift_level)) & np.uint8(3) | |
| return codes | |
| def pack_codes(codes: np.ndarray) -> np.ndarray: | |
| codes = np.asarray(codes, dtype=np.uint8) | |
| if codes.shape != (QK_K,): | |
| raise ValueError("codes must have shape (256,)") | |
| qs = np.empty(QK_K // 4, dtype=np.uint8) | |
| for base in (0, 128): | |
| q = ( | |
| codes[base : base + 32] | |
| | (codes[base + 32 : base + 64] << np.uint8(2)) | |
| | (codes[base + 64 : base + 96] << np.uint8(4)) | |
| | (codes[base + 96 : base + 128] << np.uint8(6)) | |
| ) | |
| qs[base // 4 : base // 4 + 32] = q | |
| return qs | |
| def pack_scales(ls: np.ndarray, lm: np.ndarray) -> np.ndarray: | |
| return (np.asarray(ls, dtype=np.uint8) & 0x0F) | ( | |
| (np.asarray(lm, dtype=np.uint8) & 0x0F) << np.uint8(4) | |
| ) | |
| def dequantize_params( | |
| d, dmin, ls: np.ndarray, lm: np.ndarray, codes: np.ndarray | |
| ) -> np.ndarray: | |
| ls = np.asarray(ls, dtype=np.float32) | |
| lm = np.asarray(lm, dtype=np.float32) | |
| codes = np.asarray(codes, dtype=np.float32) | |
| d_sub = np.repeat(np.float32(d) * ls, 16) | |
| m_sub = np.repeat(np.float32(dmin) * lm, 16) | |
| return d_sub * codes - m_sub | |
| def codes_from_params( | |
| x: np.ndarray, d, dmin, ls: np.ndarray, lm: np.ndarray | |
| ) -> np.ndarray: | |
| x = np.asarray(x, dtype=np.float32) | |
| d_sub = np.repeat(np.float32(d) * np.asarray(ls, dtype=np.float32), 16) | |
| m_sub = np.repeat(np.float32(dmin) * np.asarray(lm, dtype=np.float32), 16) | |
| safe = d_sub >= np.float32(1.0e-15) | |
| codes = np.zeros(QK_K, dtype=np.uint8) | |
| if np.any(safe): | |
| q = nearest_int((x[safe] + m_sub[safe]) / d_sub[safe]) | |
| codes[safe] = np.clip(q, 0, 3).astype(np.uint8) | |
| return codes | |
| def derive_subscales( | |
| scales: np.ndarray, mins: np.ndarray, d, dmin | |
| ) -> tuple[np.ndarray, np.ndarray]: | |
| scales = np.asarray(scales, dtype=np.float32) | |
| mins = np.asarray(mins, dtype=np.float32) | |
| if float(d) > 1.0e-15: | |
| ls = np.clip(nearest_int(scales / np.float32(d)), 0, 15).astype(np.uint8) | |
| else: | |
| ls = np.zeros(NS, dtype=np.uint8) | |
| if float(dmin) > 1.0e-15: | |
| lm = np.clip(nearest_int(mins / np.float32(dmin)), 0, 15).astype(np.uint8) | |
| else: | |
| lm = np.zeros(NS, dtype=np.uint8) | |
| return ls, lm | |
| def weighted_sse( | |
| x: np.ndarray, | |
| w: np.ndarray | None, | |
| d, | |
| dmin, | |
| ls: np.ndarray, | |
| lm: np.ndarray, | |
| codes: np.ndarray, | |
| ) -> float: | |
| xh = dequantize_params(d, dmin, ls, lm, codes) | |
| e = np.asarray(x, dtype=np.float64) - xh.astype(np.float64) | |
| if w is None: | |
| return float(np.dot(e, e)) | |
| return float(np.dot(np.asarray(w, dtype=np.float64), e * e)) | |
| def make_block(d, dmin, ls, lm, codes) -> Q2KBlock: | |
| return Q2KBlock( | |
| scales=pack_scales(ls, lm), | |
| qs=pack_codes(codes), | |
| d=np.uint16(to_fp16_bits(np.float32(d))), | |
| dmin=np.uint16(to_fp16_bits(np.float32(dmin))), | |
| ) | |
| def blocks_to_arrays(blocks: list[Q2KBlock]) -> tuple[np.ndarray, np.ndarray, np.ndarray]: | |
| n = len(blocks) | |
| scales = np.empty((n, NS), dtype=np.uint8) | |
| qs = np.empty((n, QK_K // 4), dtype=np.uint8) | |
| dm = np.empty((n, 2), dtype=np.uint16) | |
| for i, b in enumerate(blocks): | |
| scales[i] = b.scales | |
| qs[i] = b.qs | |
| dm[i, 0] = b.d | |
| dm[i, 1] = b.dmin | |
| return scales, qs, dm | |
| def blocks_to_bytes(scales: np.ndarray, qs: np.ndarray, dm: np.ndarray) -> bytes: | |
| """Serialize Q2_K block arrays to GGUF's 84-byte block layout.""" | |
| scales = np.asarray(scales, dtype=np.uint8) | |
| qs = np.asarray(qs, dtype=np.uint8) | |
| dm = np.asarray(dm, dtype=np.uint16) | |
| n = scales.shape[0] | |
| out = np.empty((n, 84), dtype=np.uint8) | |
| out[:, :16] = scales | |
| out[:, 16:80] = qs | |
| out[:, 80:82] = np.ascontiguousarray(dm[:, 0]).view(np.uint8).reshape(n, 2) | |
| out[:, 82:84] = np.ascontiguousarray(dm[:, 1]).view(np.uint8).reshape(n, 2) | |
| return out.tobytes() | |
| def dequantize_blocks(blocks: list[Q2KBlock]) -> np.ndarray: | |
| if not blocks: | |
| return np.empty(0, dtype=np.float32) | |
| return np.concatenate([b.dequantize() for b in blocks]) | |
| def codes_from_params_batch( | |
| x: np.ndarray, d: np.ndarray, dmin: np.ndarray, ls: np.ndarray, lm: np.ndarray | |
| ) -> np.ndarray: | |
| x = np.asarray(x, dtype=np.float32) | |
| n = x.shape[0] | |
| d_sub = np.repeat( | |
| np.asarray(d, dtype=np.float32)[:, None] * np.asarray(ls, dtype=np.float32), 16, axis=1 | |
| ) | |
| m_sub = np.repeat( | |
| np.asarray(dmin, dtype=np.float32)[:, None] * np.asarray(lm, dtype=np.float32), 16, axis=1 | |
| ) | |
| safe = d_sub >= np.float32(1.0e-15) | |
| codes = np.zeros((n, QK_K), dtype=np.uint8) | |
| if np.any(safe): | |
| q = nearest_int((x[safe] + m_sub[safe]) / d_sub[safe]) | |
| codes[safe] = np.clip(q, 0, 3).astype(np.uint8) | |
| return codes | |
| def dequantize_params_batch( | |
| d: np.ndarray, dmin: np.ndarray, ls: np.ndarray, lm: np.ndarray, codes: np.ndarray | |
| ) -> np.ndarray: | |
| n = codes.shape[0] | |
| d_sub = np.repeat( | |
| np.asarray(d, dtype=np.float32)[:, None] * np.asarray(ls, dtype=np.float32), 16, axis=1 | |
| ) | |
| m_sub = np.repeat( | |
| np.asarray(dmin, dtype=np.float32)[:, None] * np.asarray(lm, dtype=np.float32), 16, axis=1 | |
| ) | |
| return d_sub * np.asarray(codes, dtype=np.float32) - m_sub | |
| def weighted_sse_batch( | |
| x: np.ndarray, | |
| w: np.ndarray | None, | |
| d: np.ndarray, | |
| dmin: np.ndarray, | |
| ls: np.ndarray, | |
| lm: np.ndarray, | |
| codes: np.ndarray, | |
| ) -> np.ndarray: | |
| xh = dequantize_params_batch(d, dmin, ls, lm, codes) | |
| e = np.asarray(x, dtype=np.float64) - xh.astype(np.float64) | |
| if w is None: | |
| return np.sum(e * e, axis=1) | |
| return np.sum(np.asarray(w, dtype=np.float64) * e * e, axis=1) | |
| def pin_q2k_dc_residual(d, m, ls, lm, codes, X): | |
| """Zero-DC pin: re-solve (d, m) on the exact ``sum(e) == 0`` line. | |
| With e_i = X_i - d*ls_g*c_i + m*lm_g, requiring Σe = 0 makes m a line | |
| m(d) = (d·Σa - ΣX)/Σb (a = ls·c, b = lm); subtracting the residual mean | |
| along that line is a translation that can only *lower* SSE, so the | |
| closed-form optimum d* = Σ(u·v)/Σ(v²) dominates the input state on both | |
| counts and leaves dc ~ 0 (bounded below by the fp16 storage of the | |
| ``d``/``m`` fields). Blocks without a usable translation dof (Σb == 0 | |
| or Σv² == 0) keep their input parameters. | |
| """ | |
| ls64 = np.asarray(ls, dtype=np.float64) | |
| lm64 = np.asarray(lm, dtype=np.float64) | |
| c64 = np.asarray(codes, dtype=np.float64) | |
| a_el = np.repeat(ls64, 16, axis=1) * c64 | |
| b_el = np.repeat(lm64, 16, axis=1) | |
| sx0 = np.sum(np.asarray(X, dtype=np.float64), axis=1) | |
| sa = a_el.sum(axis=1) | |
| sb = b_el.sum(axis=1) | |
| sb_safe = np.where(sb > 0.0, sb, np.nan) | |
| u = np.asarray(X, dtype=np.float64) - b_el * (sx0 / sb_safe)[:, None] | |
| v = a_el - b_el * (sa / sb_safe)[:, None] | |
| vv = np.sum(v * v, axis=1) | |
| uv = np.sum(u * v, axis=1) | |
| ok = np.isfinite(vv) & (vv > 0.0) & np.isfinite(uv) & (sb > 0.0) | |
| d_new = np.where(ok, uv / np.where(ok, vv, 1.0), np.asarray(d, dtype=np.float64)) | |
| m_new = (d_new * sa - sx0) / np.where(ok, sb, 1.0) | |
| m_new = np.where(ok, m_new, np.asarray(m, dtype=np.float64)) | |
| # match the fp16 fields the block will actually carry in the file | |
| d_new = from_fp16_bits(to_fp16_bits(d_new.astype(np.float32))).astype(np.float64) | |
| m_new = from_fp16_bits(to_fp16_bits(m_new.astype(np.float32))).astype(np.float64) | |
| return d_new, m_new | |