Upload 3 files
Browse files- gguf.py +115 -7
- q3_k.py +14 -2
- 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 |
-
|
|
|
|
| 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 |
-
|
| 301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 954 |
-
|
| 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 |
-
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
| 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,
|