CompressedGemma commited on
Commit
75e8d10
·
verified ·
1 Parent(s): 746d8ee

Upload 3 files

Browse files
Files changed (3) hide show
  1. gguf.py +115 -7
  2. q3_k.py +14 -2
  3. segmentation.py +56 -0
gguf.py CHANGED
@@ -26,8 +26,16 @@ from .q2k import QK_K, blocks_to_bytes, dequantize_params_batch, unpack_codes
26
  from .q3_k import Q3_K_BLOCK_BYTES, QK3_K, dequantize_q3_k_bytes, quantize_tensor_q3_k
27
  from .q4_0 import Q4_0_BLOCK_BYTES, QK4_0, quantize_tensor_q4_0
28
  from .q1_0 import QK1_0, Q1_0_BLOCK_BYTES, quantize_tensor_q1_0
 
 
 
 
 
 
 
29
 
30
  GGUF_MAGIC = 0x46554747
 
31
  DEFAULT_ALIGNMENT = 32
32
 
33
  GGML_TYPE_F32 = 0
@@ -274,10 +282,15 @@ class GGUFReader:
274
 
275
  def read_tensor(self, name: str) -> tuple[np.ndarray, int]:
276
  raw = self.read_tensor_raw(name)
277
- return _dequantize(raw, self.info(name)), self.info(name).row_width
 
278
 
279
  def iter_tensor_row_chunks(self, name: str, max_elements: int = 8_388_608):
280
- """Stream a tensor in row-aligned float32 chunks to bound peak RAM."""
 
 
 
 
281
  ti = self.info(name)
282
  row = ti.row_width
283
  blk = BLOCK_SIZE[ti.type]
@@ -285,6 +298,10 @@ class GGUFReader:
285
  x, r = self.read_tensor(name)
286
  yield x, r, 0, ti.n_elements // max(r, 1)
287
  return
 
 
 
 
288
  n_rows = ti.n_elements // row
