CompressedGemma commited on
Commit
1c35eef
·
verified ·
1 Parent(s): 489c860

Upload 11 files

Browse files
Files changed (11) hide show
  1. __init__.py +35 -0
  2. __main__.py +67 -0
  3. config.py +92 -0
  4. factored.py +547 -0
  5. fp16.py +55 -0
  6. pipeline.py +559 -0
  7. q2k.py +223 -0
  8. seeds.py +190 -0
  9. sieve.py +106 -0
  10. spectral.py +226 -0
  11. viterbi.py +164 -0
__init__.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HPC-Quantize V2: factored Q2_K quantization in Python.
2
+
3
+ The package keeps the full HPC decision stack (batched GGML seeds, WLS
4
+ superblock solve, factored candidate field, sequential Sieve, 36-state
5
+ Viterbi, spectral/DC shaping, local-safety floors) while replacing the
6
+ original factored solver with a monotone, multi-start, fp16-exact version.
7
+ """
8
+
9
+ from .config import HPCConfig
10
+ from .factored import (
11
+ FactorResult,
12
+ factor_1d,
13
+ factor_candidates,
14
+ factor_only_solve,
15
+ factor_q2k_block,
16
+ )
17
+ from .pipeline import Q2KQuantizer, QuantizedTensor, TensorStats
18
+ from .q2k import Q2KBlock, QK_K, dequantize_params, pack_codes, unpack_codes
19
+
20
+ __all__ = [
21
+ "HPCConfig",
22
+ "FactorResult",
23
+ "Q2KBlock",
24
+ "Q2KQuantizer",
25
+ "QuantizedTensor",
26
+ "TensorStats",
27
+ "QK_K",
28
+ "dequantize_params",
29
+ "factor_1d",
30
+ "factor_candidates",
31
+ "factor_only_solve",
32
+ "factor_q2k_block",
33
+ "pack_codes",
34
+ "unpack_codes",
35
+ ]
__main__.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Command-line entry point for the V2 factored Q2_K quantizer.
2
+
3
+ Examples::
4
+
5
+ python -m hpc_quantize_v2 --demo
6
+ python -m hpc_quantize_v2 --input tensor.npy --output tensor_q2k.npz
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+
13
+ import numpy as np
14
+
15
+ from .config import HPCConfig
16
+ from .pipeline import Q2KQuantizer
17
+
18
+
19
+ def main() -> None:
20
+ parser = argparse.ArgumentParser(description="HPC-Quantize V2 factored Q2_K")
21
+ parser.add_argument("--input", help=".npy file containing float weights")
22
+ parser.add_argument("--importance", help="optional .npy importance matrix")
23
+ parser.add_argument("--output", help="output .npz with scales/qs/dm")
24
+ parser.add_argument("--demo", action="store_true", help="quantize synthetic weights")
25
+ parser.add_argument("--blocks", type=int, default=64, help="demo block count")
26
+ parser.add_argument("--std", type=float, default=0.02, help="demo weight std")
27
+ parser.add_argument("--seed", type=int, default=1234, help="demo RNG seed")
28
+ parser.add_argument("--cand-d", type=int, default=None, help="candidate d count")
29
+ parser.add_argument("--cand-m", type=int, default=None, help="candidate min count")
30
+ parser.add_argument("--mse", action="store_true", help="disable HPC sequence search")
31
+ parser.add_argument("--verbose", action="store_true")
32
+ args = parser.parse_args()
33
+
34
+ if args.demo:
35
+ rng = np.random.default_rng(args.seed)
36
+ weights = rng.normal(0.0, args.std, size=(args.blocks, 256)).astype(np.float32)
37
+ importance = None
38
+ elif args.input:
39
+ weights = np.load(args.input)
40
+ importance = np.load(args.importance) if args.importance else None
41
+ else:
42
+ parser.error("pass --demo or --input")
43
+
44
+ cfg = HPCConfig(verbose=True)
45
+ if args.cand_d is not None:
46
+ cfg.n_cand_d = args.cand_d
47
+ if args.cand_m is not None:
48
+ cfg.n_cand_m = args.cand_m
49
+ if args.mse:
50
+ cfg.optimizer = "mse"
51
+
52
+ result = Q2KQuantizer(cfg).quantize(weights, importance=importance)
53
+ if args.output:
54
+ np.savez_compressed(
55
+ args.output,
56
+ scales=result.scales,
57
+ qs=result.qs,
58
+ dm=result.dm,
59
+ dequantized=result.dequantize(),
60
+ )
61
+ print(f"wrote {args.output}")
62
+ else:
63
+ _ = result.dequantize()
64
+
65
+
66
+ if __name__ == "__main__":
67
+ main()
config.py ADDED
@@ -0,0 +1,92 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Configuration for the HPC-Quantize V2 Q2_K engine.
2
+
3
+ Every tunable that was a compile-time macro, environment variable or hidden
4
+ global in the C engine is collected here as an explicit, validated field.
5
+ The defaults are the C engine's production defaults unless marked
6
+ ``V2`` in the docstring, in which case they are deliberate improvements.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from dataclasses import dataclass
12
+
13
+
14
+ @dataclass(slots=True)
15
+ class HPCConfig:
16
+ # ---- block geometry -------------------------------------------------
17
+ qk_k: int = 256
18
+
19
+ # ---- candidate field ------------------------------------------------
20
+ # V2 improvement: the default is the 6x6 symbolic lattice (36 exact
21
+ # factored solves) instead of the 24x24 physical grid (576 solves).
22
+ # Set to 24/24 to reproduce the full C candidate field.
23
+ n_cand_d: int = 6
24
+ n_cand_m: int = 6
25
+ factor_pin: int = 3
26
+ factor_iters: int = 1
27
+ factor_standalone_iters: int = 16
28
+ factor_starts: int = 3
29
+ factor_ulp_radius: int = 1
30
+ factor_refine_1d: bool = True
31
+ factor_spectral_refine: bool = True
32
+ factor_exact_emit: bool = True
33
+
34
+ # ---- objective ------------------------------------------------------
35
+ use_spectral: bool = True
36
+ dc_lambda: float = 1.0
37
+ vw_lambda: float = 1.0
38
+ fold_depth: int = 1
39
+ fold_gamma: float = 2.0
40
+ dc_decay: float = 0.85
41
+ carry_cumulative: bool = True
42
+ sse_budget: float = 5.0e-4
43
+ greedy_override_ratio: float = 0.995
44
+
45
+ # ---- sieve ----------------------------------------------------------
46
+ same_penalty: float = 0.85
47
+ log_slack: float = 1.5
48
+ quhits_per_block: int = 2
49
+ graph_max_blocks: int = 8192
50
+
51
+ # ---- Viterbi --------------------------------------------------------
52
+ optimizer: str = "hpc"
53
+ viterbi_beta: float = 0.25
54
+ viterbi_alpha: float = 0.08
55
+ temperature_sample_blocks: int = 128
56
+
57
+ # ---- assembly -------------------------------------------------------
58
+ refine_passes: int = 3
59
+ ulp_radius: int = 4
60
+ subblock_states: int = 6
61
+ subblock_sieve: bool = True
62
+ use_factor_floor: bool = True
63
+ use_candidate_floor: bool = True
64
+ apply_dc_carry: bool = True
65
+
66
+ # ---- seed clamp -----------------------------------------------------
67
+ clamp_kurtosis_off: float = 6.0
68
+ clamp_kurtosis_soft: float = 20.0
69
+ clamp_sigma_soft: float = 6.0
70
+ clamp_sigma_hard: float = 4.0
71
+
72
+ # ---- reporting ------------------------------------------------------
73
+ verbose: bool = False
74
+
75
+ def validate(self) -> "HPCConfig":
76
+ if self.qk_k != 256:
77
+ raise ValueError("Q2_K requires qk_k == 256")
78
+ if not (1 <= self.n_cand_d <= 24 and 1 <= self.n_cand_m <= 24):
79
+ raise ValueError("candidate counts must lie in [1, 24]")
80
+ if self.optimizer not in ("hpc", "mse"):
81
+ raise ValueError("optimizer must be 'hpc' or 'mse'")
82
+ if not 0 <= self.factor_pin <= 3:
83
+ raise ValueError("factor_pin must be a 2-bit mask")
84
+ if self.fold_depth < 0:
85
+ raise ValueError("fold_depth must be non-negative")
86
+ if not 0.0 <= self.dc_decay <= 1.0:
87
+ raise ValueError("dc_decay must lie in [0, 1]")
88
+ if self.ulp_radius < 0 or self.factor_ulp_radius < 0:
89
+ raise ValueError("ULP radii must be non-negative")
90
+ if self.refine_passes < 1:
91
+ raise ValueError("refine_passes must be >= 1")
92
+ return self
factored.py ADDED
@@ -0,0 +1,547 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Grid-free factored Q2_K solver (V2).
2
+
3
+ The reference solver alternates four exact coordinate solves::
4
+
5
+ q | (d, Ls, dmin, Lm)
6
+ s_j,t_j | q
7
+ d,Ls | s
8
+ dmin,Lm | t
9
+
10
+ V2 keeps that decomposition but fixes three weaknesses:
11
+
12
+ 1. ``factor_1d`` is no longer a fixed six-iteration Lloyd heuristic. It
13
+ starts from several deterministic candidates and finishes with an exact
14
+ integer-code coordinate refinement, so its weighted error is monotone.
15
+ 2. The block solve is multi-start, accepts only objective-decreasing states,
16
+ and returns the best snapshot rather than the last iterate.
17
+ 3. The emitted fp16 ``(d, dmin)`` pair is polished in its own domain, and the
18
+ spectral/DC shaping stage is applied inside the solver. The returned
19
+ objective therefore equals the objective of the bytes that are written.
20
+
21
+ The result object is immutable and self-describing, replacing the C
22
+ engine's many out-parameters.
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ from dataclasses import dataclass
28
+
29
+ import numpy as np
30
+
31
+ from .config import HPCConfig
32
+ from .fp16 import from_fp16_bits, fp16_ulp_neighbors, nearest_int, round_fp16, to_fp16_bits
33
+ from .q2k import NS, QK_K, codes_from_params, derive_subscales
34
+ from .spectral import objective, refine_codes_hadamard
35
+
36
+ N_CAND_D = 24
37
+ N_CAND_M = 24
38
+ TOTAL_CANDIDATES = N_CAND_D * N_CAND_M
39
+
40
+ MULTS_D = np.array(
41
+ [
42
+ 0.780,
43
+ 0.835,
44
+ 0.880,
45
+ 0.915,
46
+ 0.943,
47
+ 0.963,
48
+ 0.978,
49
+ 0.988,
50
+ 0.994,
51
+ 0.997,
52
+ 0.999,
53
+ 1.000,
54
+ 1.002,
55
+ 1.005,
56
+ 1.011,
57
+ 1.021,
58
+ 1.035,
59
+ 1.054,
60
+ 1.080,
61
+ 1.115,
62
+ 1.160,
63
+ 1.215,
64
+ 1.275,
65
+ 1.340,
66
+ ],
67
+ dtype=np.float32,
68
+ )
69
+ MULTS_M = np.array(
70
+ [
71
+ 0.750,
72
+ 0.800,
73
+ 0.840,
74
+ 0.870,
75
+ 0.900,
76
+ 0.920,
77
+ 0.940,
78
+ 0.955,
79
+ 0.970,
80
+ 0.985,
81
+ 0.995,
82
+ 1.000,
83
+ 1.005,
84
+ 1.015,
85
+ 1.030,
86
+ 1.045,
87
+ 1.060,
88
+ 1.080,
89
+ 1.100,
90
+ 1.130,
91
+ 1.160,
92
+ 1.200,
93
+ 1.250,
94
+ 1.300,
95
+ ],
96
+ dtype=np.float32,
97
+ )
98
+
99
+ CAND_TO_QUHIT = np.array(
100
+ [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5],
101
+ dtype=np.int64,
102
+ )
103
+
104
+
105
+ @dataclass(slots=True)
106
+ class FactorResult:
107
+ d: np.float32
108
+ dmin: np.float32
109
+ d16: np.uint16
110
+ m16: np.uint16
111
+ ls: np.ndarray
112
+ lm: np.ndarray
113
+ codes: np.ndarray
114
+ sse: float
115
+ objective: float
116
+ iterations: int
117
+ starts: int
118
+
119
+ def subscales(self) -> np.ndarray:
120
+ return (self.ls & 0x0F) | ((self.lm & 0x0F) << 4)
121
+
122
+
123
+ def _factor_1d_lloyd(v: np.ndarray, w: np.ndarray, nmax: int, iters: int):
124
+ vmax = float(v.max())
125
+ if vmax < 1.0e-30:
126
+ return 0.0, np.zeros(v.size, dtype=np.uint8)
127
+ best_d = vmax / nmax
128
+ best_codes = np.clip(nearest_int(v / best_d), 0, nmax)
129
+ best_obj = float(np.sum(w * (v - best_d * best_codes) ** 2))
130
+
131
+ starts = [vmax / nmax]
132
+ init_codes = np.clip(nearest_int(v / starts[0]), 0, nmax)
133
+ den0 = float(np.sum(w * init_codes * init_codes))
134
+ if den0 > 1.0e-30:
135
+ starts.append(float(np.sum(w * v * init_codes) / den0))
136
+
137
+ seen: set[float] = set()
138
+ for d0 in starts:
139
+ if not np.isfinite(d0) or d0 <= 0.0:
140
+ continue
141
+ key = float(np.float32(d0))
142
+ if key in seen:
143
+ continue
144
+ seen.add(key)
145
+ d = float(d0)
146
+ codes = np.clip(nearest_int(v / d), 0, nmax)
147
+ for _ in range(iters):
148
+ den = float(np.sum(w * codes * codes))
149
+ if den < 1.0e-30:
150
+ break
151
+ dn = float(np.sum(w * v * codes) / den)
152
+ if dn <= 0.0:
153
+ break
154
+ conv = abs(dn - d) <= 1.0e-7 * abs(d) + 1.0e-30
155
+ d = dn
156
+ codes = np.clip(nearest_int(v / d), 0, nmax)
157
+ if conv:
158
+ break
159
+ obj = float(np.sum(w * (v - d * codes) ** 2))
160
+ if obj < best_obj:
161
+ best_obj = obj
162
+ best_d = d
163
+ best_codes = codes.copy()
164
+ return best_d, best_codes
165
+
166
+
167
+ def _refine_integer_codes(v, w, d, codes, nmax, sweeps=2):
168
+ v = np.asarray(v, dtype=np.float64)
169
+ w = np.asarray(w, dtype=np.float64)
170
+ codes = np.asarray(codes, dtype=np.int64).copy()
171
+ d = float(d)
172
+ if d <= 0.0:
173
+ return d, codes.astype(np.uint8)
174
+ obj = float(np.sum(w * (v - d * codes) ** 2))
175
+ options = np.arange(nmax + 1, dtype=np.int64)
176
+ for _ in range(sweeps):
177
+ improved = False
178
+ for i in range(v.size):
179
+ wi, vi, li = w[i], v[i], codes[i]
180
+ sum_lv = float(np.sum(w * v * codes))
181
+ sum_l2 = float(np.sum(w * codes * codes))
182
+ slv = sum_lv - wi * vi * li + wi * vi * options
183
+ sl2 = sum_l2 - wi * li * li + wi * options * options
184
+ valid = sl2 > 1.0e-30
185
+ if not np.any(valid):
186
+ continue
187
+ dd = np.where(valid, slv / np.where(valid, sl2, 1.0), 0.0)
188
+ trial = codes[None, :].repeat(options.size, axis=0)
189
+ trial[:, i] = options
190
+ e = v[None, :] - dd[:, None] * trial.astype(np.float64)
191
+ scores = np.sum(w[None, :] * e * e, axis=1)
192
+ scores = np.where(valid & (dd > 0.0), scores, np.inf)
193
+ k = int(np.argmin(scores))
194
+ if scores[k] < obj - 1.0e-18 and options[k] != li:
195
+ obj = float(scores[k])
196
+ d = float(dd[k])
197
+ codes[i] = options[k]
198
+ improved = True
199
+ if not improved:
200
+ break
201
+ return d, codes.astype(np.uint8)
202
+
203
+
204
+ def factor_1d(v, w, nmax: int = 15, iters: int = 8, refine: bool = True):
205
+ """Weighted 1-D factorisation ``v ~= d * L`` with ``L in [0, nmax]``."""
206
+ v = np.maximum(np.asarray(v, dtype=np.float64), 0.0)
207
+ w = np.maximum(np.asarray(w, dtype=np.float64), 0.0)
208
+ d, codes = _factor_1d_lloyd(v, w, nmax, iters)
209
+ if refine and d > 0.0:
210
+ d, codes = _refine_integer_codes(v, w, d, codes, nmax)
211
+ return d, codes
212
+
213
+
214
+ def _range_init(x, w, cfg: HPCConfig):
215
+ x = np.asarray(x, dtype=np.float64)
216
+ w = np.asarray(w, dtype=np.float64)
217
+ blocks = x.reshape(NS, 16)
218
+ wblocks = w.reshape(NS, 16)
219
+ s = np.zeros(NS)
220
+ t = np.zeros(NS)
221
+ codes = np.zeros(QK_K, dtype=np.uint8)
222
+ for j in range(NS):
223
+ sub = blocks[j]
224
+ xmin = min(float(sub.min()), 0.0)
225
+ xmax = float(sub.max())
226
+ span = xmax - xmin
227
+ sj = span / 3.0 if span > 1.0e-30 else 0.0
228
+ tj = -xmin
229
+ s[j] = sj
230
+ t[j] = tj
231
+ if sj > 1.0e-30:
232
+ codes[16 * j : 16 * (j + 1)] = np.clip(
233
+ nearest_int((sub + tj) / sj), 0, 3
234
+ ).astype(np.uint8)
235
+ sw = wblocks.sum(axis=1) if w is not None else np.full(NS, 16.0)
236
+ d, ls = factor_1d(s, sw, 15, cfg.factor_iters, cfg.factor_refine_1d)
237
+ m, lm = factor_1d(t, sw, 15, cfg.factor_iters, cfg.factor_refine_1d)
238
+ return d, m, ls, lm, codes
239
+
240
+
241
+ def _seeded_init(x, scales, mins, d_seed, m_seed):
242
+ d = float(d_seed)
243
+ m = float(m_seed)
244
+ ls, lm = derive_subscales(scales, mins, d, m)
245
+ codes = codes_from_params(x, d, m, ls, lm)
246
+ return d, m, ls, lm, codes
247
+
248
+
249
+ def _ls_init(x, w, cfg: HPCConfig):
250
+ x = np.asarray(x, dtype=np.float64)
251
+ w = np.asarray(w, dtype=np.float64)
252
+ blocks = x.reshape(NS, 16)
253
+ codes = np.zeros(QK_K, dtype=np.uint8)
254
+ for j in range(NS):
255
+ sub = blocks[j]
256
+ xmin = min(float(sub.min()), 0.0)
257
+ xmax = float(sub.max())
258
+ span = xmax - xmin
259
+ if span > 1.0e-30:
260
+ codes[16 * j : 16 * (j + 1)] = np.clip(
261
+ nearest_int((sub - xmin) * 3.0 / span), 0, 3
262
+ ).astype(np.uint8)
263
+ s, t = _sub_ls(x, w, codes)
264
+ sw = w.reshape(NS, 16).sum(axis=1)
265
+ d, ls = factor_1d(s, sw, 15, cfg.factor_iters, cfg.factor_refine_1d)
266
+ m, lm = factor_1d(t, sw, 15, cfg.factor_iters, cfg.factor_refine_1d)
267
+ codes = codes_from_params(x, d, m, ls, lm)
268
+ return d, m, ls, lm, codes
269
+
270
+
271
+ def _sub_ls(x, w, codes):
272
+ x = np.asarray(x, dtype=np.float64)
273
+ w = np.asarray(w, dtype=np.float64)
274
+ q = np.asarray(codes, dtype=np.float64).reshape(NS, 16)
275
+ xb = x.reshape(NS, 16)
276
+ wb = w.reshape(NS, 16)
277
+ s = np.zeros(NS)
278
+ t = np.zeros(NS)
279
+ for j in range(NS):
280
+ ww = wb[j]
281
+ qq = q[j]
282
+ xx = xb[j]
283
+ Sqq = float(np.sum(ww * qq * qq))
284
+ Swq = float(np.sum(ww * qq))
285
+ Sw = float(np.sum(ww))
286
+ Sqx = float(np.sum(ww * qq * xx))
287
+ Sx = float(np.sum(ww * xx))
288
+ det = Sqq * Sw - Swq * Swq
289
+ if det > 1.0e-30:
290
+ sj = (Sqx * Sw - Swq * Sx) / det
291
+ tj = (sj * Swq - Sx) / Sw
292
+ else:
293
+ sj = 0.0
294
+ tj = 0.0
295
+ if sj < 0.0:
296
+ sj = 0.0
297
+ if tj < 0.0:
298
+ tj = 0.0
299
+ sj = Sqx / Sqq if Sqq > 1.0e-30 else 0.0
300
+ if sj < 0.0:
301
+ sj = 0.0
302
+ s[j] = sj
303
+ t[j] = tj
304
+ return s, t
305
+
306
+
307
+ def _solve(
308
+ x,
309
+ w,
310
+ cfg: HPCConfig,
311
+ d,
312
+ m,
313
+ ls,
314
+ lm,
315
+ codes,
316
+ pin: int,
317
+ use_spectral: bool,
318
+ max_iter: int,
319
+ dc_carry: float,
320
+ ):
321
+ x = np.asarray(x, dtype=np.float32)
322
+ w = np.asarray(w, dtype=np.float32)
323
+ ls = np.asarray(ls, dtype=np.uint8).copy()
324
+ lm = np.asarray(lm, dtype=np.uint8).copy()
325
+ codes = np.asarray(codes, dtype=np.uint8).copy()
326
+ d = float(d)
327
+ m = float(m)
328
+
329
+ obj, _ = objective(x, w, d, m, ls, lm, codes, cfg, dc_carry)
330
+ best = (obj, d, m, ls.copy(), lm.copy(), codes.copy())
331
+ stall = 0
332
+ iters = 0
333
+ for it in range(max(1, max_iter)):
334
+ iters = it + 1
335
+ codes = codes_from_params(x, d, m, ls, lm)
336
+ s, t = _sub_ls(x, w, codes)
337
+ q = codes.astype(np.float64).reshape(NS, 16)
338
+ wb = w.reshape(NS, 16)
339
+ ws = np.sum(wb * q * q, axis=1)
340
+ wt = np.sum(wb, axis=1)
341
+
342
+ if pin & 1:
343
+ if d > 1.0e-30:
344
+ ls = np.clip(nearest_int(s / d), 0, 15).astype(np.uint8)
345
+ else:
346
+ ls = np.zeros(NS, dtype=np.uint8)
347
+ else:
348
+ d_new, ls_new = factor_1d(
349
+ s, ws, 15, cfg.factor_iters, cfg.factor_refine_1d
350
+ )
351
+ d, ls = float(d_new), ls_new
352
+
353
+ if pin & 2:
354
+ if m > 1.0e-30:
355
+ lm = np.clip(nearest_int(t / m), 0, 15).astype(np.uint8)
356
+ else:
357
+ lm = np.zeros(NS, dtype=np.uint8)
358
+ else:
359
+ m_new, lm_new = factor_1d(
360
+ t, wt, 15, cfg.factor_iters, cfg.factor_refine_1d
361
+ )
362
+ m, lm = float(m_new), lm_new
363
+
364
+ codes = codes_from_params(x, d, m, ls, lm)
365
+ cur, _ = objective(x, w, d, m, ls, lm, codes, cfg, dc_carry)
366
+ if cur < best[0] - 1.0e-15:
367
+ best = (cur, d, m, ls.copy(), lm.copy(), codes.copy())
368
+ stall = 0
369
+ else:
370
+ stall += 1
371
+ if stall >= 2:
372
+ break
373
+
374
+ obj, d, m, ls, lm, codes = best
375
+ return d, m, ls, lm, codes, iters
376
+
377
+
378
+ def _emit_polish(x, w, cfg, d, m, ls, lm, codes, dc_carry):
379
+ d16 = np.uint16(to_fp16_bits(np.float32(d)))
380
+ m16 = np.uint16(to_fp16_bits(np.float32(m)))
381
+ d = float(from_fp16_bits(d16))
382
+ m = float(from_fp16_bits(m16))
383
+ codes = codes_from_params(x, d, m, ls, lm)
384
+ obj, sse = objective(x, w, d, m, ls, lm, codes, cfg, dc_carry)
385
+ best = (obj, sse, d, m, d16, m16, ls.copy(), lm.copy(), codes.copy())
386
+
387
+ if cfg.factor_exact_emit and cfg.factor_ulp_radius > 0:
388
+ d_choices = fp16_ulp_neighbors(d16, cfg.factor_ulp_radius)
389
+ m_choices = fp16_ulp_neighbors(m16, cfg.factor_ulp_radius)
390
+ for db in d_choices:
391
+ dd = float(from_fp16_bits(db))
392
+ for mb in m_choices:
393
+ mm = float(from_fp16_bits(mb))
394
+ c = codes_from_params(x, dd, mm, ls, lm)
395
+ o, s = objective(x, w, dd, mm, ls, lm, c, cfg, dc_carry)
396
+ if o < best[0] - 1.0e-15:
397
+ best = (o, s, dd, mm, np.uint16(db), np.uint16(mb), ls.copy(), lm.copy(), c)
398
+ obj, sse, d, m, d16, m16, ls, lm, codes = best
399
+
400
+ if cfg.factor_spectral_refine:
401
+ refined = refine_codes_hadamard(
402
+ x, w, d, m, ls, lm, codes, cfg, dc_carry
403
+ )
404
+ o, s = objective(x, w, d, m, ls, lm, refined, cfg, dc_carry)
405
+ if o < obj - 1.0e-15:
406
+ obj, sse, codes = o, s, refined
407
+ return d, m, d16, m16, ls, lm, codes, sse, obj
408
+
409
+
410
+ def factor_q2k_block(
411
+ x,
412
+ w=None,
413
+ cfg: HPCConfig | None = None,
414
+ init_s=None,
415
+ init_t=None,
416
+ d_seed: float = 0.0,
417
+ m_seed: float = 0.0,
418
+ pin: int = 0,
419
+ use_spectral: bool | None = None,
420
+ max_iter: int | None = None,
421
+ starts: int | None = None,
422
+ dc_carry: float = 0.0,
423
+ ) -> FactorResult:
424
+ cfg = (cfg or HPCConfig()).validate()
425
+ x = np.asarray(x, dtype=np.float32).reshape(QK_K)
426
+ w = (
427
+ np.ones(QK_K, dtype=np.float32)
428
+ if w is None
429
+ else np.asarray(w, dtype=np.float32).reshape(QK_K)
430
+ )
431
+ use_spectral = cfg.use_spectral if use_spectral is None else use_spectral
432
+ max_iter = cfg.factor_standalone_iters if max_iter is None else max_iter
433
+ starts = cfg.factor_starts if starts is None else starts
434
+
435
+ candidates = []
436
+ if init_s is not None and init_t is not None and d_seed > 1.0e-30 and m_seed > 1.0e-30:
437
+ d, m, ls, lm, codes = _seeded_init(x, init_s, init_t, d_seed, m_seed)
438
+ candidates.append((d, m, ls, lm, codes))
439
+ else:
440
+ d, m, ls, lm, codes = _range_init(x, w, cfg)
441
+ if d_seed > 1.0e-30:
442
+ d = float(d_seed)
443
+ if init_s is not None:
444
+ ls = np.clip(nearest_int(np.asarray(init_s) / d), 0, 15).astype(np.uint8)
445
+ if m_seed > 1.0e-30:
446
+ m = float(m_seed)
447
+ if init_t is not None:
448
+ lm = np.clip(nearest_int(np.asarray(init_t) / m), 0, 15).astype(np.uint8)
449
+ candidates.append((d, m, ls, lm, codes))
450
+ if starts > 1:
451
+ candidates.append(_ls_init(x, w, cfg))
452
+
453
+ best = None
454
+ total_iters = 0
455
+ for d, m, ls, lm, codes in candidates:
456
+ d, m, ls, lm, codes, it = _solve(
457
+ x, w, cfg, d, m, ls, lm, codes, pin, use_spectral, max_iter, dc_carry
458
+ )
459
+ total_iters += it
460
+ d, m, d16, m16, ls, lm, codes, sse, obj = _emit_polish(
461
+ x, w, cfg, d, m, ls, lm, codes, dc_carry
462
+ )
463
+ if best is None or obj < best.objective - 1.0e-15:
464
+ best = FactorResult(
465
+ d=np.float32(d),
466
+ dmin=np.float32(m),
467
+ d16=np.uint16(d16),
468
+ m16=np.uint16(m16),
469
+ ls=ls.astype(np.uint8),
470
+ lm=lm.astype(np.uint8),
471
+ codes=codes.astype(np.uint8),
472
+ sse=float(sse),
473
+ objective=float(obj),
474
+ iterations=total_iters,
475
+ starts=len(candidates),
476
+ )
477
+ return best
478
+
479
+
480
+ def factor_candidates(
481
+ x,
482
+ w,
483
+ scales,
484
+ mins,
485
+ base_d,
486
+ base_m,
487
+ cfg: HPCConfig,
488
+ dc_carry: float = 0.0,
489
+ ) -> np.ndarray:
490
+ """Factored candidate field indexed exactly like the C 24x24 lattice."""
491
+ out = np.full(TOTAL_CANDIDATES, np.inf, dtype=np.float32)
492
+ nd = int(cfg.n_cand_d)
493
+ nm = int(cfg.n_cand_m)
494
+ for a in range(nd):
495
+ di = a if nd == N_CAND_D else (a * N_CAND_D) // nd
496
+ ds = float(round_fp16(np.float32(base_d) * MULTS_D[di]))
497
+ for b in range(nm):
498
+ mi = b if nm == N_CAND_M else (b * N_CAND_M) // nm
499
+ ms = float(round_fp16(np.float32(base_m) * MULTS_M[mi]))
500
+ res = factor_q2k_block(
501
+ x,
502
+ w,
503
+ cfg,
504
+ init_s=scales,
505
+ init_t=mins,
506
+ d_seed=ds,
507
+ m_seed=ms,
508
+ pin=cfg.factor_pin,
509
+ use_spectral=cfg.use_spectral,
510
+ max_iter=cfg.factor_iters,
511
+ starts=1,
512
+ dc_carry=dc_carry,
513
+ )
514
+ if nd == N_CAND_D and nm == N_CAND_M:
515
+ out[di * N_CAND_M + mi] = np.float32(res.objective)
516
+ else:
517
+ cb = di >> 2
518
+ fb = mi >> 2
519
+ for aa in range(4):
520
+ for bb in range(4):
521
+ idx = (cb * 4 + aa) * N_CAND_M + fb * 4 + bb
522
+ if res.objective < out[idx]:
523
+ out[idx] = np.float32(res.objective)
524
+ return out
525
+
526
+
527
+ def factor_only_solve(
528
+ x, w, cfg: HPCConfig, dc_carry: float = 0.0
529
+ ) -> FactorResult:
530
+ return factor_q2k_block(
531
+ x,
532
+ w,
533
+ cfg,
534
+ pin=0,
535
+ use_spectral=cfg.use_spectral,
536
+ max_iter=cfg.factor_standalone_iters,
537
+ starts=cfg.factor_starts,
538
+ dc_carry=dc_carry,
539
+ )
540
+
541
+
542
+ def candidate_pair(base_d, base_m, cidx: int) -> tuple[np.uint16, np.uint16]:
543
+ di = int(cidx) // N_CAND_M
544
+ mi = int(cidx) % N_CAND_M
545
+ d16 = np.uint16(to_fp16_bits(np.float32(base_d) * MULTS_D[di]))
546
+ m16 = np.uint16(to_fp16_bits(np.float32(base_m) * MULTS_M[mi]))
547
+ return d16, m16
fp16.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Bit-exact GGML numerical primitives.
2
+
3
+ The C engine relies on two non-obvious primitives:
4
+
5
+ * ``gguf_nearest_int`` implements round-to-nearest-even with the classic
6
+ ``float + 1.5*2**23`` mantissa trick rather than ``rint``. The trick is
7
+ reproduced here so that every quantized code is bit-identical to the
8
+ reference implementation.
9
+ * ``gguf_fp32_to_fp16`` / ``gguf_fp16_to_fp32`` are the emitted parameter
10
+ domain. Every solver in V2 may optimise in float32, but it reports and
11
+ stores only fp16-rounded values.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import numpy as np
17
+
18
+ _MAGIC = np.float32(12582912.0)
19
+ _F16_MAX_BITS = 0x7BFF
20
+
21
+
22
+ def nearest_int(value) -> np.ndarray:
23
+ """GGML-compatible nearest-integer rounding (ties to even)."""
24
+ arr = np.asarray(value, dtype=np.float32)
25
+ shifted = arr + _MAGIC
26
+ bits = shifted.view(np.int32)
27
+ return ((bits & 0x007FFFFF) - 0x00400000).astype(np.int64)
28
+
29
+
30
+ def to_fp16_bits(value) -> np.ndarray:
31
+ arr = np.asarray(value, dtype=np.float32)
32
+ return arr.astype(np.float16).view(np.uint16).astype(np.uint16)
33
+
34
+
35
+ def from_fp16_bits(bits) -> np.ndarray:
36
+ arr = np.asarray(bits, dtype=np.uint16)
37
+ return arr.view(np.float16).astype(np.float32)
38
+
39
+
40
+ def round_fp16(value) -> np.ndarray:
41
+ return from_fp16_bits(to_fp16_bits(value))
42
+
43
+
44
+ def fp16_ulp_neighbors(bits, radius: int) -> np.ndarray:
45
+ """All non-negative finite fp16 bit patterns within ``radius`` ULPs."""
46
+ if radius < 0:
47
+ raise ValueError("radius must be non-negative")
48
+ b = int(bits)
49
+ lo = max(0, b - radius)
50
+ hi = min(_F16_MAX_BITS, b + radius)
51
+ return np.arange(lo, hi + 1, dtype=np.uint16)
52
+
53
+
54
+ def epsilon_for(scale: np.ndarray, relative: float = 1.0e-7) -> np.ndarray:
55
+ return relative * np.abs(scale) + np.float32(1.0e-30)
pipeline.py ADDED
@@ -0,0 +1,559 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """End-to-end Q2_K pipeline preserving the HPC machinery.
2
+
3
+ Pipeline stages (mirroring the C engine, with V2 fixes):
4
+
5
+ 1. kurtosis-gated outlier clamp and batched GGML seeds;
6
+ 2. vectorised WLS superblock solve;
7
+ 3. factored candidate field + standalone factored floor;
8
+ 4. sequential Sieve over coarse/fine symbolic states;
9
+ 5. 36-state Viterbi sequence optimisation;
10
+ 6. candidate-aware factor consistency pass;
11
+ 7. rolling DC residual carry;
12
+ 8. sub-block Sieve assembly, LS refit, fp16-ULP polish, spectral shaping;
13
+ 9. objective-consistent local-safety floors and DC nudge.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ from dataclasses import dataclass
19
+ from math import ceil
20
+
21
+ import numpy as np
22
+
23
+ from .config import HPCConfig
24
+ from .factored import (
25
+ CAND_TO_QUHIT,
26
+ N_CAND_M,
27
+ TOTAL_CANDIDATES,
28
+ FactorResult,
29
+ candidate_pair,
30
+ factor_candidates,
31
+ factor_only_solve,
32
+ factor_q2k_block,
33
+ )
34
+ from .fp16 import from_fp16_bits, nearest_int, round_fp16, to_fp16_bits
35
+ from .q2k import (
36
+ NS,
37
+ QK_K,
38
+ Q2KBlock,
39
+ codes_from_params,
40
+ codes_from_params_batch,
41
+ dequantize_params,
42
+ dequantize_params_batch,
43
+ derive_subscales,
44
+ make_block,
45
+ pack_codes,
46
+ pack_scales,
47
+ unpack_codes,
48
+ weighted_sse,
49
+ weighted_sse_batch,
50
+ )
51
+ from .seeds import build_seeds, seed_clamp_value
52
+ from .sieve import block_chain_edges, sieve_measure_chain, subblock_chain_edges
53
+ from .spectral import dc_nudge_codes, objective, refine_codes_hadamard
54
+ from .viterbi import robust_temperature, viterbi_select
55
+
56
+
57
+ @dataclass(slots=True)
58
+ class TensorStats:
59
+ n_blocks: int
60
+ total_sse: float
61
+ weighted_sse: float
62
+ rmse: float
63
+ sigma: float
64
+ rmse_over_sigma: float
65
+ temperature: float
66
+ factor_floor_used: int
67
+ candidate_floor_used: int
68
+
69
+
70
+ @dataclass(slots=True)
71
+ class QuantizedTensor:
72
+ blocks: list[Q2KBlock]
73
+ scales: np.ndarray
74
+ qs: np.ndarray
75
+ dm: np.ndarray
76
+ best_candidate: np.ndarray
77
+ factor_objectives: np.ndarray
78
+ config: HPCConfig
79
+ stats: TensorStats
80
+
81
+ def dequantize(self) -> np.ndarray:
82
+ return np.concatenate([b.dequantize() for b in self.blocks])
83
+
84
+
85
+ def _phase2_wls(X, W, seeds, clamp_val):
86
+ n = X.shape[0]
87
+ clipped = np.clip(X, -clamp_val, clamp_val).astype(np.float32)
88
+ base_d = seeds["d"].astype(np.float64).copy()
89
+ base_m = seeds["dmin"].astype(np.float64).copy()
90
+ ls = seeds["ls"].copy()
91
+ lm = seeds["lm"].copy()
92
+ scales = seeds["scales"].astype(np.float64)
93
+ mins = seeds["mins"].astype(np.float64)
94
+ ww = np.ones_like(X, dtype=np.float64) if W is None else W.astype(np.float64)
95
+
96
+ for _ in range(5):
97
+ codes = codes_from_params_batch(clipped, base_d, base_m, ls, lm).astype(np.float64)
98
+ A = np.repeat(ls.astype(np.float64), 16, axis=1) * codes
99
+ B = np.repeat(lm.astype(np.float64), 16, axis=1)
100
+ Saa = np.sum(ww * A * A, axis=1)
101
+ Sab = np.sum(ww * A * B, axis=1)
102
+ Sbb = np.sum(ww * B * B, axis=1)
103
+ Sxa = np.sum(ww * clipped.astype(np.float64) * A, axis=1)
104
+ Sxb = np.sum(ww * clipped.astype(np.float64) * B, axis=1)
105
+ det = Saa * Sbb - Sab * Sab
106
+ valid = det > 1.0e-30
107
+ safe_det = np.where(valid, det, 1.0)
108
+ d_new = (Sbb * Sxa - Sab * Sxb) / safe_det
109
+ m_new = (Sab * Sxa - Saa * Sxb) / safe_det
110
+ upd_d = valid & (d_new > 0.0) & (d_new < 4.0 * (seeds["d"] + 1.0e-10))
111
+ upd_m = valid & (m_new > 0.0) & (m_new < 4.0 * (seeds["dmin"] + 1.0e-10))
112
+ base_d = np.where(upd_d, round_fp16(d_new.astype(np.float32)).astype(np.float64), base_d)
113
+ base_m = np.where(upd_m, round_fp16(m_new.astype(np.float32)).astype(np.float64), base_m)
114
+
115
+ safe_d = np.where(base_d > 1.0e-15, base_d, 1.0)
116
+ safe_m = np.where(base_m > 1.0e-15, base_m, 1.0)
117
+ ls = np.where(
118
+ base_d[:, None] > 1.0e-15,
119
+ np.clip(nearest_int(scales / safe_d[:, None]), 0, 15),
120
+ 0,
121
+ ).astype(np.uint8)
122
+ lm = np.where(
123
+ base_m[:, None] > 1.0e-15,
124
+ np.clip(nearest_int(mins / safe_m[:, None]), 0, 15),
125
+ 0,
126
+ ).astype(np.uint8)
127
+
128
+ return base_d.astype(np.float32), base_m.astype(np.float32)
129
+
130
+
131
+ def _bin_geometry():
132
+ di = np.arange(TOTAL_CANDIDATES) // N_CAND_M
133
+ mi = np.arange(TOTAL_CANDIDATES) % N_CAND_M
134
+ return CAND_TO_QUHIT[di], CAND_TO_QUHIT[mi]
135
+
136
+
137
+ def _sieve_marginals(candidate_errors, graph_blocks, stride, temperature, cfg):
138
+ qi_d, qi_m = _bin_geometry()
139
+ coarse = np.zeros((graph_blocks, 6), dtype=np.float64)
140
+ fine = np.zeros((graph_blocks, 6), dtype=np.float64)
141
+ n_blocks = candidate_errors.shape[0]
142
+ for gi in range(graph_blocks):
143
+ start = gi * stride
144
+ stop = min((gi + 1) * stride, n_blocks)
145
+ group = candidate_errors[start:stop].astype(np.float64)
146
+ finite = np.isfinite(group)
147
+ if np.any(finite):
148
+ agg = np.where(finite, group, np.nan).mean(axis=0)
149
+ valid = np.isfinite(agg)
150
+ mn = float(np.min(agg[valid])) if np.any(valid) else 0.0
151
+ coeff = np.where(
152
+ valid,
153
+ np.exp(-(agg - mn) / (2.0 * max(temperature, 1.0e-12))),
154
+ 0.0,
155
+ )
156
+ else:
157
+ coeff = np.zeros(TOTAL_CANDIDATES, dtype=np.float64)
158
+ for v in range(6):
159
+ coarse[gi, v] = float(np.sum(coeff[qi_d == v]))
160
+ fine[gi, v] = float(np.sum(coeff[qi_m == v]))
161
+ cn = float(np.linalg.norm(coarse[gi]))
162
+ fn = float(np.linalg.norm(fine[gi]))
163
+ if cn > 1.0e-30:
164
+ coarse[gi] /= cn
165
+ if fn > 1.0e-30:
166
+ fine[gi] /= fn
167
+
168
+ amps = np.zeros((2 * graph_blocks, 6), dtype=np.float64)
169
+ amps[0::2] = coarse
170
+ amps[1::2] = fine
171
+ marg, _ = sieve_measure_chain(
172
+ amps,
173
+ block_chain_edges(graph_blocks),
174
+ same_penalty=cfg.same_penalty,
175
+ log_slack=cfg.log_slack,
176
+ deterministic=True,
177
+ )
178
+ return marg[0::2], marg[1::2]
179
+
180
+
181
+ def _phase3_select(candidate_errors, cfg):
182
+ n_blocks = candidate_errors.shape[0]
183
+ if cfg.optimizer != "hpc" or n_blocks < 2:
184
+ best = np.argmin(candidate_errors, axis=1).astype(np.int64)
185
+ return best, 0.0, np.zeros(n_blocks, dtype=np.int64)
186
+
187
+ stride = max(1, ceil(n_blocks / cfg.graph_max_blocks))
188
+ graph_blocks = ceil(n_blocks / stride)
189
+ temperature = robust_temperature(candidate_errors, cfg)
190
+ coarse_marg, fine_marg = _sieve_marginals(
191
+ candidate_errors, graph_blocks, stride, temperature, cfg
192
+ )
193
+ best, path = viterbi_select(
194
+ candidate_errors,
195
+ coarse_marg,
196
+ fine_marg,
197
+ temperature,
198
+ stride,
199
+ graph_blocks,
200
+ cfg,
201
+ )
202
+ return best, temperature, path
203
+
204
+
205
+ def _compute_dc_carry(X, seeds, best, base_d, base_m, cfg, row_width):
206
+ n = X.shape[0]
207
+ carry = np.zeros(n, dtype=np.float32)
208
+ blocks_per_row = row_width // QK_K if row_width and row_width % QK_K == 0 else 0
209
+ rolling = 0.0
210
+ for i in range(n):
211
+ if blocks_per_row > 0 and i % blocks_per_row == 0:
212
+ rolling = 0.0
213
+ d16, m16 = candidate_pair(base_d[i], base_m[i], best[i])
214
+ d = float(from_fp16_bits(d16))
215
+ m = float(from_fp16_bits(m16))
216
+ ls, lm = derive_subscales(seeds["scales"][i], seeds["mins"][i], d, m)
217
+ codes = codes_from_params(X[i], d, m, ls, lm)
218
+ xh = dequantize_params(d, m, ls, lm, codes)
219
+ carry[i] = np.float32(cfg.dc_decay * rolling)
220
+ dc_res = float(np.sum(X[i].astype(np.float64) - xh.astype(np.float64)))
221
+ rolling = carry[i] + dc_res if cfg.carry_cumulative else dc_res
222
+ return carry
223
+
224
+
225
+ def _per_sub_top_states(x, w, d, m, k=6):
226
+ k = max(1, min(int(k), 6))
227
+ xb = np.asarray(x, dtype=np.float32).reshape(NS, 16)
228
+ if w is None:
229
+ wb = np.ones((NS, 16), dtype=np.float32)
230
+ else:
231
+ wb = np.asarray(w, dtype=np.float32).reshape(NS, 16)
232
+ state_ls = np.zeros((NS, k), dtype=np.uint8)
233
+ state_lm = np.zeros((NS, k), dtype=np.uint8)
234
+ state_err = np.full((NS, k), np.inf, dtype=np.float64)
235
+ ls_opts = np.arange(16, dtype=np.float32)[:, None]
236
+ lm_opts = np.arange(16, dtype=np.float32)[None, :]
237
+ d_sub = np.float32(d) * ls_opts
238
+ m_sub = np.float32(m) * lm_opts
239
+ safe = d_sub[:, :, None] >= np.float32(1.0e-15)
240
+ safe_d = np.where(safe, d_sub[:, :, None], np.float32(1.0))
241
+ for j in range(NS):
242
+ sub = xb[j]
243
+ wj = wb[j]
244
+ raw = (sub[None, None, :] + m_sub[:, :, None]) / safe_d
245
+ q = np.clip(nearest_int(raw), 0, 3).astype(np.float32)
246
+ q = np.where(safe, q, 0.0)
247
+ deq = d_sub[:, :, None] * q - m_sub[:, :, None]
248
+ err = np.sum(wj[None, None, :] * (sub[None, None, :] - deq) ** 2, axis=2)
249
+ flat = err.ravel()
250
+ order = np.argsort(flat, kind="stable")[:k]
251
+ state_err[j] = flat[order]
252
+ state_ls[j] = (order // 16).astype(np.uint8)
253
+ state_lm[j] = (order % 16).astype(np.uint8)
254
+ return state_ls, state_lm, state_err
255
+
256
+
257
+ def _select_sub_states(state_ls, state_lm, state_err, cfg):
258
+ if not cfg.subblock_sieve:
259
+ return state_ls[:, 0].copy(), state_lm[:, 0].copy()
260
+ amps = np.zeros((NS, 6), dtype=np.float64)
261
+ for j in range(NS):
262
+ err0 = state_err[j, 0]
263
+ spread = float(state_err[j, 5] - state_err[j, 0])
264
+ temp = spread * 0.3 if spread > 1.0e-15 else 0.1
265
+ temp = max(temp, 1.0e-12)
266
+ amp = np.exp(-(state_err[j] - err0) / temp)
267
+ norm = float(np.linalg.norm(amp))
268
+ amps[j] = amp / norm if norm > 1.0e-30 else 1.0 / np.sqrt(6.0)
269
+ marg, _ = sieve_measure_chain(
270
+ amps,
271
+ subblock_chain_edges(NS),
272
+ same_penalty=cfg.same_penalty,
273
+ log_slack=cfg.log_slack,
274
+ deterministic=True,
275
+ )
276
+ pick = np.argmax(marg, axis=1)
277
+ ls = np.array([state_ls[j, pick[j]] for j in range(NS)], dtype=np.uint8)
278
+ lm = np.array([state_lm[j, pick[j]] for j in range(NS)], dtype=np.uint8)
279
+ return ls, lm
280
+
281
+
282
+ def _ls_refit(x, w, d, m, ls, lm, codes, max_d, max_m):
283
+ x = np.asarray(x, dtype=np.float64)
284
+ w = np.ones(QK_K, dtype=np.float64) if w is None else np.asarray(w, dtype=np.float64)
285
+ A = np.repeat(np.asarray(ls, dtype=np.float64), 16) * np.asarray(codes, dtype=np.float64)
286
+ B = np.repeat(np.asarray(lm, dtype=np.float64), 16)
287
+ Saa = float(np.sum(w * A * A))
288
+ Sab = float(np.sum(w * A * B))
289
+ Sbb = float(np.sum(w * B * B))
290
+ Sxa = float(np.sum(w * x * A))
291
+ Sxb = float(np.sum(w * x * B))
292
+ det = Saa * Sbb - Sab * Sab
293
+ if det <= 1.0e-30:
294
+ return float(d), float(m)
295
+ d_new = (Sbb * Sxa - Sab * Sxb) / det
296
+ m_new = (Sab * Sxa - Saa * Sxb) / det
297
+ if d_new <= 0.0 or d_new >= 4.0 * (max_d + 1.0e-10):
298
+ d_new = d
299
+ if m_new <= 0.0 or m_new >= 4.0 * (max_m + 1.0e-10):
300
+ m_new = m
301
+ return float(round_fp16(np.float32(d_new))), float(round_fp16(np.float32(m_new)))
302
+
303
+
304
+ def _ulp_polish(x, w, d, m, ls, lm, radius):
305
+ if radius <= 0:
306
+ codes = codes_from_params(x, d, m, ls, lm)
307
+ return float(d), float(m), codes, weighted_sse(x, w, d, m, ls, lm, codes)
308
+ d16 = np.uint16(to_fp16_bits(np.float32(d)))
309
+ m16 = np.uint16(to_fp16_bits(np.float32(m)))
310
+ best = None
311
+ for db in range(max(0, int(d16) - radius), min(0x7BFF, int(d16) + radius) + 1):
312
+ dd = float(from_fp16_bits(np.uint16(db)))
313
+ for mb in range(max(0, int(m16) - radius), min(0x7BFF, int(m16) + radius) + 1):
314
+ mm = float(from_fp16_bits(np.uint16(mb)))
315
+ c = codes_from_params(x, dd, mm, ls, lm)
316
+ s = weighted_sse(x, w, dd, mm, ls, lm, c)
317
+ if best is None or s < best[0] - 1.0e-15:
318
+ best = (s, dd, mm, c)
319
+ return best[1], best[2], best[3], best[0]
320
+
321
+
322
+ def _per_sub_scan(x, w, d, m):
323
+ state_ls, state_lm, _ = _per_sub_top_states(x, w, d, m, k=1)
324
+ return state_ls[:, 0], state_lm[:, 0]
325
+
326
+
327
+ def _assemble_block(x, w, seeds, base_d, base_m, cidx, dc_carry, cfg):
328
+ d16, m16 = candidate_pair(base_d, base_m, cidx)
329
+ d = float(from_fp16_bits(d16))
330
+ m = float(from_fp16_bits(m16))
331
+ ls, lm = derive_subscales(seeds["scales"], seeds["mins"], d, m)
332
+ max_d, max_m = d, m
333
+ prev = None
334
+ for _ in range(5):
335
+ s_ls, s_lm, s_err = _per_sub_top_states(x, w, d, m, cfg.subblock_states)
336
+ ls, lm = _select_sub_states(s_ls, s_lm, s_err, cfg)
337
+ codes = codes_from_params(x, d, m, ls, lm)
338
+ d, m = _ls_refit(x, w, d, m, ls, lm, codes, max_d, max_m)
339
+ cur = (int(to_fp16_bits(np.float32(d))), int(to_fp16_bits(np.float32(m))))
340
+ if cur == prev:
341
+ break
342
+ prev = cur
343
+
344
+ d, m, codes, _ = _ulp_polish(x, w, d, m, ls, lm, cfg.ulp_radius)
345
+ ls, lm = _per_sub_scan(x, w, d, m)
346
+ codes = codes_from_params(x, d, m, ls, lm)
347
+ codes = refine_codes_hadamard(x, w, d, m, ls, lm, codes, cfg, dc_carry)
348
+ obj, sse = objective(x, w, d, m, ls, lm, codes, cfg, dc_carry)
349
+ return d, m, ls, lm, codes, obj, sse
350
+
351
+
352
+ def _candidate_config(seeds, base_d, base_m, cidx):
353
+ d16, m16 = candidate_pair(base_d, base_m, cidx)
354
+ d = float(from_fp16_bits(d16))
355
+ m = float(from_fp16_bits(m16))
356
+ ls, lm = derive_subscales(seeds["scales"], seeds["mins"], d, m)
357
+ return d, m, ls, lm
358
+
359
+
360
+ def _apply_dc_nudge(X, W, blocks, cfg, row_width):
361
+ n = X.shape[0]
362
+ blocks_per_row = row_width // QK_K if row_width and row_width % QK_K == 0 else 0
363
+ rolling = 0.0
364
+ for i in range(n):
365
+ if blocks_per_row > 0 and i % blocks_per_row == 0:
366
+ rolling = 0.0
367
+ block = blocks[i]
368
+ carry = np.float32(cfg.dc_decay * rolling)
369
+ d = float(block.d_float)
370
+ m = float(block.dmin_float)
371
+ ls, lm = block.unpack_scales()
372
+ codes = unpack_codes(block.qs)
373
+ w = None if W is None else W[i]
374
+ codes = dc_nudge_codes(X[i], d, m, ls, lm, codes, w, float(carry), cfg)
375
+ blocks[i] = Q2KBlock(
376
+ scales=pack_scales(ls, lm),
377
+ qs=pack_codes(codes),
378
+ d=block.d,
379
+ dmin=block.dmin,
380
+ )
381
+ xh = blocks[i].dequantize()
382
+ dc_res = float(np.sum(X[i].astype(np.float64) - xh.astype(np.float64)))
383
+ rolling = float(carry) + dc_res if cfg.carry_cumulative else dc_res
384
+ return blocks
385
+
386
+
387
+ class Q2KQuantizer:
388
+ def __init__(self, config: HPCConfig | None = None):
389
+ self.config = (config or HPCConfig()).validate()
390
+
391
+ def quantize(
392
+ self,
393
+ weights: np.ndarray,
394
+ importance: np.ndarray | None = None,
395
+ row_width: int | None = None,
396
+ ) -> QuantizedTensor:
397
+ cfg = self.config
398
+ x = np.asarray(weights, dtype=np.float32)
399
+ if x.size % QK_K != 0:
400
+ raise ValueError("weight count must be a multiple of 256")
401
+ if x.ndim == 2 and row_width is None and x.shape[1] % QK_K == 0:
402
+ row_width = int(x.shape[1])
403
+ x = x.ravel()
404
+ n_blocks = x.size // QK_K
405
+ X = x.reshape(n_blocks, QK_K)
406
+
407
+ if importance is not None:
408
+ imp = np.asarray(importance, dtype=np.float32).ravel()
409
+ if imp.size != x.size:
410
+ raise ValueError("importance must match the weight count")
411
+ W = imp.reshape(n_blocks, QK_K)
412
+ else:
413
+ W = None
414
+
415
+ seeds = build_seeds(X, W, cfg)
416
+ clamp_val = seed_clamp_value(x, cfg)
417
+ base_d, base_m = _phase2_wls(X, W, seeds, clamp_val)
418
+
419
+ candidate_errors = np.full((n_blocks, TOTAL_CANDIDATES), np.inf, dtype=np.float32)
420
+ standalone: list[FactorResult] = []
421
+ for i in range(n_blocks):
422
+ wi = None if W is None else W[i]
423
+ candidate_errors[i] = factor_candidates(
424
+ X[i],
425
+ wi,
426
+ seeds["scales"][i],
427
+ seeds["mins"][i],
428
+ base_d[i],
429
+ base_m[i],
430
+ cfg,
431
+ )
432
+ standalone.append(factor_only_solve(X[i], wi, cfg))
433
+
434
+ best, temperature, _ = _phase3_select(candidate_errors, cfg)
435
+ carry = _compute_dc_carry(X, seeds, best, base_d, base_m, cfg, row_width)
436
+
437
+ at_slot: list[FactorResult] = []
438
+ for i in range(n_blocks):
439
+ wi = None if W is None else W[i]
440
+ d16, m16 = candidate_pair(base_d[i], base_m[i], best[i])
441
+ at_slot.append(
442
+ factor_q2k_block(
443
+ X[i],
444
+ wi,
445
+ cfg,
446
+ init_s=seeds["scales"][i],
447
+ init_t=seeds["mins"][i],
448
+ d_seed=float(from_fp16_bits(d16)),
449
+ m_seed=float(from_fp16_bits(m16)),
450
+ pin=cfg.factor_pin,
451
+ use_spectral=cfg.use_spectral,
452
+ max_iter=cfg.factor_iters,
453
+ starts=1,
454
+ dc_carry=0.0,
455
+ )
456
+ )
457
+
458
+ blocks: list[Q2KBlock] = []
459
+ factor_floor_used = 0
460
+ candidate_floor_used = 0
461
+ for i in range(n_blocks):
462
+ wi = None if W is None else W[i]
463
+ d, m, ls, lm, codes, obj, sse = _assemble_block(
464
+ X[i], wi, seeds, base_d[i], base_m[i], best[i], carry[i], cfg
465
+ )
466
+
467
+ if cfg.use_candidate_floor:
468
+ cd, cm, cls, clm = _candidate_config(
469
+ {k: v[i] for k, v in seeds.items() if k in ("scales", "mins")},
470
+ base_d[i],
471
+ base_m[i],
472
+ best[i],
473
+ )
474
+ ccodes = codes_from_params(X[i], cd, cm, cls, clm)
475
+ cobj, csse = objective(
476
+ X[i], wi, cd, cm, cls, clm, ccodes, cfg, float(carry[i])
477
+ )
478
+ if cobj < obj - 1.0e-15:
479
+ d, m, ls, lm, codes, obj, sse = cd, cm, cls, clm, ccodes, cobj, csse
480
+ candidate_floor_used += 1
481
+
482
+ if cfg.use_factor_floor:
483
+ fac = at_slot[i]
484
+ fobj, fsse = objective(
485
+ X[i], wi, fac.d, fac.dmin, fac.ls, fac.lm, fac.codes, cfg, float(carry[i])
486
+ )
487
+ if fobj < obj - 1.0e-15:
488
+ d = float(fac.d)
489
+ m = float(fac.dmin)
490
+ ls = fac.ls.copy()
491
+ lm = fac.lm.copy()
492
+ codes = fac.codes.copy()
493
+ obj, sse = fobj, fsse
494
+ factor_floor_used += 1
495
+
496
+ blocks.append(make_block(d, m, ls, lm, codes))
497
+
498
+ if cfg.apply_dc_carry and (cfg.dc_lambda > 0.0 or cfg.dc_decay > 0.0):
499
+ blocks = _apply_dc_nudge(X, W, blocks, cfg, row_width)
500
+
501
+ total_sse = 0.0
502
+ weighted = 0.0
503
+ for i, b in enumerate(blocks):
504
+ e = X[i].astype(np.float64) - b.dequantize().astype(np.float64)
505
+ total_sse += float(np.dot(e, e))
506
+ if W is None:
507
+ weighted += float(np.dot(e, e))
508
+ else:
509
+ weighted += float(np.dot(W[i].astype(np.float64), e * e))
510
+
511
+ sigma = float(np.sqrt(np.mean(x.astype(np.float64) ** 2)))
512
+ rmse = float(np.sqrt(total_sse / x.size))
513
+ stats = TensorStats(
514
+ n_blocks=n_blocks,
515
+ total_sse=total_sse,
516
+ weighted_sse=weighted,
517
+ rmse=rmse,
518
+ sigma=sigma,
519
+ rmse_over_sigma=rmse / sigma if sigma > 1.0e-15 else 0.0,
520
+ temperature=temperature,
521
+ factor_floor_used=factor_floor_used,
522
+ candidate_floor_used=candidate_floor_used,
523
+ )
524
+ scales, qs, dm = _stack_blocks(blocks)
525
+ factor_objectives = np.array([r.objective for r in standalone], dtype=np.float64)
526
+ result = QuantizedTensor(
527
+ blocks=blocks,
528
+ scales=scales,
529
+ qs=qs,
530
+ dm=dm,
531
+ best_candidate=best,
532
+ factor_objectives=factor_objectives,
533
+ config=cfg,
534
+ stats=stats,
535
+ )
536
+ if cfg.verbose:
537
+ _report(stats)
538
+ return result
539
+
540
+
541
+ def _stack_blocks(blocks):
542
+ n = len(blocks)
543
+ scales = np.empty((n, NS), dtype=np.uint8)
544
+ qs = np.empty((n, QK_K // 4), dtype=np.uint8)
545
+ dm = np.empty((n, 2), dtype=np.uint16)
546
+ for i, b in enumerate(blocks):
547
+ scales[i] = b.scales
548
+ qs[i] = b.qs
549
+ dm[i, 0] = b.d
550
+ dm[i, 1] = b.dmin
551
+ return scales, qs, dm
552
+
553
+
554
+ def _report(stats: TensorStats) -> None:
555
+ print(
556
+ f"Q2_K V2: blocks={stats.n_blocks} rmse={stats.rmse:.6e} "
557
+ f"rmse/sigma={stats.rmse_over_sigma:.5f} temp={stats.temperature:.3e} "
558
+ f"floors(cand={stats.candidate_floor_used}, factor={stats.factor_floor_used})"
559
+ )
q2k.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Bit-exact Q2_K block representation, packing and reconstruction.
2
+
3
+ Layout (84 bytes per 256 weights)::
4
+
5
+ scales[16] uint8 low nibble = Ls[j], high nibble = Lm[j]
6
+ qs[64] uint8 four 2-bit codes per byte
7
+ d fp16 super-block scale
8
+ dmin fp16 super-block minimum scale
9
+
10
+ The decoder reconstructs::
11
+
12
+ x_hat[16*j + k] = d * Ls[j] * L[16*j + k] - dmin * Lm[j]
13
+
14
+ with ``L in {0,1,2,3}`` and ``Ls, Lm in {0..15}``.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ from dataclasses import dataclass
20
+
21
+ import numpy as np
22
+
23
+ from .fp16 import from_fp16_bits, nearest_int, to_fp16_bits
24
+
25
+ QK_K = 256
26
+ NS = QK_K // 16
27
+ NQ = 4
28
+
29
+
30
+ @dataclass(slots=True)
31
+ class Q2KBlock:
32
+ scales: np.ndarray
33
+ qs: np.ndarray
34
+ d: np.uint16
35
+ dmin: np.uint16
36
+
37
+ @property
38
+ def d_float(self) -> np.float32:
39
+ return np.float32(from_fp16_bits(np.uint16(self.d)))
40
+
41
+ @property
42
+ def dmin_float(self) -> np.float32:
43
+ return np.float32(from_fp16_bits(np.uint16(self.dmin)))
44
+
45
+ def unpack_scales(self) -> tuple[np.ndarray, np.ndarray]:
46
+ scales = np.asarray(self.scales, dtype=np.uint8)
47
+ return scales & 0x0F, scales >> 4
48
+
49
+ def dequantize(self) -> np.ndarray:
50
+ ls, lm = self.unpack_scales()
51
+ return dequantize_params(
52
+ self.d_float, self.dmin_float, ls, lm, unpack_codes(self.qs)
53
+ )
54
+
55
+ def error(self, x: np.ndarray) -> float:
56
+ e = np.asarray(x, dtype=np.float64) - self.dequantize().astype(np.float64)
57
+ return float(np.dot(e, e))
58
+
59
+
60
+ def unpack_codes(qs: np.ndarray) -> np.ndarray:
61
+ qs = np.asarray(qs, dtype=np.uint8)
62
+ codes = np.empty(QK_K, dtype=np.uint8)
63
+ for base in (0, 128):
64
+ q = qs[base // 4 : base // 4 + 32]
65
+ for shift_level in range(4):
66
+ lo = base + 32 * shift_level
67
+ codes[lo : lo + 32] = (q >> np.uint8(2 * shift_level)) & np.uint8(3)
68
+ return codes
69
+
70
+
71
+ def pack_codes(codes: np.ndarray) -> np.ndarray:
72
+ codes = np.asarray(codes, dtype=np.uint8)
73
+ if codes.shape != (QK_K,):
74
+ raise ValueError("codes must have shape (256,)")
75
+ qs = np.empty(QK_K // 4, dtype=np.uint8)
76
+ for base in (0, 128):
77
+ q = (
78
+ codes[base : base + 32]
79
+ | (codes[base + 32 : base + 64] << np.uint8(2))
80
+ | (codes[base + 64 : base + 96] << np.uint8(4))
81
+ | (codes[base + 96 : base + 128] << np.uint8(6))
82
+ )
83
+ qs[base // 4 : base // 4 + 32] = q
84
+ return qs
85
+
86
+
87
+ def pack_scales(ls: np.ndarray, lm: np.ndarray) -> np.ndarray:
88
+ return (np.asarray(ls, dtype=np.uint8) & 0x0F) | (
89
+ (np.asarray(lm, dtype=np.uint8) & 0x0F) << np.uint8(4)
90
+ )
91
+
92
+
93
+ def dequantize_params(
94
+ d, dmin, ls: np.ndarray, lm: np.ndarray, codes: np.ndarray
95
+ ) -> np.ndarray:
96
+ ls = np.asarray(ls, dtype=np.float32)
97
+ lm = np.asarray(lm, dtype=np.float32)
98
+ codes = np.asarray(codes, dtype=np.float32)
99
+ d_sub = np.repeat(np.float32(d) * ls, 16)
100
+ m_sub = np.repeat(np.float32(dmin) * lm, 16)
101
+ return d_sub * codes - m_sub
102
+
103
+
104
+ def codes_from_params(
105
+ x: np.ndarray, d, dmin, ls: np.ndarray, lm: np.ndarray
106
+ ) -> np.ndarray:
107
+ x = np.asarray(x, dtype=np.float32)
108
+ d_sub = np.repeat(np.float32(d) * np.asarray(ls, dtype=np.float32), 16)
109
+ m_sub = np.repeat(np.float32(dmin) * np.asarray(lm, dtype=np.float32), 16)
110
+ safe = d_sub >= np.float32(1.0e-15)
111
+ codes = np.zeros(QK_K, dtype=np.uint8)
112
+ if np.any(safe):
113
+ q = nearest_int((x[safe] + m_sub[safe]) / d_sub[safe])
114
+ codes[safe] = np.clip(q, 0, 3).astype(np.uint8)
115
+ return codes
116
+
117
+
118
+ def derive_subscales(
119
+ scales: np.ndarray, mins: np.ndarray, d, dmin
120
+ ) -> tuple[np.ndarray, np.ndarray]:
121
+ scales = np.asarray(scales, dtype=np.float32)
122
+ mins = np.asarray(mins, dtype=np.float32)
123
+ if float(d) > 1.0e-15:
124
+ ls = np.clip(nearest_int(scales / np.float32(d)), 0, 15).astype(np.uint8)
125
+ else:
126
+ ls = np.zeros(NS, dtype=np.uint8)
127
+ if float(dmin) > 1.0e-15:
128
+ lm = np.clip(nearest_int(mins / np.float32(dmin)), 0, 15).astype(np.uint8)
129
+ else:
130
+ lm = np.zeros(NS, dtype=np.uint8)
131
+ return ls, lm
132
+
133
+
134
+ def weighted_sse(
135
+ x: np.ndarray,
136
+ w: np.ndarray | None,
137
+ d,
138
+ dmin,
139
+ ls: np.ndarray,
140
+ lm: np.ndarray,
141
+ codes: np.ndarray,
142
+ ) -> float:
143
+ xh = dequantize_params(d, dmin, ls, lm, codes)
144
+ e = np.asarray(x, dtype=np.float64) - xh.astype(np.float64)
145
+ if w is None:
146
+ return float(np.dot(e, e))
147
+ return float(np.dot(np.asarray(w, dtype=np.float64), e * e))
148
+
149
+
150
+ def make_block(d, dmin, ls, lm, codes) -> Q2KBlock:
151
+ return Q2KBlock(
152
+ scales=pack_scales(ls, lm),
153
+ qs=pack_codes(codes),
154
+ d=np.uint16(to_fp16_bits(np.float32(d))),
155
+ dmin=np.uint16(to_fp16_bits(np.float32(dmin))),
156
+ )
157
+
158
+
159
+ def blocks_to_arrays(blocks: list[Q2KBlock]) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
160
+ n = len(blocks)
161
+ scales = np.empty((n, NS), dtype=np.uint8)
162
+ qs = np.empty((n, QK_K // 4), dtype=np.uint8)
163
+ dm = np.empty((n, 2), dtype=np.uint16)
164
+ for i, b in enumerate(blocks):
165
+ scales[i] = b.scales
166
+ qs[i] = b.qs
167
+ dm[i, 0] = b.d
168
+ dm[i, 1] = b.dmin
169
+ return scales, qs, dm
170
+
171
+
172
+ def dequantize_blocks(blocks: list[Q2KBlock]) -> np.ndarray:
173
+ if not blocks:
174
+ return np.empty(0, dtype=np.float32)
175
+ return np.concatenate([b.dequantize() for b in blocks])
176
+
177
+
178
+ def codes_from_params_batch(
179
+ x: np.ndarray, d: np.ndarray, dmin: np.ndarray, ls: np.ndarray, lm: np.ndarray
180
+ ) -> np.ndarray:
181
+ x = np.asarray(x, dtype=np.float32)
182
+ n = x.shape[0]
183
+ d_sub = np.repeat(
184
+ np.asarray(d, dtype=np.float32)[:, None] * np.asarray(ls, dtype=np.float32), 16, axis=1
185
+ )
186
+ m_sub = np.repeat(
187
+ np.asarray(dmin, dtype=np.float32)[:, None] * np.asarray(lm, dtype=np.float32), 16, axis=1
188
+ )
189
+ safe = d_sub >= np.float32(1.0e-15)
190
+ codes = np.zeros((n, QK_K), dtype=np.uint8)
191
+ if np.any(safe):
192
+ q = nearest_int((x[safe] + m_sub[safe]) / d_sub[safe])
193
+ codes[safe] = np.clip(q, 0, 3).astype(np.uint8)
194
+ return codes
195
+
196
+
197
+ def dequantize_params_batch(
198
+ d: np.ndarray, dmin: np.ndarray, ls: np.ndarray, lm: np.ndarray, codes: np.ndarray
199
+ ) -> np.ndarray:
200
+ n = codes.shape[0]
201
+ d_sub = np.repeat(
202
+ np.asarray(d, dtype=np.float32)[:, None] * np.asarray(ls, dtype=np.float32), 16, axis=1
203
+ )
204
+ m_sub = np.repeat(
205
+ np.asarray(dmin, dtype=np.float32)[:, None] * np.asarray(lm, dtype=np.float32), 16, axis=1
206
+ )
207
+ return d_sub * np.asarray(codes, dtype=np.float32) - m_sub
208
+
209
+
210
+ def weighted_sse_batch(
211
+ x: np.ndarray,
212
+ w: np.ndarray | None,
213
+ d: np.ndarray,
214
+ dmin: np.ndarray,
215
+ ls: np.ndarray,
216
+ lm: np.ndarray,
217
+ codes: np.ndarray,
218
+ ) -> np.ndarray:
219
+ xh = dequantize_params_batch(d, dmin, ls, lm, codes)
220
+ e = np.asarray(x, dtype=np.float64) - xh.astype(np.float64)
221
+ if w is None:
222
+ return np.sum(e * e, axis=1)
223
+ return np.sum(np.asarray(w, dtype=np.float64) * e * e, axis=1)
seeds.py ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Batched GGML seed quantizers and activation-aware seed weighting.
2
+
3
+ The C engine performs ``make_qkx2_quants`` once per 16-weight sub-block and
4
+ ``make_qp_quants`` once per 16-sub-block superblock. V2 batches both across
5
+ every sub-block / superblock in the tensor with NumPy, which removes the
6
+ per-block Python overhead while preserving the exact search rules.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import numpy as np
12
+
13
+ from .config import HPCConfig
14
+ from .fp16 import nearest_int
15
+
16
+
17
+ def make_qkx2_batch(
18
+ x: np.ndarray, w: np.ndarray, nmax: int = 3
19
+ ) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
20
+ """Batched ``make_qkx2_quants``. Returns (scale, min_abs, codes)."""
21
+ x = np.asarray(x, dtype=np.float64)
22
+ w = np.asarray(w, dtype=np.float64)
23
+ rows, n = x.shape
24
+ xmin = x.min(axis=1)
25
+ xmax = x.max(axis=1)
26
+ xmin = np.minimum(xmin, 0.0)
27
+ span = xmax - xmin
28
+ degenerate = (xmax == xmin) | (span <= 1.0e-30)
29
+
30
+ sum_w = w.sum(axis=1)
31
+ sum_x = (w * x).sum(axis=1)
32
+
33
+ safe_span = np.where(span > 1.0e-30, span, 1.0)
34
+ iscale = nmax / safe_span
35
+ scale = 1.0 / iscale
36
+ codes = np.clip(nearest_int(iscale[:, None] * (x - xmin[:, None])), 0, nmax)
37
+ best_mad = np.sum(w * np.abs(scale[:, None] * codes + xmin[:, None] - x), axis=1)
38
+ best_scale = scale.copy()
39
+ best_min = xmin.copy()
40
+ best_codes = codes.copy()
41
+
42
+ for step in range(16):
43
+ try_iscale = (-0.5 + 0.1 * step + nmax) / safe_span
44
+ aux = np.clip(nearest_int(try_iscale[:, None] * (x - xmin[:, None])), 0, nmax)
45
+ sl = (w * aux).sum(axis=1)
46
+ sl2 = (w * aux * aux).sum(axis=1)
47
+ sxl = (w * aux * x).sum(axis=1)
48
+ det = sum_w * sl2 - sl * sl
49
+ valid = det > 1.0e-30
50
+ safe_det = np.where(valid, det, 1.0)
51
+ this_scale = (sum_w * sxl - sum_x * sl) / safe_det
52
+ this_min = (sl2 * sum_x - sl * sxl) / safe_det
53
+ positive_min = this_min > 0.0
54
+ this_min = np.where(positive_min, 0.0, this_min)
55
+ this_scale = np.where(
56
+ positive_min & (sl2 > 1.0e-30), sxl / np.where(sl2 > 1.0e-30, sl2, 1.0), this_scale
57
+ )
58
+ mad = np.sum(w * np.abs(this_scale[:, None] * aux + this_min[:, None] - x), axis=1)
59
+ take = valid & (mad < best_mad)
60
+ if np.any(take):
61
+ best_mad = np.where(take, mad, best_mad)
62
+ best_scale = np.where(take, this_scale, best_scale)
63
+ best_min = np.where(take, this_min, best_min)
64
+ best_codes[take] = aux[take]
65
+
66
+ best_scale = np.where(degenerate, 0.0, best_scale)
67
+ best_codes[degenerate] = 0
68
+ best_min = np.where(degenerate, xmin, best_min)
69
+ return best_scale, -best_min, best_codes.astype(np.uint8)
70
+
71
+
72
+ def make_qp_batch(
73
+ values: np.ndarray, weights: np.ndarray, nmax: int = 15
74
+ ) -> tuple[np.ndarray, np.ndarray]:
75
+ """Batched ``make_qp_quants``. Factors positive values into d * code."""
76
+ x = np.asarray(values, dtype=np.float64)
77
+ w = np.asarray(weights, dtype=np.float64)
78
+ rows, n = x.shape
79
+ xmax = x.max(axis=1)
80
+ dead = xmax < 1.0e-15
81
+ safe_max = np.where(dead, 1.0, xmax)
82
+
83
+ iscale = nmax / safe_max
84
+ codes = np.clip(nearest_int(iscale[:, None] * x), 0, nmax)
85
+ scale = 1.0 / iscale
86
+ best_mse = np.sum(w * (x - scale[:, None] * codes) ** 2, axis=1)
87
+ best_iscale = iscale.copy()
88
+
89
+ for step in range(-4, 5):
90
+ if step == 0:
91
+ continue
92
+ trial_iscale = (0.1 * step + nmax) / safe_max
93
+ trial_scale = 1.0 / trial_iscale
94
+ trial_codes = np.clip(nearest_int(trial_iscale[:, None] * x), 0, nmax)
95
+ mse = np.sum(w * (x - trial_scale[:, None] * trial_codes) ** 2, axis=1)
96
+ take = mse < best_mse
97
+ if np.any(take):
98
+ best_mse = np.where(take, mse, best_mse)
99
+ best_iscale = np.where(take, trial_iscale, best_iscale)
100
+
101
+ codes = np.clip(nearest_int(best_iscale[:, None] * x), 0, nmax)
102
+ sumlx = (w * x * codes).sum(axis=1)
103
+ suml2 = (w * codes * codes).sum(axis=1)
104
+
105
+ for _ in range(5):
106
+ changed = False
107
+ for i in range(n):
108
+ wi = w[:, i]
109
+ xi = x[:, i]
110
+ li = codes[:, i].astype(np.float64)
111
+ slx = sumlx - wi * xi * li
112
+ sl2 = suml2 - wi * li * li
113
+ valid = (slx > 0.0) & (sl2 > 0.0)
114
+ safe_slx = np.where(valid, slx, 1.0)
115
+ new_l = np.clip(nearest_int(xi * sl2 / safe_slx), 0, nmax)
116
+ trial = valid & (new_l != codes[:, i])
117
+ if not np.any(trial):
118
+ continue
119
+ slx2 = slx + wi * xi * new_l
120
+ sl2_2 = sl2 + wi * new_l * new_l
121
+ better = trial & (slx2 * slx2 * suml2 > sumlx * sumlx * sl2_2)
122
+ if np.any(better):
123
+ codes[better, i] = new_l[better].astype(np.uint8)
124
+ sumlx = np.where(better, slx2, sumlx)
125
+ suml2 = np.where(better, sl2_2, suml2)
126
+ changed = True
127
+ if not changed:
128
+ break
129
+
130
+ d = np.where(suml2 > 1.0e-30, sumlx / np.where(suml2 > 1.0e-30, suml2, 1.0), 0.0)
131
+ d = np.where(dead, 0.0, d)
132
+ codes[dead] = 0
133
+ return d, codes.astype(np.uint8)
134
+
135
+
136
+ def seed_clamp_value(weights: np.ndarray, cfg: HPCConfig) -> float:
137
+ x = np.asarray(weights, dtype=np.float64).ravel()
138
+ mean_sq = float(np.mean(x * x))
139
+ if mean_sq <= 1.0e-30:
140
+ return float("inf")
141
+ mean_fourth = float(np.mean(x * x * x * x))
142
+ kurtosis = mean_fourth / (mean_sq * mean_sq)
143
+ sigma = float(np.sqrt(mean_sq))
144
+ if kurtosis <= cfg.clamp_kurtosis_off:
145
+ return float("inf")
146
+ if kurtosis <= cfg.clamp_kurtosis_soft:
147
+ return cfg.clamp_sigma_soft * sigma
148
+ return cfg.clamp_sigma_hard * sigma
149
+
150
+
151
+ def seed_weights(
152
+ block: np.ndarray, importance: np.ndarray | None, sigma_sq: float, clamp_val: float
153
+ ) -> np.ndarray:
154
+ if importance is not None:
155
+ return np.asarray(importance, dtype=np.float64)
156
+ return np.sqrt(sigma_sq + np.minimum(block, clamp_val) ** 2)
157
+
158
+
159
+ def build_seeds(
160
+ x: np.ndarray, importance: np.ndarray | None, cfg: HPCConfig
161
+ ) -> dict[str, np.ndarray]:
162
+ """Phase-1 seeds for all blocks: per-sub (scale,min) and shared (d,dmin)."""
163
+ n_blocks = x.shape[0]
164
+ blocks = np.asarray(x, dtype=np.float64).reshape(n_blocks, 16, 16)
165
+ if importance is None:
166
+ sigma_sq = np.mean(blocks * blocks, axis=(1, 2), keepdims=True)
167
+ weights = np.sqrt(sigma_sq + blocks * blocks)
168
+ else:
169
+ weights = np.asarray(importance, dtype=np.float64).reshape(n_blocks, 16, 16)
170
+
171
+ flat_x = blocks.reshape(-1, 16)
172
+ flat_w = weights.reshape(-1, 16)
173
+ scales, mins, codes = make_qkx2_batch(flat_x, flat_w, nmax=3)
174
+ scales = scales.reshape(n_blocks, 16)
175
+ mins = mins.reshape(n_blocks, 16)
176
+ codes = codes.reshape(n_blocks, 256)
177
+ sw = weights.sum(axis=2)
178
+ d, ls = make_qp_batch(scales, sw, nmax=15)
179
+ dmin, lm = make_qp_batch(mins, sw, nmax=15)
180
+ return {
181
+ "scales": scales.astype(np.float32),
182
+ "mins": mins.astype(np.float32),
183
+ "codes": codes.astype(np.uint8),
184
+ "sw": sw.astype(np.float32),
185
+ "d": d.astype(np.float32),
186
+ "dmin": dmin.astype(np.float32),
187
+ "ls": ls.astype(np.uint8),
188
+ "lm": lm.astype(np.uint8),
189
+ "weights": weights.astype(np.float32),
190
+ }
sieve.py ADDED
@@ -0,0 +1,106 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Sequential six-state Sieve, ported from the HPC graph measurement.
2
+
3
+ The C engine builds an :class:`HPCGraph` and runs a column-first sequential
4
+ measurement with real back-action. Only the following semantics are used by
5
+ the Q2_K pipeline, so V2 implements them directly on a chain:
6
+
7
+ * local Born probabilities ``|amplitude|^2``;
8
+ * a multiplicative neighbour mix with ``same_penalty`` for equal bins;
9
+ * a log-domain slack filter;
10
+ * deterministic MAP collapse (the production setting);
11
+ * back-action that biases still-unmeasured neighbours away from the outcome.
12
+
13
+ This is mathematically identical to the production path while removing the
14
+ graph allocation, edge bookkeeping and triality view machinery.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import numpy as np
20
+
21
+
22
+ def sieve_measure_chain(
23
+ amplitudes: np.ndarray,
24
+ edges: list[tuple[int, int]],
25
+ same_penalty: float = 0.85,
26
+ log_slack: float = 1.5,
27
+ deterministic: bool = True,
28
+ rng: np.random.Generator | None = None,
29
+ ) -> tuple[np.ndarray, np.ndarray]:
30
+ amps = np.array(amplitudes, dtype=np.float64, copy=True)
31
+ n_sites, n_states = amps.shape
32
+ if n_states != 6:
33
+ raise ValueError("sieve expects six symbolic states per site")
34
+ adj: list[list[int]] = [[] for _ in range(n_sites)]
35
+ for eid, (a, b) in enumerate(edges):
36
+ adj[a].append(eid)
37
+ adj[b].append(eid)
38
+ alive = np.ones(len(edges), dtype=bool)
39
+ marginals = np.zeros((n_sites, 6), dtype=np.float64)
40
+ measured = np.zeros(n_sites, dtype=np.int64)
41
+ generator = rng or np.random.default_rng(271828)
42
+
43
+ for site in range(n_sites - 1, -1, -1):
44
+ local = amps[site] * amps[site]
45
+ total = float(local.sum())
46
+ probs0 = local / total if total > 1.0e-30 else np.full(6, 1.0 / 6.0)
47
+ log_score = np.log(probs0 + 1.0e-30)
48
+
49
+ for eid in adj[site]:
50
+ if not alive[eid]:
51
+ continue
52
+ a, b = edges[eid]
53
+ partner = b if a == site else a
54
+ q = amps[partner] * amps[partner]
55
+ qt = float(q.sum())
56
+ q = q / qt if qt > 1.0e-30 else np.full(6, 1.0 / 6.0)
57
+ mix = np.empty(6)
58
+ for d in range(6):
59
+ mix[d] = sum(q[w] * (same_penalty if w == d else 1.0) for w in range(6))
60
+ log_score += np.log(mix + 1.0e-30)
61
+
62
+ best = float(log_score.max())
63
+ probs = np.where(best - log_score > log_slack, 0.0, np.exp(log_score - best))
64
+ total = float(probs.sum())
65
+ probs = probs / total if total > 1.0e-30 else np.full(6, 1.0 / 6.0)
66
+ marginals[site] = probs
67
+
68
+ if deterministic:
69
+ outcome = int(np.argmax(probs))
70
+ else:
71
+ outcome = int(np.searchsorted(np.cumsum(probs), generator.random(), side="right"))
72
+ outcome = min(outcome, 5)
73
+ measured[site] = outcome
74
+
75
+ amps[site] = 0.0
76
+ amps[site, outcome] = 1.0
77
+ for eid in adj[site]:
78
+ if not alive[eid]:
79
+ continue
80
+ a, b = edges[eid]
81
+ partner = b if a == site else a
82
+ amps[partner, outcome] *= same_penalty
83
+ norm = float(np.linalg.norm(amps[partner]))
84
+ if norm > 1.0e-30:
85
+ amps[partner] /= norm
86
+ for eid in adj[site]:
87
+ alive[eid] = False
88
+ adj[site] = []
89
+
90
+ return marginals, measured
91
+
92
+
93
+ def block_chain_edges(n_blocks: int) -> list[tuple[int, int]]:
94
+ """The production two-site-per-block chain, including cross-block links."""
95
+ edges: list[tuple[int, int]] = []
96
+ for i in range(n_blocks):
97
+ s0, s1 = 2 * i, 2 * i + 1
98
+ edges.append((s0, s1))
99
+ if i + 1 < n_blocks:
100
+ edges.append((s0, 2 * (i + 1)))
101
+ edges.append((s1, 2 * (i + 1) + 1))
102
+ return edges
103
+
104
+
105
+ def subblock_chain_edges(n_subblocks: int) -> list[tuple[int, int]]:
106
+ return [(i, i + 1) for i in range(n_subblocks - 1)]
spectral.py ADDED
@@ -0,0 +1,226 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """HPC spectral objective: folded error pyramid, DC/vesica penalty, shaping.
2
+
3
+ The C engine scores a block with::
4
+
5
+ E = sum_i w_i e_i^2
6
+ + (dc_lambda / n) * (sum_i e_i)^2
7
+ + (vw_lambda / n) * sum_{pairs} (e_i + e_{i+n/2})^2
8
+
9
+ and the fold tree generalises the vesica term to every periodic level. V2
10
+ keeps the exact same objective and adds a vectorised implementation of the
11
+ D6/vesica greedy code descent.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import numpy as np
17
+
18
+ from .config import HPCConfig
19
+ from .q2k import QK_K, dequantize_params, weighted_sse
20
+
21
+
22
+ def fold_weights(n: int, cfg: HPCConfig) -> np.ndarray:
23
+ levels = int(np.log2(n))
24
+ if (1 << levels) != n:
25
+ raise ValueError("fold pyramid requires a power-of-two length")
26
+ weights = np.zeros(levels, dtype=np.float64)
27
+ gamma = 1.0
28
+ for k in range(1, levels + 1):
29
+ if k == levels:
30
+ weights[k - 1] = cfg.dc_lambda / n
31
+ elif k <= cfg.fold_depth:
32
+ weights[k - 1] = (cfg.vw_lambda / n) * gamma
33
+ gamma *= cfg.fold_gamma
34
+ return weights
35
+
36
+
37
+ def fold_build(errors: np.ndarray) -> np.ndarray:
38
+ e = np.asarray(errors, dtype=np.float64)
39
+ n = e.size
40
+ py = np.empty(n - 1, dtype=np.float64)
41
+ src = e
42
+ offset = 0
43
+ m = n // 2
44
+ while m >= 1:
45
+ dst = src[:m] + src[m : 2 * m]
46
+ py[offset : offset + m] = dst
47
+ src = dst
48
+ offset += m
49
+ m //= 2
50
+ return py
51
+
52
+
53
+ def fold_energy(pyramid: np.ndarray, weights: np.ndarray) -> float:
54
+ acc = 0.0
55
+ offset = 0
56
+ m = pyramid.size // 2 + 1
57
+ level = 0
58
+ while m >= 1:
59
+ if weights[level] != 0.0:
60
+ v = pyramid[offset : offset + m]
61
+ acc += float(weights[level] * np.dot(v, v))
62
+ offset += m
63
+ m //= 2
64
+ level += 1
65
+ return acc
66
+
67
+
68
+ def spectral_penalty(
69
+ errors: np.ndarray, cfg: HPCConfig, dc_carry: float = 0.0
70
+ ) -> float:
71
+ if not cfg.use_spectral or (cfg.dc_lambda == 0.0 and cfg.vw_lambda == 0.0):
72
+ return 0.0
73
+ pyramid = fold_build(errors)
74
+ pyramid[-1] += dc_carry
75
+ return fold_energy(pyramid, fold_weights(errors.size, cfg))
76
+
77
+
78
+ def objective(
79
+ x: np.ndarray,
80
+ w: np.ndarray | None,
81
+ d,
82
+ dmin,
83
+ ls: np.ndarray,
84
+ lm: np.ndarray,
85
+ codes: np.ndarray,
86
+ cfg: HPCConfig,
87
+ dc_carry: float = 0.0,
88
+ ) -> tuple[float, float]:
89
+ xh = dequantize_params(d, dmin, ls, lm, codes)
90
+ e = np.asarray(x, dtype=np.float64) - xh.astype(np.float64)
91
+ sse = float(np.dot(np.asarray(w, dtype=np.float64), e * e)) if w is not None else float(np.dot(e, e))
92
+ return sse + spectral_penalty(e, cfg, dc_carry), sse
93
+
94
+
95
+ def refine_codes_hadamard(
96
+ x: np.ndarray,
97
+ w: np.ndarray | None,
98
+ d,
99
+ dmin,
100
+ ls: np.ndarray,
101
+ lm: np.ndarray,
102
+ codes: np.ndarray,
103
+ cfg: HPCConfig,
104
+ dc_carry: float = 0.0,
105
+ ) -> np.ndarray:
106
+ """Deterministic greedy descent of ``4*vesica^2 + (DC + carry)^2``.
107
+
108
+ Same acceptance rule as the C Phase-4 shaper, but the whole 256-element
109
+ search is evaluated as four vectorised candidate matrices per pass.
110
+ """
111
+ x = np.asarray(x, dtype=np.float64)
112
+ out = np.asarray(codes, dtype=np.uint8).copy()
113
+ ww = np.ones(QK_K, dtype=np.float64) if w is None else np.asarray(w, dtype=np.float64)
114
+ d_sub = np.repeat(np.float32(d) * np.asarray(ls, dtype=np.float32), 16).astype(np.float64)
115
+ m_sub = np.repeat(np.float32(dmin) * np.asarray(lm, dtype=np.float32), 16).astype(np.float64)
116
+ active = d_sub >= 1.0e-15
117
+ if not np.any(active):
118
+ return out
119
+
120
+ xh = d_sub * out.astype(np.float64) - m_sub
121
+ e_live = x - xh
122
+ sse_live = float(np.dot(ww, e_live * e_live))
123
+ sse_cap = sse_live * (1.0 + cfg.sse_budget)
124
+ v_live = e_live[: QK_K // 2] + e_live[QK_K // 2 :]
125
+ vesica = float(np.dot(v_live, v_live))
126
+ dc = float(np.sum(e_live)) + dc_carry
127
+ metric = 4.0 * vesica + dc * dc
128
+
129
+ idx = np.arange(QK_K)
130
+ pi = np.where(idx < QK_K // 2, idx, idx - QK_K // 2)
131
+ for _ in range(QK_K):
132
+ best_delta = 0.0
133
+ best_k = -1
134
+ best_q = 0
135
+ for q_try in range(4):
136
+ e_new = x - (d_sub * q_try - m_sub)
137
+ delta_e = e_new - e_live
138
+ sse_alt = sse_live + ww * (e_new * e_new - e_live * e_live)
139
+ mask = (sse_alt <= sse_cap) & active & (out != q_try)
140
+ if not np.any(mask):
141
+ continue
142
+ v_new = v_live[pi] + delta_e
143
+ vesica_alt = vesica - v_live[pi] * v_live[pi] + v_new * v_new
144
+ dc_alt = dc + delta_e
145
+ delta = metric - (4.0 * vesica_alt + dc_alt * dc_alt)
146
+ delta = np.where(mask, delta, -np.inf)
147
+ k = int(np.argmax(delta))
148
+ if delta[k] > best_delta:
149
+ best_delta = float(delta[k])
150
+ best_k = k
151
+ best_q = q_try
152
+ if best_k < 0:
153
+ break
154
+ old_e = e_live[best_k]
155
+ new_e = x[best_k] - (d_sub[best_k] * best_q - m_sub[best_k])
156
+ de = new_e - old_e
157
+ p = pi[best_k]
158
+ v_live[p] += de
159
+ vesica += v_live[p] * v_live[p] - (v_live[p] - de) * (v_live[p] - de)
160
+ dc += de
161
+ metric = 4.0 * vesica + dc * dc
162
+ sse_live += ww[best_k] * (new_e * new_e - old_e * old_e)
163
+ e_live[best_k] = new_e
164
+ out[best_k] = np.uint8(best_q)
165
+ return out
166
+
167
+
168
+ def dc_nudge_codes(
169
+ x: np.ndarray,
170
+ d,
171
+ dmin,
172
+ ls: np.ndarray,
173
+ lm: np.ndarray,
174
+ codes: np.ndarray,
175
+ w: np.ndarray | None,
176
+ carry: float,
177
+ cfg: HPCConfig,
178
+ max_passes: int = 4,
179
+ ) -> np.ndarray:
180
+ """Spend a bounded SSE budget to cancel the rolling DC residual.
181
+
182
+ Equivalent in spirit to Phase 4.8 of the C engine: the reconstruction
183
+ target is always the true weight, only the codeword choice changes.
184
+ """
185
+ x = np.asarray(x, dtype=np.float64)
186
+ out = np.asarray(codes, dtype=np.uint8).copy()
187
+ ww = np.ones(QK_K, dtype=np.float64) if w is None else np.asarray(w, dtype=np.float64)
188
+ d_sub = np.repeat(np.float32(d) * np.asarray(ls, dtype=np.float32), 16).astype(np.float64)
189
+ m_sub = np.repeat(np.float32(dmin) * np.asarray(lm, dtype=np.float32), 16).astype(np.float64)
190
+ active = d_sub >= 1.0e-15
191
+ if not np.any(active):
192
+ return out
193
+ e = x - (d_sub * out.astype(np.float64) - m_sub)
194
+ sse = float(np.dot(ww, e * e))
195
+ cap = sse * (1.0 + cfg.sse_budget)
196
+ residual = float(np.sum(e)) + carry
197
+ for _ in range(max_passes):
198
+ best_improvement = 0.0
199
+ best_k = -1
200
+ best_q = 0
201
+ for q_try in range(4):
202
+ e_new = x - (d_sub * q_try - m_sub)
203
+ mask = active & (out != q_try)
204
+ sse_alt = sse + ww * (e_new * e_new - e * e)
205
+ mask &= sse_alt <= cap
206
+ if not np.any(mask):
207
+ continue
208
+ improvement = np.abs(residual) - np.abs(residual + (e_new - e))
209
+ improvement = np.where(mask, improvement, -np.inf)
210
+ k = int(np.argmax(improvement))
211
+ if improvement[k] > best_improvement:
212
+ best_improvement = float(improvement[k])
213
+ best_k = k
214
+ best_q = q_try
215
+ if best_k < 0:
216
+ break
217
+ old_e = e[best_k]
218
+ new_e = x[best_k] - (d_sub[best_k] * best_q - m_sub[best_k])
219
+ de = new_e - old_e
220
+ residual += de
221
+ sse += ww[best_k] * (new_e * new_e - old_e * old_e)
222
+ e[best_k] = new_e
223
+ out[best_k] = np.uint8(best_q)
224
+ if abs(residual) < 1.0e-12:
225
+ break
226
+ return out
viterbi.py ADDED
@@ -0,0 +1,164 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """36-state Viterbi selection over the Q2 symbolic lattice.
2
+
3
+ This is a direct port of Phase 3 of the C engine. Each graph block owns a
4
+ ``6 x 6`` coarse/fine lattice; candidate errors are reduced to a free-energy
5
+ per bin, the Sieve marginals become a log-prior bonus, and an exact DP finds
6
+ the minimum-cost state sequence with a Manhattan transition penalty.
7
+
8
+ The forward pass is vectorised over the 36 states instead of the C engine's
9
+ triple nested scalar loop.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import numpy as np
15
+
16
+ from .config import HPCConfig
17
+ from .factored import CAND_TO_QUHIT, N_CAND_M, TOTAL_CANDIDATES
18
+
19
+ N_STATES = 36
20
+
21
+
22
+ def _median_upper(values: np.ndarray) -> float:
23
+ arr = np.asarray(values)
24
+ if arr.size == 0:
25
+ return 0.0
26
+ k = arr.size // 2
27
+ return float(np.partition(arr, k)[k])
28
+
29
+
30
+ def robust_temperature(
31
+ candidate_errors: np.ndarray, cfg: HPCConfig
32
+ ) -> float:
33
+ n_blocks = candidate_errors.shape[0]
34
+ if n_blocks <= 0:
35
+ return 1.0e-10
36
+ step = max(1, n_blocks // cfg.temperature_sample_blocks)
37
+ medians = []
38
+ for b in range(0, n_blocks, step):
39
+ row = candidate_errors[b]
40
+ finite = row[np.isfinite(row)]
41
+ if finite.size == 0:
42
+ continue
43
+ medians.append(_median_upper(np.maximum(0.0, row - finite.min())))
44
+ if len(medians) >= cfg.temperature_sample_blocks:
45
+ break
46
+ t = _median_upper(np.asarray(medians))
47
+ return t if np.isfinite(t) and t > 1.0e-10 else 1.0e-10
48
+
49
+
50
+ def _bin_geometry():
51
+ di = np.arange(TOTAL_CANDIDATES) // N_CAND_M
52
+ mi = np.arange(TOTAL_CANDIDATES) % N_CAND_M
53
+ qi_d = CAND_TO_QUHIT[di]
54
+ qi_m = CAND_TO_QUHIT[mi]
55
+ return qi_d, qi_m, qi_d * 6 + qi_m
56
+
57
+
58
+ def viterbi_select(
59
+ candidate_errors: np.ndarray,
60
+ coarse_marg: np.ndarray,
61
+ fine_marg: np.ndarray,
62
+ temperature: float,
63
+ stride: int,
64
+ graph_blocks: int,
65
+ cfg: HPCConfig,
66
+ ) -> tuple[np.ndarray, np.ndarray]:
67
+ n_blocks = candidate_errors.shape[0]
68
+ qi_d, qi_m, bins = _bin_geometry()
69
+ bin_err = np.full((graph_blocks, N_STATES), np.inf, dtype=np.float64)
70
+ bin_cand = np.full((graph_blocks, N_STATES), -1, dtype=np.int64)
71
+ log_prior = np.zeros((graph_blocks, N_STATES), dtype=np.float64)
72
+ temp = max(float(temperature), 1.0e-12)
73
+
74
+ for gi in range(graph_blocks):
75
+ start = gi * stride
76
+ stop = min((gi + 1) * stride, n_blocks)
77
+ group = candidate_errors[start:stop].astype(np.float64)
78
+ flat = group.ravel()
79
+ finite = np.isfinite(flat)
80
+ group_min = np.full(N_STATES, np.inf)
81
+ if np.any(finite):
82
+ np.minimum.at(group_min, bins[finite], flat[finite])
83
+ counts = np.bincount(bins[finite], minlength=N_STATES)
84
+ sums = np.zeros(N_STATES, dtype=np.float64)
85
+ if np.any(finite):
86
+ shifted = flat[finite] - group_min[bins[finite]]
87
+ amps = np.exp(-shifted / (2.0 * temp))
88
+ sums = np.bincount(bins[finite], weights=amps, minlength=N_STATES)
89
+ valid = (counts > 0) & np.isfinite(group_min)
90
+ if np.any(valid):
91
+ mean_exp = np.maximum(sums[valid] / counts[valid], 1.0e-300)
92
+ bin_err[gi, valid] = group_min[valid] - 2.0 * temp * np.log(mean_exp)
93
+
94
+ rep = candidate_errors[start].astype(np.float64)
95
+ for s in range(N_STATES):
96
+ idx = np.where((bins == s) & np.isfinite(rep))[0]
97
+ if idx.size:
98
+ bin_cand[gi, s] = int(idx[int(np.argmin(rep[idx]))])
99
+
100
+ c_tot = float(coarse_marg[gi].sum())
101
+ f_tot = float(fine_marg[gi].sum())
102
+ pc = coarse_marg[gi] / c_tot if c_tot > 1.0e-30 else np.full(6, 1.0 / 6.0)
103
+ pf = fine_marg[gi] / f_tot if f_tot > 1.0e-30 else np.full(6, 1.0 / 6.0)
104
+ for s in range(N_STATES):
105
+ log_prior[gi, s] = np.log(pc[s // 6] + 1.0e-30) + np.log(pf[s % 6] + 1.0e-30)
106
+
107
+ finite_err = bin_err[np.isfinite(bin_err)]
108
+ scale_err = float(finite_err.mean()) if finite_err.size else 1.0e-10
109
+ scale_err = max(scale_err, 1.0e-20)
110
+
111
+ local = np.where(
112
+ np.isfinite(bin_err),
113
+ bin_err - cfg.viterbi_beta * scale_err * log_prior,
114
+ np.inf,
115
+ )
116
+
117
+ state_d = np.arange(N_STATES) // 6
118
+ state_m = np.arange(N_STATES) % 6
119
+ trans = np.empty((N_STATES, N_STATES), dtype=np.float64)
120
+ for sp in range(N_STATES):
121
+ trans[sp, :] = cfg.viterbi_alpha * scale_err * (
122
+ np.abs(state_d - state_d[sp]) + np.abs(state_m - state_m[sp])
123
+ )
124
+
125
+ dp = np.full((graph_blocks, N_STATES), np.inf, dtype=np.float64)
126
+ back = np.zeros((graph_blocks, N_STATES), dtype=np.int64)
127
+ dp[0] = local[0]
128
+ for gi in range(1, graph_blocks):
129
+ totals = dp[gi - 1][:, None] + trans
130
+ best = np.argmin(totals, axis=0)
131
+ best_val = totals[best, np.arange(N_STATES)]
132
+ dp[gi] = np.where(np.isfinite(best_val), best_val + local[gi], np.inf)
133
+ back[gi] = best
134
+
135
+ if not np.any(np.isfinite(dp[-1])):
136
+ path = np.zeros(graph_blocks, dtype=np.int64)
137
+ else:
138
+ path = np.zeros(graph_blocks, dtype=np.int64)
139
+ path[-1] = int(np.argmin(dp[-1]))
140
+ for gi in range(graph_blocks - 2, -1, -1):
141
+ path[gi] = back[gi + 1, path[gi + 1]]
142
+
143
+ best_candidate = np.zeros(n_blocks, dtype=np.int64)
144
+ for gi in range(graph_blocks):
145
+ s = int(path[gi])
146
+ qd, qm = s // 6, s % 6
147
+ rep_block = gi * stride
148
+ if bin_cand[gi, s] >= 0:
149
+ best_candidate[rep_block] = bin_cand[gi, s]
150
+ start = rep_block + 1
151
+ stop = min((gi + 1) * stride, n_blocks)
152
+ for b in range(start, stop):
153
+ mask = (qi_d == qd) & (qi_m == qm)
154
+ idx = np.where(mask)[0]
155
+ if idx.size:
156
+ best_candidate[b] = int(idx[int(np.argmin(candidate_errors[b, idx]))])
157
+
158
+ for b in range(n_blocks):
159
+ cur = candidate_errors[b, best_candidate[b]]
160
+ g = int(np.argmin(candidate_errors[b]))
161
+ if candidate_errors[b, g] < cur * cfg.greedy_override_ratio:
162
+ best_candidate[b] = g
163
+
164
+ return best_candidate, path