CompressedGemma commited on
Commit
603ba09
·
verified ·
1 Parent(s): e24e779

Upload 15 files

Browse files
Files changed (2) hide show
  1. factored.py +210 -99
  2. pipeline.py +101 -105
factored.py CHANGED
@@ -32,7 +32,7 @@ from .analog import build_block_analogs
32
  from .config import HPCConfig
33
  from .fp16 import from_fp16_bits, fp16_ulp_neighbors, nearest_int, round_fp16, to_fp16_bits
34
  from .q2k import NS, QK_K, codes_from_params, derive_subscales
35
- from .spectral import objective, refine_codes_hadamard
36
 
37
  N_CAND_D = 24
38
  N_CAND_M = 24
@@ -165,6 +165,10 @@ def _factor_1d_lloyd(v: np.ndarray, w: np.ndarray, nmax: int, iters: int):
165
  return best_d, best_codes
166
 
167
 
 
 
 
 
168
  def _refine_integer_codes(v, w, d, codes, nmax, sweeps=2):
169
  v = np.asarray(v, dtype=np.float64)
170
  w = np.asarray(w, dtype=np.float64)
@@ -172,33 +176,46 @@ def _refine_integer_codes(v, w, d, codes, nmax, sweeps=2):
172
  d = float(d)
173
  if d <= 0.0:
174
  return d, codes.astype(np.uint8)
175
- obj = float(np.sum(w * (v - d * codes) ** 2))
176
- options = np.arange(nmax + 1, dtype=np.int64)
 
 
 
 
 
 
 
177
  for _ in range(sweeps):
 
 
 
 
 
 
 
178
  improved = False
179
  for i in range(v.size):
180
- wi, vi, li = w[i], v[i], codes[i]
181
- sum_lv = float(np.sum(w * v * codes))
182
- sum_l2 = float(np.sum(w * codes * codes))
183
- slv = sum_lv - wi * vi * li + wi * vi * options
184
- sl2 = sum_l2 - wi * li * li + wi * options * options
185
- valid = sl2 > 1.0e-30
186
- if not np.any(valid):
187
  continue
188
- dd = np.where(valid, slv / np.where(valid, sl2, 1.0), 0.0)
189
- trial = codes[None, :].repeat(options.size, axis=0)
190
- trial[:, i] = options
191
- e = v[None, :] - dd[:, None] * trial.astype(np.float64)
192
- scores = np.sum(w[None, :] * e * e, axis=1)
193
- scores = np.where(valid & (dd > 0.0), scores, np.inf)
194
- k = int(np.argmin(scores))
195
- if scores[k] < obj - 1.0e-18 and options[k] != li:
196
- obj = float(scores[k])
197
- d = float(dd[k])
198
- codes[i] = options[k]
 
 
 
199
  improved = True
200
  if not improved:
201
  break
 
202
  return d, codes.astype(np.uint8)
203
 
204
 
@@ -242,28 +259,26 @@ def _factor_1d_for(v, w, nmax, cfg: HPCConfig, legacy: bool):
242
  )
243
 
244
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  def _range_init(x, w, cfg: HPCConfig, legacy_1d: bool = False):
246
- x = np.asarray(x, dtype=np.float64)
247
  w = np.asarray(w, dtype=np.float64)
248
- blocks = x.reshape(NS, 16)
249
- wblocks = w.reshape(NS, 16)
250
- s = np.zeros(NS)
251
- t = np.zeros(NS)
252
- codes = np.zeros(QK_K, dtype=np.uint8)
253
- for j in range(NS):
254
- sub = blocks[j]
255
- xmin = min(float(sub.min()), 0.0)
256
- xmax = float(sub.max())
257
- span = xmax - xmin
258
- sj = span / 3.0 if span > 1.0e-30 else 0.0
259
- tj = -xmin
260
- s[j] = sj
261
- t[j] = tj
262
- if sj > 1.0e-30:
263
- codes[16 * j : 16 * (j + 1)] = np.clip(
264
- nearest_int((sub + tj) / sj), 0, 3
265
- ).astype(np.uint8)
266
- sw = wblocks.sum(axis=1) if w is not None else np.full(NS, 16.0)
267
  d, ls = _factor_1d_for(s, sw, 15, cfg, legacy_1d)
268
  m, lm = _factor_1d_for(t, sw, 15, cfg, legacy_1d)
269
  return d, m, ls, lm, codes
@@ -280,17 +295,7 @@ def _seeded_init(x, scales, mins, d_seed, m_seed):
280
  def _ls_init(x, w, cfg: HPCConfig, legacy_1d: bool = False):
281
  x = np.asarray(x, dtype=np.float64)
282
  w = np.asarray(w, dtype=np.float64)
283
- blocks = x.reshape(NS, 16)
284
- codes = np.zeros(QK_K, dtype=np.uint8)
285
- for j in range(NS):
286
- sub = blocks[j]
287
- xmin = min(float(sub.min()), 0.0)
288
- xmax = float(sub.max())
289
- span = xmax - xmin
290
- if span > 1.0e-30:
291
- codes[16 * j : 16 * (j + 1)] = np.clip(
292
- nearest_int((sub - xmin) * 3.0 / span), 0, 3
293
- ).astype(np.uint8)
294
  s, t = _sub_ls(x, w, codes)
