CompressedGemma commited on
Commit
a144512
·
verified ·
1 Parent(s): 5defcdf

Highly experimental

Browse files
Files changed (5) hide show
  1. config.py +16 -0
  2. pipeline.py +59 -1
  3. q3_k.py +25 -0
  4. q4_0.py +14 -0
  5. segmentation.py +248 -0
config.py CHANGED
@@ -103,6 +103,17 @@ class HPCConfig:
103
  # (the Q2_K closed-form pin never spends any). 0 disables the expense.
104
  pin_budget: float = 0.05
105
 
 
 
 
 
 
 
 
 
 
 
 
106
  # ---- seed clamp -----------------------------------------------------
107
  clamp_kurtosis_off: float = 6.0
108
  clamp_kurtosis_soft: float = 20.0
@@ -160,6 +171,11 @@ class HPCConfig:
160
  raise ValueError("therm_t_decay must lie in (0, 1)")
161
  if self.therm_lambda_tilt < 1.0:
162
  raise ValueError("therm_lambda_tilt must be >= 1.0 (1.0 = off)")
 
 
 
 
 
163
  if self.device not in ("auto", "cuda", "cpu") and not self.device.startswith("cuda:"):
164
  raise ValueError("device must be 'auto', 'cuda', 'cuda:N', or 'cpu'")
165
  if self.quant_format not in ("mixed", "mixed_q1", "custom", "q1_0", "q3_k", "q4_0", "q2_k"):
 
103
  # (the Q2_K closed-form pin never spends any). 0 disables the expense.
104
  pin_budget: float = 0.05
105
 
106
+ # ---- self-describing segmentation (V2-only, opt-in) -----------------
107
+ # Per-superblock deterministic band clustering. Labels are a pure
108
+ # function of the block's own dequantized values, so the decoder
109
+ # recovers the slot->position permutation by recomputation -- no
110
+ # sidecar, GGUF layout untouched. Q2_K / Q3_K use segment_k bands
111
+ # over the 256-weight superblock; Q4_0's 32-wide unit block uses k=1
112
+ # (ascending value order).
113
+ segment: bool = False
114
+ segment_k: int = 16
115
+ segment_iters: int = 4
116
+
117
  # ---- seed clamp -----------------------------------------------------
118
  clamp_kurtosis_off: float = 6.0
119
  clamp_kurtosis_soft: float = 20.0
 
171
  raise ValueError("therm_t_decay must lie in (0, 1)")
172
  if self.therm_lambda_tilt < 1.0:
173
  raise ValueError("therm_lambda_tilt must be >= 1.0 (1.0 = off)")
174
+ if self.segment:
175
+ if not (2 <= self.segment_k <= 256):
176
+ raise ValueError("segment_k must lie in [2, 256]")
177
+ if self.segment_iters < 1:
178
+ raise ValueError("segment_iters must be >= 1")
179
  if self.device not in ("auto", "cuda", "cpu") and not self.device.startswith("cuda:"):
180
  raise ValueError("device must be 'auto', 'cuda', 'cuda:N', or 'cpu'")
181
  if self.quant_format not in ("mixed", "mixed_q1", "custom", "q1_0", "q3_k", "q4_0", "q2_k"):
pipeline.py CHANGED
@@ -2,6 +2,7 @@
2
 
3
  Pipeline stages (mirroring the C engine, with V2 fixes):
4
 
 
5
  1. kurtosis-gated outlier clamp and batched GGML seeds;
6
  2. vectorised WLS superblock solve;
7
  3. factored candidate field + standalone factored floor;
@@ -72,6 +73,8 @@ class TensorStats:
72
  factor_floor_used: int
73
  candidate_floor_used: int
74
  therm_improved: int = 0
 
 
75
 
76
 
77
  @dataclass(slots=True)
@@ -84,10 +87,22 @@ class QuantizedTensor:
84
  factor_objectives: np.ndarray
85
  config: HPCConfig
86
  stats: TensorStats
 
 
87
 
88
  def dequantize(self) -> np.ndarray:
89
  return np.concatenate([b.dequantize() for b in self.blocks])
90
 
 
 
 
 
 
 
 
 
 
 
91
 
92
  def _phase2_wls(X, W, seeds, cfg: HPCConfig):
