File size: 29,388 Bytes
cec4bd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e9e0544
 
 
 
 
 
 
 
 
 
 
 
 
cec4bd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e9e0544
cec4bd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e9e0544
cec4bd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e9e0544
cec4bd4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e9e0544
cec4bd4
 
 
 
 
 
 
 
 
 
 
 
 
7c4637c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49c0661
7c4637c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49c0661
7c4637c
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
"""Thermal refinery: seeded pair heat-bath annealing on the Q2_K lattice.

With ``fold_depth == 1`` the extended objective for one block is exactly::

    E = sum_i w_i e_i^2
        + (vw_lambda / n) * sum_p (e_p + e_{p+n/2})^2
        + (dc_lambda / n) * (sum_i e_i + carry)^2

so the 256 weights decompose into n/2 vesica pairs that interact with each
other only through the scalar DC residual: with every error affine in its
code, each pair's conditional energy over its joint states is exactly
quadratic and closed-form.  ``gibbs_pair_pass`` resamples the pairs from
those conditionals at a temperature (heat bath), fully batched across blocks
over any uniform lattice size (``levels``: 4 = Q2_K, 16 = Q4_0, 2 = Q1_0,
8 = Q3_K K-quants).  ``greedy=True`` resamples at the conditional argmin
instead, which is a monotone descent.  ``thermal_refine_codes`` /
``thermal_refine_uniform_codes`` schedule temperatures, then keep the
elementwise best against the pre-anneal state, so the pass can never regress
a block.  All randomness comes from a seeded generator, and a fixed seed
reproduces bit-identical quantizations.
"""

from __future__ import annotations

import numpy as np

from .config import HPCConfig
from .fp16 import nearest_int
from .q2k import QK_K

EPS_D = 1.0e-15


def thermal_spectral_lams(cfg: HPCConfig, n: int) -> tuple[float, float]:
    """Per-code-normalized ``(lam_vw, lam_dc)`` with the zero-residual tilt applied.

    ``therm_lambda_tilt`` scales both spectral lambdas during annealing so the
    exact Gibbs chain trades weighted SSE for coherent-residual (DC/vesica)
    drain; ``1.0`` (default) is the shipping objective.
    """
    if not cfg.use_spectral:
        return 0.0, 0.0
    tilt = max(1.0, float(cfg.therm_lambda_tilt))
    return (float(cfg.vw_lambda) / n) * tilt, (float(cfg.dc_lambda) / n) * tilt


