CompressedGemma commited on
Commit
dc86eef
·
verified ·
1 Parent(s): 869aa1c

Delete segmentation.py

Browse files
Files changed (1) hide show
  1. segmentation.py +0 -480
segmentation.py DELETED
@@ -1,480 +0,0 @@
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
- # ---- label channel codec (in-format persistence inside a GGUF KV) --------
114
- #
115
- # Layout: magic "HQS1" | uint8 k | uint8 enc | uint32 n_labels (little-endian)
116
- # | payload.
117
- # enc=0: packs labels 0..15 as nibbles (2 labels per byte; full per-block or small tensors)
118
- # enc=1: stores raw uint8 labels (k > 16 only)
119
- # enc=2: generalized permutation lookup table with 1-byte block indices (table_size in 4..256):
120
- # uint16 table_size | uint8 table[table_size * 128] | uint8 block_idx[n_blocks]
121
- # enc=3: generalized permutation lookup table with 2-byte block indices (table_size in 257..65535):
122
- # uint16 table_size | uint8 table[table_size * 128] | uint16 block_idx[n_blocks]
123
-
124
- import struct as _struct
125
-
126
- SEG_MAGIC = b"HQS1"
127
- SEG_TABLE_SIZE = 256
128
-
129
-
130
- def _pack_nibbles_1d(flat: np.ndarray) -> bytes:
131
- lo = flat[0::2].astype(np.uint8)
132
- hi = flat[1::2].astype(np.uint8)
133
- if hi.size < lo.size:
134
- hi = np.concatenate([hi, np.zeros(1, dtype=np.uint8)])
135
- return (lo | (hi << 4)).astype(np.uint8).tobytes()
136
-
137
-
138
- def _unpack_nibbles_1d(data: bytes, n: int) -> np.ndarray:
139
- raw = np.frombuffer(data, dtype=np.uint8)
140
- out = np.empty(raw.size * 2, dtype=np.uint8)
141
- out[0::2] = raw & np.uint8(0x0F)
142
- out[1::2] = raw >> np.uint8(4)
143
- return out[:n]
144
-
145
-
146
- def label_channel_bytes(n: int, k: int, table_size: int = SEG_TABLE_SIZE) -> int:
147
- """Exact content size of the packed label channel."""
148
- table_size = int(table_size)
149
- if int(k) <= 16:
150
- if table_size > 0 and n > table_size * 256 and n % 256 == 0:
151
- n_blocks = n // 256
152
- idx_bytes = 1 if table_size <= 256 else 2
153
- return 10 + 2 + table_size * 128 + n_blocks * idx_bytes
154
- return 10 + (n + 1) // 2
155
- return 10 + n
156
-
157
-
158
- def pack_label_channel(labels: np.ndarray, k: int, table_size: int = SEG_TABLE_SIZE) -> bytes:
159
- k = int(k)
160
- table_size = int(table_size)
161
- # Fast path when segment_stage attached pre-packed LUT data
162
- lut_cache = getattr(labels, "_hpcq_lut_bytes", None)
163
- if lut_cache is not None and lut_cache[0] == (k, table_size):
164
- return lut_cache[1]
165
-
166
- flat = np.ascontiguousarray(labels, dtype=np.uint8).ravel()
167
- n = int(flat.size)
168
- if k <= 16:
169
- if flat.size and int(flat.max()) > 15:
170
- raise ValueError(f"label {int(flat.max())} does not fit nibble packing (k={k})")
171
- if table_size > 0 and n > table_size * 256 and n % 256 == 0:
172
- enc = 2 if table_size <= 256 else 3
173
- idx_dt = np.uint8 if enc == 2 else np.dtype("<u2")
174
- n_blocks = n // 256
175
- rows = flat.reshape(n_blocks, 256)
176
- lo = rows[:, 0::2]
177
- hi = rows[:, 1::2]
178
- packed_rows = (lo | (hi << 4)).astype(np.uint8) # (n_blocks, 128)
179
- row_dt = np.dtype((np.void, 128))
180
- row_views = np.ascontiguousarray(packed_rows).view(row_dt).ravel()
181
- uniq_views, inv_idx, counts = np.unique(
182
- row_views, return_inverse=True, return_counts=True
183
- )
184
- uniq_rows = uniq_views.view(np.uint8).reshape(-1, 128)
185
- table = np.zeros((table_size, 128), dtype=np.uint8)
186
- if uniq_rows.shape[0] <= table_size:
187
- table[: uniq_rows.shape[0]] = uniq_rows
188
- block_idx = inv_idx.astype(idx_dt)
189
- else:
190
- top_u = np.argsort(-counts)[:table_size]
191
- table[:] = uniq_rows[top_u]
192
- remap = np.zeros(uniq_rows.shape[0], dtype=idx_dt)
193
- remap[top_u] = np.arange(table_size, dtype=idx_dt)
194
- block_idx = remap[inv_idx]
195
- data = _struct.pack("<H", table_size) + table.tobytes() + block_idx.tobytes()
196
- else:
197
- enc = 0
198
- data = _pack_nibbles_1d(flat)
199
- else:
200
- enc = 1
201
- if flat.size and (int(labels.max()) > 255 or int(labels.min()) < 0):
202
- raise ValueError("labels must be in [0, 255] for 8-bit packing")
203
- data = flat.tobytes()
204
- return SEG_MAGIC + bytes([k, enc]) + _struct.pack("<I", n) + data
205
-
206
-
207
- def unpack_label_channel(content: bytes) -> tuple[int, np.ndarray]:
208
- if content[:4] != SEG_MAGIC:
209
- raise ValueError(f"bad label channel magic {content[:4]!r}")
210
- k = int(content[4])
211
- enc = int(content[5])
212
- n = _struct.unpack("<I", content[6:10])[0]
213
- data = content[10:]
214
- if enc == 0:
215
- out = _unpack_nibbles_1d(data, n)
216
- elif enc in (2, 3):
217
- n_blocks = n // 256
218
- tsize = _struct.unpack("<H", data[:2])[0]
219
- tbytes = tsize * 128
220
- table = _unpack_nibbles_1d(data[2 : 2 + tbytes], tsize * 256).reshape(
221
- tsize, 256
222
- )
223
- if enc == 2:
224
- idx = np.frombuffer(data[2 + tbytes : 2 + tbytes + n_blocks], dtype=np.uint8)
225
- else:
226
- idx = np.frombuffer(
227
- data[2 + tbytes : 2 + tbytes + 2 * n_blocks], dtype=np.dtype("<u2")
228
- )
229
- out = table[idx].reshape(-1)
230
- else:
231
- out = np.frombuffer(data, dtype=np.uint8).copy()
232
- return k, out[:n]
233
-
234
-
235
- def _order_to_canonical_labels(ord_row: np.ndarray, k: int = 16) -> np.ndarray:
236
- lab = np.empty(256, dtype=np.int64)
237
- step = 256 // k
238
- for g in range(k):
239
- s0 = g * step
240
- s1 = 256 if g == k - 1 else (g + 1) * step
241
- sub = np.sort(ord_row[s0:s1])
242
- lab[sub] = g
243
- return lab
244
-
245
-
246
- def _segment_stage_dense(
247
- Xo: np.ndarray,
248
- Wo: np.ndarray | None,
249
- cfg: HPCConfig,
250
- proxy,
251
- k: int,
252
- ) -> tuple[np.ndarray, np.ndarray, int]:
253
- labels = band_labels(Xo, k)
254
- order = make_order(labels)
255
- rounds = 0
256
- for _ in range(max(1, int(cfg.segment_iters))):
257
- rounds += 1
258
- xg = apply_order(Xo, order)
259
- wgg = None if Wo is None else apply_order(Wo, order)
260
- xh = proxy(xg, wgg)
261
- labels_slot = band_labels(xh, k)
262
- labels = np.empty_like(labels_slot)
263
- np.put_along_axis(labels, order, labels_slot, axis=1)
264
- order = make_order(labels)
265
- return order, labels, rounds
266
-
267
-
268
- class _LUTLabels(np.ndarray):
269
- _hpcq_lut_bytes: tuple[tuple[int, int], bytes] | None = None
270
-
271
-
272
- def segment_stage(
273
- X: np.ndarray,
274
- W: np.ndarray | None,
275
- cfg: HPCConfig,
276
- proxy,
277
- k: int,
278
- ) -> tuple[np.ndarray, np.ndarray, int]:
279
- """Run the label fixed-point loop.
280
-
281
- For tensors with ``n_blocks <= M`` (or ``k > 16``), refines a per-block
282
- label vector directly. For larger tensors (``n_blocks > M, k <= 16``),
283
- constructs an ``M``-entry generalized permutation lookup table (Entry 0 =
284
- natural order, Entries 1..2 = column RMS/abs profiles, Entries 3..M-1 =
285
- prototype blocks refined by ``_segment_stage_dense``) and assigns each
286
- superblock its optimal table index in chunked float32 / CUDA batches.
287
- """
288
- k = max(1, int(k))
289
- n_blocks, B = X.shape
290
- M = int(getattr(cfg, "segment_table_size", SEG_TABLE_SIZE))
291
- if M <= 0 or n_blocks <= M or B != 256 or k > 16:
292
- Xo = np.asarray(X, dtype=np.float64)
293
- Wo = None if W is None else np.asarray(W, dtype=np.float64)
294
- return _segment_stage_dense(Xo, Wo, cfg, proxy, k)
295
-
296
- Xf = np.ascontiguousarray(X, dtype=np.float32)
297
- table_labels = np.empty((M, 256), dtype=np.uint8)
298
- # Entry 0: natural order (monotone labels so make_order is identity [0..255])
299
- table_labels[0] = _order_to_canonical_labels(np.arange(256, dtype=np.int64), k).astype(np.uint8)
300
- # Entry 1 & 2: column-wide RMS and abs profiles (sampled across up to 4096 blocks)
301
- s_step = max(1, n_blocks // 4096)
302
- X_sample = Xf[::s_step].astype(np.float64)
303
- col_rms = np.sqrt(np.mean(X_sample * X_sample, axis=0))
304
- table_labels[1] = _order_to_canonical_labels(np.argsort(col_rms, kind="stable"), k).astype(np.uint8)
305
- col_abs = np.mean(np.abs(X_sample), axis=0)
306
- table_labels[2] = _order_to_canonical_labels(np.argsort(col_abs, kind="stable"), k).astype(np.uint8)
307
-
308
- # Entries 3..M-1: up to 253 prototype blocks refined with the proxy loop,
309
- # plus fast direct band_labels prototypes when M > 256
310
- n_need = M - 3
311
- n_dense = min(n_need, 253)
312
- proto_idx = np.linspace(0, n_blocks - 1, n_dense, dtype=np.int64)
313
- Xp = Xf[proto_idx].astype(np.float64)
314
- Wp = None if W is None else np.asarray(W[proto_idx], dtype=np.float64)
315
- ord_p, _, rounds = _segment_stage_dense(Xp, Wp, cfg, proxy, k)
316
- for i in range(n_dense):
317
- table_labels[3 + i] = _order_to_canonical_labels(ord_p[i], k).astype(np.uint8)
318
- if n_need > n_dense:
319
- extra_idx = np.linspace(1, n_blocks - 2, n_need - n_dense, dtype=np.int64)
320
- ord_e = make_order(band_labels(Xf[extra_idx].astype(np.float64), k))
321
- for i in range(n_need - n_dense):
322
- table_labels[3 + n_dense + i] = _order_to_canonical_labels(ord_e[i], k).astype(np.uint8)
323
-
324
- table_orders = make_order(table_labels) # (M, 256) int64
325
-
326
- # Build sub-block projection matrix P: (256, M * 16)
327
- P = np.zeros((256, M * 16), dtype=np.float32)
328
- for m in range(M):
329
- for g in range(16):
330
- P[table_orders[m, 16 * g : 16 * g + 16], m * 16 + g] = 0.25
331
-
332
- topK = min(4, M)
333
- best_m = np.zeros(n_blocks, dtype=np.int64)
334
- nmax_sub = 3.0 if str(getattr(cfg, "quant_format", "q2_k")) != "q3_k" else 7.0
335
-
336
- use_torch_cuda = False
337
- if getattr(cfg, "device", "auto") != "cpu":
338
- try:
339
- import torch
340
- use_torch_cuda = torch.cuda.is_available()
341
- except Exception:
342
- use_torch_cuda = False
343
-
344
- if use_torch_cuda:
345
- import torch
346
- dev_str = "cuda" if cfg.device == "auto" else cfg.device
347
- dev = torch.device(dev_str)
348
- P_t = torch.from_numpy(P).to(dev)
349
- orders_t = torch.from_numpy(table_orders).to(dev)
350
- chunk = 16384
351
- with torch.no_grad():
352
- for s in range(0, n_blocks, chunk):
353
- e = min(s + chunk, n_blocks)
354
- xb = torch.from_numpy(Xf[s:e]).to(dev)
355
- sc = (xb @ P_t).square().view(-1, M, 16).sum(dim=2) + 0.5 * (
356
- (xb.abs() @ P_t).square().view(-1, M, 16).sum(dim=2)
357
- )
358
- top_idx = torch.topk(sc, k=topK, dim=1).indices
359
- top_idx[:, 0] = 0
360
- ords = orders_t[top_idx]
361
- u = torch.gather(xb.unsqueeze(1).expand(-1, topK, -1), 2, ords).view(
362
- -1, topK, 16, 16
363
- )
364
- umin = torch.clamp_max(u.amin(dim=3, keepdim=True), 0.0)
365
- umax = u.amax(dim=3, keepdim=True)
366
- step = torch.clamp_min((umax - umin) * (1.0 / nmax_sub), 1.0e-30)
367
- q = torch.clamp(torch.round((u - umin) / step), 0.0, nmax_sub)
368
- err = u - (umin + step * q)
369
- if W is not None:
370
- wb = torch.from_numpy(np.ascontiguousarray(W[s:e], dtype=np.float32)).to(dev)
371
- wg = torch.gather(wb.unsqueeze(1).expand(-1, topK, -1), 2, ords).view(
372
- -1, topK, 16, 16
373
- )
374
- sse = (wg * err.square()).sum(dim=(2, 3))
375
- else:
376
- sse = err.square().sum(dim=(2, 3))
377
- best_local = sse.argmin(dim=1)
378
- best_m[s:e] = (
379
- top_idx[torch.arange(e - s, device=dev), best_local].cpu().numpy()
380
- )
381
- else:
382
- chunk = 8192
383
- for s in range(0, n_blocks, chunk):
384
- e = min(s + chunk, n_blocks)
385
- xb = Xf[s:e]
386
- sc = ((xb @ P) ** 2).reshape(e - s, M, 16).sum(axis=2) + 0.5 * (
387
- (np.abs(xb) @ P) ** 2
388
- ).reshape(e - s, M, 16).sum(axis=2)
389
- top_idx = np.argpartition(-sc, topK - 1, axis=1)[:, :topK]
390
- top_idx[:, 0] = 0
391
- ords = table_orders[top_idx]
392
- u = np.take_along_axis(xb[:, None, :], ords, axis=2).reshape(
393
- e - s, topK, 16, 16
394
- )
395
- umin = np.minimum(u.min(axis=3, keepdims=True), 0.0)
396
- umax = u.max(axis=3, keepdims=True)
397
- step = np.maximum((umax - umin) * (1.0 / nmax_sub), 1.0e-30)
398
- q = np.clip(np.rint((u - umin) / step), 0.0, nmax_sub)
399
- err = u - (umin + step * q)
400
- if W is not None:
401
- wb = np.ascontiguousarray(W[s:e], dtype=np.float32)
402
- wg = np.take_along_axis(wb[:, None, :], ords, axis=2).reshape(
403
- e - s, topK, 16, 16
404
- )
405
- sse = np.sum(wg * err * err, axis=(2, 3))
406
- else:
407
- sse = np.sum(err * err, axis=(2, 3))
408
- best_local = np.argmin(sse, axis=1)
409
- best_m[s:e] = top_idx[np.arange(e - s), best_local]
410
-
411
- out_labels = table_labels[best_m].view(_LUTLabels)
412
- enc = 2 if M <= 256 else 3
413
- idx_dt = np.uint8 if enc == 2 else np.dtype("<u2")
414
- lo = table_labels[:, 0::2]
415
- hi = table_labels[:, 1::2]
416
- table_nib = (lo | (hi << 4)).astype(np.uint8)
417
- raw_lut = (
418
- SEG_MAGIC
419
- + bytes([k, enc])
420
- + _struct.pack("<I", n_blocks * 256)
421
- + _struct.pack("<H", M)
422
- + table_nib.tobytes()
423
- + best_m.astype(idx_dt).tobytes()
424
- )
425
- out_labels._hpcq_lut_bytes = ((k, M), raw_lut)
426
- return table_orders[best_m], out_labels, rounds
427
-
428
-
429
- def q2k_proxy_cfg(clamp_val: float, cfg: HPCConfig):
430
- """Cheap phase1+2 proxy for Q2_K label iteration (GGML seeds + WLS)."""
431
- from .q2k import codes_from_params_batch, dequantize_params_batch
432
- from .seeds import build_seeds
433
- from .pipeline import _phase2_wls
434
-
435
- def _proxy(xg: np.ndarray, wg: np.ndarray | None) -> np.ndarray:
436
- seeds = build_seeds(xg, wg, cfg, clamp_val)
437
- d, m = _phase2_wls(xg, wg, seeds, cfg)
438
- sd = np.where(d > 1.0e-15, d, 1.0).astype(np.float32)
439
- sm = np.where(m > 1.0e-15, m, 1.0).astype(np.float32)
440
- ls = np.where(
441
- d[:, None] > 1.0e-15,
442
- np.clip(nearest_int(seeds["scales"] / sd[:, None]), 0, 15),
443
- 0,
444
- ).astype(np.uint8)
445
- lm = np.where(
446
- m[:, None] > 1.0e-15,
447
- np.clip(nearest_int(seeds["mins"] / sm[:, None]), 0, 15),
448
- 0,
449
- ).astype(np.uint8)
450
- codes = codes_from_params_batch(xg, d, m, ls, lm)
451
- return dequantize_params_batch(d, m, ls, lm, codes).astype(np.float64)
452
-
453
- return _proxy
454
-
455
-
456
- def q3k_proxy(xg: np.ndarray, wg: np.ndarray | None) -> np.ndarray:
457
- """Cheap per-16 sub-block 3-bit ladder proxy for Q3_K label iteration."""
458
- from .seeds import make_qkx2_batch
459
-
460
- n, B = xg.shape
461
- u = np.asarray(xg, dtype=np.float64).reshape(n * (B // 16), 16)
462
- if wg is None:
463
- wu = np.ones_like(u)
464
- else:
465
- wu = np.asarray(wg, dtype=np.float64).reshape(n * (B // 16), 16)
466
- # make_qkx2_batch returns (scale, -min, codes) with the GGML positive-dmin
467
- # convention; reconstruction is scale * code - (-min).
468
- scale, neg_min, codes = make_qkx2_batch(u, wu, nmax=7)
469
- deq = scale[:, None] * codes - neg_min[:, None]
470
- return deq.reshape(n, B)
471
-
472
-
473
- def q4_0_proxy(xg: np.ndarray, wg: np.ndarray | None) -> np.ndarray:
474
- """GGML-reference Q4_0 proxy (single scale + min per 32-block)."""
475
- x = np.asarray(xg, dtype=np.float64)
476
- xmin = x.min(axis=1, keepdims=True)
477
- xmax = x.max(axis=1, keepdims=True)
478
- d = np.maximum((xmax - xmin) / 15.0, 1.0e-30)
479
- q = np.clip(nearest_int(((x - xmin) / d).astype(np.float32)), 0, 15)
480
- return xmin + d * q.astype(np.float64)