CompressedGemma commited on
Commit
e24e779
·
verified ·
1 Parent(s): 2ba27ca

Add multi-threading

Browse files
Files changed (4) hide show
  1. __main__.py +3 -0
  2. config.py +4 -1
  3. gui.py +5 -0
  4. pipeline.py +251 -111
__main__.py CHANGED
@@ -38,6 +38,8 @@ def _config_from_args(args) -> HPCConfig:
38
  cfg.n_cand_d = args.cand_d
39
  if args.cand_m is not None:
40
  cfg.n_cand_m = args.cand_m
 
 
41
  if args.mse:
42
  cfg.optimizer = "mse"
43
  if args.no_spectral:
@@ -223,6 +225,7 @@ def main() -> None:
223
  parser.add_argument("--seed", type=int, default=1234, help="demo RNG seed")
224
  parser.add_argument("--cand-d", type=int, default=None, help="candidate d count")
225
  parser.add_argument("--cand-m", type=int, default=None, help="candidate min count")
 
226
  parser.add_argument("--mse", action="store_true", help="disable HPC sequence search")
227
  parser.add_argument("--no-spectral", action="store_true", help="disable spectral/DC objective")
228
  parser.add_argument("--verbose", action="store_true")
 
38
  cfg.n_cand_d = args.cand_d
39
  if args.cand_m is not None:
40
  cfg.n_cand_m = args.cand_m
41
+ if args.workers is not None:
42
+ cfg.workers = args.workers
43
  if args.mse:
44
  cfg.optimizer = "mse"
45
  if args.no_spectral:
 
225
  parser.add_argument("--seed", type=int, default=1234, help="demo RNG seed")
226
  parser.add_argument("--cand-d", type=int, default=None, help="candidate d count")
227
  parser.add_argument("--cand-m", type=int, default=None, help="candidate min count")
228
+ parser.add_argument("--workers", type=int, default=None, help="worker processes (0=auto CPU cores, 1=single)")
229
  parser.add_argument("--mse", action="store_true", help="disable HPC sequence search")
230
  parser.add_argument("--no-spectral", action="store_true", help="disable spectral/DC objective")
231
  parser.add_argument("--verbose", action="store_true")
config.py CHANGED
@@ -75,7 +75,8 @@ class HPCConfig:
75
  clamp_sigma_soft: float = 6.0
76
  clamp_sigma_hard: float = 4.0
77
 
78
- # ---- reporting ------------------------------------------------------
 
79
  verbose: bool = False
80
 
81
  def validate(self) -> "HPCConfig":
@@ -109,4 +110,6 @@ class HPCConfig:
109
  raise ValueError("log_slack must be non-negative")
110
  if self.viterbi_alpha < 0.0 or self.viterbi_beta < 0.0:
111
  raise ValueError("Viterbi weights must be non-negative")
 
 
112
  return self
 
75
  clamp_sigma_soft: float = 6.0
76
  clamp_sigma_hard: float = 4.0
77
 
78
+ # ---- reporting & parallelism ----------------------------------------
79
+ workers: int = 0
80
  verbose: bool = False
81
 
82
  def validate(self) -> "HPCConfig":
 
110
  raise ValueError("log_slack must be non-negative")
111
  if self.viterbi_alpha < 0.0 or self.viterbi_beta < 0.0:
112
  raise ValueError("Viterbi weights must be non-negative")
113
+ if self.workers < 0:
114
+ raise ValueError("workers must be >= 0 (0 = auto CPU count)")
115
  return self
gui.py CHANGED
@@ -97,6 +97,7 @@ class HPCQuantizeGUI:
97
  self.iters_var = tk.IntVar(value=8)
98
  self.ulp_var = tk.IntVar(value=4)
99
  self.limit_var = tk.IntVar(value=0)
 
100
 
101
  self.use_analogs_var = tk.BooleanVar(value=True)
102
  self.coupled_q2k_var = tk.BooleanVar(value=True)
@@ -136,6 +137,7 @@ class HPCQuantizeGUI:
136
  self.cand_d_var,
137
  self.cand_m_var,
138
  self.limit_var,
 
139
  self.use_spectral_var,
140
  ):
141
  v.trace_add("write", lambda *_: self._update_cli_preview())