def pair_energy(
    e: np.ndarray,
    w: np.ndarray | None,
    lam_vw: float,
    lam_dc: float,
    carry: np.ndarray,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
    """Block energies plus the (sse, v, dc) live state for exact deltas."""
    half = e.shape[1] // 2
    ww = np.ones_like(e) if w is None else np.asarray(w, dtype=np.float64)
    we_sq = ww * e * e
    sse = np.sum(we_sq, axis=1)
    v = e[:, :half] + e[:, half:]
    dc = np.sum(e, axis=1) + np.asarray(carry, dtype=np.float64)
    energy = sse + lam_vw * np.sum(v * v, axis=1) + lam_dc * dc * dc
    return energy, we_sq, v, dc


def gibbs_pair_pass(
    X_f64: np.ndarray,
    w_full: np.ndarray | None,
    d_sub: np.ndarray,
    m_sub: np.ndarray,
    codes: np.ndarray,
    e: np.ndarray,
    v: np.ndarray,
    dc: np.ndarray,
    sse: np.ndarray,
    temperature: np.ndarray,
    lam_vw: float,
    lam_dc: float,
    rng: np.random.Generator,
    greedy: bool = False,
    levels: int = 4,
) -> None:
    """One exact sequential Gibbs sweep over all vesica pairs, in place.

    Updates (codes, e, v, dc, sse) for the batch of blocks.  With
    ``greedy=False`` each pair is resampled from its exact conditional
    energy via a heat bath at ``temperature`` (per-block vector); with
    ``greedy=True`` the conditional argmin is taken and only strict
    energy decreases are accepted.  ``levels`` selects the code lattice
    size (4 = Q2_K 2-bit, 16 = Q4_0 nibble, 2 = Q1_0 bit, 8 = Q3_K tri);
    ``d_sub`` holds the per-column error step so that any code move ``q``
    produces the exact error delta ``d_sub * (q_cur - q)`` (formula is
    shared across formats; Q1_0's sign trick is ``d_sub = 2*d``), and
    ``m_sub`` is accepted for signature symmetry but unused here.
    """
    n_blocks, n = X_f64.shape
    half = n // 2
    q_grid = np.arange(levels, dtype=np.int64)
    w_full = np.ones_like(e) if w_full is None else np.asarray(w_full, dtype=np.float64)
    w_lo, w_hi = w_full[:, :half], w_full[:, half:]
    ds_lo, ds_hi = d_sub[:, :half], d_sub[:, half:]
    active_lo, active_hi = np.abs(ds_lo) >= EPS_D, np.abs(ds_hi) >= EPS_D
    idx = np.arange(n_blocks)

    for p in range(half):
        e_lo = e[:, p].copy()
        e_hi = e[:, half + p].copy()
        q_lo = codes[:, p].astype(np.int64)
        q_hi = codes[:, half + p].astype(np.int64)
        v_p = v[:, p]
        dc_p = dc

        # candidate error deltas (affine in q): de(q) = d_sub * (q_cur - q)
        de_lo = ds_lo[:, p : p + 1] * (q_lo[:, None] - q_grid[None, :])
        de_hi = ds_hi[:, p : p + 1] * (q_hi[:, None] - q_grid[None, :])
        de_lo = np.where(active_lo[:, p : p + 1], de_lo, 0.0)
        de_hi = np.where(active_hi[:, p : p + 1], de_hi, 0.0)
        de_sum = de_lo[:, :, None] + de_hi[:, None, :]  # (n_blocks, L, L)

        d_e = (
            w_lo[:, p, None, None]
            * (2.0 * e_lo[:, None, None] * de_lo[:, :, None] + de_lo[:, :, None] ** 2)
            + w_hi[:, p, None, None]
            * (2.0 * e_hi[:, None, None] * de_hi[:, None, :] + de_hi[:, None, :] ** 2)
            + lam_vw * (2.0 * v_p[:, None, None] * de_sum + de_sum * de_sum)
            + lam_dc * (2.0 * dc_p[:, None, None] * de_sum + de_sum * de_sum)
        )

        if greedy:
            flat = d_e.reshape(n_blocks, -1)
            cur = q_lo * levels + q_hi
            pick = np.argmin(flat, axis=1)
            improve = flat[idx, pick] < -1.0e-18
            pick = np.where(improve, pick, cur)
        else:
            cur = q_lo * levels + q_hi
            scale = np.maximum(temperature, 1.0e-300)
            logits = -d_e / scale[:, None, None]
            logits -= logits.max(axis=(1, 2), keepdims=True)
            probs = np.exp(logits).reshape(n_blocks, -1)
            # dead pairs (both sub-scales ~0): codes carry no energy, keep current
            dead = ~(active_lo[:, p] | active_hi[:, p])
            if np.any(dead):
                probs[dead] = 0.0
                probs[dead, cur[dead]] = 1.0
            cum = np.cumsum(probs, axis=1)
            cum /= cum[:, -1:]
            pick = np.sum(cum < rng.random((n_blocks, 1)), axis=1)
            pick = np.minimum(pick, levels * levels - 1)

        ia, ib = pick // levels, pick % levels
        upd_lo = np.take_along_axis(de_lo, ia[:, None], axis=1)[:, 0]
        upd_hi = np.take_along_axis(de_hi, ib[:, None], axis=1)[:, 0]
        upd_sum = upd_lo + upd_hi
        sse += (
            w_lo[:, p] * (2.0 * e_lo * upd_lo + upd_lo * upd_lo)
            + w_hi[:, p] * (2.0 * e_hi * upd_hi + upd_hi * upd_hi)
        )
        e[:, p] = e_lo + upd_lo
        e[:, half + p] = e_hi + upd_hi
        v[:, p] = v_p + upd_sum
        dc = dc + upd_sum
        codes[:, p] = ia
        codes[:, half + p] = ib


def codes_recompute_for(
    X_f64: np.ndarray, d: np.ndarray, m: np.ndarray, ls: np.ndarray, lm: np.ndarray
) -> np.ndarray:
    """Nearest rounding of codes, matching q2k.codes_from_params semantics."""
    x32 = X_f64.astype(np.float32)
    d32 = np.asarray(d, dtype=np.float32)
    m32 = np.asarray(m, dtype=np.float32)
    d_sub = np.repeat(d32[:, None] * ls.astype(np.float32), 16, axis=1)
    m_sub = np.repeat(m32[:, None] * lm.astype(np.float32), 16, axis=1)
    safe = d_sub >= np.float32(EPS_D)
    safe_d = np.where(safe, d_sub, np.float32(1.0))
    q = np.clip(nearest_int((x32 + m_sub) / safe_d), 0, 3)
    return np.where(safe, q, 0).astype(np.int64)


def thermal_refine_codes(
    X: np.ndarray,
    w: np.ndarray | None,
    d: np.ndarray,
    m: np.ndarray,
    ls: np.ndarray,
    lm: np.ndarray,
    codes: np.ndarray,
    carry: np.ndarray,
    cfg: HPCConfig,
    dm_solve=None,
    passes: int | None = None,
    seed: int | None = None,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, int]:
    """Seeded heat-bath annealing of the code lattice for a batch of blocks.

    Returns ``(d, m, ls, lm, codes, n_improved)``.  Per block the returned
    state never has a higher extended energy than the input state: every
    end-of-pass candidate is compared against the running elementwise best
    and only strict decreases are kept.
    """
    n_blocks = X.shape[0]
    n_passes = cfg.therm_passes if passes is None else int(passes)
    lam_vw, lam_dc = thermal_spectral_lams(cfg, QK_K)
    if n_blocks == 0 or n_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0):
        return d, m, ls, lm, codes, 0
    if cfg.fold_depth > 1:
        raise ValueError("thermal refinement supports fold_depth <= 1")

    X_f64 = np.asarray(X, dtype=np.float64)
    w_f64 = None if w is None else np.asarray(w, dtype=np.float64)
    carry_f64 = np.asarray(carry, dtype=np.float64)
    ls = np.asarray(ls, dtype=np.int64)
    lm = np.asarray(lm, dtype=np.int64)
    d = np.asarray(d, dtype=np.float64)
    m = np.asarray(m, dtype=np.float64)
    codes = np.asarray(codes, dtype=np.int64).copy()
    rng = np.random.default_rng(cfg.therm_seed if seed is None else seed)

    def energies(dd, mm, cc):
        d_sub = np.repeat(dd[:, None] * ls, 16, axis=1)
        m_sub = np.repeat(mm[:, None] * lm, 16, axis=1)
        e = X_f64 - (d_sub * cc - m_sub)
        return pair_energy(e, w_f64, lam_vw, lam_dc, carry_f64)[0]

    energy_best = energies(d, m, codes)
    energy0 = energy_best.copy()
    best_d, best_m, best_codes = d.copy(), m.copy(), codes.copy()
    d_cur, m_cur, codes_cur = d.copy(), m.copy(), codes.copy()

    for t in range(n_passes):
        temperature = cfg.therm_t0 * np.maximum(energy_best, 1.0e-30) / QK_K * (cfg.therm_t_decay**t)
        for sweep in range(cfg.therm_sweeps + 2):
            greedy = sweep >= cfg.therm_sweeps
            d_sub = np.repeat(d_cur[:, None] * ls, 16, axis=1)
            m_sub = np.repeat(m_cur[:, None] * lm, 16, axis=1)
            e = X_f64 - (d_sub * codes_cur - m_sub)
            energy_cur, wesq, v, dc = pair_energy(e, w_f64, lam_vw, lam_dc, carry_f64)
            sse = energy_cur - lam_vw * np.sum(v * v, axis=1) - lam_dc * dc * dc
            gibbs_pair_pass(
                X_f64,
                w_f64,
                d_sub,
                m_sub,
                codes_cur,
                e,
                v,
                dc,
                sse,
                temperature,
                lam_vw,
                lam_dc,
                np.random.default_rng(rng.integers(0, 2**62)),
                greedy=greedy,
            )
            if greedy:
                energy_now, _, _, _ = pair_energy(
                    X_f64 - (d_sub * codes_cur - m_sub), w_f64, lam_vw, lam_dc, carry_f64
                )
                if not np.any(energy_now < energy_cur - 1.0e-18):
                    break
        codes_cur = np.clip(codes_cur, 0, 3)
        energy_a = energies(d_cur, m_cur, codes_cur)
        if dm_solve is not None:
            dm_w = w_f64 if w_f64 is not None else np.ones_like(X_f64)
            d2, m2 = dm_solve(X_f64, dm_w, ls, lm, codes_cur, d_cur, m_cur, carry_f64, cfg)
            codes2 = codes_recompute_for(X_f64, d2, m2, ls, lm)
            energy_b = energies(d2, m2, codes2)
            take_b = energy_b < energy_a - 1.0e-18
            d_cur = np.where(take_b, d2, d_cur)
            m_cur = np.where(take_b, m2, m_cur)
            codes_cur = np.where(take_b[:, None], codes2, codes_cur)
        energy_now = energies(d_cur, m_cur, codes_cur)
        take = energy_now < energy_best - 1.0e-18
        best_d = np.where(take, d_cur, best_d)
        best_m = np.where(take, m_cur, best_m)
        best_codes = np.where(take[:, None], codes_cur, best_codes)
        energy_best = np.where(take, energy_now, energy_best)

    n_improved = int(np.sum(energy_best < energy0 - 1.0e-18))
    return best_d, best_m, ls.copy(), lm.copy(), best_codes.astype(np.uint8), n_improved


