CompressedGemma commited on
Commit
de7e562
·
verified ·
1 Parent(s): d6cc83c

CUDA acceleration

Browse files
Files changed (8) hide show
  1. __init__.py +4 -0
  2. __main__.py +3 -0
  3. config.py +3 -0
  4. cuda_backend.py +1214 -0
  5. gui.py +15 -0
  6. pipeline.py +42 -28
  7. sieve.py +1 -3
  8. viterbi.py +68 -67
__init__.py CHANGED
@@ -13,6 +13,7 @@ from .analog import (
13
  vesica_gram_analog,
14
  )
15
  from .config import HPCConfig
 
16
  from .factored import (
17
  FactorResult,
18
  factor_1d,
@@ -26,6 +27,9 @@ from .q2k import Q2KBlock, QK_K, dequantize_params, pack_codes, unpack_codes
26
 
27
  __all__ = [
28
  "HPCConfig",
 
 
 
29
  "FactorResult",
30
  "Q2KBlock",
31
  "Q2KQuantizer",
 
13
  vesica_gram_analog,
14
  )
15
  from .config import HPCConfig
16
+ from .cuda_backend import cuda_device_name, cuda_is_available, quantize_cuda
17
  from .factored import (
18
  FactorResult,
19
  factor_1d,
 
27
 
28
  __all__ = [
29
  "HPCConfig",
30
+ "cuda_device_name",
31
+ "cuda_is_available",
32
+ "quantize_cuda",
33
  "FactorResult",
34
  "Q2KBlock",
35
  "Q2KQuantizer",
__main__.py CHANGED
@@ -40,6 +40,8 @@ def _config_from_args(args) -> HPCConfig:
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:
@@ -226,6 +228,7 @@ def main() -> None:
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")
 
40
  cfg.n_cand_m = args.cand_m
41
  if args.workers is not None:
42
  cfg.workers = args.workers
43
+ if args.device is not None:
44
+ cfg.device = args.device
45
  if args.mse:
46
  cfg.optimizer = "mse"
47
  if args.no_spectral:
 
228
  parser.add_argument("--cand-d", type=int, default=None, help="candidate d count")
229
  parser.add_argument("--cand-m", type=int, default=None, help="candidate min count")
230
  parser.add_argument("--workers", type=int, default=None, help="worker processes (0=auto CPU cores, 1=single)")
231
+ parser.add_argument("--device", default=None, help="compute device: auto (default), cuda, or cpu")
232
  parser.add_argument("--mse", action="store_true", help="disable HPC sequence search")
233
  parser.add_argument("--no-spectral", action="store_true", help="disable spectral/DC objective")
234
  parser.add_argument("--verbose", action="store_true")
config.py CHANGED
@@ -76,6 +76,7 @@ class HPCConfig:
76
  clamp_sigma_hard: float = 4.0
77
 
78
  # ---- reporting & parallelism ----------------------------------------
 
79
  workers: int = 0
80
  verbose: bool = False
81
 
@@ -112,4 +113,6 @@ class HPCConfig:
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
 
76
  clamp_sigma_hard: float = 4.0
77
 
78
  # ---- reporting & parallelism ----------------------------------------
79
+ device: str = "auto"
80
  workers: int = 0
81
  verbose: bool = False
82
 
 
113
  raise ValueError("Viterbi weights must be non-negative")
114
  if self.workers < 0:
115
  raise ValueError("workers must be >= 0 (0 = auto CPU count)")
116
+ if self.device not in ("auto", "cuda", "cpu") and not self.device.startswith("cuda:"):
117
+ raise ValueError("device must be 'auto', 'cuda', 'cuda:N', or 'cpu'")
118
  return self
cuda_backend.py ADDED
@@ -0,0 +1,1214 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CUDA GPU-accelerated backend for HPC-Quantize V2 (PyTorch CUDA).
2
+
3
+ Executes the entire HPC-Quantize V2 pipeline in batched FP32 GPU tensor kernels:
4
+
5
+ * Batched GGML seed solvers (``make_qkx2``, ``make_qp``) and 5-step WLS;
6
+ * Batched Geometry-Preserving Weight Analogs (Sub-block Iso-Moment,
7
+ Vesica Gram-Preserving, and Cross-Sub-block Scale-Lattice);
8
+ * Batched Coupled Orthogonal-Basis Bilinear Q2_K Factorization with rank-1
9
+ 1-D integer refinement and 36-seed candidate lattice evaluation;
10
+ * Sequential 6-state Sieve & 36-state Viterbi DP + vectorized row DC carry;
11
+ * Batched Algebraic Phase-4 Coupled Assembly, Vesica+DC augmented 2x2 Cramer
12
+ (d, dmin) solve, O(1) quadratic fp16-ULP polish, closed-form 1-D quadratic
13
+ Vesica/DC code projection, and row-parallel GPU DC residual nudge.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import time
19
+ import numpy as np
20
+
21
+ try:
22
+ import torch
23
+ except ImportError: # pragma: no cover
24
+ torch = None # type: ignore[assignment]
25
+
26
+ from .config import HPCConfig
27
+ from .factored import MULTS_D, MULTS_M, N_CAND_D, N_CAND_M, TOTAL_CANDIDATES
28
+ from .pipeline import QuantizedTensor, TensorStats, _phase3_select
29
+ from .q2k import NS, QK_K, Q2KBlock
30
+ from .seeds import seed_clamp_value
31
+ from .spectral import fold_weights
32
+
33
+
34
+ def cuda_is_available() -> bool:
35
+ return bool(torch is not None and torch.cuda.is_available())
36
+
37
+
38
+ def cuda_device_name(device: str = "cuda") -> str:
39
+ if not cuda_is_available():
40
+ return "CPU"
41
+ idx = torch.device(device).index or 0
42
+ return str(torch.cuda.get_device_name(idx))
43
+
44
+
45
+ def _nearest_int_t(val: "torch.Tensor") -> "torch.Tensor":
46
+ """Bit-exact GGML round-to-nearest-even on GPU."""
47
+ shifted = val.to(torch.float32) + 12582912.0
48
+ bits = shifted.view(torch.int32)
49
+ return ((bits & 0x007FFFFF) - 0x00400000).to(torch.int64)
50
+
51
+
52
+ def _round_fp16_t(val: "torch.Tensor") -> "torch.Tensor":
53
+ return val.to(torch.float16).to(torch.float32)
54
+
55
+
56
+ def _to_fp16_bits_t(val: "torch.Tensor") -> "torch.Tensor":
57
+ return val.to(torch.float16).view(torch.int16).to(torch.int32) & 0xFFFF
58
+
59
+
60
+ def _from_fp16_bits_t(bits: "torch.Tensor") -> "torch.Tensor":
61
+ return bits.to(torch.int16).view(torch.float16).to(torch.float32)
62
+
63
+
64
+ def _codes_from_params_t(
65
+ X: "torch.Tensor",
66
+ d: "torch.Tensor",
67
+ m: "torch.Tensor",
68
+ ls: "torch.Tensor",
69
+ lm: "torch.Tensor",
70
+ ) -> "torch.Tensor":
71
+ d_sub = (d[:, None].to(torch.float32) * ls.to(torch.float32)).repeat_interleave(16, dim=1)
72
+ m_sub = (m[:, None].to(torch.float32) * lm.to(torch.float32)).repeat_interleave(16, dim=1)
73
+ safe = d_sub >= 1.0e-15
74
+ safe_d = torch.where(safe, d_sub, torch.ones_like(d_sub))
75
+ q = _nearest_int_t((X.to(torch.float32) + m_sub) / safe_d).clamp(0, 3)
76
+ return torch.where(safe, q, torch.zeros_like(q)).to(torch.uint8)
77
+
78
+
79
+ def _dequantize_t(
80
+ d: "torch.Tensor",
81
+ m: "torch.Tensor",
82
+ ls: "torch.Tensor",
83
+ lm: "torch.Tensor",
84
+ codes: "torch.Tensor",
85
+ ) -> "torch.Tensor":
86
+ d_sub = (d[:, None].to(torch.float32) * ls.to(torch.float32)).repeat_interleave(16, dim=1)
87
+ m_sub = (m[:, None].to(torch.float32) * lm.to(torch.float32)).repeat_interleave(16, dim=1)
88
+ return d_sub * codes.to(torch.float32) - m_sub
89
+
90
+
91
+ def _objective_t(
92
+ X: "torch.Tensor",
93
+ W: "torch.Tensor",
94
+ d: "torch.Tensor",
95
+ m: "torch.Tensor",
96
+ ls: "torch.Tensor",
97
+ lm: "torch.Tensor",
98
+ codes: "torch.Tensor",
99
+ carry: "torch.Tensor | None",
100
+ cfg: HPCConfig,
101
+ ) -> tuple["torch.Tensor", "torch.Tensor"]:
102
+ xh = _dequantize_t(d, m, ls, lm, codes)
103
+ e = X - xh
104
+ sse = torch.sum(W * e * e, dim=1, dtype=torch.float32)
105
+ if not cfg.use_spectral or (cfg.dc_lambda == 0.0 and cfg.vw_lambda == 0.0):
106
+ return sse, sse
107
+ fw = fold_weights(QK_K, cfg)
108
+ pen = torch.zeros_like(sse)
109
+ src = e
110
+ m_len = QK_K // 2
111
+ lvl = 0
112
+ while m_len >= 1:
113
+ dst = src[:, :m_len] + src[:, m_len : 2 * m_len]
114
+ if m_len == 1 and carry is not None:
115
+ dst = dst + carry[:, None].to(torch.float32)
116
+ if fw[lvl] != 0.0:
117
+ pen = pen + float(fw[lvl]) * torch.sum(dst * dst, dim=1)
118
+ src = dst
119
+ m_len //= 2
120
+ lvl += 1
121
+ return sse + pen, sse
122
+
123
+
124
+ def _make_qkx2_cuda(x: "torch.Tensor", w: "torch.Tensor", nmax: int = 3):
125
+ x = x.to(torch.float32)
126
+ w = w.to(torch.float32)
127
+ xmin = torch.clamp(torch.min(x, dim=1).values, max=0.0)
128
+ xmax = torch.max(x, dim=1).values
129
+ span = xmax - xmin
130
+ degenerate = (xmax == xmin) | (span <= 1.0e-30)
131
+ sum_w = torch.sum(w, dim=1)
132
+ sum_x = torch.sum(w * x, dim=1)
133
+ safe_span = torch.where(span > 1.0e-30, span, torch.ones_like(span))
134
+ iscale = nmax / safe_span
135
+ scale = 1.0 / iscale
136
+ codes = _nearest_int_t(iscale[:, None] * (x - xmin[:, None])).clamp(0, nmax)
137
+ best_mad = torch.sum(w * torch.abs(scale[:, None] * codes.to(torch.float32) + xmin[:, None] - x), dim=1)
138
+ best_scale = scale.clone()
139
+ best_min = xmin.clone()
140
+ best_codes = codes.clone()
141
+
142
+ steps = torch.arange(16, dtype=torch.float32, device=x.device)[None, :] # (1, 16)
143
+ try_iscale = (-0.5 + 0.1 * steps + nmax) / safe_span[:, None] # (N, 16)
144
+ aux = _nearest_int_t(try_iscale[:, :, None] * (x - xmin[:, None])[:, None, :]).clamp(0, nmax)
145
+ aux_f = aux.to(torch.float32)
146
+ w_e = w[:, None, :]
147
+ x_e = x[:, None, :]
148
+ sl = torch.sum(w_e * aux_f, dim=2)
149
+ sl2 = torch.sum(w_e * aux_f * aux_f, dim=2)
150
+ sxl = torch.sum(w_e * aux_f * x_e, dim=2)
151
+ det = sum_w[:, None] * sl2 - sl * sl
152
+ valid = det > 1.0e-30
153
+ s_det = torch.where(valid, det, torch.ones_like(det))
154
+ this_scale = (sum_w[:, None] * sxl - sum_x[:, None] * sl) / s_det
155
+ this_min = (sl2 * sum_x[:, None] - sl * sxl) / s_det
156
+ pos_min = this_min > 0.0
157
+ this_min = torch.where(pos_min, torch.zeros_like(this_min), this_min)
158
+ s_sl2 = torch.where(sl2 > 1.0e-30, sl2, torch.ones_like(sl2))
159
+ this_scale = torch.where(pos_min & (sl2 > 1.0e-30), sxl / s_sl2, this_scale)
160
+ mad = torch.sum(w_e * torch.abs(this_scale[:, :, None] * aux_f + this_min[:, :, None] - x_e), dim=2)
161
+ mad = torch.where(valid, mad, torch.full_like(mad, float("inf")))
162
+ min_mad, best_k = torch.min(mad, dim=1)
163
+ take = min_mad < best_mad
164
+ rows = torch.arange(x.shape[0], device=x.device)
165
+ best_scale = torch.where(take, this_scale[rows, best_k], best_scale)
166
+ best_min = torch.where(take, this_min[rows, best_k], best_min)
167
+ best_codes = torch.where(take[:, None], aux[rows, best_k], best_codes)
168
+
169
+ best_scale = torch.where(degenerate, torch.zeros_like(best_scale), best_scale)
170
+ best_codes = torch.where(degenerate[:, None], torch.zeros_like(best_codes), best_codes)
171
+ best_min = torch.where(degenerate, xmin, best_min)
172
+ return best_scale, -best_min, best_codes.to(torch.uint8)
173
+
174
+
175
+ def _make_qp_cuda(values: "torch.Tensor", weights: "torch.Tensor", nmax: int = 15):
176
+ x = values.to(torch.float32)
177
+ w = weights.to(torch.float32)
178
+ xmax = torch.max(x, dim=1).values
179
+ dead = xmax < 1.0e-15
180
+ safe_max = torch.where(dead, torch.ones_like(xmax), xmax)
181
+
182
+ steps = torch.arange(-4, 5, dtype=torch.float32, device=x.device)[None, :] # (1, 9)
183
+ t_is = (0.1 * steps + nmax) / safe_max[:, None] # (N, 9)
184
+ t_sc = 1.0 / t_is
185
+ t_cd = _nearest_int_t(t_is[:, :, None] * x[:, None, :]).clamp(0, nmax).to(torch.float32)
186
+ mse = torch.sum(w[:, None, :] * (x[:, None, :] - t_sc[:, :, None] * t_cd) ** 2, dim=2)
187
+ best_k = torch.argmin(mse, dim=1)
188
+ rows = torch.arange(x.shape[0], device=x.device)
189
+ best_iscale = t_is[rows, best_k]
190
+
191
+ codes = _nearest_int_t(best_iscale[:, None] * x).clamp(0, nmax)
192
+ cf = codes.to(torch.float32)
193
+ sumlx = torch.sum(w * x * cf, dim=1)
194
+ suml2 = torch.sum(w * cf * cf, dim=1)
195
+
196
+ for _ in range(3):
197
+ changed = False
198
+ for i in range(x.shape[1]):
199
+ wi = w[:, i]
200
+ xi = x[:, i]
201
+ li = codes[:, i].to(torch.float32)
202
+ slx = sumlx - wi * xi * li
203
+ sl2 = suml2 - wi * li * li
204
+ valid = (slx > 0.0) & (sl2 > 0.0)
205
+ s_slx = torch.where(valid, slx, torch.ones_like(slx))
206
+ new_l = _nearest_int_t(xi * sl2 / s_slx).clamp(0, nmax)
207
+ nl_f = new_l.to(torch.float32)
208
+ trial = valid & (new_l != codes[:, i])
209
+ slx2 = slx + wi * xi * nl_f
210
+ sl2_2 = sl2 + wi * nl_f * nl_f
211
+ better = trial & (slx2 * slx2 * suml2 > sumlx * sumlx * sl2_2)
212
+ if torch.any(better):
213
+ codes[:, i] = torch.where(better, new_l, codes[:, i])
214
+ sumlx = torch.where(better, slx2, sumlx)
215
+ suml2 = torch.where(better, sl2_2, suml2)
216
+ changed = True
217
+ if not changed:
218
+ break
219
+
220
+ s_l2 = torch.where(suml2 > 1.0e-30, suml2, torch.ones_like(suml2))
221
+ d = torch.where((suml2 > 1.0e-30) & (~dead), sumlx / s_l2, torch.zeros_like(sumlx))
222
+ codes = torch.where(dead[:, None], torch.zeros_like(codes), codes)
223
+ return d, codes.to(torch.uint8)
224
+
225
+
226
+ def _build_seeds_cuda(X: "torch.Tensor", W: "torch.Tensor | None", clamp_val: float):
227
+ B = X.shape[0]
228
+ blocks = X.reshape(B, 16, 16)
229
+ clipped = torch.clamp(blocks, min=-clamp_val, max=clamp_val)
230
+ if W is None:
231
+ sigma_sq = torch.mean(blocks * blocks, dim=(1, 2), keepdim=True)
232
+ weights = torch.sqrt(sigma_sq + clipped * clipped)
233
+ else:
234
+ weights = W.reshape(B, 16, 16)
235
+ scales, mins, codes = _make_qkx2_cuda(clipped.reshape(-1, 16), weights.reshape(-1, 16), 3)
236
+ scales = scales.reshape(B, 16)
237
+ mins = mins.reshape(B, 16)
238
+ codes = codes.reshape(B, 256)
239
+ sw = torch.sum(weights, dim=2)
240
+ d, ls = _make_qp_cuda(scales, sw, 15)
241
+ dmin, lm = _make_qp_cuda(mins, sw, 15)
242
+ return {
243
+ "scales": scales,
244
+ "mins": mins,
245
+ "codes": codes,
246
+ "sw": sw,
247
+ "d": d,
248
+ "dmin": dmin,
249
+ "ls": ls,
250
+ "lm": lm,
251
+ }
252
+
253
+
254
+ def _phase2_wls_cuda(X: "torch.Tensor", W: "torch.Tensor", seeds: dict, clamp_val: float):
255
+ clipped = torch.clamp(X, min=-clamp_val, max=clamp_val)
256
+ base_d = seeds["d"].clone()
257
+ base_m = seeds["dmin"].clone()
258
+ ls = seeds["ls"].clone()
259
+ lm = seeds["lm"].clone()
260
+ scales = seeds["scales"]
261
+ mins = seeds["mins"]
262
+ seed_d = seeds["d"]
263
+ seed_m = seeds["dmin"]
264
+
265
+ for _ in range(5):
266
+ codes = _codes_from_params_t(clipped, base_d, base_m, ls, lm).to(torch.float32)
267
+ A = ls.to(torch.float32).repeat_interleave(16, dim=1) * codes
268
+ Bm = lm.to(torch.float32).repeat_interleave(16, dim=1)
269
+ Saa = torch.sum(W * A * A, dim=1)
270
+ Sab = torch.sum(W * A * Bm, dim=1)
271
+ Sbb = torch.sum(W * Bm * Bm, dim=1)
272
+ Sxa = torch.sum(W * clipped * A, dim=1)
273
+ Sxb = torch.sum(W * clipped * Bm, dim=1)
274
+ det = Saa * Sbb - Sab * Sab
275
+ valid = det > 1.0e-30
276
+ s_det = torch.where(valid, det, torch.ones_like(det))
277
+ d_new = (Sbb * Sxa - Sab * Sxb) / s_det
278
+ m_new = (Sab * Sxa - Saa * Sxb) / s_det
279
+ upd_d = valid & (d_new > 0.0) & (d_new < 4.0 * (seed_d + 1.0e-10))
280
+ upd_m = valid & (m_new > 0.0) & (m_new < 4.0 * (seed_m + 1.0e-10))
281
+ base_d = torch.where(upd_d, _round_fp16_t(d_new), base_d)
282
+ base_m = torch.where(upd_m, _round_fp16_t(m_new), base_m)
283
+ sd = torch.where(base_d > 1.0e-15, base_d, torch.ones_like(base_d))
284
+ sm = torch.where(base_m > 1.0e-15, base_m, torch.ones_like(base_m))
285
+ ls = torch.where(
286
+ base_d[:, None] > 1.0e-15,
287
+ _nearest_int_t(scales / sd[:, None]).clamp(0, 15),
288
+ torch.zeros_like(ls, dtype=torch.int64),
289
+ ).to(torch.uint8)
290
+ lm = torch.where(
291
+ base_m[:, None] > 1.0e-15,
292
+ _nearest_int_t(mins / sm[:, None]).clamp(0, 15),
293
+ torch.zeros_like(lm, dtype=torch.int64),
294
+ ).to(torch.uint8)
295
+
296
+ return base_d, base_m
297
+
298
+
299
+ def _factor_1d_cuda(
300
+ V: "torch.Tensor",
301
+ W: "torch.Tensor",
302
+ nmax: int = 15,
303
+ iters: int = 5,
304
+ refine: bool = True,
305
+ ) -> tuple["torch.Tensor", "torch.Tensor"]:
306
+ """Batched FP32 1-D factorization across B blocks on GPU with rank-1 sweep."""
307
+ V = torch.clamp(V.to(torch.float32), min=0.0)
308
+ W = torch.clamp(W.to(torch.float32), min=0.0)
309
+ B, n = V.shape
310
+ vmax = torch.max(V, dim=1).values
311
+ active = vmax >= 1.0e-30
312
+ d = torch.where(active, vmax / nmax, torch.zeros_like(vmax))
313
+ sd = torch.where(d > 1.0e-30, d, torch.ones_like(d))
314
+ codes = torch.where(
315
+ active[:, None] & (d[:, None] > 1.0e-30),
316
+ _nearest_int_t(V / sd[:, None]).clamp(0, nmax),
317
+ torch.zeros((B, n), dtype=torch.int64, device=V.device),
318
+ )
319
+ for _ in range(iters):
320
+ cf = codes.to(torch.float32)
321
+ den = torch.sum(W * cf * cf, dim=1)
322
+ valid = active & (den > 1.0e-30)
323
+ s_den = torch.where(valid, den, torch.ones_like(den))
324
+ dn = torch.sum(W * V * cf, dim=1) / s_den
325
+ d = torch.where(valid & (dn > 0.0), dn, d)
326
+ sd = torch.where(d > 1.0e-30, d, torch.ones_like(d))
327
+ codes = torch.where(
328
+ active[:, None] & (d[:, None] > 1.0e-30),
329
+ _nearest_int_t(V / sd[:, None]).clamp(0, nmax),
330
+ torch.zeros_like(codes),
331
+ )
332
+
333
+ if refine:
334
+ opts = torch.arange(nmax + 1, dtype=torch.float32, device=V.device)[None, :]
335
+ opts_sq = opts * opts
336
+ wv = W * V
337
+ cf = codes.to(torch.float32)
338
+ sum_lv = torch.sum(wv * cf, dim=1)
339
+ sum_l2 = torch.sum(W * cf * cf, dim=1)
340
+ s_l2 = torch.where(sum_l2 > 1.0e-30, sum_l2, torch.ones_like(sum_l2))
341
+ best_gain = torch.where(sum_l2 > 1.0e-30, (sum_lv * sum_lv) / s_l2, torch.zeros_like(sum_lv))
342
+ rows = torch.arange(B, device=V.device)
343
+
344
+ for i in range(n):
345
+ wi = W[:, i : i + 1]
346
+ wvi = wv[:, i : i + 1]
347
+ li = codes[:, i : i + 1].to(torch.float32)
348
+ slv = (sum_lv[:, None] - wvi * li) + wvi * opts
349
+ sl2 = (sum_l2[:, None] - wi * (li * li)) + wi * opts_sq
350
+ valid = (wi > 0.0) & (sl2 > 1.0e-30) & (slv > 0.0)
351
+ gains = torch.where(valid, (slv * slv) / torch.where(valid, sl2, torch.ones_like(sl2)), -torch.ones_like(slv))
352
+ max_g, k_idx = torch.max(gains, dim=1)
353
+ improve = active & (k_idx != codes[:, i]) & (max_g > best_gain + 1.0e-12)
354
+ if torch.any(improve):
355
+ best_gain = torch.where(improve, max_g, best_gain)
356
+ sum_lv = torch.where(improve, slv[rows, k_idx], sum_lv)
357
+ sum_l2 = torch.where(improve, sl2[rows, k_idx], sum_l2)
358
+ codes[:, i] = torch.where(improve, k_idx, codes[:, i])
359
+
360
+ s_l2 = torch.where(sum_l2 > 1.0e-30, sum_l2, torch.ones_like(sum_l2))
361
+ d = torch.where(active & (sum_l2 > 1.0e-30), sum_lv / s_l2, d)
362
+
363
+ return d, codes.to(torch.uint8)
364
+
365
+
366
+ def _analogs_cuda(X: "torch.Tensor", W: "torch.Tensor") -> list["torch.Tensor"]:
367
+ B = X.shape[0]
368
+ xb = X.reshape(B, NS, 16)
369
+ wb = W.reshape(B, NS, 16)
370
+ wsum = torch.clamp(torch.sum(wb, dim=2, keepdim=True), min=1.0e-30)
371
+ mu = torch.sum(wb * xb, dim=2, keepdim=True) / wsum
372
+ centered = xb - mu
373
+ var = torch.sum(wb * centered * centered, dim=2, keepdim=True) / wsum
374
+ sigma = torch.sqrt(torch.clamp(var, min=1.0e-30))
375
+ active = sigma >= 1.0e-15
376
+ z = centered / sigma
377
+ q_ideal = torch.linspace(-1.35, 1.35, 16, dtype=torch.float32, device=X.device)
378
+ order = torch.argsort(z, dim=2, stable=True)
379
+ z_quant = torch.empty_like(z)
380
+ z_quant.scatter_(2, order, q_ideal[None, None, :].expand_as(z))
381
+
382
+ analogs = []
383
+ for tau, beta, q_blend in ((1.60, 0.25, 0.30), (2.15, 0.15, 0.15)):
384
+ w_rel = torch.clamp(wb / (wsum / 16.0), min=1.0e-12) ** beta
385
+ z_comp = tau * torch.tanh((z * w_rel) / tau)
386
+ z_a = (1.0 - q_blend) * z_comp + q_blend * z_quant
387
+ mu_a = torch.sum(wb * z_a, dim=2, keepdim=True) / wsum
388
+ z_a = z_a - mu_a
389
+ var_a = torch.sum(wb * z_a * z_a, dim=2, keepdim=True) / wsum
390
+ scale = torch.where(var_a > 1.0e-30, torch.sqrt(var / torch.clamp(var_a, min=1.0e-30)), torch.zeros_like(var_a))
391
+ sub_a = torch.where(active, mu + z_a * scale, xb)
392
+ orig_min = torch.min(xb, dim=2, keepdim=True).values
393
+ new_min = torch.min(sub_a, dim=2, keepdim=True).values
394
+ shift = torch.where((orig_min <= 0.0) & (new_min > 0.0), new_min, torch.zeros_like(new_min))
395
+ analogs.append((sub_a - shift).reshape(B, QK_K))
396
+
397
+ return analogs
398
+
399
+
400
+ def _sub_ls_cuda(X: "torch.Tensor", W: "torch.Tensor", codes: "torch.Tensor"):
401
+ B = X.shape[0]
402
+ q = codes.to(torch.float32).reshape(B, NS, 16)
403
+ xb = X.reshape(B, NS, 16)
404
+ wb = W.reshape(B, NS, 16)
405
+ Sqq = torch.sum(wb * q * q, dim=2)
406
+ Swq = torch.sum(wb * q, dim=2)
407
+ Sw = torch.sum(wb, dim=2)
408
+ Sqx = torch.sum(wb * q * xb, dim=2)
409
+ Sx = torch.sum(wb * xb, dim=2)
410
+ det = Sqq * Sw - Swq * Swq
411
+ valid = det > 1.0e-30
412
+ s_det = torch.where(valid, det, torch.ones_like(det))
413
+ s_sw = torch.where(Sw > 1.0e-30, Sw, torch.ones_like(Sw))
414
+ sj = torch.clamp(torch.where(valid, (Sqx * Sw - Swq * Sx) / s_det, torch.zeros_like(det)), min=0.0)
415
+ tj = torch.where(valid, (sj * Swq - Sx) / s_sw, torch.zeros_like(det))
416
+ bad_tj = tj < 0.0
417
+ tj = torch.where(bad_tj, torch.zeros_like(tj), tj)
418
+ s_sqq = torch.where(Sqq > 1.0e-30, Sqq, torch.ones_like(Sqq))
419
+ fallback = torch.clamp(torch.where(Sqq > 1.0e-30, Sqx / s_sqq, torch.zeros_like(Sqq)), min=0.0)
420
+ sj = torch.where(bad_tj, fallback, sj)
421
+ return sj, tj, Sqq, Swq, Sw, Sx
422
+
423
+
424
+ def _factor_candidates_cuda(
425
+ X: "torch.Tensor",
426
+ W: "torch.Tensor",
427
+ scales: "torch.Tensor",
428
+ mins: "torch.Tensor",
429
+ base_d: "torch.Tensor",
430
+ base_m: "torch.Tensor",
431
+ cfg: HPCConfig,
432
+ ) -> np.ndarray:
433
+ """Vectorized FP32 36-seed candidate lattice across B blocks on GPU."""
434
+ B = X.shape[0]
435
+ device = X.device
436
+ nd, nm = int(cfg.n_cand_d), int(cfg.n_cand_m)
437
+ di_idx = [a if nd == N_CAND_D else (a * N_CAND_D) // nd for a in range(nd)]
438
+ mi_idx = [b if nm == N_CAND_M else (b * N_CAND_M) // nm for b in range(nm)]
439
+ md = torch.tensor(MULTS_D[di_idx], dtype=torch.float32, device=device)
440
+ mm = torch.tensor(MULTS_M[mi_idx], dtype=torch.float32, device=device)
441
+
442
+ ds_mat = _round_fp16_t(base_d[:, None] * md[None, :])
443
+ ms_mat = _round_fp16_t(base_m[:, None] * mm[None, :])
444
+ ds = ds_mat.repeat_interleave(nm, dim=1)
445
+ ms = ms_mat.repeat(1, nd)
446
+ K = ds.shape[1]
447
+
448
+ sc = scales[:, None, :]
449
+ mn = mins[:, None, :]
450
+ s_ds = torch.where(ds[:, :, None] > 1.0e-30, ds[:, :, None], torch.ones_like(ds[:, :, None]))
451
+ s_ms = torch.where(ms[:, :, None] > 1.0e-30, ms[:, :, None], torch.ones_like(ms[:, :, None]))
452
+ ls0 = torch.where(
453
+ ds[:, :, None] > 1.0e-30,
454
+ _nearest_int_t(sc / s_ds).clamp(0, 15),
455
+ torch.zeros((B, K, NS), dtype=torch.int64, device=device),
456
+ ).to(torch.float32)
457
+ lm0 = torch.where(
458
+ ms[:, :, None] > 1.0e-30,
459
+ _nearest_int_t(mn / s_ms).clamp(0, 15),
460
+ torch.zeros((B, K, NS), dtype=torch.int64, device=device),
461
+ ).to(torch.float32)
462
+
463
+ X_e = X[:, None, :]
464
+ W_e = W[:, None, :]
465
+ fw = fold_weights(QK_K, cfg)
466
+
467
+ def _eval_k(ls_k: "torch.Tensor", lm_k: "torch.Tensor"):
468
+ d_sub = (ds[:, :, None] * ls_k).repeat_interleave(16, dim=2)
469
+ m_sub = (ms[:, :, None] * lm_k).repeat_interleave(16, dim=2)
470
+ safe = d_sub >= 1.0e-15
471
+ sd = torch.where(safe, d_sub, torch.ones_like(d_sub))
472
+ q = torch.where(
473
+ safe,
474
+ _nearest_int_t((X_e + m_sub) / sd).clamp(0, 3),
475
+ torch.zeros((B, K, QK_K), dtype=torch.int64, device=device),
476
+ ).to(torch.float32)
477
+ e = X_e - (d_sub * q - m_sub)
478
+ sse = torch.sum(W_e * e * e, dim=2)
479
+ if cfg.use_spectral and (cfg.dc_lambda != 0.0 or cfg.vw_lambda != 0.0):
480
+ pen = torch.zeros_like(sse)
481
+ src = e
482
+ m_len = QK_K // 2
483
+ lvl = 0
484
+ while m_len >= 1:
485
+ dst = src[:, :, :m_len] + src[:, :, m_len : 2 * m_len]
486
+ if fw[lvl] != 0.0:
487
+ pen = pen + float(fw[lvl]) * torch.sum(dst * dst, dim=2)
488
+ src = dst
489
+ m_len //= 2
490
+ lvl += 1
491
+ return sse + pen, q
492
+ return sse, q
493
+
494
+ obj0, q0 = _eval_k(ls0, lm0)
495
+ q_sub = q0.reshape(B, K, NS, 16)
496
+ xb = X.reshape(B, 1, NS, 16)
497
+ wb = W.reshape(B, 1, NS, 16)
498
+ Sqq = torch.sum(wb * q_sub * q_sub, dim=3)
499
+ Swq = torch.sum(wb * q_sub, dim=3)
500
+ Sw = torch.sum(wb, dim=3)
501
+ Sqx = torch.sum(wb * q_sub * xb, dim=3)
502
+ Sx = torch.sum(wb * xb, dim=3)
503
+ det = Sqq * Sw - Swq * Swq
504
+ valid = det > 1.0e-30
505
+ s_det = torch.where(valid, det, torch.ones_like(det))
506
+ s_sw = torch.where(Sw > 1.0e-30, Sw, torch.ones_like(Sw))
507
+ sj = torch.clamp(torch.where(valid, (Sqx * Sw - Swq * Sx) / s_det, torch.zeros_like(det)), min=0.0)
508
+ tj = torch.where(valid, (sj * Swq - Sx) / s_sw, torch.zeros_like(det))
509
+ bad_tj = tj < 0.0
510
+ tj = torch.where(bad_tj, torch.zeros_like(tj), tj)
511
+ s_sqq = torch.where(Sqq > 1.0e-30, Sqq, torch.ones_like(Sqq))
512
+ sj = torch.where(bad_tj, torch.clamp(torch.where(Sqq > 1.0e-30, Sqx / s_sqq, torch.zeros_like(Sqq)), min=0.0), sj)
513
+
514
+ ls1 = torch.where(
515
+ ds[:, :, None] > 1.0e-30,
516
+ _nearest_int_t(sj / s_ds).clamp(0, 15),
517
+ torch.zeros((B, K, NS), dtype=torch.int64, device=device),
518
+ ).to(torch.float32)
519
+ t_tgt = torch.clamp((ds[:, :, None] * ls1 * Swq - Sx) / s_sw, min=0.0) if cfg.factor_coupled_q2k else tj
520
+ lm1 = torch.where(
521
+ ms[:, :, None] > 1.0e-30,
522
+ _nearest_int_t(t_tgt / s_ms).clamp(0, 15),
523
+ torch.zeros((B, K, NS), dtype=torch.int64, device=device),
524
+ ).to(torch.float32)
525
+ obj1, _ = _eval_k(ls1, lm1)
526
+ best_k = torch.minimum(obj0, obj1).cpu().numpy()
527
+
528
+ out = np.full((B, TOTAL_CANDIDATES), np.inf, dtype=np.float32)
529
+ if nd == N_CAND_D and nm == N_CAND_M:
530
+ out[:, :] = best_k
531
+ else:
532
+ for a, di in enumerate(di_idx):
533
+ cb = int(di) >> 2
534
+ for b, mi in enumerate(mi_idx):
535
+ fb = int(mi) >> 2
536
+ col = best_k[:, a * nm + b]
537
+ for aa in range(4):
538
+ for bb in range(4):
539
+ idx = (cb * 4 + aa) * N_CAND_M + fb * 4 + bb
540
+ out[:, idx] = np.minimum(out[:, idx], col)
541
+ return out
542
+
543
+
544
+ def _standalone_coupled_solve_cuda(
545
+ X: "torch.Tensor", W: "torch.Tensor", cfg: HPCConfig
546
+ ) -> tuple["torch.Tensor", "torch.Tensor", "torch.Tensor", "torch.Tensor", "torch.Tensor", "torch.Tensor"]:
547
+ B = X.shape[0]
548
+ Sw = torch.sum(W.reshape(B, NS, 16), dim=2)
549
+ s_Sw = torch.clamp(Sw, min=1.0e-30)
550
+
551
+ sources = [X]
552
+ if cfg.use_weight_analogs:
553
+ sources.extend(_analogs_cuda(X, W))
554
+
555
+ best_obj = torch.full((B,), float("inf"), dtype=torch.float32, device=X.device)
556
+ best_d = torch.zeros((B,), dtype=torch.float32, device=X.device)
557
+ best_m = torch.zeros((B,), dtype=torch.float32, device=X.device)
558
+ best_ls = torch.zeros((B, NS), dtype=torch.uint8, device=X.device)
559
+ best_lm = torch.zeros((B, NS), dtype=torch.uint8, device=X.device)
560
+ best_codes = torch.zeros((B, QK_K), dtype=torch.uint8, device=X.device)
561
+
562
+ for src in sources:
563
+ sb = src.reshape(B, NS, 16)
564
+ xmin = torch.clamp(torch.min(sb, dim=2).values, max=0.0)
565
+ xmax = torch.max(sb, dim=2).values
566
+ span = xmax - xmin
567
+ s0 = torch.where(span > 1.0e-30, span / 3.0, torch.zeros_like(span))
568
+ t0 = -xmin
569
+ s_s0 = torch.where(s0 > 1.0e-30, s0, torch.ones_like(s0))
570
+ q0 = torch.where(
571
+ s0[:, :, None] > 1.0e-30,
572
+ _nearest_int_t((sb + t0[:, :, None]) / s_s0[:, :, None]).clamp(0, 3),
573
+ torch.zeros((B, NS, 16), dtype=torch.int64, device=X.device),
574
+ ).reshape(B, QK_K).to(torch.uint8)
575
+
576
+ codes = q0
577
+ for _ in range(3):
578
+ s, t, Sqq, Swq, _, Sx = _sub_ls_cuda(X, W, codes)
579
+ q_bar = Swq / s_Sw
580
+ Vqq = torch.clamp(Sqq - Swq * q_bar, min=1.0e-12 * Sw)
581
+ d, ls = _factor_1d_cuda(s, Vqq, 15, 4, cfg.factor_refine_1d)
582
+ t_c = torch.clamp((d[:, None] * ls.to(torch.float32) * Swq - Sx) / s_Sw, min=0.0)
583
+ m, lm = _factor_1d_cuda(t_c, Sw, 15, 4, cfg.factor_refine_1d)
584
+ d = _round_fp16_t(d)
585
+ m = _round_fp16_t(m)
586
+ codes = _codes_from_params_t(X, d, m, ls, lm)
587
+ obj, _ = _objective_t(X, W, d, m, ls, lm, codes, None, cfg)
588
+ take = obj < best_obj - 1.0e-12
589
+ if torch.any(take):
590
+ best_obj = torch.where(take, obj, best_obj)
591
+ best_d = torch.where(take, d, best_d)
592
+ best_m = torch.where(take, m, best_m)
593
+ best_ls = torch.where(take[:, None], ls, best_ls)
594
+ best_lm = torch.where(take[:, None], lm, best_lm)
595
+ best_codes = torch.where(take[:, None], codes, best_codes)
596
+
597
+ return best_d, best_m, best_ls, best_lm, best_codes, best_obj
598
+
599
+
600
+ def _extended_dm_solve_and_ulp_cuda(
601
+ X: "torch.Tensor",
602
+ W: "torch.Tensor",
603
+ ls: "torch.Tensor",
604
+ lm: "torch.Tensor",
605
+ codes: "torch.Tensor",
606
+ d_cur: "torch.Tensor",
607
+ m_cur: "torch.Tensor",
608
+ carry: "torch.Tensor",
609
+ cfg: HPCConfig,
610
+ ) -> tuple["torch.Tensor", "torch.Tensor"]:
611
+ B = X.shape[0]
612
+ A = ls.to(torch.float32).repeat_interleave(16, dim=1) * codes.to(torch.float32)
613
+ Bm = lm.to(torch.float32).repeat_interleave(16, dim=1)
614
+ Saa = torch.sum(W * A * A, dim=1)
615
+ Sab = torch.sum(W * A * Bm, dim=1)
616
+ Sbb = torch.sum(W * Bm * Bm, dim=1)
617
+ Sxa = torch.sum(W * X * A, dim=1)
618
+ Sxb = torch.sum(W * X * Bm, dim=1)
619
+
620
+ if cfg.use_spectral:
621
+ if cfg.vw_lambda > 0.0:
622
+ vw_w = float(cfg.vw_lambda) / QK_K
623
+ half = QK_K // 2
624
+ Ap = A[:, :half] + A[:, half:]
625
+ Bp = Bm[:, :half] + Bm[:, half:]
626
+ Xp = X[:, :half] + X[:, half:]
627
+ Saa = Saa + vw_w * torch.sum(Ap * Ap, dim=1)
628
+ Sab = Sab + vw_w * torch.sum(Ap * Bp, dim=1)
629
+ Sbb = Sbb + vw_w * torch.sum(Bp * Bp, dim=1)
630
+ Sxa = Sxa + vw_w * torch.sum(Xp * Ap, dim=1)
631
+ Sxb = Sxb + vw_w * torch.sum(Xp * Bp, dim=1)
632
+ if cfg.dc_lambda > 0.0:
633
+ dc_w = float(cfg.dc_lambda) / QK_K
634
+ rA = torch.sum(A, dim=1)
635
+ rB = torch.sum(Bm, dim=1)
636
+ rS = torch.sum(X, dim=1) + carry
637
+ Saa = Saa + dc_w * rA * rA
638
+ Sab = Sab + dc_w * rA * rB
639
+ Sbb = Sbb + dc_w * rB * rB
640
+ Sxa = Sxa + dc_w * rS * rA
641
+ Sxb = Sxb + dc_w * rS * rB
642
+
643
+ det = Saa * Sbb - Sab * Sab
644
+ valid = det > 1.0e-30
645
+ s_det = torch.where(valid, det, torch.ones_like(det))
646
+ d_new = (Sbb * Sxa - Sab * Sxb) / s_det
647
+ m_new = (Sab * Sxa - Saa * Sxb) / s_det
648
+ d_opt = torch.where(valid & (d_new > 0.0) & torch.isfinite(d_new), d_new, d_cur)
649
+ m_opt = torch.where(valid & (m_new >= 0.0) & torch.isfinite(m_new), m_new, m_cur)
650
+
651
+ rad = max(int(cfg.ulp_radius), 1)
652
+ deltas = torch.arange(-rad, rad + 1, dtype=torch.int32, device=X.device)
653
+ d16_c = _to_fp16_bits_t(d_opt)
654
+ m16_c = _to_fp16_bits_t(m_opt)
655
+ d_bits = torch.clamp(d16_c[:, None] + deltas[None, :], 0, 0x7BFF)
656
+ m_bits = torch.clamp(m16_c[:, None] + deltas[None, :], 0, 0x7BFF)
657
+ dd = _from_fp16_bits_t(d_bits)[:, :, None]
658
+ mm = _from_fp16_bits_t(m_bits)[:, None, :]
659
+
660
+ q_form = (
661
+ Saa[:, None, None] * (dd * dd)
662
+ - 2.0 * Sab[:, None, None] * (dd * mm)
663
+ + Sbb[:, None, None] * (mm * mm)
664
+ - 2.0 * Sxa[:, None, None] * dd
665
+ + 2.0 * Sxb[:, None, None] * mm
666
+ ).reshape(B, -1)
667
+ best_idx = torch.argmin(q_form, dim=1)
668
+ K = deltas.numel()
669
+ rows = torch.arange(B, device=X.device)
670
+ return dd[rows, best_idx // K, 0], mm[rows, 0, best_idx % K]
671
+
672
+
673
+ def _refine_trajectory_cuda(
674
+ X: "torch.Tensor",
675
+ W: "torch.Tensor",
676
+ Xb: "torch.Tensor",
677
+ Wb: "torch.Tensor",
678
+ Sw: "torch.Tensor",
679
+ Sx: "torch.Tensor",
680
+ safe_Sw: "torch.Tensor",
681
+ d0: "torch.Tensor",
682
+ m0: "torch.Tensor",
683
+ ls0: "torch.Tensor",
684
+ lm0: "torch.Tensor",
685
+ codes0: "torch.Tensor",
686
+ carry: "torch.Tensor",
687
+ cfg: HPCConfig,
688
+ ):
689
+ B = X.shape[0]
690
+ device = X.device
691
+ best_d, best_m = d0.clone(), m0.clone()
692
+ best_ls, best_lm = ls0.clone(), lm0.clone()
693
+ best_codes = codes0.clone()
694
+ best_obj, _ = _objective_t(X, W, best_d, best_m, best_ls, best_lm, best_codes, carry, cfg)
695
+ d, m, ls, lm, codes = best_d.clone(), best_m.clone(), best_ls.clone(), best_lm.clone(), best_codes.clone()
696
+
697
+ ls_deltas = torch.tensor([-3, -2, -1, 0, 1, 2, 3], dtype=torch.int64, device=device)
698
+ lm_deltas = torch.tensor([-1, 0, 1], dtype=torch.int64, device=device).repeat(7)
699
+ b_idx = torch.arange(B, device=device)[:, None]
700
+ s_idx = torch.arange(NS, device=device)[None, :]
701
+
702
+ for _ in range(cfg.refine_passes):
703
+ d_new, m_new = _extended_dm_solve_and_ulp_cuda(X, W, ls, lm, codes, d, m, carry, cfg)
704
+ obj_dm, _ = _objective_t(X, W, d_new, m_new, ls, lm, codes, carry, cfg)
705
+ imp_dm = obj_dm < best_obj - 1.0e-12
706
+ if torch.any(imp_dm):
707
+ best_obj = torch.where(imp_dm, obj_dm, best_obj)
708
+ best_d = torch.where(imp_dm, d_new, best_d)
709
+ best_m = torch.where(imp_dm, m_new, best_m)
710
+ best_ls = torch.where(imp_dm[:, None], ls, best_ls)
711
+ best_lm = torch.where(imp_dm[:, None], lm, best_lm)
712
+ best_codes = torch.where(imp_dm[:, None], codes, best_codes)
713
+ d, m = best_d.clone(), best_m.clone()
714
+
715
+ codes_re = _codes_from_params_t(X, d, m, ls, lm)
716
+ obj_re, _ = _objective_t(X, W, d, m, ls, lm, codes_re, carry, cfg)
717
+ imp_re = obj_re < best_obj - 1.0e-12
718
+ if torch.any(imp_re):
719
+ best_obj = torch.where(imp_re, obj_re, best_obj)
720
+ best_codes = torch.where(imp_re[:, None], codes_re, best_codes)
721
+ codes = best_codes.clone()
722
+
723
+ q = codes.to(torch.float32).reshape(B, NS, 16)
724
+ Sqq = torch.sum(Wb * q * q, dim=2)
725
+ Swq = torch.sum(Wb * q, dim=2)
726
+ Sqx = torch.sum(Wb * q * Xb, dim=2)
727
+ q_bar = Swq / safe_Sw
728
+ Vqq = torch.clamp(Sqq - Swq * q_bar, min=0.0)
729
+ Cqx = Sqx - Sx * q_bar
730
+ s_star = torch.where(
731
+ Vqq > 1.0e-30,
732
+ torch.clamp(Cqx / torch.clamp(Vqq, min=1.0e-30), min=0.0),
733
+ d[:, None] * ls.to(torch.float32),
734
+ )
735
+ sd = torch.where(d[:, None] > 1.0e-15, d[:, None], torch.ones((B, 1), dtype=torch.float32, device=device))
736
+ sm = torch.where(m[:, None] > 1.0e-15, m[:, None], torch.ones((B, 1), dtype=torch.float32, device=device))
737
+ ls_center = torch.where(
738
+ d[:, None] > 1.0e-15,
739
+ _nearest_int_t(s_star / sd).clamp(0, 15),
740
+ torch.zeros((B, NS), dtype=torch.int64, device=device),
741
+ )
742
+ c_ls_base = torch.clamp(ls_center[:, :, None] + ls_deltas[None, None, :], 0, 15).to(torch.float32)
743
+ d_sub_base = d[:, None, None] * c_ls_base
744
+ t_opt_base = torch.clamp((d_sub_base * Swq[:, :, None] - Sx[:, :, None]) / safe_Sw[:, :, None], min=0.0)
745
+ lm_center = torch.where(
746
+ m[:, None, None] > 1.0e-15,
747
+ _nearest_int_t(t_opt_base / sm[:, :, None]).clamp(0, 15),
748
+ torch.zeros_like(c_ls_base, dtype=torch.int64),
749
+ )
750
+ cand_ls = c_ls_base.repeat_interleave(3, dim=2)
751
+ cand_lm = torch.clamp(lm_center.repeat_interleave(3, dim=2) + lm_deltas[None, None, :], 0, 15).to(torch.float32)
752
+
753
+ d_sub_c = d[:, None, None] * cand_ls
754
+ m_sub_c = m[:, None, None] * cand_lm
755
+ safe_dc = d_sub_c >= 1.0e-15
756
+ sd_c = torch.where(safe_dc, d_sub_c, torch.ones_like(d_sub_c))
757
+ q_c = torch.where(
758
+ safe_dc[:, :, :, None],
759
+ _nearest_int_t((Xb[:, :, None, :] + m_sub_c[:, :, :, None]) / sd_c[:, :, :, None]).clamp(0, 3),
760
+ torch.zeros((B, NS, 21, 16), dtype=torch.int64, device=device),
761
+ ).to(torch.float32)
762
+ e_c = Xb[:, :, None, :] - (d_sub_c[:, :, :, None] * q_c - m_sub_c[:, :, :, None])
763
+ err_c = torch.sum(Wb[:, :, None, :] * e_c * e_c, dim=3)
764
+ pick = torch.argmin(err_c, dim=2)
765
+ ls_try = cand_ls[b_idx, s_idx, pick].to(torch.uint8)
766
+ lm_try = cand_lm[b_idx, s_idx, pick].to(torch.uint8)
767
+ codes_try = _codes_from_params_t(X, d, m, ls_try, lm_try)
768
+ d_try, m_try = _extended_dm_solve_and_ulp_cuda(X, W, ls_try, lm_try, codes_try, d, m, carry, cfg)
769
+ obj_try, _ = _objective_t(X, W, d_try, m_try, ls_try, lm_try, codes_try, carry, cfg)
770
+ imp_sub = obj_try < best_obj - 1.0e-12
771
+ if torch.any(imp_sub):
772
+ best_obj = torch.where(imp_sub, obj_try, best_obj)
773
+ best_d = torch.where(imp_sub, d_try, best_d)
774
+ best_m = torch.where(imp_sub, m_try, best_m)
775
+ best_ls = torch.where(imp_sub[:, None], ls_try, best_ls)
776
+ best_lm = torch.where(imp_sub[:, None], lm_try, best_lm)
777
+ best_codes = torch.where(imp_sub[:, None], codes_try, best_codes)
778
+ d, m, ls, lm, codes = best_d.clone(), best_m.clone(), best_ls.clone(), best_lm.clone(), best_codes.clone()
779
+
780
+ return best_d, best_m, best_ls, best_lm, best_codes, best_obj
781
+
782
+
783
+ def _compute_dc_carry_fast(
784
+ X_np: np.ndarray,
785
+ scales_seed: np.ndarray,
786
+ mins_seed: np.ndarray,
787
+ best: np.ndarray,
788
+ base_d: np.ndarray,
789
+ base_m: np.ndarray,
790
+ cfg: HPCConfig,
791
+ row_width: int | None,
792
+ ) -> np.ndarray:
793
+ """Vectorized residual DC computation + fast row recurrence."""
794
+ n = X_np.shape[0]
795
+ di = best // N_CAND_M
796
+ mi = best % N_CAND_M
797
+ d = (base_d * MULTS_D[di]).astype(np.float16).astype(np.float32)
798
+ m = (base_m * MULTS_M[mi]).astype(np.float16).astype(np.float32)
799
+ sd = np.where(d[:, None] > 1.0e-15, d[:, None], 1.0)
800
+ sm = np.where(m[:, None] > 1.0e-15, m[:, None], 1.0)
801
+ ls = np.where(d[:, None] > 1.0e-15, np.clip(np.rint(scales_seed / sd), 0, 15), 0).astype(np.float32)
802
+ lm = np.where(m[:, None] > 1.0e-15, np.clip(np.rint(mins_seed / sm), 0, 15), 0).astype(np.float32)
803
+ d_sub = np.repeat(d[:, None] * ls, 16, axis=1)
804
+ m_sub = np.repeat(m[:, None] * lm, 16, axis=1)
805
+ safe = d_sub >= 1.0e-15
806
+ q = np.where(safe, np.clip(np.rint((X_np + m_sub) / np.where(safe, d_sub, 1.0)), 0, 3), 0.0)
807
+ dc_res = np.sum(X_np - (d_sub * q - m_sub), axis=1, dtype=np.float64)
808
+
809
+ bpr = row_width // QK_K if row_width and row_width % QK_K == 0 else 0
810
+ carry = np.zeros(n, dtype=np.float32)
811
+ decay = float(cfg.dc_decay)
812
+ cum = bool(cfg.carry_cumulative)
813
+ if bpr > 0 and n % bpr == 0:
814
+ n_rows = n // bpr
815
+ res_2d = dc_res.reshape(n_rows, bpr)
816
+ car_2d = np.zeros((n_rows, bpr), dtype=np.float64)
817
+ rolling = np.zeros(n_rows, dtype=np.float64)
818
+ for col in range(bpr):
819
+ car_2d[:, col] = decay * rolling
820
+ rolling = (car_2d[:, col] + res_2d[:, col]) if cum else res_2d[:, col]
821
+ carry = car_2d.reshape(n).astype(np.float32)
822
+ else:
823
+ rolling_s = 0.0
824
+ for i in range(n):
825
+ if bpr > 0 and i % bpr == 0:
826
+ rolling_s = 0.0
827
+ carry[i] = np.float32(decay * rolling_s)
828
+ rolling_s = (carry[i] + dc_res[i]) if cum else dc_res[i]
829
+ return carry
830
+
831
+
832
+ def _apply_dc_nudge_cuda(
833
+ X_np: np.ndarray,
834
+ W_np: np.ndarray | None,
835
+ final_d: np.ndarray,
836
+ final_m: np.ndarray,
837
+ final_ls: np.ndarray,
838
+ final_lm: np.ndarray,
839
+ final_codes: np.ndarray,
840
+ cfg: HPCConfig,
841
+ row_width: int | None,
842
+ dev: "torch.device",
843
+ ) -> np.ndarray:
844
+ """Row-parallel GPU DC residual nudge across all rows simultaneously."""
845
+ n = X_np.shape[0]
846
+ bpr = row_width // QK_K if row_width and row_width % QK_K == 0 else 0
847
+ if bpr <= 0 or n % bpr != 0:
848
+ return final_codes
849
+
850
+ n_rows = n // bpr
851
+ codes_out = final_codes.copy().reshape(n_rows, bpr, QK_K)
852
+ X_r = X_np.reshape(n_rows, bpr, QK_K)
853
+ W_r = None if W_np is None else W_np.reshape(n_rows, bpr, QK_K)
854
+ d_r = final_d.reshape(n_rows, bpr)
855
+ m_r = final_m.reshape(n_rows, bpr)
856
+ ls_r = final_ls.reshape(n_rows, bpr, NS)
857
+ lm_r = final_lm.reshape(n_rows, bpr, NS)
858
+
859
+ decay = float(cfg.dc_decay)
860
+ cum = bool(cfg.carry_cumulative)
861
+ row_batch = 1024
862
+ q_opts = torch.arange(4, dtype=torch.float32, device=dev)[None, :, None] # (1, 4, 1)
863
+
864
+ for r0 in range(0, n_rows, row_batch):
865
+ r1 = min(r0 + row_batch, n_rows)
866
+ R = r1 - r0
867
+ rolling = torch.zeros(R, dtype=torch.float32, device=dev)
868
+ X_batch = torch.from_numpy(X_r[r0:r1]).to(dev)
869
+ W_batch = (
870
+ torch.ones_like(X_batch)
871
+ if W_r is None
872
+ else torch.from_numpy(W_r[r0:r1]).to(dev)
873
+ )
874
+ d_batch = torch.from_numpy(d_r[r0:r1]).to(dev)
875
+ m_batch = torch.from_numpy(m_r[r0:r1]).to(dev)
876
+ ls_batch = torch.from_numpy(ls_r[r0:r1]).to(dev, dtype=torch.float32)
877
+ lm_batch = torch.from_numpy(lm_r[r0:r1]).to(dev, dtype=torch.float32)
878
+ cd_batch = torch.from_numpy(codes_out[r0:r1]).to(dev, dtype=torch.int64)
879
+ rows = torch.arange(R, device=dev)
880
+
881
+ for col in range(bpr):
882
+ carry = decay * rolling
883
+ x_c = X_batch[:, col]
884
+ w_c = W_batch[:, col]
885
+ d_c = d_batch[:, col]
886
+ m_c = m_batch[:, col]
887
+ ls_c = ls_batch[:, col]
888
+ lm_c = lm_batch[:, col]
889
+ cd_c = cd_batch[:, col]
890
+
891
+ d_sub = (d_c[:, None] * ls_c).repeat_interleave(16, dim=1)
892
+ m_sub = (m_c[:, None] * lm_c).repeat_interleave(16, dim=1)
893
+ active = d_sub >= 1.0e-15
894
+ e = x_c - (d_sub * cd_c.to(torch.float32) - m_sub)
895
+ sse = torch.sum(w_c * e * e, dim=1)
896
+ cap = sse * (1.0 + float(cfg.sse_budget))
897
+ res = torch.sum(e, dim=1) + carry
898
+
899
+ for _ in range(2):
900
+ e_new = x_c[:, None, :] - (d_sub[:, None, :] * q_opts - m_sub[:, None, :])
901
+ dsse = w_c[:, None, :] * (e_new * e_new - e[:, None, :] * e[:, None, :])
902
+ mask = (
903
+ active[:, None, :]
904
+ & (q_opts.to(torch.int64) != cd_c[:, None, :])
905
+ & ((sse[:, None, None] + dsse) <= cap[:, None, None])
906
+ )
907
+ new_res = res[:, None, None] + (e_new - e[:, None, :])
908
+ impr = torch.where(
909
+ mask,
910
+ torch.abs(res)[:, None, None] - torch.abs(new_res),
911
+ torch.full_like(new_res, -float("inf")),
912
+ ).reshape(R, -1)
913
+ best_imp, flat_idx = torch.max(impr, dim=1)
914
+ do_upd = best_imp > 1.0e-12
915
+ if not torch.any(do_upd):
916
+ break
917
+ q_pick = flat_idx // QK_K
918
+ k_pick = flat_idx % QK_K
919
+ old_e = e[rows, k_pick]
920
+ chosen_e = e_new[rows, q_pick, k_pick]
921
+ de = chosen_e - old_e
922
+ res = torch.where(do_upd, res + de, res)
923
+ sse = torch.where(
924
+ do_upd,
925
+ sse + w_c[rows, k_pick] * (chosen_e * chosen_e - old_e * old_e),
926
+ sse,
927
+ )
928
+ e[rows, k_pick] = torch.where(do_upd, chosen_e, old_e)
929
+ cd_c[rows, k_pick] = torch.where(do_upd, q_pick, cd_c[rows, k_pick])
930
+
931
+ cd_batch[:, col] = cd_c
932
+ dc_res = torch.sum(e, dim=1)
933
+ rolling = (carry + dc_res) if cum else dc_res
934
+
935
+ codes_out[r0:r1] = cd_batch.cpu().numpy().astype(np.uint8)
936
+
937
+ return codes_out.reshape(n, QK_K)
938
+
939
+
940
+ def quantize_cuda(
941
+ weights: np.ndarray,
942
+ importance: np.ndarray | None,
943
+ row_width: int | None,
944
+ cfg: HPCConfig,
945
+ status_callback=None,
946
+ stop_event=None,
947
+ device: str = "cuda",
948
+ ) -> QuantizedTensor:
949
+ """Run the full HPC-Quantize V2 pipeline on a CUDA GPU."""
950
+ if not cuda_is_available():
951
+ raise RuntimeError("PyTorch CUDA is not available on this system")
952
+
953
+ def _emit(phase: str, done: int, total: int, detail: str, **extra) -> None:
954
+ if status_callback is not None:
955
+ status_callback({"phase": phase, "done": done, "total": total, "detail": detail, **extra})
956
+
957
+ def _check_stop() -> None:
958
+ if stop_event is not None and stop_event.is_set():
959
+ raise InterruptedError("Quantization cancelled by user")
960
+
961
+ x = np.asarray(weights, dtype=np.float32)
962
+ if x.size == 0:
963
+ raise ValueError("weight tensor is empty")
964
+ if x.size % QK_K != 0:
965
+ raise ValueError("weight count must be a multiple of 256")
966
+ if x.ndim == 2 and row_width is None and x.shape[1] % QK_K == 0:
967
+ row_width = int(x.shape[1])
968
+ x = x.ravel()
969
+ n_blocks = x.size // QK_K
970
+ X_np = x.reshape(n_blocks, QK_K)
971
+
972
+ if importance is not None:
973
+ imp = np.asarray(importance, dtype=np.float32).ravel()
974
+ if imp.size != x.size:
975
+ raise ValueError("importance must match the weight count")
976
+ if np.any(imp < 0.0) or not np.all(np.isfinite(imp)):
977
+ raise ValueError("importance must be finite and non-negative")
978
+ W_np = imp.reshape(n_blocks, QK_K)
979
+ else:
980
+ W_np = None
981
+
982
+ sigma = float(np.sqrt(np.mean(x.astype(np.float64) ** 2)))
983
+ clamp_val = seed_clamp_value(x, cfg)
984
+ dev = torch.device(device)
985
+ gpu_name = cuda_device_name(device)
986
+ batch_blocks = 4096
987
+
988
+ candidate_errors = np.empty((n_blocks, TOTAL_CANDIDATES), dtype=np.float32)
989
+ scales_seed = np.empty((n_blocks, NS), dtype=np.float32)
990
+ mins_seed = np.empty((n_blocks, NS), dtype=np.float32)
991
+ base_d = np.empty((n_blocks,), dtype=np.float32)
992
+ base_m = np.empty((n_blocks,), dtype=np.float32)
993
+ solo_d = np.empty((n_blocks,), dtype=np.float32)
994
+ solo_m = np.empty((n_blocks,), dtype=np.float32)
995
+ solo_ls = np.empty((n_blocks, NS), dtype=np.uint8)
996
+ solo_lm = np.empty((n_blocks, NS), dtype=np.uint8)
997
+ solo_codes = np.empty((n_blocks, QK_K), dtype=np.uint8)
998
+ solo_obj = np.empty((n_blocks,), dtype=np.float64)
999
+
1000
+ t0 = time.time()
1001
+ with torch.no_grad():
1002
+ for start in range(0, n_blocks, batch_blocks):
1003
+ _check_stop()
1004
+ stop = min(start + batch_blocks, n_blocks)
1005
+ X_t = torch.from_numpy(X_np[start:stop]).to(dev)
1006
+ W_t = (
1007
+ torch.ones_like(X_t)
1008
+ if W_np is None
1009
+ else torch.from_numpy(W_np[start:stop]).to(dev)
1010
+ )
1011
+ seeds_t = _build_seeds_cuda(X_t, None if W_np is None else W_t, clamp_val)
1012
+ bd_t, bm_t = _phase2_wls_cuda(X_t, W_t, seeds_t, clamp_val)
1013
+
1014
+ scales_seed[start:stop] = seeds_t["scales"].cpu().numpy()
1015
+ mins_seed[start:stop] = seeds_t["mins"].cpu().numpy()
1016
+ base_d[start:stop] = bd_t.cpu().numpy()
1017
+ base_m[start:stop] = bm_t.cpu().numpy()
1018
+
1019
+ candidate_errors[start:stop] = _factor_candidates_cuda(
1020
+ X_t, W_t, seeds_t["scales"], seeds_t["mins"], bd_t, bm_t, cfg
1021
+ )
1022
+ sd_t, sm_t, sls_t, slm_t, sc_t, sobj_t = _standalone_coupled_solve_cuda(X_t, W_t, cfg)
1023
+ solo_d[start:stop] = sd_t.cpu().numpy()
1024
+ solo_m[start:stop] = sm_t.cpu().numpy()
1025
+ solo_ls[start:stop] = sls_t.cpu().numpy()
1026
+ solo_lm[start:stop] = slm_t.cpu().numpy()
1027
+ solo_codes[start:stop] = sc_t.cpu().numpy()
1028
+ solo_obj[start:stop] = sobj_t.cpu().numpy()
1029
+
1030
+ elapsed = max(time.time() - t0, 1.0e-6)
1031
+ rate = stop / elapsed
1032
+ eta = (n_blocks - stop) / max(rate, 1.0e-9)
1033
+ _emit(
1034
+ f"CUDA Phase 1–2b ({gpu_name}): Seeds + Analogs + Factored Field",
1035
+ stop,
1036
+ n_blocks,
1037
+ f"blk {stop}/{n_blocks} ({rate:.1f} blk/s, ETA {eta:.1f}s) "
1038
+ f"mean_obj={float(np.mean(solo_obj[:stop])):.5e}",
1039
+ rate=rate,
1040
+ eta=eta,
1041
+ sigma=sigma,
1042
+ )
1043
+
1044
+ _check_stop()
1045
+ _emit(
1046
+ "Phase 3: 6-State Sieve & 36-State Viterbi DP",
1047
+ 0,
1048
+ n_blocks,
1049
+ f"Running Sieve + 36-state Viterbi sequence optimization ({cfg.optimizer.upper()})",
1050
+ sigma=sigma,
1051
+ )
1052
+ best, temperature, _ = _phase3_select(candidate_errors, cfg)
1053
+ carry = _compute_dc_carry_fast(
1054
+ X_np, scales_seed, mins_seed, best, base_d, base_m, cfg, row_width
1055
+ )
1056
+
1057
+ final_d = np.empty((n_blocks,), dtype=np.float32)
1058
+ final_m = np.empty((n_blocks,), dtype=np.float32)
1059
+ final_ls = np.empty((n_blocks, NS), dtype=np.uint8)
1060
+ final_lm = np.empty((n_blocks, NS), dtype=np.uint8)
1061
+ final_codes = np.empty((n_blocks, QK_K), dtype=np.uint8)
1062
+ factor_floor_used = 0
1063
+ t_p4 = time.time()
1064
+
1065
+ for start in range(0, n_blocks, batch_blocks):
1066
+ _check_stop()
1067
+ stop = min(start + batch_blocks, n_blocks)
1068
+ B_c = stop - start
1069
+ X_t = torch.from_numpy(X_np[start:stop]).to(dev)
1070
+ W_t = (
1071
+ torch.ones_like(X_t)
1072
+ if W_np is None
1073
+ else torch.from_numpy(W_np[start:stop]).to(dev)
1074
+ )
1075
+ Xb = X_t.reshape(B_c, NS, 16)
1076
+ Wb = W_t.reshape(B_c, NS, 16)
1077
+ Sw = torch.sum(Wb, dim=2)
1078
+ Sx = torch.sum(Wb * Xb, dim=2)
1079
+ safe_Sw = torch.clamp(Sw, min=1.0e-30)
1080
+ carry_t = torch.from_numpy(carry[start:stop]).to(dev)
1081
+
1082
+ b_slice = best[start:stop]
1083
+ di = b_slice // N_CAND_M
1084
+ mi = b_slice % N_CAND_M
1085
+ d_v = torch.from_numpy(base_d[start:stop] * MULTS_D[di]).to(dev)
1086
+ m_v = torch.from_numpy(base_m[start:stop] * MULTS_M[mi]).to(dev)
1087
+ d_v = _round_fp16_t(d_v)
1088
+ m_v = _round_fp16_t(m_v)
1089
+ sc_s = torch.from_numpy(scales_seed[start:stop]).to(dev)
1090
+ mn_s = torch.from_numpy(mins_seed[start:stop]).to(dev)
1091
+ sd_v = torch.where(d_v[:, None] > 1.0e-15, d_v[:, None], torch.ones_like(d_v[:, None]))
1092
+ sm_v = torch.where(m_v[:, None] > 1.0e-15, m_v[:, None], torch.ones_like(m_v[:, None]))
1093
+ ls_v = torch.where(
1094
+ d_v[:, None] > 1.0e-15,
1095
+ _nearest_int_t(sc_s / sd_v).clamp(0, 15),
1096
+ torch.zeros((B_c, NS), dtype=torch.int64, device=dev),
1097
+ ).to(torch.uint8)
1098
+ lm_v = torch.where(
1099
+ m_v[:, None] > 1.0e-15,
1100
+ _nearest_int_t(mn_s / sm_v).clamp(0, 15),
1101
+ torch.zeros((B_c, NS), dtype=torch.int64, device=dev),
1102
+ ).to(torch.uint8)
1103
+ codes_v = _codes_from_params_t(X_t, d_v, m_v, ls_v, lm_v)
1104
+
1105
+ d1, m1, ls1, lm1, c1, obj1 = _refine_trajectory_cuda(
1106
+ X_t, W_t, Xb, Wb, Sw, Sx, safe_Sw, d_v, m_v, ls_v, lm_v, codes_v, carry_t, cfg
1107
+ )
1108
+
1109
+ if cfg.use_factor_floor:
1110
+ d_f = torch.from_numpy(solo_d[start:stop]).to(dev)
1111
+ m_f = torch.from_numpy(solo_m[start:stop]).to(dev)
1112
+ ls_f = torch.from_numpy(solo_ls[start:stop]).to(dev)
1113
+ lm_f = torch.from_numpy(solo_lm[start:stop]).to(dev)
1114
+ c_f = torch.from_numpy(solo_codes[start:stop]).to(dev)
1115
+ d2, m2, ls2, lm2, c2, obj2 = _refine_trajectory_cuda(
1116
+ X_t, W_t, Xb, Wb, Sw, Sx, safe_Sw, d_f, m_f, ls_f, lm_f, c_f, carry_t, cfg
1117
+ )
1118
+ take2 = obj2 < obj1 - 1.0e-12
1119
+ factor_floor_used += int(torch.sum(take2).item())
1120
+ d1 = torch.where(take2, d2, d1)
1121
+ m1 = torch.where(take2, m2, m1)
1122
+ ls1 = torch.where(take2[:, None], ls2, ls1)
1123
+ lm1 = torch.where(take2[:, None], lm2, lm1)
1124
+ c1 = torch.where(take2[:, None], c2, c1)
1125
+
1126
+ final_d[start:stop] = d1.cpu().numpy()
1127
+ final_m[start:stop] = m1.cpu().numpy()
1128
+ final_ls[start:stop] = ls1.cpu().numpy()
1129
+ final_lm[start:stop] = lm1.cpu().numpy()
1130
+ final_codes[start:stop] = c1.cpu().numpy()
1131
+
1132
+ elapsed = max(time.time() - t_p4, 1.0e-6)
1133
+ rate = stop / elapsed
1134
+ eta = (n_blocks - stop) / max(rate, 1.0e-9)
1135
+ _emit(
1136
+ f"CUDA Phase 4 ({gpu_name}): Algebraic Coupled Bilinear Assembly",
1137
+ stop,
1138
+ n_blocks,
1139
+ f"blk {stop}/{n_blocks} ({rate:.1f} blk/s, ETA {eta:.1f}s) floors(fac={factor_floor_used})",
1140
+ rate=rate,
1141
+ eta=eta,
1142
+ sigma=sigma,
1143
+ temperature=temperature,
1144
+ cand_floors=0,
1145
+ fac_floors=factor_floor_used,
1146
+ )
1147
+
1148
+ if cfg.apply_dc_carry and (cfg.dc_lambda > 0.0 or cfg.dc_decay > 0.0):
1149
+ _check_stop()
1150
+ final_codes = _apply_dc_nudge_cuda(
1151
+ X_np, W_np, final_d, final_m, final_ls, final_lm, final_codes, cfg, row_width, dev
1152
+ )
1153
+
1154
+ scales = (final_ls & 0x0F) | ((final_lm & 0x0F) << np.uint8(4))
1155
+ qs = np.empty((n_blocks, QK_K // 4), dtype=np.uint8)
1156
+ for base in (0, 128):
1157
+ qs[:, base // 4 : base // 4 + 32] = (
1158
+ final_codes[:, base : base + 32]
1159
+ | (final_codes[:, base + 32 : base + 64] << np.uint8(2))
1160
+ | (final_codes[:, base + 64 : base + 96] << np.uint8(4))
1161
+ | (final_codes[:, base + 96 : base + 128] << np.uint8(6))
1162
+ )
1163
+ dm = np.empty((n_blocks, 2), dtype=np.uint16)
1164
+ dm[:, 0] = final_d.astype(np.float16).view(np.uint16)
1165
+ dm[:, 1] = final_m.astype(np.float16).view(np.uint16)
1166
+
1167
+ d_f32 = dm[:, 0].view(np.float16).astype(np.float32)
1168
+ m_f32 = dm[:, 1].view(np.float16).astype(np.float32)
1169
+ d_sub = np.repeat(d_f32[:, None] * final_ls.astype(np.float32), 16, axis=1)
1170
+ m_sub = np.repeat(m_f32[:, None] * final_lm.astype(np.float32), 16, axis=1)
1171
+ xh_all = d_sub * final_codes.astype(np.float32) - m_sub
1172
+ err_mat = X_np.astype(np.float64) - xh_all.astype(np.float64)
1173
+ total_sse = float(np.sum(err_mat * err_mat))
1174
+ weighted = total_sse if W_np is None else float(np.sum(W_np.astype(np.float64) * err_mat * err_mat))
1175
+ rmse = float(np.sqrt(total_sse / x.size))
1176
+
1177
+ blocks = [
1178
+ Q2KBlock(scales=scales[i], qs=qs[i], d=np.uint16(dm[i, 0]), dmin=np.uint16(dm[i, 1]))
1179
+ for i in range(n_blocks)
1180
+ ]
1181
+
1182
+ stats = TensorStats(
1183
+ n_blocks=n_blocks,
1184
+ total_sse=total_sse,
1185
+ weighted_sse=weighted,
1186
+ rmse=rmse,
1187
+ sigma=sigma,
1188
+ rmse_over_sigma=rmse / sigma if sigma > 1.0e-15 else 0.0,
1189
+ temperature=temperature,
1190
+ factor_floor_used=factor_floor_used,
1191
+ candidate_floor_used=0,
1192
+ )
1193
+ _emit(
1194
+ f"Phase 5: Tensor Complete ({gpu_name})",
1195
+ n_blocks,
1196
+ n_blocks,
1197
+ f"Done: blocks={n_blocks} rmse={rmse:.6e} rmse/sigma={stats.rmse_over_sigma:.5f} "
1198
+ f"wSSE={weighted:.5e} temp={temperature:.3e} floors(fac={factor_floor_used})",
1199
+ rmse=rmse,
1200
+ sigma=sigma,
1201
+ temperature=temperature,
1202
+ cand_floors=0,
1203
+ fac_floors=factor_floor_used,
1204
+ )
1205
+ return QuantizedTensor(
1206
+ blocks=blocks,
1207
+ scales=scales,
1208
+ qs=qs,
1209
+ dm=dm,
1210
+ best_candidate=best,
1211
+ factor_objectives=solo_obj,
1212
+ config=cfg,
1213
+ stats=stats,
1214
+ )
gui.py CHANGED
@@ -34,6 +34,8 @@ from hpc_quantize_v2 import (
34
  GGUFReader,
35
  HPCConfig,
36
  Q2KQuantizer,
 
 
37
  expand_importance,
38
  factor_only_solve,
39
  lookup_imatrix,
@@ -98,6 +100,7 @@ class HPCQuantizeGUI:
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)
@@ -138,6 +141,7 @@ class HPCQuantizeGUI:
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())
@@ -274,6 +278,11 @@ class HPCQuantizeGUI:
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)
@@ -421,6 +430,10 @@ class HPCQuantizeGUI:
421
  sp.grid(row=row, column=col + 1, sticky=tk.EW, padx=4, pady=2)
422
 
423
  def _auto_populate_defaults(self) -> None:
 
 
 
 
424
  default_gguf = Path("Raon.gguf")
425
  default_imat = Path("imatrix.gguf")
426
  if default_imat.exists():
@@ -484,6 +497,7 @@ class HPCQuantizeGUI:
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
  )
@@ -507,6 +521,7 @@ class HPCQuantizeGUI:
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":
 
34
  GGUFReader,
35
  HPCConfig,
36
  Q2KQuantizer,
37
+ cuda_device_name,
38
+ cuda_is_available,
39
  expand_importance,
40
  factor_only_solve,
41
  lookup_imatrix,
 
100
  self.ulp_var = tk.IntVar(value=4)
101
  self.limit_var = tk.IntVar(value=0)
102
  self.workers_var = tk.IntVar(value=0)
103
+ self.device_var = tk.StringVar(value="auto")
104
 
105
  self.use_analogs_var = tk.BooleanVar(value=True)
106
  self.coupled_q2k_var = tk.BooleanVar(value=True)
 
141
  self.cand_m_var,
142
  self.limit_var,
143
  self.workers_var,
144
+ self.device_var,
145
  self.use_spectral_var,
146
  ):
147
  v.trace_add("write", lambda *_: self._update_cli_preview())
 
278
  self._spin_pair(grid_f, 2, 0, "FP16 ULP Radius:", self.ulp_var, 0, 16)
279
  self._spin_pair(grid_f, 2, 2, "Tensor Limit (0=all):", self.limit_var, 0, 9999)
280
  self._spin_pair(grid_f, 3, 0, "Workers (0=Auto CPU):", self.workers_var, 0, 256)
281
+ ttk.Label(grid_f, text="Compute Device:").grid(row=3, column=2, sticky=tk.W, padx=4, pady=2)
282
+ dev_cb = ttk.Combobox(
283
+ grid_f, textvariable=self.device_var, values=("auto", "cuda", "cpu"), state="readonly", width=8
284
+ )
285
+ dev_cb.grid(row=3, column=3, sticky=tk.EW, padx=4, pady=2)
286
 
287
  spec_f = ttk.LabelFrame(opt_frame, text="Spectral & DC Parameters", padding=6)
288
  spec_f.pack(fill=tk.X, pady=2)
 
430
  sp.grid(row=row, column=col + 1, sticky=tk.EW, padx=4, pady=2)
431
 
432
  def _auto_populate_defaults(self) -> None:
433
+ if cuda_is_available():
434
+ self._log(f"CUDA GPU detected: {cuda_device_name()} (device=auto will use GPU)")
435
+ else:
436
+ self._log("CUDA GPU not detected (using multi-core CPU backend)")
437
  default_gguf = Path("Raon.gguf")
438
  default_imat = Path("imatrix.gguf")
439
  if default_imat.exists():
 
497
  vw_lambda=float(self.vw_lambda_var.get()),
498
  dc_decay=float(self.dc_decay_var.get()),
499
  sse_budget=float(self.sse_budget_var.get()),
500
+ device=self.device_var.get(),
501
  workers=int(self.workers_var.get()),
502
  verbose=False,
503
  )
 
521
  cmd += ["--output", out]
522
  if self.limit_var.get() > 0:
523
  cmd += ["--limit", str(self.limit_var.get())]
524
+ cmd += ["--device", self.device_var.get()]
525
  cmd += ["--workers", str(self.workers_var.get())]
526
  cmd += ["--cand-d", str(self.cand_d_var.get()), "--cand-m", str(self.cand_m_var.get())]
527
  if self.optimizer_var.get() == "mse":
pipeline.py CHANGED
@@ -139,35 +139,28 @@ def _bin_geometry():
139
 
140
 
141
  def _sieve_marginals(candidate_errors, graph_blocks, stride, temperature, cfg):
142
- qi_d, qi_m = _bin_geometry()
143
- coarse = np.zeros((graph_blocks, 6), dtype=np.float64)
144
- fine = np.zeros((graph_blocks, 6), dtype=np.float64)
145
  n_blocks = candidate_errors.shape[0]
146
- for gi in range(graph_blocks):
147
- start = gi * stride
148
- stop = min((gi + 1) * stride, n_blocks)
149
- group = candidate_errors[start:stop].astype(np.float64)
150
- finite = np.isfinite(group)
151
- counts = finite.sum(axis=0)
152
- valid = counts > 0
153
- sums = np.where(finite, group, 0.0).sum(axis=0)
154
- agg = np.where(valid, sums / np.maximum(counts, 1), 0.0)
155
- mn = float(np.min(agg[valid])) if np.any(valid) else 0.0
156
- expo = np.where(
157
- valid,
158
- (agg - mn) / (2.0 * max(temperature, 1.0e-12)),
159
- np.inf,
160
- )
161
- coeff = np.exp(-expo)
162
- for v in range(6):
163
- coarse[gi, v] = float(np.sum(coeff[qi_d == v]))
164
- fine[gi, v] = float(np.sum(coeff[qi_m == v]))
165
- cn = float(np.linalg.norm(coarse[gi]))
166
- fn = float(np.linalg.norm(fine[gi]))
167
- if cn > 1.0e-30:
168
- coarse[gi] /= cn
169
- if fn > 1.0e-30:
170
- fine[gi] /= fn
171
 
172
  amps = np.zeros((2 * graph_blocks, 6), dtype=np.float64)
173
  amps[0::2] = coarse
@@ -889,6 +882,27 @@ class Q2KQuantizer:
889
  row_width = int(x.shape[1])
890
  x = x.ravel()
891
  n_blocks = x.size // QK_K
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
892
  X = x.reshape(n_blocks, QK_K)
893
 
894
  if importance is not None:
 
139
 
140
 
141
  def _sieve_marginals(candidate_errors, graph_blocks, stride, temperature, cfg):
 
 
 
142
  n_blocks = candidate_errors.shape[0]
143
+ pad = graph_blocks * stride - n_blocks
144
+ ce = candidate_errors.astype(np.float64)
145
+ if pad > 0:
146
+ ce = np.concatenate([ce, np.full((pad, TOTAL_CANDIDATES), np.inf, dtype=np.float64)], axis=0)
147
+ grouped = ce.reshape(graph_blocks, stride, TOTAL_CANDIDATES)
148
+ finite = np.isfinite(grouped)
149
+ counts = np.sum(finite, axis=1) # (graph_blocks, 576)
150
+ valid = counts > 0
151
+ sums = np.sum(np.where(finite, grouped, 0.0), axis=1)
152
+ agg = np.where(valid, sums / np.maximum(counts, 1), np.inf)
153
+ mn = np.min(agg, axis=1, keepdims=True)
154
+ mn = np.where(np.isfinite(mn), mn, 0.0)
155
+ expo = np.where(valid, (agg - mn) / (2.0 * max(temperature, 1.0e-12)), np.inf)
156
+ coeff = np.exp(-expo).reshape(graph_blocks, 6, 4, 6, 4)
157
+
158
+ coarse = np.sum(coeff, axis=(2, 3, 4)) # (graph_blocks, 6)
159
+ fine = np.sum(coeff, axis=(1, 2, 4)) # (graph_blocks, 6)
160
+ cn = np.linalg.norm(coarse, axis=1, keepdims=True)
161
+ fn = np.linalg.norm(fine, axis=1, keepdims=True)
162
+ coarse = np.where(cn > 1.0e-30, coarse / np.maximum(cn, 1.0e-30), 0.0)
163
+ fine = np.where(fn > 1.0e-30, fine / np.maximum(fn, 1.0e-30), 0.0)
 
 
 
 
164
 
165
  amps = np.zeros((2 * graph_blocks, 6), dtype=np.float64)
166
  amps[0::2] = coarse
 
882
  row_width = int(x.shape[1])
883
  x = x.ravel()
884
  n_blocks = x.size // QK_K
885
+
886
+ if cfg.device != "cpu":
887
+ from .cuda_backend import cuda_is_available, quantize_cuda
888
+
889
+ if cfg.device.startswith("cuda") or (
890
+ cfg.device == "auto" and cfg.workers == 0 and n_blocks >= 32 and cuda_is_available()
891
+ ):
892
+ dev_str = "cuda" if cfg.device == "auto" else cfg.device
893
+ res = quantize_cuda(
894
+ weights,
895
+ importance,
896
+ row_width,
897
+ cfg,
898
+ status_callback=status_callback,
899
+ stop_event=stop_event,
900
+ device=dev_str,
901
+ )
902
+ if cfg.verbose:
903
+ _report(res.stats)
904
+ return res
905
+
906
  X = x.reshape(n_blocks, QK_K)
907
 
908
  if importance is not None:
sieve.py CHANGED
@@ -54,9 +54,7 @@ def sieve_measure_chain(
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())
 
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 = 1.0 - (1.0 - same_penalty) * q
 
 
58
  log_score += np.log(mix + 1.0e-30)
59
 
60
  best = float(log_score.max())
viterbi.py CHANGED
@@ -5,8 +5,8 @@ This is a direct port of Phase 3 of the C engine. Each graph block owns a
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
@@ -18,6 +18,13 @@ 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)
@@ -65,45 +72,46 @@ def viterbi_select(
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
- bins_flat = np.tile(bins, group.shape[0])
80
- finite = np.isfinite(flat)
81
- group_min = np.full(N_STATES, np.inf)
82
- if np.any(finite):
83
- np.minimum.at(group_min, bins_flat[finite], flat[finite])
84
- counts = np.bincount(bins_flat[finite], minlength=N_STATES)
85
- sums = np.zeros(N_STATES, dtype=np.float64)
86
- if np.any(finite):
87
- shifted = flat[finite] - group_min[bins_flat[finite]]
88
- amps = np.exp(-shifted / (2.0 * temp))
89
- sums = np.bincount(bins_flat[finite], weights=amps, minlength=N_STATES)
90
- valid = (counts > 0) & np.isfinite(group_min)
91
- if np.any(valid):
92
- mean_exp = np.maximum(sums[valid] / counts[valid], 1.0e-300)
93
- bin_err[gi, valid] = group_min[valid] - 2.0 * temp * np.log(mean_exp)
94
-
95
- rep = candidate_errors[start].astype(np.float64)
96
- for s in range(N_STATES):
97
- idx = np.where((bins == s) & np.isfinite(rep))[0]
98
- if idx.size:
99
- bin_cand[gi, s] = int(idx[int(np.argmin(rep[idx]))])
100
-
101
- c_tot = float(coarse_marg[gi].sum())
102
- f_tot = float(fine_marg[gi].sum())
103
- pc = coarse_marg[gi] / c_tot if c_tot > 1.0e-30 else np.full(6, 1.0 / 6.0)
104
- pf = fine_marg[gi] / f_tot if f_tot > 1.0e-30 else np.full(6, 1.0 / 6.0)
105
- for s in range(N_STATES):
106
- log_prior[gi, s] = np.log(pc[s // 6] + 1.0e-30) + np.log(pf[s % 6] + 1.0e-30)
 
 
 
 
 
107
 
108
  finite_err = bin_err[np.isfinite(bin_err)]
109
  scale_err = float(finite_err.mean()) if finite_err.size else 1.0e-10
@@ -117,21 +125,22 @@ def viterbi_select(
117
 
118
  state_d = np.arange(N_STATES) // 6
119
  state_m = np.arange(N_STATES) % 6
120
- trans = np.empty((N_STATES, N_STATES), dtype=np.float64)
121
- for sp in range(N_STATES):
122
- trans[sp, :] = cfg.viterbi_alpha * scale_err * (
123
- np.abs(state_d - state_d[sp]) + np.abs(state_m - state_m[sp])
124
- )
125
 
126
  dp = np.full((graph_blocks, N_STATES), np.inf, dtype=np.float64)
127
  back = np.zeros((graph_blocks, N_STATES), dtype=np.int64)
128
  dp[0] = local[0]
 
129
  for gi in range(1, graph_blocks):
130
  totals = dp[gi - 1][:, None] + trans
131
- best = np.argmin(totals, axis=0)
132
- best_val = totals[best, np.arange(N_STATES)]
133
  dp[gi] = np.where(np.isfinite(best_val), best_val + local[gi], np.inf)
134
- back[gi] = best
135
 
136
  if not np.any(np.isfinite(dp[-1])):
137
  path = np.zeros(graph_blocks, dtype=np.int64)
@@ -141,25 +150,17 @@ def viterbi_select(
141
  for gi in range(graph_blocks - 2, -1, -1):
142
  path[gi] = back[gi + 1, path[gi + 1]]
143
 
144
- best_candidate = np.argmin(candidate_errors, axis=1).astype(np.int64)
145
- for gi in range(graph_blocks):
146
- s = int(path[gi])
147
- qd, qm = s // 6, s % 6
148
- rep_block = gi * stride
149
- if bin_cand[gi, s] >= 0:
150
- best_candidate[rep_block] = bin_cand[gi, s]
151
- start = rep_block + 1
152
- stop = min((gi + 1) * stride, n_blocks)
153
- for b in range(start, stop):
154
- mask = (qi_d == qd) & (qi_m == qm)
155
- idx = np.where(mask)[0]
156
- if idx.size:
157
- best_candidate[b] = int(idx[int(np.argmin(candidate_errors[b, idx]))])
158
-
159
- for b in range(n_blocks):
160
- cur = candidate_errors[b, best_candidate[b]]
161
- g = int(np.argmin(candidate_errors[b]))
162
- if candidate_errors[b, g] < cur * cfg.greedy_override_ratio:
163
- best_candidate[b] = g
164
 
165
  return best_candidate, path
 
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
+ Both the 576->36 bin reduction and the path->candidate mapping are fully
9
+ vectorised by reshaping the ``(24, 24)`` candidate grid into ``(6, 4, 6, 4)``.
10
  """
11
 
12
  from __future__ import annotations
 
18
 
19
  N_STATES = 36
20
 
21
+ _BIN_CAND_MAP = (
22
+ np.arange(TOTAL_CANDIDATES, dtype=np.int64)
23
+ .reshape(6, 4, 6, 4)
24
+ .transpose(0, 2, 1, 3)
25
+ .reshape(N_STATES, 16)
26
+ )
27
+
28
 
29
  def _median_upper(values: np.ndarray) -> float:
30
  arr = np.asarray(values)
 
72
  cfg: HPCConfig,
73
  ) -> tuple[np.ndarray, np.ndarray]:
74
  n_blocks = candidate_errors.shape[0]
 
 
 
 
75
  temp = max(float(temperature), 1.0e-12)
76
 
77
+ # Reshape (n_blocks, 576) -> view (n_blocks, 6, 4, 6, 4) without copying
78
+ ce_f32 = np.asarray(candidate_errors, dtype=np.float32)
79
+ ce_view = ce_f32.reshape(n_blocks, 6, 4, 6, 4)
80
+ blk_bin_min = np.min(ce_view, axis=(2, 4)).reshape(n_blocks, N_STATES).astype(np.float64)
81
+
82
+ ce_bins = ce_view.transpose(0, 1, 3, 2, 4).reshape(n_blocks, N_STATES, 16)
83
+ blk_bin_arg = np.argmin(ce_bins, axis=2)
84
+ blk_bin_cand = _BIN_CAND_MAP[np.arange(N_STATES)[None, :], blk_bin_arg]
85
+ blk_bin_cand = np.where(np.isfinite(blk_bin_min), blk_bin_cand, -1)
86
+
87
+ pad = graph_blocks * stride - n_blocks
88
+ if pad > 0:
89
+ pad_arr = np.full((pad, 6, 4, 6, 4), np.inf, dtype=np.float32)
90
+ grouped = np.concatenate([ce_view, pad_arr], axis=0).reshape(graph_blocks, stride, 6, 4, 6, 4)
91
+ else:
92
+ grouped = ce_view.reshape(graph_blocks, stride, 6, 4, 6, 4)
93
+
94
+ group_min_4d = np.min(grouped, axis=(1, 3, 5)) # (graph_blocks, 6, 6)
95
+ group_min = group_min_4d.reshape(graph_blocks, N_STATES).astype(np.float64)
96
+ finite = np.isfinite(grouped)
97
+ counts = np.sum(finite, axis=(1, 3, 5)).reshape(graph_blocks, N_STATES)
98
+ valid = (counts > 0) & np.isfinite(group_min)
99
+ gm_b = np.where( np.isfinite(group_min_4d), group_min_4d, 0.0)[:, None, :, None, :, None]
100
+ shifted = np.where(finite, (grouped - gm_b) / np.float32(2.0 * temp), np.inf)
101
+ sums = np.sum(np.exp(-shifted), axis=(1, 3, 5), dtype=np.float64).reshape(graph_blocks, N_STATES)
102
+ mean_exp = np.maximum(sums / np.maximum(counts, 1), 1.0e-300)
103
+ bin_err = np.where(valid, group_min - 2.0 * temp * np.log(mean_exp), np.inf)
104
+
105
+ rep_idx = np.arange(graph_blocks) * stride
106
+ bin_cand = blk_bin_cand[rep_idx] # (graph_blocks, 36)
107
+
108
+ c_tot = np.sum(coarse_marg, axis=1, keepdims=True)
109
+ f_tot = np.sum(fine_marg, axis=1, keepdims=True)
110
+ pc = np.where(c_tot > 1.0e-30, coarse_marg / np.maximum(c_tot, 1.0e-30), 1.0 / 6.0)
111
+ pf = np.where(f_tot > 1.0e-30, fine_marg / np.maximum(f_tot, 1.0e-30), 1.0 / 6.0)
112
+ log_prior = (
113
+ np.log(pc[:, :, None] + 1.0e-30) + np.log(pf[:, None, :] + 1.0e-30)
114
+ ).reshape(graph_blocks, N_STATES)
115
 
116
  finite_err = bin_err[np.isfinite(bin_err)]
117
  scale_err = float(finite_err.mean()) if finite_err.size else 1.0e-10
 
125
 
126
  state_d = np.arange(N_STATES) // 6
127
  state_m = np.arange(N_STATES) % 6
128
+ trans = (
129
+ cfg.viterbi_alpha
130
+ * scale_err
131
+ * (np.abs(state_d[None, :] - state_d[:, None]) + np.abs(state_m[None, :] - state_m[:, None]))
132
+ )
133
 
134
  dp = np.full((graph_blocks, N_STATES), np.inf, dtype=np.float64)
135
  back = np.zeros((graph_blocks, N_STATES), dtype=np.int64)
136
  dp[0] = local[0]
137
+ s_range = np.arange(N_STATES)
138
  for gi in range(1, graph_blocks):
139
  totals = dp[gi - 1][:, None] + trans
140
+ best_sp = np.argmin(totals, axis=0)
141
+ best_val = totals[best_sp, s_range]
142
  dp[gi] = np.where(np.isfinite(best_val), best_val + local[gi], np.inf)
143
+ back[gi] = best_sp
144
 
145
  if not np.any(np.isfinite(dp[-1])):
146
  path = np.zeros(graph_blocks, dtype=np.int64)
 
150
  for gi in range(graph_blocks - 2, -1, -1):
151
  path[gi] = back[gi + 1, path[gi + 1]]
152
 
153
+ # Vectorized path -> block candidate mapping:
154
+ blk_gi = np.arange(n_blocks) // stride
155
+ chosen_state = path[blk_gi]
156
+ chosen_cand = blk_bin_cand[np.arange(n_blocks), chosen_state]
157
+ global_best_cand = np.argmin(candidate_errors, axis=1).astype(np.int64)
158
+ best_candidate = np.where(chosen_cand >= 0, chosen_cand, global_best_cand)
159
+
160
+ cur_err = candidate_errors[np.arange(n_blocks), best_candidate]
161
+ g_err = candidate_errors[np.arange(n_blocks), global_best_cand]
162
+ best_candidate = np.where(
163
+ g_err < cur_err * cfg.greedy_override_ratio, global_best_cand, best_candidate
164
+ )
 
 
 
 
 
 
 
 
165
 
166
  return best_candidate, path