@@ -271,6 +273,7 @@ class HPCQuantizeGUI:
271
  self._spin_pair(grid_f, 1, 2, "Solver Iters:", self.iters_var, 1, 32)
272
  self._spin_pair(grid_f, 2, 0, "FP16 ULP Radius:", self.ulp_var, 0, 16)
273
  self._spin_pair(grid_f, 2, 2, "Tensor Limit (0=all):", self.limit_var, 0, 9999)
 
274
 
275
  spec_f = ttk.LabelFrame(opt_frame, text="Spectral & DC Parameters", padding=6)
276
  spec_f.pack(fill=tk.X, pady=2)
@@ -481,6 +484,7 @@ class HPCQuantizeGUI:
481
  vw_lambda=float(self.vw_lambda_var.get()),
482
  dc_decay=float(self.dc_decay_var.get()),
483
  sse_budget=float(self.sse_budget_var.get()),
 
484
  verbose=False,
485
  )
486
  return cfg.validate()
@@ -503,6 +507,7 @@ class HPCQuantizeGUI:
503
  cmd += ["--output", out]
504
  if self.limit_var.get() > 0:
505
  cmd += ["--limit", str(self.limit_var.get())]
 
506
  cmd += ["--cand-d", str(self.cand_d_var.get()), "--cand-m", str(self.cand_m_var.get())]
507
  if self.optimizer_var.get() == "mse":
508
  cmd.append("--mse")
 
97
  self.iters_var = tk.IntVar(value=8)
98
  self.ulp_var = tk.IntVar(value=4)
99
  self.limit_var = tk.IntVar(value=0)
100
+ self.workers_var = tk.IntVar(value=0)
101
 
102
  self.use_analogs_var = tk.BooleanVar(value=True)
103
  self.coupled_q2k_var = tk.BooleanVar(value=True)
 
137
  self.cand_d_var,
138
  self.cand_m_var,
139
  self.limit_var,
140
+ self.workers_var,
141
  self.use_spectral_var,
142
  ):
143
  v.trace_add("write", lambda *_: self._update_cli_preview())
 
273
  self._spin_pair(grid_f, 1, 2, "Solver Iters:", self.iters_var, 1, 32)
274
  self._spin_pair(grid_f, 2, 0, "FP16 ULP Radius:", self.ulp_var, 0, 16)
275
  self._spin_pair(grid_f, 2, 2, "Tensor Limit (0=all):", self.limit_var, 0, 9999)
276
+ self._spin_pair(grid_f, 3, 0, "Workers (0=Auto CPU):", self.workers_var, 0, 256)
277
 
278
  spec_f = ttk.LabelFrame(opt_frame, text="Spectral & DC Parameters", padding=6)
279
  spec_f.pack(fill=tk.X, pady=2)
 
484
  vw_lambda=float(self.vw_lambda_var.get()),
485
  dc_decay=float(self.dc_decay_var.get()),
486
  sse_budget=float(self.sse_budget_var.get()),
487
+ workers=int(self.workers_var.get()),
488
  verbose=False,
489
  )
490
  return cfg.validate()
 
507
  cmd += ["--output", out]
508
  if self.limit_var.get() > 0:
509
  cmd += ["--limit", str(self.limit_var.get())]
510
+ cmd += ["--workers", str(self.workers_var.get())]
511
  cmd += ["--cand-d", str(self.cand_d_var.get()), "--cand-m", str(self.cand_m_var.get())]
512
  if self.optimizer_var.get() == "mse":
513
  cmd.append("--mse")
pipeline.py CHANGED
@@ -17,6 +17,8 @@ from __future__ import annotations
17
 
18
  from dataclasses import dataclass
19
  from math import ceil
 
 
20
  import time
21
 
22
  import numpy as np
@@ -405,6 +407,107 @@ def _apply_dc_nudge(X, W, blocks, cfg, row_width):
405
  return blocks
406
 
407
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  class Q2KQuantizer:
409
  def __init__(self, config: HPCConfig | None = None):
410
  self.config = (config or HPCConfig()).validate()
@@ -481,37 +584,75 @@ class Q2KQuantizer:
481
  t_cand = time.time()