93
  n = X.shape[0]
@@ -864,6 +879,33 @@ class Q2KQuantizer:
864
  x = x.ravel()
865
  n_blocks = x.size // QK_K
866
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
867
  if cfg.device != "cpu":
868
  from .cuda_backend import cuda_is_available, quantize_cuda
869
 
@@ -872,7 +914,7 @@ class Q2KQuantizer:
872
  ):
873
  dev_str = "cuda" if cfg.device == "auto" else cfg.device
874
  res = quantize_cuda(
875
- weights,
876
  importance,
877
  row_width,
878
  cfg,
@@ -1199,6 +1241,16 @@ class Q2KQuantizer:
1199
  else:
1200
  weighted += float(np.dot(W[i].astype(np.float64), e * e))
1201
 
 
 
 
 
 
 
 
 
 
 
1202
  rmse = float(np.sqrt(total_sse / x.size))
1203
  stats = TensorStats(
1204
  n_blocks=n_blocks,
@@ -1211,6 +1263,8 @@ class Q2KQuantizer:
1211
  factor_floor_used=factor_floor_used,
1212
  candidate_floor_used=candidate_floor_used,
1213
  therm_improved=therm_improved,
 
 
1214
  )
1215
  _emit(
1216
  "Phase 5: Tensor Complete",
@@ -1237,6 +1291,8 @@ class Q2KQuantizer:
1237
  factor_objectives=factor_objectives,
1238
  config=cfg,
1239
  stats=stats,
 
 
1240
  )
1241
  if cfg.verbose:
1242
  _report(stats)
@@ -1258,6 +1314,8 @@ def _stack_blocks(blocks):
1258
 
1259
  def _report(stats: TensorStats) -> None:
1260
  extra = f" therm={stats.therm_improved}" if stats.therm_improved else ""
 
 
1261
  print(
1262
  f"Q2_K V2: blocks={stats.n_blocks} rmse={stats.rmse:.6e} "
1263
  f"rmse/sigma={stats.rmse_over_sigma:.5f} temp={stats.temperature:.3e} "
 
2
 
3
  Pipeline stages (mirroring the C engine, with V2 fixes):
4
 
5
+ 0. self-describing per-superblock segmentation (opt-in, ``cfg.segment``);
6
  1. kurtosis-gated outlier clamp and batched GGML seeds;
7
  2. vectorised WLS superblock solve;
8
  3. factored candidate field + standalone factored floor;
 
73
  factor_floor_used: int
74
  candidate_floor_used: int
75
  therm_improved: int = 0
76
+ segment_rounds: int = 0
77
+ segment_flips: int = 0
78
 
79
 
80
  @dataclass(slots=True)
 
87
  factor_objectives: np.ndarray
88
  config: HPCConfig
89
  stats: TensorStats
90
+ segment_perm: np.ndarray | None = None
91
+ segment_labels: np.ndarray | None = None
92
 
93
  def dequantize(self) -> np.ndarray:
94
  return np.concatenate([b.dequantize() for b in self.blocks])
95
 
96
+ def dequantize_original(self) -> np.ndarray:
97
+ """Consumer-side reconstruction: scatter dequantized slots back to
98
+ original positions using the stored label channel (exact)."""
99
+ if self.segment_perm is None:
100
+ return self.dequantize()
101
+ Xh = np.stack([b.dequantize() for b in self.blocks])
102
+ out = np.empty_like(Xh)
103
+ np.put_along_axis(out, self.segment_perm, Xh, axis=1)
104
+ return out.ravel()
105
+
106
 
107
  def _phase2_wls(X, W, seeds, cfg: HPCConfig):
108
  n = X.shape[0]
 
879
  x = x.ravel()
880
  n_blocks = x.size // QK_K
881
 
882
+ seg_perm = None
883
+ seg_labels = None
884
+ seg_rounds = 0
885
+ seg_flips = 0
886
+
887
+ if cfg.segment:
888
+ from .segmentation import apply_order, q2k_proxy_cfg, segment_stage
889
+
890
+ X_p = x.reshape(n_blocks, QK_K)
891
+ imp2d = (
892
+ None
893
+ if importance is None
894
+ else np.asarray(importance, dtype=np.float32).ravel().reshape(n_blocks, QK_K)
895
+ )
896
+ clamp_val0 = float(seed_clamp_value(x, cfg))
897
+ proxy = q2k_proxy_cfg(clamp_val0, cfg)
898
+ seg_perm, seg_labels, seg_rounds = segment_stage(
899
+ X_p, imp2d, cfg, proxy, max(2, int(cfg.segment_k))
900
+ )
901
+ X_p = apply_order(X_p, seg_perm).astype(np.float32)
902
+ x = np.ascontiguousarray(X_p.ravel())
903
+ if imp2d is not None:
904
+ importance = np.ascontiguousarray(
905
+ apply_order(imp2d, seg_perm).astype(np.float32).ravel()
906
+ )
907
+ X_p = None
908
+
909
  if cfg.device != "cpu":
910
  from .cuda_backend import cuda_is_available, quantize_cuda
911
 
 
914
  ):
915
  dev_str = "cuda" if cfg.device == "auto" else cfg.device
916
  res = quantize_cuda(
917
+ x if seg_labels is not None else weights,
918
  importance,
919
  row_width,
920
  cfg,
 
1241
  else:
1242
  weighted += float(np.dot(W[i].astype(np.float64), e * e))
1243
 
1244
+ if seg_perm is not None:
1245
+ # informational: how many labels a channelless decoder would
1246
+ # mis-derive from the stored slot values
1247
+ from .segmentation import band_labels
1248
+
1249
+ Xh = np.stack([b.dequantize() for b in blocks]).astype(np.float64)
1250
+ labels_slot = np.take_along_axis(seg_labels, seg_perm, axis=1)
1251
+ labels_rec = band_labels(Xh, max(2, int(cfg.segment_k)))
1252
+ seg_flips = int(np.count_nonzero(labels_rec != labels_slot))
1253
+
1254
  rmse = float(np.sqrt(total_sse / x.size))
1255
  stats = TensorStats(
1256
  n_blocks=n_blocks,
 
1263
  factor_floor_used=factor_floor_used,
1264
  candidate_floor_used=candidate_floor_used,
1265
  therm_improved=therm_improved,
1266
+ segment_rounds=seg_rounds,
1267
+ segment_flips=seg_flips,
1268
  )
1269
  _emit(
1270
  "Phase 5: Tensor Complete",
 
1291
  factor_objectives=factor_objectives,
1292
  config=cfg,
1293
  stats=stats,
1294
+ segment_perm=seg_perm,
1295
+ segment_labels=seg_labels,
1296
  )
1297
  if cfg.verbose:
1298
  _report(stats)
 
1314
 
1315
  def _report(stats: TensorStats) -> None:
1316
  extra = f" therm={stats.therm_improved}" if stats.therm_improved else ""
1317
+ if stats.segment_rounds:
1318
+ extra += f" seg(rounds={stats.segment_rounds}, flips={stats.segment_flips})"
1319
  print(
1320
  f"Q2_K V2: blocks={stats.n_blocks} rmse={stats.rmse:.6e} "
1321
  f"rmse/sigma={stats.rmse_over_sigma:.5f} temp={stats.temperature:.3e} "
q3_k.py CHANGED
@@ -1005,6 +1005,18 @@ def quantize_tensor_q3_k(
1005
  else:
1006
  W_raw = None
1007
 
 
 
 
 
 
 
 
 
 
 
 
 
1008
  cuda_ok = cfg.device != "cpu" and cuda_is_available()
1009
  use_cuda = cuda_ok and (
1010
  cfg.device.startswith("cuda") or (cfg.device == "auto" and n_blocks >= 64)
@@ -1041,4 +1053,17 @@ def quantize_tensor_q3_k(
1041
  total_sse += sse_b
1042
 
1043
  rmse = float(np.sqrt(total_sse / x.size))
 
 
 
 
 
 
 
 
 
 
 
 
 
1044
  return b"".join(chunks), n_blocks, rmse
 
1005
  else:
1006
  W_raw = None
1007
 
1008
+ if cfg.segment:
1009
+ from .segmentation import apply_order, band_labels, q3k_proxy, segment_stage
1010
+
1011
+ seg_order, seg_labels, seg_rounds = segment_stage(
1012
+ X, W_raw, cfg, q3k_proxy, max(2, int(cfg.segment_k))
1013
+ )
1014
+ X = np.ascontiguousarray(apply_order(X.astype(np.float64), seg_order)).astype(np.float32)
1015
+ if W_raw is not None:
1016
+ W_raw = np.ascontiguousarray(
1017
+ apply_order(W_raw.astype(np.float64), seg_order)
1018
+ ).astype(np.float32)
1019
+
1020
  cuda_ok = cfg.device != "cpu" and cuda_is_available()
1021
  use_cuda = cuda_ok and (
1022
  cfg.device.startswith("cuda") or (cfg.device == "auto" and n_blocks >= 64)
 
1053
  total_sse += sse_b
1054
 
1055
  rmse = float(np.sqrt(total_sse / x.size))
1056
+
1057
+ if cfg.segment:
1058
+ xh = np.asarray(dequantize_q3_k_bytes(b"".join(chunks), n_blocks), dtype=np.float64).reshape(
1059
+ n_blocks, QK3_K
1060
+ )
1061
+ labels_slot = np.take_along_axis(seg_labels, seg_order, axis=1)
1062
+ labels_rec = band_labels(xh, max(2, int(cfg.segment_k)))
1063
+ seg_flips = int(np.count_nonzero(labels_rec != labels_slot))
1064
+ _log.info(
1065
+ "Q3_K segmentation: rounds=%d channelless flips=%d rmse=%.6e",
1066
+ seg_rounds, seg_flips, rmse,
1067
+ )
1068
+
1069
  return b"".join(chunks), n_blocks, rmse
q4_0.py CHANGED
@@ -28,6 +28,8 @@ per 18-byte block = 4.5 bpw):
28
  from __future__ import annotations
29
 
30
  from math import ceil
 
 
31
  import numpy as np
32
 
33
  try:
@@ -48,6 +50,8 @@ QK4_0 = 32
48
  Q4_0_BLOCK_BYTES = 18
49
  Q4_N_CAND = 24
50
  Q4_N_STATES = 6
 
 
51
  Q4_N_LEVELS = 16
52
 
53
 
@@ -583,6 +587,16 @@ def quantize_tensor_q4_0(
583
  else:
584
  W_raw = None
585
 
 
 
 
 
 
 
 
 
 
 
586
  use_cuda = cfg.device != "cpu" and cuda_is_available() and (
587
  cfg.device.startswith("cuda") or (cfg.device == "auto" and n_blocks >= 64)
588
  )
 
28
  from __future__ import annotations
29
 
30
  from math import ceil
31
+ import logging
32
+
33
  import numpy as np
34
 
35
  try:
 
50
  Q4_0_BLOCK_BYTES = 18
51
  Q4_N_CAND = 24
52
  Q4_N_STATES = 6
53
+
54
+ _log = logging.getLogger(__name__)
55
  Q4_N_LEVELS = 16
56
 
57
 
 
587
  else:
588
  W_raw = None
589
 
590
+ if cfg.segment:
591
+ # Q4_0's unit block (32 weights, one scale+min) has no per-band
592
+ # sub-scale dof to align, position-ascending k=1 layout is the
593
+ # identity permutation, and the permutation channel would pay 8
594
+ # bits per weight for nothing. Explicit no-op for now.
595
+ _log.info(
596
+ "Q4_0 segmentation disabled: unit blocks (d, min) have no band dof "
597
+ "to align (k=1 layout is the identity)"
598
+ )
599
+
600
  use_cuda = cfg.device != "cpu" and cuda_is_available() and (
601
  cfg.device.startswith("cuda") or (cfg.device == "auto" and n_blocks >= 64)
602
  )
segmentation.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Self-describing per-block segmentation (V2-only stage).
2
+
3
+ Deterministic cluster labels are recomputed from a block's own DEQUANTIZED
4
+ values with an unweighted K-means (Lloyd, quantile init, centers sorted
5
+ ascending, stable value tie-breaks). Because the label rule is a pure
6
+ function of the stored slot values, the decoder recovers the identical
7
+ slot->position permutation by recomputation -- no sidecar storage, the
8
+ GGUF block layout is untouched.
9
+
10
+ Encode loop:
11
+
12
+ order_0 = S(x) (labels from the originals)
13
+ x_slot = x[order_0]
14
+ order_{n+1} = S(x̂_slot) (labels from the dequantized view)
15
+ ... repeat until the permutation is stable; store quantize(x[order]).
16
+
17
+ Decode:
18
+
19
+ x̂_slot = dequantize(payload)
20
+ order = S(x̂_slot)
21
+ x̂_orig = scatter(x̂_slot, order)
22
+
23
+ Q2_K / Q3_K segment the 256-weight superblock into ``k`` bands aligned with
24
+ the 16-weight sub-scale groups; Q4_0's unit block is 32 wide with a single
25
+ scale, so it uses ``k=1`` (pure ascending value order, which the spectral
26
+ pair solvers can exploit).
27
+ """
28
+
29
+ from __future__ import annotations
30
+
31
+ import numpy as np
32
+
33
+ from .config import HPCConfig
34
+ from .fp16 import nearest_int
35
+
36
+ _ROW_CHUNK = 4096
37
+
38
+
39
+ def _rowwise(fn, v: np.ndarray, chunk: int = _ROW_CHUNK) -> np.ndarray:
40
+ n = v.shape[0]
41
+ if n <= chunk:
42
+ return fn(v)
43
+ return np.concatenate([fn(v[s : s + chunk]) for s in range(0, n, chunk)], axis=0)
44
+
45
+
46
+ def band_labels(values: np.ndarray, k: int, iters: int = 12) -> np.ndarray:
47
+ """Per-row unweighted K-means labels; centers sorted ascending (canonical)."""
48
+ v = np.asarray(values, dtype=np.float64)
49
+ if k <= 1:
50
+ return np.zeros(v.shape, dtype=np.int64)
51
+
52
+ def _run(v: np.ndarray) -> np.ndarray:
53
+ qs = (np.arange(k) + 0.5) / k
54
+ c = np.quantile(v, qs, axis=1).T
55
+ a = None
56
+ for _ in range(iters):
57
+ a = np.abs(v[:, :, None] - c[:, None, :]).argmin(axis=2)
58
+ mask = a[:, :, None] == np.arange(k)[None, None, :]
59
+ num = (v[:, :, None] * mask).sum(axis=1)
60
+ cnt = mask.sum(axis=1)
61
+ c = np.sort(num / np.maximum(cnt, 1), axis=1)
62
+ return np.abs(v[:, :, None] - c[:, None, :]).argmin(axis=2)
63
+
64
+ return _rowwise(_run, v).astype(np.int64)
65
+
66
+
67
+ def make_order(labels: np.ndarray, values: np.ndarray | None = None) -> np.ndarray:
68
+ """Per-row slot->original index: band-major layout, position-ascending
69
+ inside each band (stable argsort). With this layout the label channel
70
+ (one label per ORIGINAL position) inverts the permutation exactly, and
71
+ per-16 ladder error depends only on band width, not intra-band order.
72
+ ``values`` is accepted but ignored (kept for call-site compatibility)."""
73
+
74
+ def _run(labels: np.ndarray) -> np.ndarray:
75
+ return np.argsort(labels, axis=1, kind="stable").astype(np.int64)
76
+
77
+ n = labels.shape[0]
78
+ if n <= _ROW_CHUNK:
79
+ return _run(labels)
80
+ outs = []
81
+ for s in range(0, n, _ROW_CHUNK):
82
+ e = min(s + _ROW_CHUNK, n)
83
+ outs.append(_run(labels[s:e]))
84
+ return np.concatenate(outs, axis=0)
85
+
86
+
87
+ def apply_order(a: np.ndarray, order: np.ndarray) -> np.ndarray:
88
+ """Slot view: out[b, s] = a[b, order[b, s]]."""
89
+ return np.take_along_axis(a, order, axis=1)
90
+
91
+
92
+ def invert_order(order: np.ndarray) -> np.ndarray:
93
+ """Inverse permutation: inv[b, orig] = slot."""
94
+ inv = np.empty_like(order)
95
+ rows = np.arange(order.shape[0])[:, None]
96
+ cols = np.arange(order.shape[1])[None, :]
97
+ inv[rows, order] = cols
98
+ return inv
99
+
100
+
101
+ def recover_order(labels: np.ndarray) -> np.ndarray:
102
+ """Exact inversion given a label channel (band-major, position-ascending)."""
103
+ return make_order(labels)
104
+
105
+
106
+ def scatter_to_original(xh_slots: np.ndarray, order: np.ndarray) -> np.ndarray:
107
+ """Inverse of apply_order: out[b, order[b, s]] = xh_slots[b, s]."""
108
+ out = np.empty(xh_slots.shape, dtype=xh_slots.dtype)
109
+ np.put_along_axis(out, order, xh_slots, axis=1)
110
+ return out
111
+
112
+
113
+ def segment_stage(
114
+ X: np.ndarray,
115
+ W: np.ndarray | None,
116
+ cfg: HPCConfig,
117
+ proxy,
118
+ k: int,
119
+ ) -> tuple[np.ndarray, np.ndarray, int]:
120
+ """Run the label fixed-point loop.
121
+
122
+ Returns ``(order, labels, rounds)`` where ``labels`` holds one band id
123
+ per ORIGINAL position (this is the label channel the decoder needs for
124
+ exact inversion) and ``order`` is the slot->original permutation.
125
+ ``proxy(xg, wg)`` must return a dequantized slot view from a cheap
126
+ quantize/dequantize of the permuted view; labels are refined from that
127
+ proxy reconstruction so the stored channel matches what a channelless
128
+ re-derivation would see.
129
+ """
130
+ Xo = np.asarray(X, dtype=np.float64)
131
+ Wo = None if W is None else np.asarray(W, dtype=np.float64)
132
+ k = max(1, int(k))
133
+ labels = band_labels(Xo, k) # label channel, ORIGINAL position order
134
+ order = make_order(labels)
135
+ wg = None if Wo is None else np.asarray(Wo, dtype=np.float64)
136
+ rounds = 0
137
+ for _ in range(max(1, int(cfg.segment_iters))):
138
+ rounds += 1
139
+ xg = apply_order(Xo, order)
140
+ wgg = None if wg is None else apply_order(wg, order)
141
+ xh = proxy(xg, wgg) # slot-ordered reconstruction
142
+ labels_slot = band_labels(xh, k) # labels in SLOT order
143
+ labels = np.empty_like(labels_slot)
144
+ np.put_along_axis(labels, order, labels_slot, axis=1) # back to original order
145
+ order = make_order(labels)
146
+ return order, labels, rounds
147
+
148
+
149
+ def q2k_proxy_cfg(clamp_val: float, cfg: HPCConfig):
150
+ """Cheap phase1+2 proxy for Q2_K label iteration (GGML seeds + WLS)."""
151
+ from .q2k import codes_from_params_batch, dequantize_params_batch
152
+ from .seeds import build_seeds
153
+
154
+ def _proxy(xg: np.ndarray, wg: np.ndarray | None) -> np.ndarray:
155
+ seeds = build_seeds(xg, wg, cfg, clamp_val)
156
+ d, m = _wls(xg, wg, seeds, cfg)
157
+ sd = np.where(d > 1.0e-15, d, 1.0).astype(np.float32)
158
+ sm = np.where(m > 1.0e-15, m, 1.0).astype(np.float32)
159
+ ls = np.where(
160
+ d[:, None] > 1.0e-15,
161
+ np.clip(nearest_int(seeds["scales"] / sd[:, None]), 0, 15),
162
+ 0,
163
+ ).astype(np.uint8)
164
+ lm = np.where(
165
+ m[:, None] > 1.0e-15,
166
+ np.clip(nearest_int(seeds["mins"] / sm[:, None]), 0, 15),
167
+ 0,
168
+ ).astype(np.uint8)
169
+ codes = codes_from_params_batch(xg, d, m, ls, lm)
170
+ return dequantize_params_batch(d, m, ls, lm, codes).astype(np.float64)
171
+
172
+ return _proxy
173
+
174
+
175
+ def _wls(xg, wg, seeds, cfg):
176
+ """Row-level WLS (same iteration as pipeline._phase2_wls, kept local to
177
+ avoid a circular import)."""
178
+ from .fp16 import round_fp16
179
+
180
+ n = xg.shape[0]
181
+ per_blk_clamp = np.zeros((n, 1), dtype=np.float32)
182
+ clipped = xg.astype(np.float32)
183
+ base_d = seeds["d"].astype(np.float64).copy()
184
+ base_m = seeds["dmin"].astype(np.float64).copy()
185
+ ls = seeds["ls"].copy()
186
+ lm = seeds["lm"].copy()
187
+ scales = seeds["scales"].astype(np.float64)
188
+ mins = seeds["mins"].astype(np.float64)
189
+ ww = np.ones_like(xg, dtype=np.float64) if wg is None else wg.astype(np.float64)
190
+
191
+ from .q2k import codes_from_params_batch
192
+
193
+ for _ in range(5):
194
+ codes = codes_from_params_batch(clipped, base_d, base_m, ls, lm).astype(np.float64)
195
+ A = np.repeat(ls.astype(np.float64), 16, axis=1) * codes
196
+ B = np.repeat(lm.astype(np.float64), 16, axis=1)
197
+ Saa = np.sum(ww * A * A, axis=1)
198
+ Sab = np.sum(ww * A * B, axis=1)
199
+ Sbb = np.sum(ww * B * B, axis=1)
200
+ Sxa = np.sum(ww * clipped.astype(np.float64) * A, axis=1)
201
+ Sxb = np.sum(ww * clipped.astype(np.float64) * B, axis=1)
202
+ det = Saa * Sbb - Sab * Sab
203
+ valid = det > 1.0e-30
204
+ safe_det = np.where(valid, det, 1.0)
205
+ d_new = (Sbb * Sxa - Sab * Sxb) / safe_det
206
+ m_new = (Sab * Sxa - Saa * Sxb) / safe_det
207
+ upd_d = valid & (d_new > 0.0) & np.isfinite(d_new)
208
+ upd_m = valid & (m_new > 0.0) & np.isfinite(m_new)
209
+ base_d = np.where(upd_d, round_fp16(d_new.astype(np.float32)).astype(np.float64), base_d)
210
+ base_m = np.where(upd_m, round_fp16(m_new.astype(np.float32)).astype(np.float64), base_m)
211
+ safe_d = np.where(base_d > 1.0e-15, base_d, 1.0)
212
+ safe_m = np.where(base_m > 1.0e-15, base_m, 1.0)
213
+ ls = np.where(
214
+ base_d[:, None] > 1.0e-15,
215
+ np.clip(nearest_int(scales / safe_d[:, None]), 0, 15),
216
+ 0,
217
+ ).astype(np.uint8)
218
+ lm = np.where(
219
+ base_m[:, None] > 1.0e-15,
220
+ np.clip(nearest_int(mins / safe_m[:, None]), 0, 15),
221
+ 0,
222
+ ).astype(np.uint8)
223
+ return base_d, base_m
224
+
225
+
226
+ def q3k_proxy(xg: np.ndarray, wg: np.ndarray | None) -> np.ndarray:
227
+ """Cheap per-16 sub-block 3-bit ladder proxy for Q3_K label iteration."""
228
+ from .seeds import make_qkx2_batch
229
+
230
+ n, B = xg.shape
231
+ u = np.asarray(xg, dtype=np.float64).reshape(n * (B // 16), 16)
232
+ if wg is None:
233
+ wu = np.ones_like(u)
234
+ else:
235
+ wu = np.asarray(wg, dtype=np.float64).reshape(n * (B // 16), 16)
236
+ scale, xmin, codes = make_qkx2_batch(u, wu, nmax=7)
237
+ deq = scale[:, None] * codes + xmin[:, None]
238
+ return deq.reshape(n, B)
239
+
240
+
241
+ def q4_0_proxy(xg: np.ndarray, wg: np.ndarray | None) -> np.ndarray:
242
+ """GGML-reference Q4_0 proxy (single scale + min per 32-block)."""
243
+ x = np.asarray(xg, dtype=np.float64)
244
+ xmin = x.min(axis=1, keepdims=True)
245
+ xmax = x.max(axis=1, keepdims=True)
246
+ d = np.maximum((xmax - xmin) / 15.0, 1.0e-30)
247
+ q = np.clip(nearest_int(((x - xmin) / d).astype(np.float32)), 0, 15)
248
+ return xmin + d * q.astype(np.float64)