CompressedGemma commited on
Commit
7c4637c
·
verified ·
1 Parent(s): e9e0544

Upload 5 files

Browse files
Files changed (2) hide show
  1. anneal.py +177 -0
  2. q3_k.py +10 -11
anneal.py CHANGED
@@ -563,3 +563,180 @@ def thermal_refine_q3_k(
563
  lam_vw, lam_dc, 8, cfg, chunk=chunk, progress=progress,
564
  )
565
  return codes_out - 4, improved
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563
  lam_vw, lam_dc, 8, cfg, chunk=chunk, progress=progress,
564
  )
565
  return codes_out - 4, improved
566
+
567
+
568
+ def thermal_refine_uniform_codes_cuda(
569
+ X: "torch.Tensor",
570
+ w: "torch.Tensor | None",
571
+ codes: "torch.Tensor",
572
+ step: "torch.Tensor",
573
+ m_const: "torch.Tensor",
574
+ carry: "torch.Tensor | None",
575
+ lam_vw: float,
576
+ lam_dc: float,
577
+ levels: int,
578
+ cfg: HPCConfig,
579
+ passes: int | None = None,
580
+ seed: int | None = None,
581
+ ) -> tuple["torch.Tensor", int]:
582
+ """Torch/CUDA sibling of ``thermal_refine_uniform_codes``.
583
+
584
+ Identical algorithm and acceptance structure (extended-nonincreasing
585
+ best-state with an ``sse_budget`` guard, seeded heat-bath sweeps plus a
586
+ greedy endgame), run on-device in float64 so results are deterministic
587
+ per device class (bit-exact across runs at the same seed, but not
588
+ bit-identical to the numpy CPU chain — floating-point ordering differs).
589
+
590
+ All tensor inputs live on one CUDA device: ``codes`` int64 (B, n),
591
+ the rest float32/float64. Returns ``(codes, n_improved)`` with
592
+ ``codes`` still on-device.
593
+ """
594
+ import torch # local import keeps anneal.py torch-free for CPU installs
595
+
596
+ dev = X.device
597
+ f64 = torch.float64
598
+ B, n = X.shape
599
+ half = n // 2
600
+ n_passes = cfg.therm_passes if passes is None else int(passes)
601
+ if B == 0 or n % 2 != 0:
602
+ return codes.to(torch.int64), 0
603
+ if n_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0):
604
+ return codes.to(torch.int64).clone(), 0
605
+ if cfg.fold_depth > 1:
606
+ raise ValueError("thermal refinement supports fold_depth <= 1")
607
+
608
+ Xf = X.to(f64)
609
+ wf = None if w is None else w.to(f64)
610
+ cf = None if carry is None else carry.to(f64)
611
+ if cf is None:
612
+ cf = torch.zeros(B, dtype=f64, device=dev)
613
+ sf = step.to(f64)
614
+ mf_data = m_const.to(f64)
615
+ cc = codes.to(torch.int64).clone()
616
+ grid = torch.arange(levels, device=dev, dtype=torch.int64)
617
+ ones_col = torch.ones(B, dtype=f64, device=dev)
618
+
619
+ def energies(c):
620
+ e = Xf - (sf * c.to(f64) - mf_data)
621
+ w2 = torch.sum(e * e, dim=1) if wf is None else torch.sum(wf * e * e, dim=1)
622
+ v = e[:, :half] + e[:, half:]
623
+ dc = torch.sum(e, dim=1) + cf
624
+ return (w2 + lam_vw * torch.sum(v * v, dim=1) + lam_dc * dc * dc), e, w2, v, dc
625
+
626
+ energy_best, e, sse0, v, dc = energies(cc)
627
+ energy0 = energy_best.clone()
628
+ sse_cap = sse0 * (1.0 + float(cfg.sse_budget))
629
+ best = cc.clone()
630
+ gen = torch.Generator(device=dev)
631
+ gen.manual_seed(int(cfg.therm_seed if seed is None else seed) & 0x7FFFFFFFFFFFFFFF)
632
+
633
+ for t in range(n_passes):
634
+ temperature = (
635
+ cfg.therm_t0
636
+ * torch.maximum(energy_best, torch.full_like(energy_best, 1.0e-30))
637
+ / n
638
+ * (cfg.therm_t_decay ** t)
639
+ )
640
+ broke = False
641
+ for sweep in range(cfg.therm_sweeps + 2):
642
+ greedy = sweep >= cfg.therm_sweeps
643
+ energy_cur, e, _, v, dc = energies(cc)
644
+ for p in range(half):
645
+ e_lo = e[:, p]
646
+ e_hi = e[:, half + p]
647
+ q_lo = cc[:, p]
648
+ q_hi = cc[:, half + p]
649
+ ds_lo = sf[:, p]
650
+ ds_hi = sf[:, half + p]
651
+ act_lo = ds_lo.abs() >= EPS_D
652
+ act_hi = ds_hi.abs() >= EPS_D
653
+ w_lo = ones_col if wf is None else wf[:, p]
654
+ w_hi = ones_col if wf is None else wf[:, half + p]
655
+ de_lo = ds_lo[:, None] * (q_lo[:, None] - grid[None, :])
656
+ de_lo = torch.where(act_lo[:, None], de_lo, torch.zeros_like(de_lo))
657
+ de_hi = ds_hi[:, None] * (q_hi[:, None] - grid[None, :])
658
+ de_hi = torch.where(act_hi[:, None], de_hi, torch.zeros_like(de_hi))
659
+ de_sum = de_lo[:, :, None] + de_hi[:, None, :]
660
+ v_p = v[:, p]
661
+ d_e = (
662
+ w_lo[:, None, None] * (2.0 * e_lo[:, None, None] * de_lo[:, :, None] + de_lo[:, :, None] ** 2)
663
+ + w_hi[:, None, None] * (2.0 * e_hi[:, None, None] * de_hi[:, None, :] + de_hi[:, None, :] ** 2)
664
+ + lam_vw * (2.0 * v_p[:, None, None] * de_sum + de_sum * de_sum)
665
+ + lam_dc * (2.0 * dc[:, None, None] * de_sum + de_sum * de_sum)
666
+ )
667
+ cur = q_lo * levels + q_hi
668
+ if greedy:
669
+ flat = d_e.reshape(B, -1)
670
+ pick = flat.argmin(dim=1)
671
+ improve = torch.gather(flat, 1, pick[:, None]).squeeze(1) < -1.0e-18
672
+ pick = torch.where(improve, pick, cur)
673
+ else:
674
+ scc = torch.clamp(temperature, min=1.0e-300)[:, None, None]
675
+ logits = -d_e / scc
676
+ logits = logits - logits.amax(dim=(1, 2), keepdim=True)
677
+ probs = logits.exp().reshape(B, -1)
678
+ dead = ~(act_lo | act_hi)
679
+ if bool(dead.any()):
680
+ probs[dead] = 0.0
681
+ probs[dead, cur[dead]] = 1.0
682
+ cum = probs.cumsum(dim=1)
683
+ cum = cum / cum[:, -1:]
684
+ u = torch.rand(B, 1, generator=gen, device=dev, dtype=f64)
685
+ pick = (cum < u).sum(dim=1).clamp(max=levels * levels - 1)
686
+ ia = pick // levels
687
+ ib = pick - ia * levels
688
+ upd_lo = torch.gather(de_lo, 1, ia[:, None]).squeeze(1)
689
+ upd_hi = torch.gather(de_hi, 1, ib[:, None]).squeeze(1)
690
+ usum = upd_lo + upd_hi
691
+ e[:, p] = e_lo + upd_lo
692
+ e[:, half + p] = e_hi + upd_hi
693
+ v[:, p] += usum
694
+ dc = dc + usum
695
+ cc[:, p] = ia
696
+ cc[:, half + p] = ib
697
+ if greedy:
698
+ energy_now, _, _, _, _ = energies(cc)
699
+ if not bool(torch.any(energy_now < energy_cur - 1.0e-18)):
700
+ broke = True
701
+ break
702
+ _, _, sse_now, _, _ = energies(cc)
703
+ take = (energy_now < energy_best - 1.0e-18) & (sse_now <= sse_cap)
704
+ best = torch.where(take[:, None], cc, best)
705
+ energy_best = torch.where(take, energy_now, energy_best)
706
+ cc = best.clone()
707
+ if broke and bool(torch.all(energy_now >= energy_best)):
708
+ pass # keep exact CPU-loop semantics: continue across passes regardless
709
+
710
+ n_improved = int((energy_best < energy0 - 1.0e-18).sum().item())
711
+ return best, n_improved
712
+
713
+
714
+ def thermal_refine_q3_k_cuda(
715
+ X: "torch.Tensor",
716
+ W: "torch.Tensor | None",
717
+ scale: "torch.Tensor",
718
+ codes: "torch.Tensor",
719
+ cfg: HPCConfig,
720
+ ) -> tuple["torch.Tensor", int]:
721
+ """On-device anneal for Q3_K signed 3-bit codes (see the CPU twin).
722
+
723
+ ``scale``: per-superblock scale values (B, 16) on-device, ``codes``:
724
+ signed values (B, 256) in [-4, 3]. Exactly mirrors
725
+ ``thermal_refine_q3_k`` without any host round-trip.
726
+ """
727
+ import torch
728
+ n_blocks, n = X.shape
729
+ lam_vw, lam_dc = thermal_spectral_lams(cfg, n)
730
+ if n_blocks == 0 or cfg.therm_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0):
731
+ return codes.to(torch.int64).clone(), 0
732
+
733
+ def build_state(start, stop):
734
+ ds_full = scale.repeat_interleave(16, dim=1) # (B, 256)
735
+ return ds_full, 4.0 * ds_full
736
+
737
+ ds_full, m_full = build_state(0, n_blocks)
738
+ codes_out, improved = thermal_refine_uniform_codes_cuda(
739
+ X, W, codes.to(torch.int64) + 4, ds_full, m_full, None,
740
+ lam_vw, lam_dc, 8, cfg,
741
+ )
742
+ return codes_out - 4, improved
q3_k.py CHANGED
@@ -48,7 +48,7 @@ try:
48
  except ImportError: # pragma: no cover