482
  last_emit = 0.0
483
  step_report = max(1, min(32, n_blocks // 20 or 1))
484
- for i in range(n_blocks):
485
- _check_stop()
486
- wi = None if W is None else W[i]
487
- candidate_errors[i] = factor_candidates(
488
- X[i],
489
- wi,
490
- seeds["scales"][i],
491
- seeds["mins"][i],
492
- base_d[i],
493
- base_m[i],
494
- cfg,
495
- )
496
- fac_res = factor_only_solve(X[i], wi, cfg)
497
- standalone.append(fac_res)
498
- now = time.time()
499
- if (i + 1) % step_report == 0 or (i + 1) == n_blocks or (now - last_emit) >= 0.25:
500
- last_emit = now
501
- elapsed = max(now - t_cand, 1.0e-6)
502
- rate = (i + 1) / elapsed
503
- eta = (n_blocks - (i + 1)) / max(rate, 1.0e-9)
504
- _emit(
505
- "Phase 2b: Analog-Guided Factored Candidate Field",
506
- i + 1,
507
- n_blocks,
508
- f"blk {i+1}/{n_blocks} ({rate:.1f} blk/s, ETA {eta:.1f}s) "
509
- f"d={float(fac_res.d):.5f} dmin={float(fac_res.dmin):.5f} "
510
- f"obj={fac_res.objective:.5e} starts={fac_res.starts}",
511
- rate=rate,
512
- eta=eta,
513
- sigma=sigma,
514
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
515
 
516
  _check_stop()
517
  _emit(
@@ -532,27 +673,13 @@ class Q2KQuantizer:
532
  sigma=sigma,
533
  )
534
 
535
- at_slot: list[FactorResult] = []
536
- for i in range(n_blocks):
537
- _check_stop()
538
- wi = None if W is None else W[i]
539
- d16, m16 = candidate_pair(base_d[i], base_m[i], best[i])
540
- at_slot.append(
541
- factor_q2k_block(
542
- X[i],
543
- wi,
544
- cfg,
545
- init_s=seeds["scales"][i],
546
- init_t=seeds["mins"][i],
547
- d_seed=float(from_fp16_bits(d16)),
548
- m_seed=float(from_fp16_bits(m16)),
549
- pin=cfg.factor_pin,
550
- use_spectral=cfg.use_spectral,
551
- max_iter=cfg.factor_iters,
552
- starts=1,
553
- dc_carry=0.0,
554
- )
555
- )
556
 
557
  blocks: list[Q2KBlock] = []
558
  factor_floor_used = 0
@@ -560,67 +687,80 @@ class Q2KQuantizer:
560
  running_sse = 0.0
561
  t_asm = time.time()
562
  last_emit = 0.0
563
- for i in range(n_blocks):
564
- _check_stop()
565
- wi = None if W is None else W[i]
566
- d, m, ls, lm, codes, obj, sse = _assemble_block(
567
- X[i], wi, seeds, base_d[i], base_m[i], best[i], carry[i], cfg
568
- )
569
 
570
- if cfg.use_candidate_floor:
571
- cd, cm, cls, clm = _candidate_config(
572
- seeds["scales"][i], seeds["mins"][i], base_d[i], base_m[i], best[i]
573
- )
574
- ccodes = codes_from_params(X[i], cd, cm, cls, clm)
575
- cobj, csse = objective(
576
- X[i], wi, cd, cm, cls, clm, ccodes, cfg, float(carry[i])
577
- )
578
- if cobj < obj - 1.0e-15:
579
- d, m, ls, lm, codes, obj, sse = cd, cm, cls, clm, ccodes, cobj, csse
580
- candidate_floor_used += 1
581
-
582
- if cfg.use_factor_floor:
583
- for fac in (at_slot[i], standalone[i]):
584
- fcodes = refine_codes_hadamard(
585
- X[i], wi, fac.d, fac.dmin, fac.ls, fac.lm, fac.codes, cfg, float(carry[i])
586
- )
587
- fobj, fsse = objective(
588
- X[i], wi, fac.d, fac.dmin, fac.ls, fac.lm, fcodes, cfg, float(carry[i])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
589
  )
590
- if fobj < obj - 1.0e-15:
591
- d = float(fac.d)
592
- m = float(fac.dmin)
593
- ls = fac.ls.copy()
594
- lm = fac.lm.copy()
595
- codes = fcodes.copy()
596
- obj, sse = fobj, fsse
597
- factor_floor_used += 1
598
-
599
- blk_obj = make_block(d, m, ls, lm, codes)
600
- blocks.append(blk_obj)
601
- running_sse += blk_obj.error(X[i])
602
- now = time.time()
603
- if (i + 1) % step_report == 0 or (i + 1) == n_blocks or (now - last_emit) >= 0.25:
604
- last_emit = now
605
- elapsed = max(now - t_asm, 1.0e-6)
606
- rate = (i + 1) / elapsed
607
- eta = (n_blocks - (i + 1)) / max(rate, 1.0e-9)
608
- cur_rmse = float(np.sqrt(running_sse / ((i + 1) * QK_K)))
609
- _emit(
610
- "Phase 4: Sub-block Sieve + ULP + Hadamard Assembly",
611
- i + 1,
612
- n_blocks,
613
- f"blk {i+1}/{n_blocks} ({rate:.1f} blk/s, ETA {eta:.1f}s) "
614
- f"rmse={cur_rmse:.6e} rmse/sigma={cur_rmse/max(sigma, 1e-15):.4f} "
615
- f"floors(cand={candidate_floor_used}, fac={factor_floor_used})",
616
- rmse=cur_rmse,
617
- sigma=sigma,
618
- temperature=temperature,
619
- cand_floors=candidate_floor_used,
620
- fac_floors=factor_floor_used,
621
- rate=rate,
622
- eta=eta,
623
- )
624
 
625
  if cfg.apply_dc_carry and (cfg.dc_lambda > 0.0 or cfg.dc_decay > 0.0):
626
  _check_stop()
 
17
 
18
  from dataclasses import dataclass
19
  from math import ceil
20
+ import multiprocessing as mp
21
+ import os
22
  import time
23
 
24
  import numpy as np
 
407
  return blocks
408
 
409
 
410
+ _MP_STATE: dict[str, object] = {}
411
+
412
+
413
+ def _resolve_workers(cfg: HPCConfig, n_blocks: int) -> int:
414
+ if cfg.workers == 1 or n_blocks < 4:
415
+ return 1
416
+ if cfg.workers > 1:
417
+ return min(int(cfg.workers), n_blocks)
418
+ if n_blocks < 16:
419
+ return 1
420
+ cpu = os.cpu_count() or 1
421
+ return min(cpu, n_blocks)
422
+
423
+
424
+ def _phase2b_one(i: int) -> tuple[np.ndarray, FactorResult]:
425
+ X = _MP_STATE["X"]
426
+ W = _MP_STATE["W"]
427
+ seeds = _MP_STATE["seeds"]
428
+ base_d = _MP_STATE["base_d"]
429
+ base_m = _MP_STATE["base_m"]
430
+ cfg = _MP_STATE["cfg"]
431
+ wi = None if W is None else W[i]
432
+ cand = factor_candidates(
433
+ X[i],
434
+ wi,
435
+ seeds["scales"][i],
436
+ seeds["mins"][i],
437
+ base_d[i],
438
+ base_m[i],
439
+ cfg,
440
+ )
441
+ fac_res = factor_only_solve(X[i], wi, cfg)
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])
509
+
510
+
511
  class Q2KQuantizer:
512
  def __init__(self, config: HPCConfig | None = None):
513
  self.config = (config or HPCConfig()).validate()
 
584
  t_cand = time.time()
585
  last_emit = 0.0
586
  step_report = max(1, min(32, n_blocks // 20 or 1))
587
+ n_workers = _resolve_workers(cfg, n_blocks)
588
+ _MP_STATE.update(
589
+ {
590
+ "X": X,
591
+ "W": W,
592
+ "seeds": seeds,
593
+ "base_d": base_d,
594
+ "base_m": base_m,
595
+ "cfg": cfg,
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))
602
+ pool = ctx.Pool(processes=n_workers)
603
+ try:
604
+ for i, (cand_row, fac_res) in enumerate(
605
+ pool.imap(_phase2b_one, range(n_blocks), chunksize=chunksize)
606
+ ):
607
+ _check_stop()
608
+ candidate_errors[i] = cand_row
609
+ standalone.append(fac_res)
610
+ now = time.time()
611
+ if (i + 1) % step_report == 0 or (i + 1) == n_blocks or (now - last_emit) >= 0.25:
612
+ last_emit = now
613
+ elapsed = max(now - t_cand, 1.0e-6)
614
+ rate = (i + 1) / elapsed
615
+ eta = (n_blocks - (i + 1)) / max(rate, 1.0e-9)
616
+ _emit(
617
+ f"Phase 2b: Analog-Guided Factored Field ({n_workers} workers)",
618
+ i + 1,
619
+ n_blocks,
620
+ f"blk {i+1}/{n_blocks} ({rate:.1f} blk/s, ETA {eta:.1f}s) "
621
+ f"d={float(fac_res.d):.5f} dmin={float(fac_res.dmin):.5f} "
622
+ f"obj={fac_res.objective:.5e} starts={fac_res.starts}",
623
+ rate=rate,
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(
 
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
 
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)
701
+ candidate_floor_used += cand_hit
702
+ factor_floor_used += fac_hit
703
+ running_sse += blk_err
704
+ now = time.time()
705
+ if (i + 1) % step_report == 0 or (i + 1) == n_blocks or (now - last_emit) >= 0.25:
706
+ last_emit = now
707
+ elapsed = max(now - t_asm, 1.0e-6)
708
+ rate = (i + 1) / elapsed
709
+ eta = (n_blocks - (i + 1)) / max(rate, 1.0e-9)
710
+ cur_rmse = float(np.sqrt(running_sse / ((i + 1) * QK_K)))
711
+ _emit(
712
+ f"Phase 4: Sub-block Sieve + ULP + Hadamard Assembly ({n_workers} workers)",
713
+ i + 1,
714
+ n_blocks,
715
+ f"blk {i+1}/{n_blocks} ({rate:.1f} blk/s, ETA {eta:.1f}s) "
716
+ f"rmse={cur_rmse:.6e} rmse/sigma={cur_rmse/max(sigma, 1e-15):.4f} "
717
+ f"floors(cand={candidate_floor_used}, fac={factor_floor_used})",
718
+ rmse=cur_rmse,
719
+ sigma=sigma,
720
+ temperature=temperature,
721
+ cand_floors=candidate_floor_used,
722
+ fac_floors=factor_floor_used,
723
+ rate=rate,
724
+ eta=eta,
725
+ )
726
+ pool.close()
727
+ pool.join()
728
+ except BaseException:
729
+ pool.terminate()
730
+ pool.join()
731
+ raise
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
739
+ running_sse += blk_err
740
+ now = time.time()
741
+ if (i + 1) % step_report == 0 or (i + 1) == n_blocks or (now - last_emit) >= 0.25:
742
+ last_emit = now
743
+ elapsed = max(now - t_asm, 1.0e-6)
744
+ rate = (i + 1) / elapsed
745
+ eta = (n_blocks - (i + 1)) / max(rate, 1.0e-9)
746
+ cur_rmse = float(np.sqrt(running_sse / ((i + 1) * QK_K)))
747
+ _emit(
748
+ "Phase 4: Sub-block Sieve + ULP + Hadamard Assembly",
749
+ i + 1,
750
+ n_blocks,
751
+ f"blk {i+1}/{n_blocks} ({rate:.1f} blk/s, ETA {eta:.1f}s) "
752
+ f"rmse={cur_rmse:.6e} rmse/sigma={cur_rmse/max(sigma, 1e-15):.4f} "
753
+ f"floors(cand={candidate_floor_used}, fac={factor_floor_used})",
754
+ rmse=cur_rmse,
755
+ sigma=sigma,
756
+ temperature=temperature,
757
+ cand_floors=candidate_floor_used,
758
+ fac_floors=factor_floor_used,
759
+ rate=rate,
760
+ eta=eta,
761
  )
762
+
763
+ _MP_STATE.clear()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
764
 
765
  if cfg.apply_dc_carry and (cfg.dc_lambda > 0.0 or cfg.dc_decay > 0.0):
766
  _check_stop()