289
  rows_per_chunk = max(1, max_elements // row)
290
  bytes_per_row = (row // blk) * BLOCK_BYTES[ti.type]
@@ -297,8 +314,57 @@ class GGUFReader:
297
  raw = f.read(chunk_bytes)
298
  if len(raw) != chunk_bytes:
299
  raise ValueError(f"{name}: truncated tensor data")
300
- sub_ti = TensorInfo(ti.name, (chunk_elems,), ti.type, 0)
301
- yield _dequantize(raw, sub_ti), row, r0, r1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
302
 
303
 
304
  def _dequantize(raw: bytes, ti: TensorInfo) -> np.ndarray:
@@ -718,6 +784,8 @@ def write_quantized_gguf(
718
  attn_q4 = bool(getattr(cfg, "promote_attn_q4", False))
719
  q_format = str(getattr(cfg, "quant_format", "mixed"))
720
  custom_map = getattr(cfg, "custom_quant_map", None) or None
 
 
721
 
722
  plan: list[str | None] = []
723
  quantized_count = 0
@@ -812,11 +880,24 @@ def write_quantized_gguf(
812
  ftype_code = 11 if has_q3k else (10 if has_q2k else (40 if has_q1_0 else 2))
813
  kv_pairs = []
814
  for key, vtype, raw in reader.kv_pairs:
 
 
815
  if quantized_count and key == "general.file_type" and vtype == 4:
816
  raw = struct.pack("<I", ftype_code)
817
  elif quantized_count and key == "general.quantization_version" and vtype == 4:
818
  raw = struct.pack("<I", 2)
819
  kv_pairs.append((key, vtype, raw))
 
 
 
 
 
 
 
 
 
 
 
820
 
821
  output_path = Path(output_path)
822
  output_path.parent.mkdir(parents=True, exist_ok=True)
@@ -831,10 +912,22 @@ def write_quantized_gguf(
831
  model_t0 = time.time()
832
  with output_path.open("wb") as fout:
833
  fout.write(struct.pack("<IIQQ", GGUF_MAGIC, reader.version, len(out_infos), len(kv_pairs)))
 
834
  for key, vtype, raw in kv_pairs:
835
  _write_string(fout, key)
836
  fout.write(struct.pack("<I", vtype))
 
 
837
  fout.write(raw)
 
 
 
 
 
 
 
 
 
838
  for ti, out_type, out_size, offset in out_infos:
839
  _write_string(fout, ti.name)
840
  fout.write(struct.pack("<I", len(ti.dims)))
@@ -918,6 +1011,8 @@ def write_quantized_gguf(
918
  if len(payload) != out_size:
919
  raise RuntimeError(f"{ti.name}: Q2_K payload size mismatch")
920
  fout.write(payload)
 
 
921
  total_rmse += res.stats.rmse * res.stats.rmse * x.size
922
  total_elements += x.size
923
  total_quantized += 1
@@ -945,14 +1040,25 @@ def write_quantized_gguf(
945
  sse_q3 = 0.0
946
  done_q3_blks = 0
947
  t_q3_start = time.time()
 
948
 
949
  for x_chunk, row, _r0, _r1 in reader.iter_tensor_row_chunks(ti.name, max_elements=8_388_608):
950
  if stop_event is not None and stop_event.is_set():
951
  raise InterruptedError("Quantization cancelled by user")
952
  imp_chunk = expand_importance(raw_imp, x_chunk.size, row) if has_imat else None
953
- q3_bytes, nb_chunk, chunk_rmse = quantize_tensor_q3_k(
954
- x_chunk, importance=imp_chunk, cfg=cfg, chunk_blocks=8192
955
- )
 
 
 
 
 
 
 
 
 
 
956
  fout.write(q3_bytes)
957
  written_bytes += len(q3_bytes)
958
  sse_q3 += (chunk_rmse * chunk_rmse) * x_chunk.size
@@ -988,6 +1094,8 @@ def write_quantized_gguf(
988
 
989
  if written_bytes != out_size:
990
  raise RuntimeError(f"{ti.name}: Q3_K payload size mismatch")
 
 
991
  completed_planned_blocks += eff_tb
992
  q3_rmse = float(np.sqrt(sse_q3 / ti.n_elements))
993
  total_rmse += sse_q3
 
26
  from .q3_k import Q3_K_BLOCK_BYTES, QK3_K, dequantize_q3_k_bytes, quantize_tensor_q3_k
27
  from .q4_0 import Q4_0_BLOCK_BYTES, QK4_0, quantize_tensor_q4_0
28
  from .q1_0 import QK1_0, Q1_0_BLOCK_BYTES, quantize_tensor_q1_0
29
+ from .segmentation import (
30
+ label_channel_bytes,
31
+ pack_label_channel,
32
+ recover_order,
33
+ scatter_to_original,
34
+ unpack_label_channel,
35
+ )
36
 
37
  GGUF_MAGIC = 0x46554747
38
+ SEG_KV_PREFIX = "HPCQ.SEG.V1."
39
  DEFAULT_ALIGNMENT = 32
40
 
41
  GGML_TYPE_F32 = 0
 
282
 
283
  def read_tensor(self, name: str) -> tuple[np.ndarray, int]:
284
  raw = self.read_tensor_raw(name)
285
+ x = _dequantize(raw, self.info(name))
286
+ return self._unsegment(x, name), self.info(name).row_width
287
 
288
  def iter_tensor_row_chunks(self, name: str, max_elements: int = 8_388_608):
289
+ """Stream a tensor in row-aligned float32 chunks to bound peak RAM.
290
+
291
+ Chunks are unsegmented: if the tensor carries an HPCQ label channel,
292
+ each yielded chunk is scattered back to original order.
293
+ """
294
  ti = self.info(name)
295
  row = ti.row_width
296
  blk = BLOCK_SIZE[ti.type]
 
298
  x, r = self.read_tensor(name)
299
  yield x, r, 0, ti.n_elements // max(r, 1)
300
  return
301
+ seg_2d = None
302
+ seg = self.seg_labels(name)
303
+ if seg is not None:
304
+ seg_2d = seg[0]
305
  n_rows = ti.n_elements // row
306
  rows_per_chunk = max(1, max_elements // row)
307
  bytes_per_row = (row // blk) * BLOCK_BYTES[ti.type]
 
314
  raw = f.read(chunk_bytes)
315
  if len(raw) != chunk_bytes:
316
  raise ValueError(f"{name}: truncated tensor data")
317
+ sub = _dequantize(raw, TensorInfo(ti.name, (chunk_elems,), ti.type, 0))
318
+ if seg_2d is not None:
319
+ b0 = r0 * (row // blk)
320
+ b1 = b0 + (r1 - r0) * (row // blk)
321
+ sub = _scatter_rows(sub, seg_2d[b0:b1])
322
+ yield sub, row, r0, r1
323
+
324
+ def _seg_content(self, name: str) -> bytes | None:
325
+ """Raw packed label channel bytes for ``name`` (undecoded KV value)."""
326
+ for key, vtype, raw in self.kv_pairs:
327
+ if key == SEG_KV_PREFIX + name:
328
+ if vtype != 8:
329
+ raise ValueError(f"{name}: label channel KV has non-string type {vtype}")
330
+ if len(raw) < 8:
331
+ raise ValueError(f"{name}: corrupt label channel KV")
332
+ return raw[8:]
333
+ return None
334
+
335
+ def seg_labels(self, name: str) -> tuple[np.ndarray, int] | None:
336
+ """Decode a tensor's label channel (labels per ORIGINAL position).
337
+
338
+ Returns ``(labels_2d, k)`` where ``labels_2d`` is shaped
339
+ ``(n_blocks, block_qk)``, or ``None`` when no channel is present.
340
+ """
341
+ content = self._seg_content(name)
342
+ if content is None:
343
+ return None
344
+ ti = self.info(name)
345
+ if ti.type not in (GGML_TYPE_Q2_K, GGML_TYPE_Q3_K):
346
+ raise ValueError(f"{name}: label channel on non-segmentable type {ti.type_name}")
347
+ blk = QK_K if ti.type == GGML_TYPE_Q2_K else QK3_K
348
+ k, labels = unpack_label_channel(content)
349
+ if labels.size != ti.n_elements:
350
+ raise ValueError(
351
+ f"{name}: label channel size {labels.size} != tensor elements {ti.n_elements}"
352
+ )
353
+ return labels.reshape(ti.n_elements // blk, blk), k
354
+
355
+ def _unsegment(self, x: np.ndarray, name: str) -> np.ndarray:
356
+ seg = self.seg_labels(name)
357
+ if seg is None:
358
+ return x
359
+ return _scatter_rows(x, seg[0])
360
+
361
+
362
+ def _scatter_rows(x: np.ndarray, labels_2d: np.ndarray) -> np.ndarray:
363
+ """Scatter slot-order values back to original positions via the channel."""
364
+ out = scatter_to_original(
365
+ x.reshape(labels_2d.shape).astype(np.float32), recover_order(labels_2d)
366
+ )
367
+ return out.reshape(-1)
368
 
369
 
370
  def _dequantize(raw: bytes, ti: TensorInfo) -> np.ndarray:
 
784
  attn_q4 = bool(getattr(cfg, "promote_attn_q4", False))
785
  q_format = str(getattr(cfg, "quant_format", "mixed"))
786
  custom_map = getattr(cfg, "custom_quant_map", None) or None
787
+ seg_active = bool(getattr(cfg, "segment", False))
788
+ seg_k = max(2, int(getattr(cfg, "segment_k", 16)))
789
 
790
  plan: list[str | None] = []
791
  quantized_count = 0
 
880
  ftype_code = 11 if has_q3k else (10 if has_q2k else (40 if has_q1_0 else 2))
881
  kv_pairs = []
882
  for key, vtype, raw in reader.kv_pairs:
883
+ if key.startswith(SEG_KV_PREFIX):
884
+ continue
885
  if quantized_count and key == "general.file_type" and vtype == 4:
886
  raw = struct.pack("<I", ftype_code)
887
  elif quantized_count and key == "general.quantization_version" and vtype == 4:
888
  raw = struct.pack("<I", 2)
889
  kv_pairs.append((key, vtype, raw))
890
+ # Reserve label channels for every planned segmented tensor so the KV
891
+ # section layout is final before data streaming; contents are patched
892
+ # in place (same length) after each tensor is quantized.
893
+ seg_reserve: dict[str, int] = {}
894
+ if seg_active:
895
+ for ti, fmt in zip(reader.tensors, plan):
896
+ if fmt in ("Q2_K", "Q3_K"):
897
+ key = SEG_KV_PREFIX + ti.name
898
+ content_len = label_channel_bytes(ti.n_elements, seg_k)
899
+ seg_reserve[key] = content_len
900
+ kv_pairs.append((key, 8, struct.pack("<Q", content_len) + bytes(content_len)))
901
 
902
  output_path = Path(output_path)
903
  output_path.parent.mkdir(parents=True, exist_ok=True)
 
912
  model_t0 = time.time()
913
  with output_path.open("wb") as fout:
914
  fout.write(struct.pack("<IIQQ", GGUF_MAGIC, reader.version, len(out_infos), len(kv_pairs)))
915
+ seg_patch_offsets: dict[str, int] = {}
916
  for key, vtype, raw in kv_pairs:
917
  _write_string(fout, key)
918
  fout.write(struct.pack("<I", vtype))
919
+ if key in seg_reserve:
920
+ seg_patch_offsets[key] = fout.tell()
921
  fout.write(raw)
922
+
923
+ def _patch_seg_kv(fout, key: str, labels: np.ndarray) -> None:
924
+ packed = pack_label_channel(labels, seg_k)
925
+ if len(packed) != seg_reserve.get(key, -1):
926
+ raise RuntimeError(f"{key}: label channel size {len(packed)} != reserved")
927
+ pos = fout.tell()
928
+ fout.seek(seg_patch_offsets[key] + 8) # skip the u64 value length prefix
929
+ fout.write(packed)
930
+ fout.seek(pos)
931
  for ti, out_type, out_size, offset in out_infos:
932
  _write_string(fout, ti.name)
933
  fout.write(struct.pack("<I", len(ti.dims)))
 
1011
  if len(payload) != out_size:
1012
  raise RuntimeError(f"{ti.name}: Q2_K payload size mismatch")
1013
  fout.write(payload)
1014
+ if seg_active and res.segment_labels is not None:
1015
+ _patch_seg_kv(fout, SEG_KV_PREFIX + ti.name, res.segment_labels)
1016
  total_rmse += res.stats.rmse * res.stats.rmse * x.size
1017
  total_elements += x.size
1018
  total_quantized += 1
 
1040
  sse_q3 = 0.0
1041
  done_q3_blks = 0
1042
  t_q3_start = time.time()
1043
+ q3_labels: list[np.ndarray] = []
1044
 
1045
  for x_chunk, row, _r0, _r1 in reader.iter_tensor_row_chunks(ti.name, max_elements=8_388_608):
1046
  if stop_event is not None and stop_event.is_set():
1047
  raise InterruptedError("Quantization cancelled by user")
1048
  imp_chunk = expand_importance(raw_imp, x_chunk.size, row) if has_imat else None
1049
+ if seg_active:
1050
+ q3_bytes, nb_chunk, chunk_rmse, chunk_labels = quantize_tensor_q3_k(
1051
+ x_chunk,
1052
+ importance=imp_chunk,
1053
+ cfg=cfg,
1054
+ chunk_blocks=8192,
1055
+ return_labels=True,
1056
+ )
1057
+ q3_labels.append(np.asarray(chunk_labels, dtype=np.int64).ravel())
1058
+ else:
1059
+ q3_bytes, nb_chunk, chunk_rmse = quantize_tensor_q3_k(
1060
+ x_chunk, importance=imp_chunk, cfg=cfg, chunk_blocks=8192
1061
+ )
1062
  fout.write(q3_bytes)
1063
  written_bytes += len(q3_bytes)
1064
  sse_q3 += (chunk_rmse * chunk_rmse) * x_chunk.size
 
1094
 
1095
  if written_bytes != out_size:
1096
  raise RuntimeError(f"{ti.name}: Q3_K payload size mismatch")
1097
+ if seg_active:
1098
+ _patch_seg_kv(fout, SEG_KV_PREFIX + ti.name, np.concatenate(q3_labels))
1099
  completed_planned_blocks += eff_tb
1100
  q3_rmse = float(np.sqrt(sse_q3 / ti.n_elements))
1101
  total_rmse += sse_q3
q3_k.py CHANGED
@@ -983,12 +983,17 @@ def quantize_tensor_q3_k(
983
  importance: np.ndarray | None = None,
984
  cfg: HPCConfig | None = None,
985
  chunk_blocks: int | None = None,
986
- ) -> tuple[bytes, int, float]:
 
987
  """Quantize a tensor to GGML Q3_K using the full HPC V2 pipeline.
988
 
989
  ``chunk_blocks`` (256-weight super-blocks per chunk) defaults to
990
  ``Q3_K_CHUNK_BLOCKS_CUDA`` on the GPU path (same weights-per-chunk as the
991
  Q4_0 reference) and ``Q3_K_CHUNK_BLOCKS_CPU`` on the CPU path.
 
 
 
 
992
  """
993
  cfg = cfg or HPCConfig()
994
  x = np.asarray(weights, dtype=np.float32).ravel()
@@ -1016,6 +1021,10 @@ def quantize_tensor_q3_k(
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 (
@@ -1066,4 +1075,7 @@ def quantize_tensor_q3_k(
1066
  seg_rounds, seg_flips, rmse,
1067
  )
1068
 
1069
- return b"".join(chunks), n_blocks, rmse
 
 
 
 
983
  importance: np.ndarray | None = None,
984
  cfg: HPCConfig | None = None,
985
  chunk_blocks: int | None = None,
986
+ return_labels: bool = False,
987
+ ):
988
  """Quantize a tensor to GGML Q3_K using the full HPC V2 pipeline.
989
 
990
  ``chunk_blocks`` (256-weight super-blocks per chunk) defaults to
991
  ``Q3_K_CHUNK_BLOCKS_CUDA`` on the GPU path (same weights-per-chunk as the
992
  Q4_0 reference) and ``Q3_K_CHUNK_BLOCKS_CPU`` on the CPU path.
993
+
994
+ With ``return_labels=True`` returns ``(payload, n_blocks, rmse, labels)``
995
+ where ``labels`` is the segmentation label channel (one band id per
996
+ original position, or None when segmentation is off).
997
  """
998
  cfg = cfg or HPCConfig()
999
  x = np.asarray(weights, dtype=np.float32).ravel()
 
1021
  W_raw = np.ascontiguousarray(
1022
  apply_order(W_raw.astype(np.float64), seg_order)
1023
  ).astype(np.float32)
1024
+ else:
1025
+ seg_order = None
1026
+ seg_labels = None
1027
+ seg_rounds = 0
1028
 
1029
  cuda_ok = cfg.device != "cpu" and cuda_is_available()
1030
  use_cuda = cuda_ok and (
 
1075
  seg_rounds, seg_flips, rmse,
1076
  )
1077
 
1078
+ payload = b"".join(chunks)
1079
+ if return_labels:
1080
+ return payload, n_blocks, rmse, seg_labels
1081
+ return payload, n_blocks, rmse
segmentation.py CHANGED
@@ -110,6 +110,62 @@ def scatter_to_original(xh_slots: np.ndarray, order: np.ndarray) -> np.ndarray:
110
  return out
111
 
112
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  def segment_stage(
114
  X: np.ndarray,
115
  W: np.ndarray | None,
 
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. enc=0 packs labels 0..15 as nibbles (2 labels per byte),
117
+ # enc=1 stores raw uint8 labels (k > 16 only).
118
+
119
+ import struct as _struct
120
+
121
+ SEG_MAGIC = b"HQS1"
122
+
123
+
124
+ def label_channel_bytes(n: int, k: int) -> int:
125
+ """Exact content size of the packed label channel."""
126
+ if int(k) <= 16:
127
+ return 10 + (n + 1) // 2
128
+ return 10 + n
129
+
130
+
131
+ def pack_label_channel(labels: np.ndarray, k: int) -> bytes:
132
+ flat = np.ascontiguousarray(labels, dtype=np.int64).ravel()
133
+ n = int(flat.size)
134
+ k = int(k)
135
+ if k <= 16:
136
+ if flat.size and int(flat.max()) > 15:
137
+ raise ValueError(f"label {int(flat.max())} does not fit nibble packing (k={k})")
138
+ enc = 0
139
+ lo = flat[0::2].astype(np.uint8)
140
+ hi = flat[1::2].astype(np.uint8)
141
+ if hi.size < lo.size:
142
+ hi = np.concatenate([hi, np.zeros(1, dtype=np.uint8)])
143
+ data = (lo | (hi << 4)).astype(np.uint8).tobytes()
144
+ else:
145
+ enc = 1
146
+ if flat.size and (int(flat.max()) > 255 or int(flat.min()) < 0):
147
+ raise ValueError("labels must be in [0, 255] for 8-bit packing")
148
+ data = flat.astype(np.uint8).tobytes()
149
+ return SEG_MAGIC + bytes([k, enc]) + _struct.pack("<I", n) + data
150
+
151
+
152
+ def unpack_label_channel(content: bytes) -> tuple[int, np.ndarray]:
153
+ if content[:4] != SEG_MAGIC:
154
+ raise ValueError(f"bad label channel magic {content[:4]!r}")
155
+ k = int(content[4])
156
+ enc = int(content[5])
157
+ n = _struct.unpack("<I", content[6:10])[0]
158
+ data = content[10:]
159
+ if enc == 0:
160
+ raw = np.frombuffer(data, dtype=np.uint8)
161
+ out = np.empty(raw.size * 2, dtype=np.uint8)
162
+ out[0::2] = raw & np.uint8(0x0F)
163
+ out[1::2] = raw >> np.uint8(4)
164
+ else:
165
+ out = np.frombuffer(data, dtype=np.uint8).copy()
166
+ return k, out[:n]
167
+
168
+
169
  def segment_stage(
170
  X: np.ndarray,
171
  W: np.ndarray | None,