49
  torch = None # type: ignore[assignment]
50
 
51
- from .anneal import thermal_refine_q3_k
52
  from .config import HPCConfig
53
  from .cuda_backend import cuda_is_available
54
  from .factored import MULTS_D
@@ -814,18 +814,17 @@ def _fit_q3_k_blocks_cuda(
814
  code = torch.where(accept[:, None], code_new, code_f).reshape(B, Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK)
815
 
816
  # 5. Optional Thermal Refinery (Pair Heat-Bath, non-regressing):
 
 
 
817
  if cfg.therm_passes > 0:
818
- scale_rp = (best_d[:, None] * sc.to(torch.float32)).cpu().numpy()
819
- thermal_c, _ = thermal_refine_q3_k(
820
- Xf.cpu().numpy(),
821
- Wf.cpu().numpy(),
822
- scale_rp,
823
- code.reshape(B, QK3_K).cpu().numpy(),
824
- cfg,
825
  )
826
- code = torch.from_numpy(
827
- thermal_c.astype(np.int16).reshape(B, Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK)
828
- ).to(dev)
829
 
830
  return (
831
  best_d16.cpu().numpy().astype(np.uint16),
 
48
  except ImportError: # pragma: no cover
49
  torch = None # type: ignore[assignment]
50
 
51
+ from .anneal import thermal_refine_q3_k, thermal_refine_q3_k_cuda
52
  from .config import HPCConfig
53
  from .cuda_backend import cuda_is_available
54
  from .factored import MULTS_D
 
814
  code = torch.where(accept[:, None], code_new, code_f).reshape(B, Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK)
815
 
816
  # 5. Optional Thermal Refinery (Pair Heat-Bath, non-regressing):
817
+ # On CUDA the anneal keeps every tensor on-device — the CPU/numpy
818
+ # chain previously forced a full D2H→CPU→D2H round-trip per chunk
819
+ # and made the GPU idle for the whole refinery.
820
  if cfg.therm_passes > 0:
821
+ scale_rp = best_d[:, None] * sc.to(torch.float32) # (B, 16) per-superblock
822
+ thermal_c, _ = thermal_refine_q3_k_cuda(
823
+ Xf, None if Wf is None else Wf, scale_rp,
824
+ code.reshape(B, QK3_K), cfg,
 
 
 
825
  )
826
+ # thermal_refine_q3_k_cuda already returns source-signed codes [-4, 3]
827
+ code = thermal_c.reshape(B, Q3_K_N_SUBBLOCKS, Q3_K_SUBBLOCK).to(torch.int16)
 
828
 
829
  return (
830
  best_d16.cpu().numpy().astype(np.uint16),