295
  sw = w.reshape(NS, 16).sum(axis=1)
296
  d, ls = _factor_1d_for(s, sw, 15, cfg, legacy_1d)
@@ -302,7 +307,7 @@ def _ls_init(x, w, cfg: HPCConfig, legacy_1d: bool = False):
302
  def _analog_init(x, w, x_analog, cfg: HPCConfig):
303
  """Extract discrete Q2_K lattice coordinates from a geometry-preserving analog
304
  and project continuous scales onto the true weights ``x``."""
305
- _, _, _, _, analog_codes = _range_init(x_analog, w, cfg, False)
306
  s, t = _sub_ls(x, w, analog_codes)
307
  q = analog_codes.astype(np.float64).reshape(NS, 16)
308
  wb = np.asarray(w, dtype=np.float64).reshape(NS, 16)
@@ -311,7 +316,7 @@ def _analog_init(x, w, x_analog, cfg: HPCConfig):
311
  Swq = np.sum(wb * q, axis=1)
312
  Sqq = np.sum(wb * q * q, axis=1)
313
  Sx = np.sum(wb * xb, axis=1)
314
- q_bar = np.where(Sw > 1.0e-30, Swq / np.maximum(Sw, 1e-30), 0.0)
315
  Vqq = np.maximum(Sqq - Swq * q_bar, 1.0e-12 * Sw)
316
  d, ls = _factor_1d_for(s, Vqq, 15, cfg, False)