def thermal_refine_tensor(
    X: np.ndarray,
    w: np.ndarray | None,
    d: np.ndarray,
    m: np.ndarray,
    ls: np.ndarray,
    lm: np.ndarray,
    codes: np.ndarray,
    carry: np.ndarray,
    cfg: HPCConfig,
    dm_solve=None,
    chunk: int = 2048,
    progress=None,
) -> tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray, np.ndarray, int]:
    """Chunked wrapper so peak memory stays bounded on large tensors."""
    n_blocks = X.shape[0]
    step = max(1, int(chunk))
    total_improved = 0
    for start in range(0, n_blocks, step):
        stop = min(start + step, n_blocks)
        sel = slice(start, stop)
        d_c, m_c, ls_c, lm_c, c_c, improved = thermal_refine_codes(
            X[sel],
            None if w is None else w[sel],
            d[sel],
            m[sel],
            ls[sel],
            lm[sel],
            codes[sel],
            carry[sel],
            cfg,
            dm_solve=dm_solve,
            seed=cfg.therm_seed ^ (0x9E3779B97F4A7C15 * (start // step) & 0x3FFFFFFFFFFFFFFF),
        )
        d[sel], m[sel], ls[sel], lm[sel], codes[sel] = d_c, m_c, ls_c, lm_c, c_c
        total_improved += improved
        if progress is not None:
            progress(stop, n_blocks, improved)
    return d, m, ls, lm, codes, total_improved


def thermal_refine_uniform_codes(
    X: np.ndarray,
    w: np.ndarray | None,
    codes: np.ndarray,
    step: np.ndarray,
    m_const: np.ndarray,
    carry: np.ndarray,
    cfg: HPCConfig,
    lam_vw: float,
    lam_dc: float,
    levels: int,
    passes: int | None = None,
    seed: int | None = None,
) -> tuple[np.ndarray, int]:
    """Heat-bath annealing of a level-index code lattice with FIXED step scales.

    Uniform variant of ``thermal_refine_codes`` for the format quantizers
    (Q4_0 / Q1_0 / Q3_K): the per-code error step ``step`` (shape (B, n)) and
    the affine constant ``m_const`` (shape (B, n)) are fixed inputs — the
    dequantized value of level index ``q`` is ``step*q - m_const`` so the
    error is ``e = X - (step*q - m_const)`` and every move ``q -> q'`` has
    the exact closed-form delta again affine in the level grid.  ``codes``
    are integer level indices in ``[0, levels)``.

    Returns ``(codes, n_improved)``.  Per block the returned state never has
    a higher extended energy than the input state and its SSE never exceeds
    the input SSE by more than ``cfg.sse_budget`` (extended-nonincreasing
    with an SSE guard, matching the pipeline's shaper semantics).
    """
    n_blocks, n = X.shape
    if n_blocks == 0:
        return codes.astype(np.int64).copy(), 0
    if n % 2 != 0:
        raise ValueError("thermal refinement needs an even row width")
    n_passes = cfg.therm_passes if passes is None else int(passes)
    if n_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0):
        return codes.astype(np.int64).copy(), 0
    if cfg.fold_depth > 1:
        raise ValueError("thermal refinement supports fold_depth <= 1")

    X_f64 = np.asarray(X, dtype=np.float64)
    w_f64 = None if w is None else np.asarray(w, dtype=np.float64)
    carry_f64 = np.asarray(carry, dtype=np.float64)
    step = np.asarray(step, dtype=np.float64)
    m_b = np.asarray(m_const, dtype=np.float64)
    codes = np.asarray(codes, dtype=np.int64).copy()
    rng = np.random.default_rng(cfg.therm_seed if seed is None else seed)

    def energies(cc):
        e = X_f64 - (step * cc - m_b)
        return pair_energy(e, w_f64, lam_vw, lam_dc, carry_f64)

    energy_best, we_sq, v0, dc0 = energies(codes)
    energy0 = energy_best.copy()
    sse0 = np.sum(we_sq, axis=1)
    sse_cap = sse0 * (1.0 + float(cfg.sse_budget))
    best_codes = codes.copy()
    codes_cur = codes.copy()

    for t in range(n_passes):
        temperature = cfg.therm_t0 * np.maximum(energy_best, 1.0e-30) / n * (cfg.therm_t_decay**t)
        for sweep in range(cfg.therm_sweeps + 2):
            greedy = sweep >= cfg.therm_sweeps
            e = X_f64 - (step * codes_cur - m_b)
            energy_cur, we_sq, v, dc = pair_energy(e, w_f64, lam_vw, lam_dc, carry_f64)
            sse = np.sum(we_sq, axis=1)
            gibbs_pair_pass(
                X_f64,
                w_f64,
                step,
                m_b,
                codes_cur,
                e,
                v,
                dc,
                sse,
                temperature,
                lam_vw,
                lam_dc,
                np.random.default_rng(rng.integers(0, 2**62)),
                greedy=greedy,
                levels=levels,
            )
            if greedy:
                e = X_f64 - (step * codes_cur - m_b)
                energy_now, _, _, _ = pair_energy(e, w_f64, lam_vw, lam_dc, carry_f64)
                if not np.any(energy_now < energy_cur - 1.0e-18):
                    break
        energy_now, we_sq, v_now, dc_now = energies(codes_cur)
        sse_now = np.sum(we_sq, axis=1)
        take = (energy_now < energy_best - 1.0e-18) & (sse_now <= sse_cap)
        best_codes = np.where(take[:, None], codes_cur, best_codes)
        energy_best = np.where(take, energy_now, energy_best)
        codes_cur = best_codes.copy()

    n_improved = int(np.sum(energy_best < energy0 - 1.0e-18))
    return best_codes, n_improved


def _refine_uniform_driver(
    X: np.ndarray,
    w: np.ndarray | None,
    codes: np.ndarray,
    build_state,
    carry: np.ndarray | None,
    lam_vw: float,
    lam_dc: float,
    levels: int,
    cfg: HPCConfig,
    chunk: int = 2048,
    progress=None,
) -> tuple[np.ndarray, int]:
    """Chunked uniform annealing so peak memory stays bounded on big tensors."""
    n_blocks = X.shape[0]
    codes_out = np.asarray(codes, dtype=np.int64).copy()
    step = max(1, int(chunk))
    total_improved = 0
    for start in range(0, n_blocks, step):
        stop = min(start + step, n_blocks)
        sel = slice(start, stop)
        step_c, m_c = build_state(start, stop)
        carry_c = np.zeros(stop - start) if carry is None else carry[sel]
        c_c, improved = thermal_refine_uniform_codes(
            X[sel],
            None if w is None else w[sel],
            codes_out[sel],
            step_c,
            m_c,
            carry_c,
            cfg,
            lam_vw,
            lam_dc,
            levels,
            seed=cfg.therm_seed ^ (0x9E3779B97F4A7C15 * (start // step) & 0x3FFFFFFFFFFFFFFF),
        )
        codes_out[sel] = c_c
        total_improved += improved
        if progress is not None:
            progress(stop, n_blocks, improved)
    return codes_out, total_improved


def thermal_refine_q4_0(
    X: np.ndarray,
    W: np.ndarray | None,
    d: np.ndarray,
    q: np.ndarray,
    cfg: HPCConfig,
    chunk: int = 2048,
    progress=None,
) -> tuple[np.ndarray, int]:
    """Anneal Q4_0 nibble codes against fixed fp16 super-block scales.

    ``X``: raw block targets (B, 32); ``W``: importance weights (B, 32) or
    None; ``d``: fp16-precision block scales (B,), possibly negative
    (sign-flip polish outputs); ``q``: nibble codes (B, 32) with
    dequantization ``d*(q-8)``.  The Gibbs step grid is the signed slope
    ``d`` itself, so negatives fall out naturally with no code remapping.
    Returns refined uint8 codes and the number of strictly improved blocks;
    a no-op when the refinery or spectrals are disabled.
    """
    n_blocks, n = X.shape
    lam_vw, lam_dc = thermal_spectral_lams(cfg, n)
    if n_blocks == 0 or cfg.therm_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0):
        return q.copy(), 0
    d64 = np.asarray(d, dtype=np.float64)

    def build_state(start, stop):
        ds = np.repeat(d64[start:stop][:, None], n, axis=1)
        return ds, 8.0 * ds

    codes_out, improved = _refine_uniform_driver(
        X, W, np.asarray(q, dtype=np.int64), build_state, None,
        lam_vw, lam_dc, 16, cfg, chunk=chunk, progress=progress,
    )
    return codes_out.astype(np.uint8), improved


def thermal_refine_q1_0(
    X: np.ndarray,
    W: np.ndarray | None,
    d: np.ndarray,
    bits: np.ndarray,
    cfg: HPCConfig,
    chunk: int = 2048,
    progress=None,
) -> tuple[np.ndarray, int]:
    """Anneal Q1_0 bits against fixed fp16 super-block magnitudes.

    ``bits``: binary codes (B, 32) with dequantization ``d*(2b-1)``; the
    Gibbs step grid is ``2*d`` with affine constant ``d``.  Returns refined
    uint8 bits and the number of strictly improved blocks.
    """
    n_blocks, n = X.shape
    lam_vw, lam_dc = thermal_spectral_lams(cfg, n)
    if n_blocks == 0 or cfg.therm_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0):
        return bits.copy(), 0
    d64 = np.asarray(d, dtype=np.float64)

    def build_state(start, stop):
        ds = np.repeat(2.0 * np.abs(d64[start:stop])[:, None], n, axis=1)
        return ds, np.repeat(np.abs(d64[start:stop])[:, None], n, axis=1)

    codes_out, improved = _refine_uniform_driver(
        X, W, np.asarray(bits, dtype=np.int64), build_state, None,
        lam_vw, lam_dc, 2, cfg, chunk=chunk, progress=progress,
    )
    return codes_out.astype(np.uint8), improved


def thermal_refine_q3_k(
    X: np.ndarray,
    W: np.ndarray | None,
    scale: np.ndarray,
    codes: np.ndarray,
    cfg: HPCConfig,
    chunk: int = 2048,
    progress=None,
) -> tuple[np.ndarray, int]:
    """Anneal Q3_K signed 3-bit codes against fixed per-superblock scales.

    ``scale``: per-superblock scale values (B, 16) broadcast over the 16
    codes each; ``codes``: source-signed values (B, 256) in [-4, 3].  The
    Gibbs grid covers all eight signed levels; negative scales (sign-flip
    polish outputs) are handled exactly via signed steps.
    """
    n_blocks, n = X.shape
    lam_vw, lam_dc = thermal_spectral_lams(cfg, n)
    if n_blocks == 0 or cfg.therm_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0):
        return np.asarray(codes).copy(), 0
    scale64 = np.asarray(scale, dtype=np.float64)

    def build_state(start, stop):
        ds = np.repeat(scale64[start:stop], 16, axis=1)
        return ds, 4.0 * ds

    codes_out, improved = _refine_uniform_driver(
        X, W, np.asarray(codes, dtype=np.int64) + 4, build_state, None,
        lam_vw, lam_dc, 8, cfg, chunk=chunk, progress=progress,
    )
    return codes_out - 4, improved


