Download anneal.py from CompressedGemma/HPC-Quantize-V2: direct link, hf CLI and curl.
- Browser
- Download file 29.4 kB
-
https://huggingface.co/CompressedGemma/HPC-Quantize-V2/resolve/main/anneal.py
- Command line
-
hf download hf://CompressedGemma/HPC-Quantize-V2/anneal.py
-
curl -L -o anneal.py https://huggingface.co/CompressedGemma/HPC-Quantize-V2/resolve/main/anneal.py
29.4 kB
| """Thermal refinery: seeded pair heat-bath annealing on the Q2_K lattice. | |
| With ``fold_depth == 1`` the extended objective for one block is exactly:: | |
| E = sum_i w_i e_i^2 | |
| + (vw_lambda / n) * sum_p (e_p + e_{p+n/2})^2 | |
| + (dc_lambda / n) * (sum_i e_i + carry)^2 | |
| so the 256 weights decompose into n/2 vesica pairs that interact with each | |
| other only through the scalar DC residual: with every error affine in its | |
| code, each pair's conditional energy over its joint states is exactly | |
| quadratic and closed-form. ``gibbs_pair_pass`` resamples the pairs from | |
| those conditionals at a temperature (heat bath), fully batched across blocks | |
| over any uniform lattice size (``levels``: 4 = Q2_K, 16 = Q4_0, 2 = Q1_0, | |
| 8 = Q3_K K-quants). ``greedy=True`` resamples at the conditional argmin | |
| instead, which is a monotone descent. ``thermal_refine_codes`` / | |
| ``thermal_refine_uniform_codes`` schedule temperatures, then keep the | |
| elementwise best against the pre-anneal state, so the pass can never regress | |
| a block. All randomness comes from a seeded generator, and a fixed seed | |
| reproduces bit-identical quantizations. | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| from .config import HPCConfig | |
| from .fp16 import nearest_int | |
| from .q2k import QK_K | |
| EPS_D = 1.0e-15 | |
| def thermal_spectral_lams(cfg: HPCConfig, n: int) -> tuple[float, float]: | |
| """Per-code-normalized ``(lam_vw, lam_dc)`` with the zero-residual tilt applied. | |
| ``therm_lambda_tilt`` scales both spectral lambdas during annealing so the | |
| exact Gibbs chain trades weighted SSE for coherent-residual (DC/vesica) | |
| drain; ``1.0`` (default) is the shipping objective. | |
| """ | |
| if not cfg.use_spectral: | |
| return 0.0, 0.0 | |
| tilt = max(1.0, float(cfg.therm_lambda_tilt)) | |
| return (float(cfg.vw_lambda) / n) * tilt, (float(cfg.dc_lambda) / n) * tilt | |
| def pair_energy( | |
| e: np.ndarray, | |
| w: np.ndarray | None, | |
| lam_vw: float, | |
| lam_dc: float, | |
| carry: np.ndarray, | |
| ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: | |
| """Block energies plus the (sse, v, dc) live state for exact deltas.""" | |
| half = e.shape[1] // 2 | |
| ww = np.ones_like(e) if w is None else np.asarray(w, dtype=np.float64) | |
| we_sq = ww * e * e | |
| sse = np.sum(we_sq, axis=1) | |
| v = e[:, :half] + e[:, half:] | |
| dc = np.sum(e, axis=1) + np.asarray(carry, dtype=np.float64) | |
| energy = sse + lam_vw * np.sum(v * v, axis=1) + lam_dc * dc * dc | |
| return energy, we_sq, v, dc | |
| def gibbs_pair_pass( | |
| X_f64: np.ndarray, | |
| w_full: np.ndarray | None, | |
| d_sub: np.ndarray, | |
| m_sub: np.ndarray, | |
| codes: np.ndarray, | |
| e: np.ndarray, | |
| v: np.ndarray, | |
| dc: np.ndarray, | |
| sse: np.ndarray, | |
| temperature: np.ndarray, | |
| lam_vw: float, | |
| lam_dc: float, | |
| rng: np.random.Generator, | |
| greedy: bool = False, | |
| levels: int = 4, | |
| ) -> None: | |
| """One exact sequential Gibbs sweep over all vesica pairs, in place. | |
| Updates (codes, e, v, dc, sse) for the batch of blocks. With | |
| ``greedy=False`` each pair is resampled from its exact conditional | |
| energy via a heat bath at ``temperature`` (per-block vector); with | |
| ``greedy=True`` the conditional argmin is taken and only strict | |
| energy decreases are accepted. ``levels`` selects the code lattice | |
| size (4 = Q2_K 2-bit, 16 = Q4_0 nibble, 2 = Q1_0 bit, 8 = Q3_K tri); | |
| ``d_sub`` holds the per-column error step so that any code move ``q`` | |
| produces the exact error delta ``d_sub * (q_cur - q)`` (formula is | |
| shared across formats; Q1_0's sign trick is ``d_sub = 2*d``), and | |
| ``m_sub`` is accepted for signature symmetry but unused here. | |
| """ | |
| n_blocks, n = X_f64.shape | |
| half = n // 2 | |
| q_grid = np.arange(levels, dtype=np.int64) | |
| w_full = np.ones_like(e) if w_full is None else np.asarray(w_full, dtype=np.float64) | |
| w_lo, w_hi = w_full[:, :half], w_full[:, half:] | |
| ds_lo, ds_hi = d_sub[:, :half], d_sub[:, half:] | |
| active_lo, active_hi = np.abs(ds_lo) >= EPS_D, np.abs(ds_hi) >= EPS_D | |
| idx = np.arange(n_blocks) | |
| for p in range(half): | |
| e_lo = e[:, p].copy() | |
| e_hi = e[:, half + p].copy() | |
| q_lo = codes[:, p].astype(np.int64) | |
| q_hi = codes[:, half + p].astype(np.int64) | |
| v_p = v[:, p] | |
| dc_p = dc | |
| # candidate error deltas (affine in q): de(q) = d_sub * (q_cur - q) | |
| de_lo = ds_lo[:, p : p + 1] * (q_lo[:, None] - q_grid[None, :]) | |
| de_hi = ds_hi[:, p : p + 1] * (q_hi[:, None] - q_grid[None, :]) | |
| de_lo = np.where(active_lo[:, p : p + 1], de_lo, 0.0) | |
| de_hi = np.where(active_hi[:, p : p + 1], de_hi, 0.0) | |
| de_sum = de_lo[:, :, None] + de_hi[:, None, :] # (n_blocks, L, L) | |
| d_e = ( | |
| w_lo[:, p, None, None] | |
| * (2.0 * e_lo[:, None, None] * de_lo[:, :, None] + de_lo[:, :, None] ** 2) | |
| + w_hi[:, p, None, None] | |
| * (2.0 * e_hi[:, None, None] * de_hi[:, None, :] + de_hi[:, None, :] ** 2) | |
| + lam_vw * (2.0 * v_p[:, None, None] * de_sum + de_sum * de_sum) | |
| + lam_dc * (2.0 * dc_p[:, None, None] * de_sum + de_sum * de_sum) | |
| ) | |
| if greedy: | |
| flat = d_e.reshape(n_blocks, -1) | |
| cur = q_lo * levels + q_hi | |
| pick = np.argmin(flat, axis=1) | |
| improve = flat[idx, pick] < -1.0e-18 | |
| pick = np.where(improve, pick, cur) | |
| else: | |
| cur = q_lo * levels + q_hi | |
| scale = np.maximum(temperature, 1.0e-300) | |
| logits = -d_e / scale[:, None, None] | |
| logits -= logits.max(axis=(1, 2), keepdims=True) | |
| probs = np.exp(logits).reshape(n_blocks, -1) | |
| # dead pairs (both sub-scales ~0): codes carry no energy, keep current | |
| dead = ~(active_lo[:, p] | active_hi[:, p]) | |
| if np.any(dead): | |
| probs[dead] = 0.0 | |
| probs[dead, cur[dead]] = 1.0 | |
| cum = np.cumsum(probs, axis=1) | |
| cum /= cum[:, -1:] | |
| pick = np.sum(cum < rng.random((n_blocks, 1)), axis=1) | |
| pick = np.minimum(pick, levels * levels - 1) | |
| ia, ib = pick // levels, pick % levels | |
| upd_lo = np.take_along_axis(de_lo, ia[:, None], axis=1)[:, 0] | |
| upd_hi = np.take_along_axis(de_hi, ib[:, None], axis=1)[:, 0] | |
| upd_sum = upd_lo + upd_hi | |
| sse += ( | |
| w_lo[:, p] * (2.0 * e_lo * upd_lo + upd_lo * upd_lo) | |
| + w_hi[:, p] * (2.0 * e_hi * upd_hi + upd_hi * upd_hi) | |
| ) | |
| e[:, p] = e_lo + upd_lo | |
| e[:, half + p] = e_hi + upd_hi | |
| v[:, p] = v_p + upd_sum | |
| dc = dc + upd_sum | |
| codes[:, p] = ia | |
| codes[:, half + p] = ib | |
| def codes_recompute_for( | |
| X_f64: np.ndarray, d: np.ndarray, m: np.ndarray, ls: np.ndarray, lm: np.ndarray | |
| ) -> np.ndarray: | |
| """Nearest rounding of codes, matching q2k.codes_from_params semantics.""" | |
| x32 = X_f64.astype(np.float32) | |
| d32 = np.asarray(d, dtype=np.float32) | |
| m32 = np.asarray(m, dtype=np.float32) | |
| d_sub = np.repeat(d32[:, None] * ls.astype(np.float32), 16, axis=1) | |
| m_sub = np.repeat(m32[:, None] * lm.astype(np.float32), 16, axis=1) | |
| safe = d_sub >= np.float32(EPS_D) | |
| safe_d = np.where(safe, d_sub, np.float32(1.0)) | |
| q = np.clip(nearest_int((x32 + m_sub) / safe_d), 0, 3) | |
| return np.where(safe, q, 0).astype(np.int64) | |
| def thermal_refine_codes( | |
| X: np.ndarray, | |
| w: np.ndarray | None, | |
| d: np.ndarray, | |
| m: np.ndarray, | |
| ls: np.ndarray, | |
| lm: np.ndarray, | |
| codes: np.ndarray, | |
| carry: np.ndarray, | |
| cfg: HPCConfig, | |
| dm_solve=None, | |
| passes: int | None = None, | |
| seed: int | None = None, | |
| ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, int]: | |
| """Seeded heat-bath annealing of the code lattice for a batch of blocks. | |
| Returns ``(d, m, ls, lm, codes, n_improved)``. Per block the returned | |
| state never has a higher extended energy than the input state: every | |
| end-of-pass candidate is compared against the running elementwise best | |
| and only strict decreases are kept. | |
| """ | |
| n_blocks = X.shape[0] | |
| n_passes = cfg.therm_passes if passes is None else int(passes) | |
| lam_vw, lam_dc = thermal_spectral_lams(cfg, QK_K) | |
| if n_blocks == 0 or n_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0): | |
| return d, m, ls, lm, codes, 0 | |
| if cfg.fold_depth > 1: | |
| raise ValueError("thermal refinement supports fold_depth <= 1") | |
| X_f64 = np.asarray(X, dtype=np.float64) | |
| w_f64 = None if w is None else np.asarray(w, dtype=np.float64) | |
| carry_f64 = np.asarray(carry, dtype=np.float64) | |
| ls = np.asarray(ls, dtype=np.int64) | |
| lm = np.asarray(lm, dtype=np.int64) | |
| d = np.asarray(d, dtype=np.float64) | |
| m = np.asarray(m, dtype=np.float64) | |
| codes = np.asarray(codes, dtype=np.int64).copy() | |
| rng = np.random.default_rng(cfg.therm_seed if seed is None else seed) | |
| def energies(dd, mm, cc): | |
| d_sub = np.repeat(dd[:, None] * ls, 16, axis=1) | |
| m_sub = np.repeat(mm[:, None] * lm, 16, axis=1) | |
| e = X_f64 - (d_sub * cc - m_sub) | |
| return pair_energy(e, w_f64, lam_vw, lam_dc, carry_f64)[0] | |
| energy_best = energies(d, m, codes) | |
| energy0 = energy_best.copy() | |
| best_d, best_m, best_codes = d.copy(), m.copy(), codes.copy() | |
| d_cur, m_cur, codes_cur = d.copy(), m.copy(), codes.copy() | |
| for t in range(n_passes): | |
| temperature = cfg.therm_t0 * np.maximum(energy_best, 1.0e-30) / QK_K * (cfg.therm_t_decay**t) | |
| for sweep in range(cfg.therm_sweeps + 2): | |
| greedy = sweep >= cfg.therm_sweeps | |
| d_sub = np.repeat(d_cur[:, None] * ls, 16, axis=1) | |
| m_sub = np.repeat(m_cur[:, None] * lm, 16, axis=1) | |
| e = X_f64 - (d_sub * codes_cur - m_sub) | |
| energy_cur, wesq, v, dc = pair_energy(e, w_f64, lam_vw, lam_dc, carry_f64) | |
| sse = energy_cur - lam_vw * np.sum(v * v, axis=1) - lam_dc * dc * dc | |
| gibbs_pair_pass( | |
| X_f64, | |
| w_f64, | |
| d_sub, | |
| m_sub, | |
| codes_cur, | |
| e, | |
| v, | |
| dc, | |
| sse, | |
| temperature, | |
| lam_vw, | |
| lam_dc, | |
| np.random.default_rng(rng.integers(0, 2**62)), | |
| greedy=greedy, | |
| ) | |
| if greedy: | |
| energy_now, _, _, _ = pair_energy( | |
| X_f64 - (d_sub * codes_cur - m_sub), w_f64, lam_vw, lam_dc, carry_f64 | |
| ) | |
| if not np.any(energy_now < energy_cur - 1.0e-18): | |
| break | |
| codes_cur = np.clip(codes_cur, 0, 3) | |
| energy_a = energies(d_cur, m_cur, codes_cur) | |
| if dm_solve is not None: | |
| dm_w = w_f64 if w_f64 is not None else np.ones_like(X_f64) | |
| d2, m2 = dm_solve(X_f64, dm_w, ls, lm, codes_cur, d_cur, m_cur, carry_f64, cfg) | |
| codes2 = codes_recompute_for(X_f64, d2, m2, ls, lm) | |
| energy_b = energies(d2, m2, codes2) | |
| take_b = energy_b < energy_a - 1.0e-18 | |
| d_cur = np.where(take_b, d2, d_cur) | |
| m_cur = np.where(take_b, m2, m_cur) | |
| codes_cur = np.where(take_b[:, None], codes2, codes_cur) | |
| energy_now = energies(d_cur, m_cur, codes_cur) | |
| take = energy_now < energy_best - 1.0e-18 | |
| best_d = np.where(take, d_cur, best_d) | |
| best_m = np.where(take, m_cur, best_m) | |
| best_codes = np.where(take[:, None], codes_cur, best_codes) | |
| energy_best = np.where(take, energy_now, energy_best) | |
| n_improved = int(np.sum(energy_best < energy0 - 1.0e-18)) | |
| return best_d, best_m, ls.copy(), lm.copy(), best_codes.astype(np.uint8), n_improved | |
| def thermal_refine_tensor( | |
| X: np.ndarray, | |
| w: np.ndarray | None, | |
| d: np.ndarray, | |
| m: np.ndarray, | |
| ls: np.ndarray, | |
| lm: np.ndarray, | |
| codes: np.ndarray, | |
| carry: np.ndarray, | |
| cfg: HPCConfig, | |
| dm_solve=None, | |
| chunk: int = 2048, | |
| progress=None, | |
| ) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, int]: | |
| """Chunked wrapper so peak memory stays bounded on large tensors.""" | |
| n_blocks = X.shape[0] | |
| step = max(1, int(chunk)) | |
| total_improved = 0 | |
| for start in range(0, n_blocks, step): | |
| stop = min(start + step, n_blocks) | |
| sel = slice(start, stop) | |
| d_c, m_c, ls_c, lm_c, c_c, improved = thermal_refine_codes( | |
| X[sel], | |
| None if w is None else w[sel], | |
| d[sel], | |
| m[sel], | |
| ls[sel], | |
| lm[sel], | |
| codes[sel], | |
| carry[sel], | |
| cfg, | |
| dm_solve=dm_solve, | |
| seed=cfg.therm_seed ^ (0x9E3779B97F4A7C15 * (start // step) & 0x3FFFFFFFFFFFFFFF), | |
| ) | |
| d[sel], m[sel], ls[sel], lm[sel], codes[sel] = d_c, m_c, ls_c, lm_c, c_c | |
| total_improved += improved | |
| if progress is not None: | |
| progress(stop, n_blocks, improved) | |
| return d, m, ls, lm, codes, total_improved | |
| def thermal_refine_uniform_codes( | |
| X: np.ndarray, | |
| w: np.ndarray | None, | |
| codes: np.ndarray, | |
| step: np.ndarray, | |
| m_const: np.ndarray, | |
| carry: np.ndarray, | |
| cfg: HPCConfig, | |
| lam_vw: float, | |
| lam_dc: float, | |
| levels: int, | |
| passes: int | None = None, | |
| seed: int | None = None, | |
| ) -> tuple[np.ndarray, int]: | |
| """Heat-bath annealing of a level-index code lattice with FIXED step scales. | |
| Uniform variant of ``thermal_refine_codes`` for the format quantizers | |
| (Q4_0 / Q1_0 / Q3_K): the per-code error step ``step`` (shape (B, n)) and | |
| the affine constant ``m_const`` (shape (B, n)) are fixed inputs — the | |
| dequantized value of level index ``q`` is ``step*q - m_const`` so the | |
| error is ``e = X - (step*q - m_const)`` and every move ``q -> q'`` has | |
| the exact closed-form delta again affine in the level grid. ``codes`` | |
| are integer level indices in ``[0, levels)``. | |
| Returns ``(codes, n_improved)``. Per block the returned state never has | |
| a higher extended energy than the input state and its SSE never exceeds | |
| the input SSE by more than ``cfg.sse_budget`` (extended-nonincreasing | |
| with an SSE guard, matching the pipeline's shaper semantics). | |
| """ | |
| n_blocks, n = X.shape | |
| if n_blocks == 0: | |
| return codes.astype(np.int64).copy(), 0 | |
| if n % 2 != 0: | |
| raise ValueError("thermal refinement needs an even row width") | |
| n_passes = cfg.therm_passes if passes is None else int(passes) | |
| if n_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0): | |
| return codes.astype(np.int64).copy(), 0 | |
| if cfg.fold_depth > 1: | |
| raise ValueError("thermal refinement supports fold_depth <= 1") | |
| X_f64 = np.asarray(X, dtype=np.float64) | |
| w_f64 = None if w is None else np.asarray(w, dtype=np.float64) | |
| carry_f64 = np.asarray(carry, dtype=np.float64) | |
| step = np.asarray(step, dtype=np.float64) | |
| m_b = np.asarray(m_const, dtype=np.float64) | |
| codes = np.asarray(codes, dtype=np.int64).copy() | |
| rng = np.random.default_rng(cfg.therm_seed if seed is None else seed) | |
| def energies(cc): | |
| e = X_f64 - (step * cc - m_b) | |
| return pair_energy(e, w_f64, lam_vw, lam_dc, carry_f64) | |
| energy_best, we_sq, v0, dc0 = energies(codes) | |
| energy0 = energy_best.copy() | |
| sse0 = np.sum(we_sq, axis=1) | |
| sse_cap = sse0 * (1.0 + float(cfg.sse_budget)) | |
| best_codes = codes.copy() | |
| codes_cur = codes.copy() | |
| for t in range(n_passes): | |
| temperature = cfg.therm_t0 * np.maximum(energy_best, 1.0e-30) / n * (cfg.therm_t_decay**t) | |
| for sweep in range(cfg.therm_sweeps + 2): | |
| greedy = sweep >= cfg.therm_sweeps | |
| e = X_f64 - (step * codes_cur - m_b) | |
| energy_cur, we_sq, v, dc = pair_energy(e, w_f64, lam_vw, lam_dc, carry_f64) | |
| sse = np.sum(we_sq, axis=1) | |
| gibbs_pair_pass( | |
| X_f64, | |
| w_f64, | |
| step, | |
| m_b, | |
| codes_cur, | |
| e, | |
| v, | |
| dc, | |
| sse, | |
| temperature, | |
| lam_vw, | |
| lam_dc, | |
| np.random.default_rng(rng.integers(0, 2**62)), | |
| greedy=greedy, | |
| levels=levels, | |
| ) | |
| if greedy: | |
| e = X_f64 - (step * codes_cur - m_b) | |
| energy_now, _, _, _ = pair_energy(e, w_f64, lam_vw, lam_dc, carry_f64) | |
| if not np.any(energy_now < energy_cur - 1.0e-18): | |
| break | |
| energy_now, we_sq, v_now, dc_now = energies(codes_cur) | |
| sse_now = np.sum(we_sq, axis=1) | |
| take = (energy_now < energy_best - 1.0e-18) & (sse_now <= sse_cap) | |
| best_codes = np.where(take[:, None], codes_cur, best_codes) | |
| energy_best = np.where(take, energy_now, energy_best) | |
| codes_cur = best_codes.copy() | |
| n_improved = int(np.sum(energy_best < energy0 - 1.0e-18)) | |
| return best_codes, n_improved | |
| def _refine_uniform_driver( | |
| X: np.ndarray, | |
| w: np.ndarray | None, | |
| codes: np.ndarray, | |
| build_state, | |
| carry: np.ndarray | None, | |
| lam_vw: float, | |
| lam_dc: float, | |
| levels: int, | |
| cfg: HPCConfig, | |
| chunk: int = 2048, | |
| progress=None, | |
| ) -> tuple[np.ndarray, int]: | |
| """Chunked uniform annealing so peak memory stays bounded on big tensors.""" | |
| n_blocks = X.shape[0] | |
| codes_out = np.asarray(codes, dtype=np.int64).copy() | |
| step = max(1, int(chunk)) | |
| total_improved = 0 | |
| for start in range(0, n_blocks, step): | |
| stop = min(start + step, n_blocks) | |
| sel = slice(start, stop) | |
| step_c, m_c = build_state(start, stop) | |
| carry_c = np.zeros(stop - start) if carry is None else carry[sel] | |
| c_c, improved = thermal_refine_uniform_codes( | |
| X[sel], | |
| None if w is None else w[sel], | |
| codes_out[sel], | |
| step_c, | |
| m_c, | |
| carry_c, | |
| cfg, | |
| lam_vw, | |
| lam_dc, | |
| levels, | |
| seed=cfg.therm_seed ^ (0x9E3779B97F4A7C15 * (start // step) & 0x3FFFFFFFFFFFFFFF), | |
| ) | |
| codes_out[sel] = c_c | |
| total_improved += improved | |
| if progress is not None: | |
| progress(stop, n_blocks, improved) | |
| return codes_out, total_improved | |
| def thermal_refine_q4_0( | |
| X: np.ndarray, | |
| W: np.ndarray | None, | |
| d: np.ndarray, | |
| q: np.ndarray, | |
| cfg: HPCConfig, | |
| chunk: int = 2048, | |
| progress=None, | |
| ) -> tuple[np.ndarray, int]: | |
| """Anneal Q4_0 nibble codes against fixed fp16 super-block scales. | |
| ``X``: raw block targets (B, 32); ``W``: importance weights (B, 32) or | |
| None; ``d``: fp16-precision block scales (B,), possibly negative | |
| (sign-flip polish outputs); ``q``: nibble codes (B, 32) with | |
| dequantization ``d*(q-8)``. The Gibbs step grid is the signed slope | |
| ``d`` itself, so negatives fall out naturally with no code remapping. | |
| Returns refined uint8 codes and the number of strictly improved blocks; | |
| a no-op when the refinery or spectrals are disabled. | |
| """ | |
| n_blocks, n = X.shape | |
| lam_vw, lam_dc = thermal_spectral_lams(cfg, n) | |
| if n_blocks == 0 or cfg.therm_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0): | |
| return q.copy(), 0 | |
| d64 = np.asarray(d, dtype=np.float64) | |
| def build_state(start, stop): | |
| ds = np.repeat(d64[start:stop][:, None], n, axis=1) | |
| return ds, 8.0 * ds | |
| codes_out, improved = _refine_uniform_driver( | |
| X, W, np.asarray(q, dtype=np.int64), build_state, None, | |
| lam_vw, lam_dc, 16, cfg, chunk=chunk, progress=progress, | |
| ) | |
| return codes_out.astype(np.uint8), improved | |
| def thermal_refine_q1_0( | |
| X: np.ndarray, | |
| W: np.ndarray | None, | |
| d: np.ndarray, | |
| bits: np.ndarray, | |
| cfg: HPCConfig, | |
| chunk: int = 2048, | |
| progress=None, | |
| ) -> tuple[np.ndarray, int]: | |
| """Anneal Q1_0 bits against fixed fp16 super-block magnitudes. | |
| ``bits``: binary codes (B, 32) with dequantization ``d*(2b-1)``; the | |
| Gibbs step grid is ``2*d`` with affine constant ``d``. Returns refined | |
| uint8 bits and the number of strictly improved blocks. | |
| """ | |
| n_blocks, n = X.shape | |
| lam_vw, lam_dc = thermal_spectral_lams(cfg, n) | |
| if n_blocks == 0 or cfg.therm_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0): | |
| return bits.copy(), 0 | |
| d64 = np.asarray(d, dtype=np.float64) | |
| def build_state(start, stop): | |
| ds = np.repeat(2.0 * np.abs(d64[start:stop])[:, None], n, axis=1) | |
| return ds, np.repeat(np.abs(d64[start:stop])[:, None], n, axis=1) | |
| codes_out, improved = _refine_uniform_driver( | |
| X, W, np.asarray(bits, dtype=np.int64), build_state, None, | |
| lam_vw, lam_dc, 2, cfg, chunk=chunk, progress=progress, | |
| ) | |
| return codes_out.astype(np.uint8), improved | |
| def thermal_refine_q3_k( | |
| X: np.ndarray, | |
| W: np.ndarray | None, | |
| scale: np.ndarray, | |
| codes: np.ndarray, | |
| cfg: HPCConfig, | |
| chunk: int = 2048, | |
| progress=None, | |
| ) -> tuple[np.ndarray, int]: | |
| """Anneal Q3_K signed 3-bit codes against fixed per-superblock scales. | |
| ``scale``: per-superblock scale values (B, 16) broadcast over the 16 | |
| codes each; ``codes``: source-signed values (B, 256) in [-4, 3]. The | |
| Gibbs grid covers all eight signed levels; negative scales (sign-flip | |
| polish outputs) are handled exactly via signed steps. | |
| """ | |
| n_blocks, n = X.shape | |
| lam_vw, lam_dc = thermal_spectral_lams(cfg, n) | |
| if n_blocks == 0 or cfg.therm_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0): | |
| return np.asarray(codes).copy(), 0 | |
| scale64 = np.asarray(scale, dtype=np.float64) | |
| def build_state(start, stop): | |
| ds = np.repeat(scale64[start:stop], 16, axis=1) | |
| return ds, 4.0 * ds | |
| codes_out, improved = _refine_uniform_driver( | |
| X, W, np.asarray(codes, dtype=np.int64) + 4, build_state, None, | |
| lam_vw, lam_dc, 8, cfg, chunk=chunk, progress=progress, | |
| ) | |
| return codes_out - 4, improved | |
| def thermal_refine_uniform_codes_cuda( | |
| X: "torch.Tensor", | |
| w: "torch.Tensor | None", | |
| codes: "torch.Tensor", | |
| step: "torch.Tensor", | |
| m_const: "torch.Tensor", | |
| carry: "torch.Tensor | None", | |
| lam_vw: float, | |
| lam_dc: float, | |
| levels: int, | |
| cfg: HPCConfig, | |
| passes: int | None = None, | |
| seed: int | None = None, | |
| ) -> tuple["torch.Tensor", int]: | |
| """Torch/CUDA sibling of ``thermal_refine_uniform_codes``. | |
| Identical algorithm and acceptance structure (extended-nonincreasing | |
| best-state with an ``sse_budget`` guard, seeded heat-bath sweeps plus a | |
| greedy endgame), run on-device in float64 so results are deterministic | |
| per device class (bit-exact across runs at the same seed, but not | |
| bit-identical to the numpy CPU chain — floating-point ordering differs). | |
| All tensor inputs live on one CUDA device: ``codes`` int64 (B, n), | |
| the rest float32/float64. Returns ``(codes, n_improved)`` with | |
| ``codes`` still on-device. | |
| """ | |
| import torch # local import keeps anneal.py torch-free for CPU installs | |
| dev = X.device | |
| f64 = torch.float64 | |
| B, n = X.shape | |
| half = n // 2 | |
| n_passes = cfg.therm_passes if passes is None else int(passes) | |
| if B == 0 or n % 2 != 0: | |
| return codes.to(torch.int64), 0 | |
| if n_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0): | |
| return codes.to(torch.int64).clone(), 0 | |
| if cfg.fold_depth > 1: | |
| raise ValueError("thermal refinement supports fold_depth <= 1") | |
| Xf = X.to(f64) | |
| wf = None if w is None else w.to(f64) | |
| cf = None if carry is None else carry.to(f64) | |
| if cf is None: | |
| cf = torch.zeros(B, dtype=f64, device=dev) | |
| sf = step.to(f64) | |
| mf_data = m_const.to(f64) | |
| cc = codes.to(torch.int64).clone() | |
| grid = torch.arange(levels, device=dev, dtype=torch.int64) | |
| ones_col = torch.ones(B, dtype=f64, device=dev) | |
| def energies(c): | |
| e = Xf - (sf * c.to(f64) - mf_data) | |
| w2 = torch.sum(e * e, dim=1) if wf is None else torch.sum(wf * e * e, dim=1) | |
| v = e[:, :half] + e[:, half:] | |
| dc = torch.sum(e, dim=1) + cf | |
| return (w2 + lam_vw * torch.sum(v * v, dim=1) + lam_dc * dc * dc), e, w2, v, dc | |
| energy_best, e, sse0, v, dc = energies(cc) | |
| energy0 = energy_best.clone() | |
| sse_cap = sse0 * (1.0 + float(cfg.sse_budget)) | |
| best = cc.clone() | |
| gen = torch.Generator(device=dev) | |
| gen.manual_seed(int(cfg.therm_seed if seed is None else seed) & 0x7FFFFFFFFFFFFFFF) | |
| for t in range(n_passes): | |
| temperature = ( | |
| cfg.therm_t0 | |
| * torch.maximum(energy_best, torch.full_like(energy_best, 1.0e-30)) | |
| / n | |
| * (cfg.therm_t_decay ** t) | |
| ) | |
| broke = False | |
| for sweep in range(cfg.therm_sweeps + 2): | |
| greedy = sweep >= cfg.therm_sweeps | |
| energy_cur, e, _, v, dc = energies(cc) | |
| for p in range(half): | |
| e_lo = e[:, p] | |
| e_hi = e[:, half + p] | |
| q_lo = cc[:, p] | |
| q_hi = cc[:, half + p] | |
| ds_lo = sf[:, p] | |
| ds_hi = sf[:, half + p] | |
| act_lo = ds_lo.abs() >= EPS_D | |
| act_hi = ds_hi.abs() >= EPS_D | |
| w_lo = ones_col if wf is None else wf[:, p] | |
| w_hi = ones_col if wf is None else wf[:, half + p] | |
| de_lo = ds_lo[:, None] * (q_lo[:, None] - grid[None, :]) | |
| de_lo = torch.where(act_lo[:, None], de_lo, torch.zeros_like(de_lo)) | |
| de_hi = ds_hi[:, None] * (q_hi[:, None] - grid[None, :]) | |
| de_hi = torch.where(act_hi[:, None], de_hi, torch.zeros_like(de_hi)) | |
| de_sum = de_lo[:, :, None] + de_hi[:, None, :] | |
| v_p = v[:, p] | |
| d_e = ( | |
| w_lo[:, None, None] * (2.0 * e_lo[:, None, None] * de_lo[:, :, None] + de_lo[:, :, None] ** 2) | |
| + w_hi[:, None, None] * (2.0 * e_hi[:, None, None] * de_hi[:, None, :] + de_hi[:, None, :] ** 2) | |
| + lam_vw * (2.0 * v_p[:, None, None] * de_sum + de_sum * de_sum) | |
| + lam_dc * (2.0 * dc[:, None, None] * de_sum + de_sum * de_sum) | |
| ) | |
| cur = q_lo * levels + q_hi | |
| if greedy: | |
| flat = d_e.reshape(B, -1) | |
| pick = flat.argmin(dim=1) | |
| improve = torch.gather(flat, 1, pick[:, None]).squeeze(1) < -1.0e-18 | |
| pick = torch.where(improve, pick, cur) | |
| else: | |
| scc = torch.clamp(temperature, min=1.0e-300)[:, None, None] | |
| logits = -d_e / scc | |
| logits = logits - logits.amax(dim=(1, 2), keepdim=True) | |
| probs = logits.exp().reshape(B, -1) | |
| dead = ~(act_lo | act_hi) | |
| if bool(dead.any()): | |
| probs[dead] = 0.0 | |
| probs[dead, cur[dead]] = 1.0 | |
| cum = probs.cumsum(dim=1) | |
| cum = cum / cum[:, -1:] | |
| u = torch.rand(B, 1, generator=gen, device=dev, dtype=f64) | |
| pick = (cum < u).sum(dim=1).clamp(max=levels * levels - 1) | |
| ia = pick // levels | |
| ib = pick - ia * levels | |
| upd_lo = torch.gather(de_lo, 1, ia[:, None]).squeeze(1) | |
| upd_hi = torch.gather(de_hi, 1, ib[:, None]).squeeze(1) | |
| usum = upd_lo + upd_hi | |
| e[:, p] = e_lo + upd_lo | |
| e[:, half + p] = e_hi + upd_hi | |
| v[:, p] += usum | |
| dc = dc + usum | |
| cc[:, p] = ia | |
| cc[:, half + p] = ib | |
| if greedy: | |
| energy_now, _, _, _, _ = energies(cc) | |
| if not bool(torch.any(energy_now < energy_cur - 1.0e-18)): | |
| broke = True | |
| break | |
| _, _, sse_now, _, _ = energies(cc) | |
| take = (energy_now < energy_best - 1.0e-18) & (sse_now <= sse_cap) | |
| best = torch.where(take[:, None], cc, best) | |
| energy_best = torch.where(take, energy_now, energy_best) | |
| cc = best.clone() | |
| if broke and bool(torch.all(energy_now >= energy_best)): | |
| pass # keep exact CPU-loop semantics: continue across passes regardless | |
| n_improved = int((energy_best < energy0 - 1.0e-18).sum().item()) | |
| return best, n_improved | |
| def thermal_refine_q3_k_cuda( | |
| X: "torch.Tensor", | |
| W: "torch.Tensor | None", | |
| scale: "torch.Tensor", | |
| codes: "torch.Tensor", | |
| cfg: HPCConfig, | |
| seed: int | None = None, | |
| ) -> tuple["torch.Tensor", int]: | |
| """On-device anneal for Q3_K signed 3-bit codes (see the CPU twin). | |
| ``scale``: per-superblock scale values (B, 16) on-device, ``codes``: | |
| signed values (B, 256) in [-4, 3]. Exactly mirrors | |
| ``thermal_refine_q3_k`` without any host round-trip. | |
| """ | |
| import torch | |
| n_blocks, n = X.shape | |
| lam_vw, lam_dc = thermal_spectral_lams(cfg, n) | |
| if n_blocks == 0 or cfg.therm_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0): | |
| return codes.to(torch.int64).clone(), 0 | |
| def build_state(start, stop): | |
| ds_full = scale.repeat_interleave(16, dim=1) # (B, 256) | |
| return ds_full, 4.0 * ds_full | |
| ds_full, m_full = build_state(0, n_blocks) | |
| codes_out, improved = thermal_refine_uniform_codes_cuda( | |
| X, W, codes.to(torch.int64) + 4, ds_full, m_full, None, | |
| lam_vw, lam_dc, 8, cfg, seed=seed, | |
| ) | |
| return codes_out - 4, improved | |