317
  t_coupled = np.maximum(
@@ -474,22 +479,11 @@ def _solve(
474
  m, lm = float(m_new), lm_new
475
 
476
  codes = codes_from_params(x, d, m, ls, lm)
477
- if coupled:
478
- if pin == 0:
479
- d, m = _global_bilinear_dm_solve(
480
- x, w, d, m, ls, lm, codes, cfg, dc_carry, use_spectral
481
- )
482
- if cfg.factor_joint_subscales:
483
- d_f16 = float(d) if (pin & 1) else float(round_fp16(np.float32(d)))
484
- m_f16 = float(m) if (pin & 2) else float(round_fp16(np.float32(m)))
485
- ls_j, lm_j = _joint_subscale_scan(x, w, d_f16, m_f16)
486
- codes_j = codes_from_params(x, d_f16, m_f16, ls_j, lm_j)
487
- obj_j, _ = objective(x, w, d_f16, m_f16, ls_j, lm_j, codes_j, cfg, dc_carry)
488
- if obj_j < best[0] - 1.0e-15:
489
- d, m, ls, lm, codes = d_f16, m_f16, ls_j, lm_j, codes_j
490
- best = (obj_j, d, m, ls.copy(), lm.copy(), codes.copy())
491
- stall = 0
492
- continue
493
 
494
  cur, _ = objective(x, w, d, m, ls, lm, codes, cfg, dc_carry)
495
  if cur < best[0] - 1.0e-15:
@@ -501,6 +495,21 @@ def _solve(
501
  break
502
 
503
  obj, d, m, ls, lm, codes = best
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
504
  return d, m, ls, lm, codes, iters
505
 
506
 
@@ -588,6 +597,7 @@ def factor_q2k_block(
588
  n_starts = 0
589
  portfolio = cfg.factor_portfolio and (starts > 1 or max_iter > 1)
590
  for legacy_1d in ((False, True) if portfolio else (False,)):
 
591
  for d, m, ls, lm, codes in build_candidates(legacy_1d):
592
  n_starts += 1
593
  solve_iters = max(max_iter, 16) if legacy_1d else max_iter
@@ -596,6 +606,15 @@ def factor_q2k_block(
596
  solve_iters, dc_carry, legacy_1d,
597
  )
598
  total_iters += it
 
 
 
 
 
 
 
 
 
599
  d, m, d16, m16, ls, lm, codes, sse, obj = _emit_polish(
600
  x, w, cfg, d, m, ls, lm, codes, dc_carry
601
  )
@@ -626,40 +645,132 @@ def factor_candidates(
626
  cfg: HPCConfig,
627
  dc_carry: float = 0.0,
628
  ) -> np.ndarray:
629
- """Factored candidate field indexed exactly like the C 24x24 lattice."""
630
- out = np.full(TOTAL_CANDIDATES, np.inf, dtype=np.float32)
 
 
 
 
 
 
 
 
631
  nd = int(cfg.n_cand_d)
632
  nm = int(cfg.n_cand_m)
633
- for a in range(nd):
634
- di = a if nd == N_CAND_D else (a * N_CAND_D) // nd
635
- ds = float(round_fp16(np.float32(base_d) * MULTS_D[di]))
636
- for b in range(nm):
637
- mi = b if nm == N_CAND_M else (b * N_CAND_M) // nm
638
- ms = float(round_fp16(np.float32(base_m) * MULTS_M[mi]))
639
- res = factor_q2k_block(
640
- x,
641
- w,
642
- cfg,
643
- init_s=scales,
644
- init_t=mins,
645
- d_seed=ds,
646
- m_seed=ms,
647
- pin=cfg.factor_pin,
648
- use_spectral=cfg.use_spectral,
649
- max_iter=cfg.factor_iters,
650
- starts=1,
651
- dc_carry=dc_carry,
652
- )
653
- if nd == N_CAND_D and nm == N_CAND_M:
654
- out[di * N_CAND_M + mi] = np.float32(res.objective)
655
- else:
656
- cb = di >> 2
657
- fb = mi >> 2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
658
  for aa in range(4):
659
  for bb in range(4):
660
  idx = (cb * 4 + aa) * N_CAND_M + fb * 4 + bb
661
- if res.objective < out[idx]:
662
- out[idx] = np.float32(res.objective)
663
  return out
664
 
665
 
 
32
  from .config import HPCConfig
33
  from .fp16 import from_fp16_bits, fp16_ulp_neighbors, nearest_int, round_fp16, to_fp16_bits
34
  from .q2k import NS, QK_K, codes_from_params, derive_subscales
35
+ from .spectral import fold_weights, objective, refine_codes_hadamard
36
 
37
  N_CAND_D = 24
38
  N_CAND_M = 24
 
165
  return best_d, best_codes
166
 
167
 
168
+ _OPTS_15 = np.arange(16, dtype=np.float64)
169
+ _OPTS_15_SQ = _OPTS_15 * _OPTS_15
170
+
171
+
172
  def _refine_integer_codes(v, w, d, codes, nmax, sweeps=2):
173
  v = np.asarray(v, dtype=np.float64)
174
  w = np.asarray(w, dtype=np.float64)
 
176
  d = float(d)
177
  if d <= 0.0:
178
  return d, codes.astype(np.uint8)
179
+ options = _OPTS_15 if nmax == 15 else np.arange(nmax + 1, dtype=np.float64)
180
+ opt_sq = _OPTS_15_SQ if nmax == 15 else options * options
181
+ wv = w * v
182
+ sum_lv = float(np.dot(wv, codes))
183
+ sum_l2 = float(np.dot(w, codes * codes))
184
+ if sum_l2 <= 1.0e-30:
185
+ return d, codes.astype(np.uint8)
186
+ best_gain = (sum_lv * sum_lv) / sum_l2
187
+
188
  for _ in range(sweeps):
189
+ c_f = codes[:, None].astype(np.float64)
190
+ slv_all = sum_lv + wv[:, None] * (options[None, :] - c_f)
191
+ sl2_all = sum_l2 + w[:, None] * (opt_sq[None, :] - c_f * c_f)
192
+ val_all = (sl2_all > 1.0e-30) & (slv_all > 0.0)
193
+ g_all = np.where(val_all, (slv_all * slv_all) / np.where(val_all, sl2_all, 1.0), -1.0)
194
+ if float(np.max(g_all)) <= best_gain + 1.0e-18:
195
+ break
196
  improved = False
197
  for i in range(v.size):
198
+ wi = w[i]
199
+ if wi <= 0.0:
 
 
 
 
 
200
  continue
201
+ wvi = wv[i]
202
+ li = int(codes[i])
203
+ base_lv = sum_lv - wvi * li
204
+ base_l2 = sum_l2 - wi * (li * li)
205
+ slv = base_lv + wvi * options
206
+ sl2 = base_l2 + wi * opt_sq
207
+ valid = (sl2 > 1.0e-30) & (slv > 0.0)
208
+ gains = np.where(valid, (slv * slv) / np.where(valid, sl2, 1.0), -1.0)
209
+ k = int(np.argmax(gains))
210
+ if k != li and gains[k] > best_gain + 1.0e-18:
211
+ best_gain = float(gains[k])
212
+ sum_lv = float(slv[k])
213
+ sum_l2 = float(sl2[k])
214
+ codes[i] = k
215
  improved = True
216
  if not improved:
217
  break
218
+ d = sum_lv / sum_l2 if sum_l2 > 1.0e-30 else d
219
  return d, codes.astype(np.uint8)
220
 
221
 
 
259
  )
260
 
261
 
262
+ def _range_codes_and_st(x: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
263
+ blocks = np.asarray(x, dtype=np.float64).reshape(NS, 16)
264
+ xmin = np.minimum(np.min(blocks, axis=1), 0.0)
265
+ xmax = np.max(blocks, axis=1)
266
+ span = xmax - xmin
267
+ s = np.where(span > 1.0e-30, span / 3.0, 0.0)
268
+ t = -xmin
269
+ safe_s = np.where(s > 1.0e-30, s, 1.0)
270
+ q = np.where(
271
+ s[:, None] > 1.0e-30,
272
+ np.clip(nearest_int(((blocks + t[:, None]) / safe_s[:, None]).astype(np.float32)), 0, 3),
273
+ 0,
274
+ ).astype(np.uint8)
275
+ return s, t, q.reshape(QK_K)
276
+
277
+
278
  def _range_init(x, w, cfg: HPCConfig, legacy_1d: bool = False):
 
279
  w = np.asarray(w, dtype=np.float64)
280
+ s, t, codes = _range_codes_and_st(x)
281
+ sw = w.reshape(NS, 16).sum(axis=1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
  d, ls = _factor_1d_for(s, sw, 15, cfg, legacy_1d)
283
  m, lm = _factor_1d_for(t, sw, 15, cfg, legacy_1d)
284
  return d, m, ls, lm, codes
 
295
  def _ls_init(x, w, cfg: HPCConfig, legacy_1d: bool = False):
296
  x = np.asarray(x, dtype=np.float64)
297
  w = np.asarray(w, dtype=np.float64)
298
+ _, _, codes = _range_codes_and_st(x)
 
 
 
 
 
 
 
 
 
 
299
  s, t = _sub_ls(x, w, codes)
300
  sw = w.reshape(NS, 16).sum(axis=1)
301
  d, ls = _factor_1d_for(s, sw, 15, cfg, legacy_1d)
 
307
  def _analog_init(x, w, x_analog, cfg: HPCConfig):
308
  """Extract discrete Q2_K lattice coordinates from a geometry-preserving analog
309
  and project continuous scales onto the true weights ``x``."""
310
+ _, _, analog_codes = _range_codes_and_st(x_analog)
311
  s, t = _sub_ls(x, w, analog_codes)
312
  q = analog_codes.astype(np.float64).reshape(NS, 16)
313
  wb = np.asarray(w, dtype=np.float64).reshape(NS, 16)
 
316
  Swq = np.sum(wb * q, axis=1)
317
  Sqq = np.sum(wb * q * q, axis=1)
318
  Sx = np.sum(wb * xb, axis=1)
319
+ q_bar = np.where(Sw > 1.0e-30, Swq / np.maximum(Sw, 1.0e-30), 0.0)
320
  Vqq = np.maximum(Sqq - Swq * q_bar, 1.0e-12 * Sw)
321
  d, ls = _factor_1d_for(s, Vqq, 15, cfg, False)
322
  t_coupled = np.maximum(
 
479
  m, lm = float(m_new), lm_new
480
 
481
  codes = codes_from_params(x, d, m, ls, lm)
482
+ if coupled and pin == 0:
483
+ d, m = _global_bilinear_dm_solve(
484
+ x, w, d, m, ls, lm, codes, cfg, dc_carry, use_spectral
485
+ )
486
+ codes = codes_from_params(x, d, m, ls, lm)
 
 
 
 
 
 
 
 
 
 
 
487
 
488
  cur, _ = objective(x, w, d, m, ls, lm, codes, cfg, dc_carry)
489
  if cur < best[0] - 1.0e-15:
 
495
  break
496
 
497
  obj, d, m, ls, lm, codes = best
498
+ if coupled and cfg.factor_joint_subscales:
499
+ d_f16 = float(d) if (pin & 1) else float(round_fp16(np.float32(d)))
500
+ m_f16 = float(m) if (pin & 2) else float(round_fp16(np.float32(m)))
501
+ ls_j, lm_j = _joint_subscale_scan(x, w, d_f16, m_f16)
502
+ codes_j = codes_from_params(x, d_f16, m_f16, ls_j, lm_j)
503
+ if pin == 0:
504
+ d_j, m_j = _global_bilinear_dm_solve(
505
+ x, w, d_f16, m_f16, ls_j, lm_j, codes_j, cfg, dc_carry, use_spectral
506
+ )
507
+ d_f16 = float(round_fp16(np.float32(d_j)))
508
+ m_f16 = float(round_fp16(np.float32(m_j)))
509
+ codes_j = codes_from_params(x, d_f16, m_f16, ls_j, lm_j)
510
+ obj_j, _ = objective(x, w, d_f16, m_f16, ls_j, lm_j, codes_j, cfg, dc_carry)
511
+ if obj_j < obj - 1.0e-15:
512
+ obj, d, m, ls, lm, codes = obj_j, d_f16, m_f16, ls_j, lm_j, codes_j
513
  return d, m, ls, lm, codes, iters
514
 
515
 
 
597
  n_starts = 0
598
  portfolio = cfg.factor_portfolio and (starts > 1 or max_iter > 1)
599
  for legacy_1d in ((False, True) if portfolio else (False,)):
600
+ branch_best = None
601
  for d, m, ls, lm, codes in build_candidates(legacy_1d):
602
  n_starts += 1
603
  solve_iters = max(max_iter, 16) if legacy_1d else max_iter
 
606
  solve_iters, dc_carry, legacy_1d,
607
  )
608
  total_iters += it
609
+ d_f16 = float(round_fp16(np.float32(d)))
610
+ m_f16 = float(round_fp16(np.float32(m)))
611
+ codes_f16 = codes_from_params(x, d_f16, m_f16, ls, lm)
612
+ raw_obj, _ = objective(x, w, d_f16, m_f16, ls, lm, codes_f16, cfg, dc_carry)
613
+ if branch_best is None or raw_obj < branch_best[0] - 1.0e-15:
614
+ branch_best = (raw_obj, d, m, ls, lm, codes)
615
+
616
+ if branch_best is not None:
617
+ _, d, m, ls, lm, codes = branch_best
618
  d, m, d16, m16, ls, lm, codes, sse, obj = _emit_polish(
619
  x, w, cfg, d, m, ls, lm, codes, dc_carry
620
  )
 
645
  cfg: HPCConfig,
646
  dc_carry: float = 0.0,
647
  ) -> np.ndarray:
648
+ """Vectorized factored candidate field across the (nd x nm) lattice."""
649
+ x_f32 = np.asarray(x, dtype=np.float32).reshape(QK_K)
650
+ w_f64 = (
651
+ np.ones(QK_K, dtype=np.float64)
652
+ if w is None
653
+ else np.asarray(w, dtype=np.float64).reshape(QK_K)
654
+ )
655
+ scales_f32 = np.asarray(scales, dtype=np.float32).reshape(NS)
656
+ mins_f32 = np.asarray(mins, dtype=np.float32).reshape(NS)
657
+
658
  nd = int(cfg.n_cand_d)
659
  nm = int(cfg.n_cand_m)
660
+ di_idx = np.array(
661
+ [a if nd == N_CAND_D else (a * N_CAND_D) // nd for a in range(nd)],
662
+ dtype=np.int64,
663
+ )
664
+ mi_idx = np.array(
665
+ [b if nm == N_CAND_M else (b * N_CAND_M) // nm for b in range(nm)],
666
+ dtype=np.int64,
667
+ )
668
+
669
+ ds_vec = round_fp16(np.float32(base_d) * MULTS_D[di_idx]).astype(np.float64)
670
+ ms_vec = round_fp16(np.float32(base_m) * MULTS_M[mi_idx]).astype(np.float64)
671
+ ds = np.repeat(ds_vec, nm)
672
+ ms = np.tile(ms_vec, nd)
673
+ K = ds.size
674
+
675
+ safe_ds = np.where(ds[:, None] > 1.0e-30, ds[:, None], 1.0)
676
+ safe_ms = np.where(ms[:, None] > 1.0e-30, ms[:, None], 1.0)
677
+ ls0 = np.where(
678
+ ds[:, None] > 1.0e-30,
679
+ np.clip(nearest_int((scales_f32[None, :] / safe_ds).astype(np.float32)), 0, 15),
680
+ 0,
681
+ ).astype(np.uint8)
682
+ lm0 = np.where(
683
+ ms[:, None] > 1.0e-30,
684
+ np.clip(nearest_int((mins_f32[None, :] / safe_ms).astype(np.float32)), 0, 15),
685
+ 0,
686
+ ).astype(np.uint8)
687
+
688
+ def _eval_batch(ls_mat: np.ndarray, lm_mat: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
689
+ d_sub = np.repeat(ds[:, None] * ls_mat.astype(np.float64), 16, axis=1)
690
+ m_sub = np.repeat(ms[:, None] * lm_mat.astype(np.float64), 16, axis=1)
691
+ safe = d_sub >= 1.0e-15
692
+ sd = np.where(safe, d_sub, 1.0)
693
+ q = np.where(
694
+ safe,
695
+ np.clip(nearest_int(((x_f32[None, :] + m_sub) / sd).astype(np.float32)), 0, 3),
696
+ 0,
697
+ ).astype(np.float64)
698
+ e = x_f32.astype(np.float64)[None, :] - (d_sub * q - m_sub)
699
+ sse = np.sum(e * e * w_f64[None, :], axis=1)
700
+ if cfg.use_spectral and (cfg.dc_lambda != 0.0 or cfg.vw_lambda != 0.0):
701
+ fw = fold_weights(QK_K, cfg)
702
+ pen = np.zeros(K, dtype=np.float64)
703
+ src = e
704
+ m_len = QK_K // 2
705
+ lvl = 0
706
+ while m_len >= 1:
707
+ dst = src[:, :m_len] + src[:, m_len : 2 * m_len]
708
+ if m_len == 1 and dc_carry != 0.0:
709
+ dst = dst + dc_carry
710
+ if fw[lvl] != 0.0:
711
+ pen += fw[lvl] * np.sum(dst * dst, axis=1)
712
+ src = dst
713
+ m_len //= 2
714
+ lvl += 1
715
+ return sse + pen, q
716
+ return sse, q
717
+
718
+ obj0, q0 = _eval_batch(ls0, lm0)
719
+ best_obj = obj0
720
+
721
+ for _ in range(max(1, int(cfg.factor_iters))):
722
+ q_sub = q0.reshape(K, NS, 16)
723
+ xb = x_f32.astype(np.float64).reshape(1, NS, 16)
724
+ wb = w_f64.reshape(1, NS, 16)
725
+ Sqq = np.sum(wb * q_sub * q_sub, axis=2)
726
+ Swq = np.sum(wb * q_sub, axis=2)
727
+ Sw = np.sum(wb, axis=2)
728
+ Sqx = np.sum(wb * q_sub * xb, axis=2)
729
+ Sx = np.sum(wb * xb, axis=2)
730
+ det = Sqq * Sw - Swq * Swq
731
+ valid = det > 1.0e-30
732
+ s_det = np.where(valid, det, 1.0)
733
+ s_sw = np.where(Sw > 1.0e-30, Sw, 1.0)
734
+ sj = np.maximum(np.where(valid, (Sqx * Sw - Swq * Sx) / s_det, 0.0), 0.0)
735
+ tj = np.where(valid, (sj * Swq - Sx) / s_sw, 0.0)
736
+ bad_tj = tj < 0.0
737
+ tj = np.where(bad_tj, 0.0, tj)
738
+ fallback = np.where(Sqq > 1.0e-30, Sqx / np.where(Sqq > 1.0e-30, Sqq, 1.0), 0.0)
739
+ sj = np.where(bad_tj, np.maximum(fallback, 0.0), sj)
740
+
741
+ ls1 = np.where(
742
+ ds[:, None] > 1.0e-30,
743
+ np.clip(nearest_int((sj / safe_ds).astype(np.float32)), 0, 15),
744
+ 0,
745
+ ).astype(np.uint8)
746
+ t_tgt = (
747
+ np.maximum(0.0, (ds[:, None] * ls1.astype(np.float64) * Swq - Sx) / s_sw)
748
+ if cfg.factor_coupled_q2k
749
+ else tj
750
+ )
751
+ lm1 = np.where(
752
+ ms[:, None] > 1.0e-30,
753
+ np.clip(nearest_int((t_tgt / safe_ms).astype(np.float32)), 0, 15),
754
+ 0,
755
+ ).astype(np.uint8)
756
+ obj1, q0 = _eval_batch(ls1, lm1)
757
+ best_obj = np.minimum(best_obj, obj1)
758
+
759
+ best_f32 = best_obj.astype(np.float32)
760
+ out = np.full(TOTAL_CANDIDATES, np.inf, dtype=np.float32)
761
+ if nd == N_CAND_D and nm == N_CAND_M:
762
+ out[:] = best_f32
763
+ else:
764
+ for a, di in enumerate(di_idx):
765
+ cb = int(di) >> 2
766
+ for b, mi in enumerate(mi_idx):
767
+ fb = int(mi) >> 2
768
+ val = best_f32[a * nm + b]
769
  for aa in range(4):
770
  for bb in range(4):
771
  idx = (cb * 4 + aa) * N_CAND_M + fb * 4 + bb
772
+ if val < out[idx]:
773
+ out[idx] = val
774
  return out
775
 
776
 
pipeline.py CHANGED
@@ -442,67 +442,55 @@ def _phase2b_one(i: int) -> tuple[np.ndarray, FactorResult]:
442
  return cand, fac_res
443
 
444
 
445
- def _phase4_one(i: int) -> tuple[Q2KBlock, int, int, float]:
 
446
  X = _MP_STATE["X"]
447
  W = _MP_STATE["W"]
448
  seeds = _MP_STATE["seeds"]
449
  base_d = _MP_STATE["base_d"]
450
  base_m = _MP_STATE["base_m"]
451
- best = _MP_STATE["best"]
452
- carry = _MP_STATE["carry"]
453
- standalone = _MP_STATE["standalone"]
454
  cfg = _MP_STATE["cfg"]
455
 
456
  wi = None if W is None else W[i]
457
- d16, m16 = candidate_pair(base_d[i], base_m[i], best[i])
458
- at_slot = factor_q2k_block(
459
- X[i],
460
- wi,
461
- cfg,
462
- init_s=seeds["scales"][i],
463
- init_t=seeds["mins"][i],
464
- d_seed=float(from_fp16_bits(d16)),
465
- m_seed=float(from_fp16_bits(m16)),
466
- pin=cfg.factor_pin,
467
- use_spectral=cfg.use_spectral,
468
- max_iter=cfg.factor_iters,
469
- starts=1,
470
- dc_carry=0.0,
471
- )
472
  d, m, ls, lm, codes, obj, sse = _assemble_block(
473
- X[i], wi, seeds, base_d[i], base_m[i], best[i], carry[i], cfg
474
  )
475
  cand_hit = 0
476
  fac_hit = 0
477
 
478
  if cfg.use_candidate_floor:
479
  cd, cm, cls, clm = _candidate_config(
480
- seeds["scales"][i], seeds["mins"][i], base_d[i], base_m[i], best[i]
481
  )
482
  ccodes = codes_from_params(X[i], cd, cm, cls, clm)
483
  cobj, csse = objective(
484
- X[i], wi, cd, cm, cls, clm, ccodes, cfg, float(carry[i])
485
  )
486
  if cobj < obj - 1.0e-15:
487
  d, m, ls, lm, codes, obj, sse = cd, cm, cls, clm, ccodes, cobj, csse
488
  cand_hit = 1
489
 
490
  if cfg.use_factor_floor:
491
- for fac in (at_slot, standalone[i]):
492
- fcodes = refine_codes_hadamard(
493
- X[i], wi, fac.d, fac.dmin, fac.ls, fac.lm, fac.codes, cfg, float(carry[i])
494
  )
495
- fobj, fsse = objective(
496
- X[i], wi, fac.d, fac.dmin, fac.ls, fac.lm, fcodes, cfg, float(carry[i])
497
- )
498
- if fobj < obj - 1.0e-15:
499
- d = float(fac.d)
500
- m = float(fac.dmin)
501
- ls = fac.ls.copy()
502
- lm = fac.lm.copy()
503
- codes = fcodes.copy()
504
- obj, sse = fobj, fsse
505
- fac_hit = 1
 
 
 
 
 
506
 
507
  blk_obj = make_block(d, m, ls, lm, codes)
508
  return blk_obj, cand_hit, fac_hit, blk_obj.error(X[i])
@@ -596,6 +584,11 @@ class Q2KQuantizer:
596
  }
597
  )
598
 
 
 
 
 
 
599
  if n_workers > 1 and "fork" in mp.get_all_start_methods():
600
  ctx = mp.get_context("fork")
601
  chunksize = max(1, min(16, n_blocks // (n_workers * 4) or 1))
@@ -624,77 +617,34 @@ class Q2KQuantizer:
624
  eta=eta,
625
  sigma=sigma,
626
  )
627
- pool.close()
628
- pool.join()
629
- except BaseException:
630
- pool.terminate()
631
- pool.join()
632
- raise
633
- else:
634
- for i in range(n_blocks):
635
- _check_stop()
636
- cand_row, fac_res = _phase2b_one(i)
637
- candidate_errors[i] = cand_row
638
- standalone.append(fac_res)
639
- now = time.time()
640
- if (i + 1) % step_report == 0 or (i + 1) == n_blocks or (now - last_emit) >= 0.25:
641
- last_emit = now
642
- elapsed = max(now - t_cand, 1.0e-6)
643
- rate = (i + 1) / elapsed
644
- eta = (n_blocks - (i + 1)) / max(rate, 1.0e-9)
645
- _emit(
646
- "Phase 2b: Analog-Guided Factored Candidate Field",
647
- i + 1,
648
- n_blocks,
649
- f"blk {i+1}/{n_blocks} ({rate:.1f} blk/s, ETA {eta:.1f}s) "
650
- f"d={float(fac_res.d):.5f} dmin={float(fac_res.dmin):.5f} "
651
- f"obj={fac_res.objective:.5e} starts={fac_res.starts}",
652
- rate=rate,
653
- eta=eta,
654
- sigma=sigma,
655
- )
656
-
657
- _check_stop()
658
- _emit(
659
- "Phase 3: 6-State Sieve & 36-State Viterbi DP",
660
- 0,
661
- n_blocks,
662
- f"Running Sieve + 36-state Viterbi sequence optimization ({cfg.optimizer.upper()})",
663
- sigma=sigma,
664
- )
665
- best, temperature, _ = _phase3_select(candidate_errors, cfg)
666
- carry = _compute_dc_carry(X, seeds, best, base_d, base_m, cfg, row_width)
667
- _emit(
668
- "Phase 3.9: Viterbi Path & Rolling DC Carry",
669
- n_blocks,
670
- n_blocks,
671
- f"Boltzmann temp={temperature:.4e}, max |DC carry|={float(np.max(np.abs(carry))):.4e}",
672
- temperature=temperature,
673
- sigma=sigma,
674
- )
675
 
676
- _MP_STATE.update(
677
- {
678
- "best": best,
679
- "carry": carry,
680
- "standalone": standalone,
681
- }
682
- )
683
-
684
- blocks: list[Q2KBlock] = []
685
- factor_floor_used = 0
686
- candidate_floor_used = 0
687
- running_sse = 0.0
688
- t_asm = time.time()
689
- last_emit = 0.0
 
 
 
 
690
 
691
- if n_workers > 1 and "fork" in mp.get_all_start_methods():
692
- ctx = mp.get_context("fork")
693
- chunksize = max(1, min(16, n_blocks // (n_workers * 4) or 1))
694
- pool = ctx.Pool(processes=n_workers)
695
- try:
 
696
  for i, (blk_obj, cand_hit, fac_hit, blk_err) in enumerate(
697
- pool.imap(_phase4_one, range(n_blocks), chunksize=chunksize)
698
  ):
699
  _check_stop()
700
  blocks.append(blk_obj)
@@ -732,7 +682,53 @@ class Q2KQuantizer:
732
  else:
733
  for i in range(n_blocks):
734
  _check_stop()
735
- blk_obj, cand_hit, fac_hit, blk_err = _phase4_one(i)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
736
  blocks.append(blk_obj)
737
  candidate_floor_used += cand_hit
738
  factor_floor_used += fac_hit
 
442
  return cand, fac_res
443
 
444
 
445
+ def _phase4_one(args: tuple[int, int, float, FactorResult]) -> tuple[Q2KBlock, int, int, float]:
446
+ i, best_i, carry_i, fac = args
447
  X = _MP_STATE["X"]
448
  W = _MP_STATE["W"]
449
  seeds = _MP_STATE["seeds"]
450
  base_d = _MP_STATE["base_d"]
451
  base_m = _MP_STATE["base_m"]
 
 
 
452
  cfg = _MP_STATE["cfg"]
453
 
454
  wi = None if W is None else W[i]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
455
  d, m, ls, lm, codes, obj, sse = _assemble_block(
456
+ X[i], wi, seeds, base_d[i], base_m[i], best_i, carry_i, cfg
457
  )
458
  cand_hit = 0
459
  fac_hit = 0
460
 
461
  if cfg.use_candidate_floor:
462
  cd, cm, cls, clm = _candidate_config(
463
+ seeds["scales"][i], seeds["mins"][i], base_d[i], base_m[i], best_i
464
  )
465
  ccodes = codes_from_params(X[i], cd, cm, cls, clm)
466
  cobj, csse = objective(
467
+ X[i], wi, cd, cm, cls, clm, ccodes, cfg, carry_i
468
  )
469
  if cobj < obj - 1.0e-15:
470
  d, m, ls, lm, codes, obj, sse = cd, cm, cls, clm, ccodes, cobj, csse
471
  cand_hit = 1
472
 
473
  if cfg.use_factor_floor:
474
+ fcodes = (
475
+ refine_codes_hadamard(
476
+ X[i], wi, fac.d, fac.dmin, fac.ls, fac.lm, fac.codes, cfg, carry_i
477
  )
478
+ if carry_i != 0.0
479
+ else fac.codes
480
+ )
481
+ fobj, fsse = (
482
+ objective(X[i], wi, fac.d, fac.dmin, fac.ls, fac.lm, fcodes, cfg, carry_i)
483
+ if carry_i != 0.0
484
+ else (fac.objective, fac.sse)
485
+ )
486
+ if fobj < obj - 1.0e-15:
487
+ d = float(fac.d)
488
+ m = float(fac.dmin)
489
+ ls = fac.ls.copy()
490
+ lm = fac.lm.copy()
491
+ codes = fcodes.copy()
492
+ obj, sse = fobj, fsse
493
+ fac_hit = 1
494
 
495
  blk_obj = make_block(d, m, ls, lm, codes)
496
  return blk_obj, cand_hit, fac_hit, blk_obj.error(X[i])
 
584
  }
585
  )
586
 
587
+ blocks: list[Q2KBlock] = []
588
+ factor_floor_used = 0
589
+ candidate_floor_used = 0
590
+ running_sse = 0.0
591
+
592
  if n_workers > 1 and "fork" in mp.get_all_start_methods():
593
  ctx = mp.get_context("fork")
594
  chunksize = max(1, min(16, n_blocks // (n_workers * 4) or 1))
 
617
  eta=eta,
618
  sigma=sigma,
619
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
620
 
621
+ _check_stop()
622
+ _emit(
623
+ "Phase 3: 6-State Sieve & 36-State Viterbi DP",
624
+ 0,
625
+ n_blocks,
626
+ f"Running Sieve + 36-state Viterbi sequence optimization ({cfg.optimizer.upper()})",
627
+ sigma=sigma,
628
+ )
629
+ best, temperature, _ = _phase3_select(candidate_errors, cfg)
630
+ carry = _compute_dc_carry(X, seeds, best, base_d, base_m, cfg, row_width)
631
+ _emit(
632
+ "Phase 3.9: Viterbi Path & Rolling DC Carry",
633
+ n_blocks,
634
+ n_blocks,
635
+ f"Boltzmann temp={temperature:.4e}, max |DC carry|={float(np.max(np.abs(carry))):.4e}",
636
+ temperature=temperature,
637
+ sigma=sigma,
638
+ )
639
 
640
+ t_asm = time.time()
641
+ last_emit = 0.0
642
+ p4_args = [
643
+ (i, int(best[i]), float(carry[i]), standalone[i])
644
+ for i in range(n_blocks)
645
+ ]
646
  for i, (blk_obj, cand_hit, fac_hit, blk_err) in enumerate(
647
+ pool.imap(_phase4_one, p4_args, chunksize=chunksize)
648
  ):
649
  _check_stop()
650
  blocks.append(blk_obj)
 
682
  else:
683
  for i in range(n_blocks):
684
  _check_stop()
685
+ cand_row, fac_res = _phase2b_one(i)
686
+ candidate_errors[i] = cand_row
687
+ standalone.append(fac_res)
688
+ now = time.time()
689
+ if (i + 1) % step_report == 0 or (i + 1) == n_blocks or (now - last_emit) >= 0.25:
690
+ last_emit = now
691
+ elapsed = max(now - t_cand, 1.0e-6)
692
+ rate = (i + 1) / elapsed
693
+ eta = (n_blocks - (i + 1)) / max(rate, 1.0e-9)
694
+ _emit(
695
+ "Phase 2b: Analog-Guided Factored Candidate Field",
696
+ i + 1,
697
+ n_blocks,
698
+ f"blk {i+1}/{n_blocks} ({rate:.1f} blk/s, ETA {eta:.1f}s) "
699
+ f"d={float(fac_res.d):.5f} dmin={float(fac_res.dmin):.5f} "
700
+ f"obj={fac_res.objective:.5e} starts={fac_res.starts}",
701
+ rate=rate,
702
+ eta=eta,
703
+ sigma=sigma,
704
+ )
705
+
706
+ _check_stop()
707
+ _emit(
708
+ "Phase 3: 6-State Sieve & 36-State Viterbi DP",
709
+ 0,
710
+ n_blocks,
711
+ f"Running Sieve + 36-state Viterbi sequence optimization ({cfg.optimizer.upper()})",
712
+ sigma=sigma,
713
+ )
714
+ best, temperature, _ = _phase3_select(candidate_errors, cfg)
715
+ carry = _compute_dc_carry(X, seeds, best, base_d, base_m, cfg, row_width)
716
+ _emit(
717
+ "Phase 3.9: Viterbi Path & Rolling DC Carry",
718
+ n_blocks,
719
+ n_blocks,
720
+ f"Boltzmann temp={temperature:.4e}, max |DC carry|={float(np.max(np.abs(carry))):.4e}",
721
+ temperature=temperature,
722
+ sigma=sigma,
723
+ )
724
+
725
+ t_asm = time.time()
726
+ last_emit = 0.0
727
+ for i in range(n_blocks):
728
+ _check_stop()
729
+ blk_obj, cand_hit, fac_hit, blk_err = _phase4_one(
730
+ (i, int(best[i]), float(carry[i]), standalone[i])
731
+ )
732
  blocks.append(blk_obj)
733
  candidate_floor_used += cand_hit
734
  factor_floor_used += fac_hit