def thermal_refine_uniform_codes_cuda(
    X: "torch.Tensor",
    w: "torch.Tensor | None",
    codes: "torch.Tensor",
    step: "torch.Tensor",
    m_const: "torch.Tensor",
    carry: "torch.Tensor | None",
    lam_vw: float,
    lam_dc: float,
    levels: int,
    cfg: HPCConfig,
    passes: int | None = None,
    seed: int | None = None,
) -> tuple["torch.Tensor", int]:
    """Torch/CUDA sibling of ``thermal_refine_uniform_codes``.

    Identical algorithm and acceptance structure (extended-nonincreasing
    best-state with an ``sse_budget`` guard, seeded heat-bath sweeps plus a
    greedy endgame), run on-device in float64 so results are deterministic
    per device class (bit-exact across runs at the same seed, but not
    bit-identical to the numpy CPU chain — floating-point ordering differs).

    All tensor inputs live on one CUDA device: ``codes`` int64 (B, n),
    the rest float32/float64.  Returns ``(codes, n_improved)`` with
    ``codes`` still on-device.
    """
    import torch  # local import keeps anneal.py torch-free for CPU installs

    dev = X.device
    f64 = torch.float64
    B, n = X.shape
    half = n // 2
    n_passes = cfg.therm_passes if passes is None else int(passes)
    if B == 0 or n % 2 != 0:
        return codes.to(torch.int64), 0
    if n_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0):
        return codes.to(torch.int64).clone(), 0
    if cfg.fold_depth > 1:
        raise ValueError("thermal refinement supports fold_depth <= 1")

    Xf = X.to(f64)
    wf = None if w is None else w.to(f64)
    cf = None if carry is None else carry.to(f64)
    if cf is None:
        cf = torch.zeros(B, dtype=f64, device=dev)
    sf = step.to(f64)
    mf_data = m_const.to(f64)
    cc = codes.to(torch.int64).clone()
    grid = torch.arange(levels, device=dev, dtype=torch.int64)
    ones_col = torch.ones(B, dtype=f64, device=dev)

    def energies(c):
        e = Xf - (sf * c.to(f64) - mf_data)
        w2 = torch.sum(e * e, dim=1) if wf is None else torch.sum(wf * e * e, dim=1)
        v = e[:, :half] + e[:, half:]
        dc = torch.sum(e, dim=1) + cf
        return (w2 + lam_vw * torch.sum(v * v, dim=1) + lam_dc * dc * dc), e, w2, v, dc

    energy_best, e, sse0, v, dc = energies(cc)
    energy0 = energy_best.clone()
    sse_cap = sse0 * (1.0 + float(cfg.sse_budget))
    best = cc.clone()
    gen = torch.Generator(device=dev)
    gen.manual_seed(int(cfg.therm_seed if seed is None else seed) & 0x7FFFFFFFFFFFFFFF)

    for t in range(n_passes):
        temperature = (
            cfg.therm_t0
            * torch.maximum(energy_best, torch.full_like(energy_best, 1.0e-30))
            / n
            * (cfg.therm_t_decay ** t)
        )
        broke = False
        for sweep in range(cfg.therm_sweeps + 2):
            greedy = sweep >= cfg.therm_sweeps
            energy_cur, e, _, v, dc = energies(cc)
            for p in range(half):
                e_lo = e[:, p]
                e_hi = e[:, half + p]
                q_lo = cc[:, p]
                q_hi = cc[:, half + p]
                ds_lo = sf[:, p]
                ds_hi = sf[:, half + p]
                act_lo = ds_lo.abs() >= EPS_D
                act_hi = ds_hi.abs() >= EPS_D
                w_lo = ones_col if wf is None else wf[:, p]
                w_hi = ones_col if wf is None else wf[:, half + p]
                de_lo = ds_lo[:, None] * (q_lo[:, None] - grid[None, :])
                de_lo = torch.where(act_lo[:, None], de_lo, torch.zeros_like(de_lo))
                de_hi = ds_hi[:, None] * (q_hi[:, None] - grid[None, :])
                de_hi = torch.where(act_hi[:, None], de_hi, torch.zeros_like(de_hi))
                de_sum = de_lo[:, :, None] + de_hi[:, None, :]
                v_p = v[:, p]
                d_e = (
                    w_lo[:, None, None] * (2.0 * e_lo[:, None, None] * de_lo[:, :, None] + de_lo[:, :, None] ** 2)
                    + w_hi[:, None, None] * (2.0 * e_hi[:, None, None] * de_hi[:, None, :] + de_hi[:, None, :] ** 2)
                    + lam_vw * (2.0 * v_p[:, None, None] * de_sum + de_sum * de_sum)
                    + lam_dc * (2.0 * dc[:, None, None] * de_sum + de_sum * de_sum)
                )
                cur = q_lo * levels + q_hi
                if greedy:
                    flat = d_e.reshape(B, -1)
                    pick = flat.argmin(dim=1)
                    improve = torch.gather(flat, 1, pick[:, None]).squeeze(1) < -1.0e-18
                    pick = torch.where(improve, pick, cur)
                else:
                    scc = torch.clamp(temperature, min=1.0e-300)[:, None, None]
                    logits = -d_e / scc
                    logits = logits - logits.amax(dim=(1, 2), keepdim=True)
                    probs = logits.exp().reshape(B, -1)
                    dead = ~(act_lo | act_hi)
                    if bool(dead.any()):
                        probs[dead] = 0.0
                        probs[dead, cur[dead]] = 1.0
                    cum = probs.cumsum(dim=1)
                    cum = cum / cum[:, -1:]
                    u = torch.rand(B, 1, generator=gen, device=dev, dtype=f64)
                    pick = (cum < u).sum(dim=1).clamp(max=levels * levels - 1)
                ia = pick // levels
                ib = pick - ia * levels
                upd_lo = torch.gather(de_lo, 1, ia[:, None]).squeeze(1)
                upd_hi = torch.gather(de_hi, 1, ib[:, None]).squeeze(1)
                usum = upd_lo + upd_hi
                e[:, p] = e_lo + upd_lo
                e[:, half + p] = e_hi + upd_hi
                v[:, p] += usum
                dc = dc + usum
                cc[:, p] = ia
                cc[:, half + p] = ib
            if greedy:
                energy_now, _, _, _, _ = energies(cc)
                if not bool(torch.any(energy_now < energy_cur - 1.0e-18)):
                    broke = True
                    break
        _, _, sse_now, _, _ = energies(cc)
        take = (energy_now < energy_best - 1.0e-18) & (sse_now <= sse_cap)
        best = torch.where(take[:, None], cc, best)
        energy_best = torch.where(take, energy_now, energy_best)
        cc = best.clone()
        if broke and bool(torch.all(energy_now >= energy_best)):
            pass  # keep exact CPU-loop semantics: continue across passes regardless

    n_improved = int((energy_best < energy0 - 1.0e-18).sum().item())
    return best, n_improved


def thermal_refine_q3_k_cuda(
    X: "torch.Tensor",
    W: "torch.Tensor | None",
    scale: "torch.Tensor",
    codes: "torch.Tensor",
    cfg: HPCConfig,
    seed: int | None = None,
) -> tuple["torch.Tensor", int]:
    """On-device anneal for Q3_K signed 3-bit codes (see the CPU twin).

    ``scale``: per-superblock scale values (B, 16) on-device, ``codes``:
    signed values (B, 256) in [-4, 3].  Exactly mirrors
    ``thermal_refine_q3_k`` without any host round-trip.
    """
    import torch
    n_blocks, n = X.shape
    lam_vw, lam_dc = thermal_spectral_lams(cfg, n)
    if n_blocks == 0 or cfg.therm_passes == 0 or (lam_vw == 0.0 and lam_dc == 0.0):
        return codes.to(torch.int64).clone(), 0

    def build_state(start, stop):
        ds_full = scale.repeat_interleave(16, dim=1)   # (B, 256)
        return ds_full, 4.0 * ds_full

    ds_full, m_full = build_state(0, n_blocks)
    codes_out, improved = thermal_refine_uniform_codes_cuda(
        X, W, codes.to(torch.int64) + 4, ds_full, m_full, None,
        lam_vw, lam_dc, 8, cfg, seed=seed,
    )
    return codes_out - 4, improved