Upload 12 files
Browse files- __main__.py +33 -1
- gguf.py +176 -4
- q2k.py +14 -0
__main__.py
CHANGED
|
@@ -14,6 +14,7 @@ Examples::
|
|
| 14 |
from __future__ import annotations
|
| 15 |
|
| 16 |
import argparse
|
|
|
|
| 17 |
|
| 18 |
import numpy as np
|
| 19 |
|
|
@@ -25,6 +26,7 @@ from .gguf import (
|
|
| 25 |
is_q2k_eligible,
|
| 26 |
lookup_imatrix,
|
| 27 |
read_imatrix,
|
|
|
|
| 28 |
)
|
| 29 |
from .pipeline import Q2KQuantizer
|
| 30 |
from .reference import reference_solve, synthetic_cases
|
|
@@ -84,6 +86,29 @@ def _run_gguf(args, cfg: HPCConfig) -> None:
|
|
| 84 |
return
|
| 85 |
|
| 86 |
quantizer = Q2KQuantizer(cfg)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
if args.all_q2k:
|
| 88 |
payload: dict[str, np.ndarray] = {}
|
| 89 |
done = 0
|
|
@@ -191,7 +216,7 @@ def main() -> None:
|
|
| 191 |
parser.add_argument("--importance", help="optional .npy importance array")
|
| 192 |
parser.add_argument("--imatrix-gguf", help="llama.cpp iMatrix .gguf file")
|
| 193 |
parser.add_argument("--imatrix", help="alias for --imatrix-gguf (GGUF or legacy .dat)")
|
| 194 |
-
parser.add_argument("--output", help="output .npz")
|
| 195 |
parser.add_argument("--demo", action="store_true", help="quantize synthetic weights")
|
| 196 |
parser.add_argument("--blocks", type=int, default=64, help="demo block count")
|
| 197 |
parser.add_argument("--std", type=float, default=0.02, help="demo weight std")
|
|
@@ -212,6 +237,13 @@ def main() -> None:
|
|
| 212 |
return
|
| 213 |
|
| 214 |
if args.gguf:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 215 |
_run_gguf(args, cfg)
|
| 216 |
return
|
| 217 |
|
|
|
|
| 14 |
from __future__ import annotations
|
| 15 |
|
| 16 |
import argparse
|
| 17 |
+
from pathlib import Path
|
| 18 |
|
| 19 |
import numpy as np
|
| 20 |
|
|
|
|
| 26 |
is_q2k_eligible,
|
| 27 |
lookup_imatrix,
|
| 28 |
read_imatrix,
|
| 29 |
+
write_quantized_gguf,
|
| 30 |
)
|
| 31 |
from .pipeline import Q2KQuantizer
|
| 32 |
from .reference import reference_solve, synthetic_cases
|
|
|
|
| 86 |
return
|
| 87 |
|
| 88 |
quantizer = Q2KQuantizer(cfg)
|
| 89 |
+
wants_gguf = bool(args.output and str(args.output).lower().endswith(".gguf"))
|
| 90 |
+
if wants_gguf:
|
| 91 |
+
if not args.all_q2k and not args.tensor:
|
| 92 |
+
args.all_q2k = True
|
| 93 |
+
if args.rows:
|
| 94 |
+
raise SystemExit("--rows is only valid for .npz sampling output")
|
| 95 |
+
names = {args.tensor} if args.tensor else None
|
| 96 |
+
stats = write_quantized_gguf(
|
| 97 |
+
args.gguf,
|
| 98 |
+
args.output,
|
| 99 |
+
quantizer,
|
| 100 |
+
imat=imat,
|
| 101 |
+
quantize_all=args.all_q2k,
|
| 102 |
+
tensor_names=names,
|
| 103 |
+
limit=args.limit,
|
| 104 |
+
verbose=True,
|
| 105 |
+
)
|
| 106 |
+
print(
|
| 107 |
+
f"wrote {args.output}: {int(stats['quantized'])}/{int(stats['tensors'])} tensors Q2_K, "
|
| 108 |
+
f"rmse={stats['rmse']:.6e}"
|
| 109 |
+
)
|
| 110 |
+
return
|
| 111 |
+
|
| 112 |
if args.all_q2k:
|
| 113 |
payload: dict[str, np.ndarray] = {}
|
| 114 |
done = 0
|
|
|
|
| 216 |
parser.add_argument("--importance", help="optional .npy importance array")
|
| 217 |
parser.add_argument("--imatrix-gguf", help="llama.cpp iMatrix .gguf file")
|
| 218 |
parser.add_argument("--imatrix", help="alias for --imatrix-gguf (GGUF or legacy .dat)")
|
| 219 |
+
parser.add_argument("--output", help="output .gguf (full model) or .npz (block arrays)")
|
| 220 |
parser.add_argument("--demo", action="store_true", help="quantize synthetic weights")
|
| 221 |
parser.add_argument("--blocks", type=int, default=64, help="demo block count")
|
| 222 |
parser.add_argument("--std", type=float, default=0.02, help="demo weight std")
|
|
|
|
| 237 |
return
|
| 238 |
|
| 239 |
if args.gguf:
|
| 240 |
+
if (
|
| 241 |
+
not args.output
|
| 242 |
+
and not args.list_tensors
|
| 243 |
+
and (args.all_q2k or args.tensor)
|
| 244 |
+
):
|
| 245 |
+
stem = Path(args.gguf).stem
|
| 246 |
+
args.output = str(Path(args.gguf).with_name(f"{stem}-Q2K.gguf"))
|
| 247 |
_run_gguf(args, cfg)
|
| 248 |
return
|
| 249 |
|
gguf.py
CHANGED
|
@@ -20,7 +20,7 @@ from pathlib import Path
|
|
| 20 |
|
| 21 |
import numpy as np
|
| 22 |
|
| 23 |
-
from .q2k import QK_K, dequantize_params_batch, unpack_codes
|
| 24 |
|
| 25 |
GGUF_MAGIC = 0x46554747
|
| 26 |
DEFAULT_ALIGNMENT = 32
|
|
@@ -69,6 +69,14 @@ BLOCK_BYTES = {
|
|
| 69 |
GGML_TYPE_Q8_0: 34,
|
| 70 |
GGML_TYPE_Q2_K: 84,
|
| 71 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
|
| 74 |
@dataclass(slots=True)
|
|
@@ -154,17 +162,26 @@ class GGUFReader:
|
|
| 154 |
def __init__(self, path: str | Path):
|
| 155 |
self.path = Path(path)
|
| 156 |
self.metadata: dict[str, object] = {}
|
|
|
|
| 157 |
self.tensors: list[TensorInfo] = []
|
| 158 |
with self.path.open("rb") as f:
|
| 159 |
magic, version = struct.unpack("<II", f.read(8))
|
| 160 |
if magic != GGUF_MAGIC:
|
| 161 |
raise ValueError(f"{self.path} is not a GGUF file")
|
| 162 |
self.version = version
|
|
|
|
| 163 |
n_tensors, n_kv = struct.unpack("<QQ", f.read(16))
|
| 164 |
for _ in range(n_kv):
|
| 165 |
key = _read_string(f)
|
| 166 |
vtype = struct.unpack("<I", f.read(4))[0]
|
|
|
|
| 167 |
self.metadata[key] = _read_value(f, vtype)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
for _ in range(n_tensors):
|
| 169 |
name = _read_string(f)
|
| 170 |
n_dims = struct.unpack("<I", f.read(4))[0]
|
|
@@ -172,7 +189,9 @@ class GGUFReader:
|
|
| 172 |
ttype = struct.unpack("<I", f.read(4))[0]
|
| 173 |
offset = struct.unpack("<Q", f.read(8))[0]
|
| 174 |
self.tensors.append(TensorInfo(name, dims, ttype, offset))
|
| 175 |
-
self.
|
|
|
|
|
|
|
| 176 |
self._by_name = {ti.name: ti for ti in self.tensors}
|
| 177 |
|
| 178 |
def info(self, name: str) -> TensorInfo:
|
|
@@ -184,14 +203,30 @@ class GGUFReader:
|
|
| 184 |
def list_tensors(self) -> list[TensorInfo]:
|
| 185 |
return list(self.tensors)
|
| 186 |
|
| 187 |
-
def
|
| 188 |
ti = self.info(name)
|
| 189 |
with self.path.open("rb") as f:
|
| 190 |
f.seek(self.data_start + ti.offset)
|
| 191 |
raw = f.read(ti.n_bytes)
|
| 192 |
if len(raw) != ti.n_bytes:
|
| 193 |
raise ValueError(f"{name}: truncated tensor data")
|
| 194 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 195 |
|
| 196 |
|
| 197 |
def _dequantize(raw: bytes, ti: TensorInfo) -> np.ndarray:
|
|
@@ -324,3 +359,140 @@ def is_q2k_eligible(name: str, dims: tuple[int, ...]) -> bool:
|
|
| 324 |
for d in dims:
|
| 325 |
n_elements *= int(d)
|
| 326 |
return n_elements >= QK_K and n_elements % QK_K == 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 20 |
|
| 21 |
import numpy as np
|
| 22 |
|
| 23 |
+
from .q2k import QK_K, blocks_to_bytes, dequantize_params_batch, unpack_codes
|
| 24 |
|
| 25 |
GGUF_MAGIC = 0x46554747
|
| 26 |
DEFAULT_ALIGNMENT = 32
|
|
|
|
| 69 |
GGML_TYPE_Q8_0: 34,
|
| 70 |
GGML_TYPE_Q2_K: 84,
|
| 71 |
}
|
| 72 |
+
SUPPORTED_READ_TYPES = {
|
| 73 |
+
GGML_TYPE_F32,
|
| 74 |
+
GGML_TYPE_F16,
|
| 75 |
+
GGML_TYPE_BF16,
|
| 76 |
+
GGML_TYPE_Q4_0,
|
| 77 |
+
GGML_TYPE_Q8_0,
|
| 78 |
+
GGML_TYPE_Q2_K,
|
| 79 |
+
}
|
| 80 |
|
| 81 |
|
| 82 |
@dataclass(slots=True)
|
|
|
|
| 162 |
def __init__(self, path: str | Path):
|
| 163 |
self.path = Path(path)
|
| 164 |
self.metadata: dict[str, object] = {}
|
| 165 |
+
self.kv_pairs: list[tuple[str, int, bytes]] = []
|
| 166 |
self.tensors: list[TensorInfo] = []
|
| 167 |
with self.path.open("rb") as f:
|
| 168 |
magic, version = struct.unpack("<II", f.read(8))
|
| 169 |
if magic != GGUF_MAGIC:
|
| 170 |
raise ValueError(f"{self.path} is not a GGUF file")
|
| 171 |
self.version = version
|
| 172 |
+
self.kv_start = f.tell()
|
| 173 |
n_tensors, n_kv = struct.unpack("<QQ", f.read(16))
|
| 174 |
for _ in range(n_kv):
|
| 175 |
key = _read_string(f)
|
| 176 |
vtype = struct.unpack("<I", f.read(4))[0]
|
| 177 |
+
value_start = f.tell()
|
| 178 |
self.metadata[key] = _read_value(f, vtype)
|
| 179 |
+
value_end = f.tell()
|
| 180 |
+
f.seek(value_start)
|
| 181 |
+
raw = f.read(value_end - value_start)
|
| 182 |
+
f.seek(value_end)
|
| 183 |
+
self.kv_pairs.append((key, vtype, raw))
|
| 184 |
+
self.kv_end = f.tell()
|
| 185 |
for _ in range(n_tensors):
|
| 186 |
name = _read_string(f)
|
| 187 |
n_dims = struct.unpack("<I", f.read(4))[0]
|
|
|
|
| 189 |
ttype = struct.unpack("<I", f.read(4))[0]
|
| 190 |
offset = struct.unpack("<Q", f.read(8))[0]
|
| 191 |
self.tensors.append(TensorInfo(name, dims, ttype, offset))
|
| 192 |
+
self.info_end = f.tell()
|
| 193 |
+
self.alignment = int(self.metadata.get("general.alignment", DEFAULT_ALIGNMENT))
|
| 194 |
+
self.data_start = _align(self.info_end, self.alignment)
|
| 195 |
self._by_name = {ti.name: ti for ti in self.tensors}
|
| 196 |
|
| 197 |
def info(self, name: str) -> TensorInfo:
|
|
|
|
| 203 |
def list_tensors(self) -> list[TensorInfo]:
|
| 204 |
return list(self.tensors)
|
| 205 |
|
| 206 |
+
def read_tensor_raw(self, name: str) -> bytes:
|
| 207 |
ti = self.info(name)
|
| 208 |
with self.path.open("rb") as f:
|
| 209 |
f.seek(self.data_start + ti.offset)
|
| 210 |
raw = f.read(ti.n_bytes)
|
| 211 |
if len(raw) != ti.n_bytes:
|
| 212 |
raise ValueError(f"{name}: truncated tensor data")
|
| 213 |
+
return raw
|
| 214 |
+
|
| 215 |
+
def copy_tensor_raw_to(self, name: str, out, chunk: int = 1 << 20) -> None:
|
| 216 |
+
ti = self.info(name)
|
| 217 |
+
remaining = ti.n_bytes
|
| 218 |
+
with self.path.open("rb") as f:
|
| 219 |
+
f.seek(self.data_start + ti.offset)
|
| 220 |
+
while remaining > 0:
|
| 221 |
+
data = f.read(min(chunk, remaining))
|
| 222 |
+
if not data:
|
| 223 |
+
raise ValueError(f"{name}: truncated tensor data")
|
| 224 |
+
out.write(data)
|
| 225 |
+
remaining -= len(data)
|
| 226 |
+
|
| 227 |
+
def read_tensor(self, name: str) -> tuple[np.ndarray, int]:
|
| 228 |
+
raw = self.read_tensor_raw(name)
|
| 229 |
+
return _dequantize(raw, self.info(name)), self.info(name).row_width
|
| 230 |
|
| 231 |
|
| 232 |
def _dequantize(raw: bytes, ti: TensorInfo) -> np.ndarray:
|
|
|
|
| 359 |
for d in dims:
|
| 360 |
n_elements *= int(d)
|
| 361 |
return n_elements >= QK_K and n_elements % QK_K == 0
|
| 362 |
+
|
| 363 |
+
|
| 364 |
+
def _write_string(f, value: str) -> None:
|
| 365 |
+
data = value.encode("utf-8")
|
| 366 |
+
f.write(struct.pack("<Q", len(data)))
|
| 367 |
+
f.write(data)
|
| 368 |
+
|
| 369 |
+
|
| 370 |
+
def write_quantized_gguf(
|
| 371 |
+
input_path: str | Path,
|
| 372 |
+
output_path: str | Path,
|
| 373 |
+
quantizer,
|
| 374 |
+
imat: dict[str, np.ndarray] | None = None,
|
| 375 |
+
quantize_all: bool = True,
|
| 376 |
+
tensor_names: set[str] | None = None,
|
| 377 |
+
limit: int = 0,
|
| 378 |
+
verbose: bool = True,
|
| 379 |
+
) -> dict[str, float]:
|
| 380 |
+
"""Quantize a source GGUF with the V2 engine and emit a valid GGUF.
|
| 381 |
+
|
| 382 |
+
Eligible tensors become Q2_K; everything else is copied byte-for-byte,
|
| 383 |
+
including the metadata/tokenizer section. Returns aggregate statistics.
|
| 384 |
+
"""
|
| 385 |
+
reader = GGUFReader(input_path)
|
| 386 |
+
alignment = reader.alignment
|
| 387 |
+
names = set(tensor_names) if tensor_names else None
|
| 388 |
+
if names is not None:
|
| 389 |
+
missing = names - set(reader._by_name)
|
| 390 |
+
if missing:
|
| 391 |
+
raise KeyError(f"tensors not found: {sorted(missing)}")
|
| 392 |
+
|
| 393 |
+
plan: list[bool] = []
|
| 394 |
+
quantized_count = 0
|
| 395 |
+
for ti in reader.tensors:
|
| 396 |
+
if names is not None:
|
| 397 |
+
want = ti.name in names
|
| 398 |
+
compatible = (
|
| 399 |
+
len(ti.dims) >= 2
|
| 400 |
+
and ti.dims[0] % QK_K == 0
|
| 401 |
+
and ti.n_elements % QK_K == 0
|
| 402 |
+
)
|
| 403 |
+
else:
|
| 404 |
+
want = quantize_all and is_q2k_eligible(ti.name, ti.dims)
|
| 405 |
+
compatible = True
|
| 406 |
+
if want and ti.type not in SUPPORTED_READ_TYPES:
|
| 407 |
+
if verbose:
|
| 408 |
+
print(f" copy (unsupported source {ti.type_name}): {ti.name}")
|
| 409 |
+
want = False
|
| 410 |
+
if want and not compatible:
|
| 411 |
+
if verbose:
|
| 412 |
+
print(f" copy (row width not Q2_K aligned): {ti.name}")
|
| 413 |
+
want = False
|
| 414 |
+
if want and limit and quantized_count >= limit:
|
| 415 |
+
want = False
|
| 416 |
+
if want:
|
| 417 |
+
quantized_count += 1
|
| 418 |
+
plan.append(want)
|
| 419 |
+
|
| 420 |
+
out_infos = []
|
| 421 |
+
data_offset = 0
|
| 422 |
+
for ti, want in zip(reader.tensors, plan):
|
| 423 |
+
if want:
|
| 424 |
+
n_blocks = ti.n_elements // QK_K
|
| 425 |
+
out_type = GGML_TYPE_Q2_K
|
| 426 |
+
out_size = n_blocks * 84
|
| 427 |
+
else:
|
| 428 |
+
out_type = ti.type
|
| 429 |
+
out_size = ti.n_bytes
|
| 430 |
+
out_infos.append((ti, out_type, out_size, data_offset))
|
| 431 |
+
data_offset = _align(data_offset + out_size, alignment)
|
| 432 |
+
|
| 433 |
+
kv_pairs = []
|
| 434 |
+
for key, vtype, raw in reader.kv_pairs:
|
| 435 |
+
if quantized_count and key == "general.file_type" and vtype == 4:
|
| 436 |
+
raw = struct.pack("<I", 10)
|
| 437 |
+
elif quantized_count and key == "general.quantization_version" and vtype == 4:
|
| 438 |
+
raw = struct.pack("<I", 2)
|
| 439 |
+
kv_pairs.append((key, vtype, raw))
|
| 440 |
+
|
| 441 |
+
output_path = Path(output_path)
|
| 442 |
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
| 443 |
+
total_rmse = 0.0
|
| 444 |
+
total_quantized = 0
|
| 445 |
+
total_elements = 0
|
| 446 |
+
with output_path.open("wb") as fout:
|
| 447 |
+
fout.write(struct.pack("<IIQQ", GGUF_MAGIC, reader.version, len(out_infos), len(kv_pairs)))
|
| 448 |
+
for key, vtype, raw in kv_pairs:
|
| 449 |
+
_write_string(fout, key)
|
| 450 |
+
fout.write(struct.pack("<I", vtype))
|
| 451 |
+
fout.write(raw)
|
| 452 |
+
for ti, out_type, out_size, offset in out_infos:
|
| 453 |
+
_write_string(fout, ti.name)
|
| 454 |
+
fout.write(struct.pack("<I", len(ti.dims)))
|
| 455 |
+
for d in ti.dims:
|
| 456 |
+
fout.write(struct.pack("<Q", int(d)))
|
| 457 |
+
fout.write(struct.pack("<I", out_type))
|
| 458 |
+
fout.write(struct.pack("<Q", offset))
|
| 459 |
+
|
| 460 |
+
data_start = _align(fout.tell(), alignment)
|
| 461 |
+
fout.write(b"\x00" * (data_start - fout.tell()))
|
| 462 |
+
|
| 463 |
+
for idx, (ti, out_type, out_size, offset) in enumerate(out_infos):
|
| 464 |
+
target = data_start + offset
|
| 465 |
+
if target > fout.tell():
|
| 466 |
+
fout.write(b"\x00" * (target - fout.tell()))
|
| 467 |
+
if plan[idx]:
|
| 468 |
+
x, row = reader.read_tensor(ti.name)
|
| 469 |
+
imp = None
|
| 470 |
+
if imat is not None:
|
| 471 |
+
raw_imp = lookup_imatrix(imat, ti.name)
|
| 472 |
+
if raw_imp is not None:
|
| 473 |
+
imp = expand_importance(raw_imp, x.size, row)
|
| 474 |
+
res = quantizer.quantize(x, importance=imp, row_width=row)
|
| 475 |
+
payload = blocks_to_bytes(res.scales, res.qs, res.dm)
|
| 476 |
+
if len(payload) != out_size:
|
| 477 |
+
raise RuntimeError(f"{ti.name}: Q2_K payload size mismatch")
|
| 478 |
+
fout.write(payload)
|
| 479 |
+
total_rmse += res.stats.rmse * res.stats.rmse * x.size
|
| 480 |
+
total_elements += x.size
|
| 481 |
+
total_quantized += 1
|
| 482 |
+
if verbose:
|
| 483 |
+
print(
|
| 484 |
+
f" Q2_K {ti.name:48s} row={row:6d} "
|
| 485 |
+
f"rmse={res.stats.rmse:.6e} imatrix={'yes' if imp is not None else 'no'}"
|
| 486 |
+
)
|
| 487 |
+
else:
|
| 488 |
+
reader.copy_tensor_raw_to(ti.name, fout)
|
| 489 |
+
if verbose and idx % 25 == 0:
|
| 490 |
+
print(f" copy {ti.name}")
|
| 491 |
+
|
| 492 |
+
rmse = float(np.sqrt(total_rmse / total_elements)) if total_elements else 0.0
|
| 493 |
+
return {
|
| 494 |
+
"tensors": float(len(out_infos)),
|
| 495 |
+
"quantized": float(total_quantized),
|
| 496 |
+
"elements": float(total_elements),
|
| 497 |
+
"rmse": rmse,
|
| 498 |
+
}
|
q2k.py
CHANGED
|
@@ -169,6 +169,20 @@ def blocks_to_arrays(blocks: list[Q2KBlock]) -> tuple[np.ndarray, np.ndarray, np
|
|
| 169 |
return scales, qs, dm
|
| 170 |
|
| 171 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
def dequantize_blocks(blocks: list[Q2KBlock]) -> np.ndarray:
|
| 173 |
if not blocks:
|
| 174 |
return np.empty(0, dtype=np.float32)
|
|
|
|
| 169 |
return scales, qs, dm
|
| 170 |
|
| 171 |
|
| 172 |
+
def blocks_to_bytes(scales: np.ndarray, qs: np.ndarray, dm: np.ndarray) -> bytes:
|
| 173 |
+
"""Serialize Q2_K block arrays to GGUF's 84-byte block layout."""
|
| 174 |
+
scales = np.asarray(scales, dtype=np.uint8)
|
| 175 |
+
qs = np.asarray(qs, dtype=np.uint8)
|
| 176 |
+
dm = np.asarray(dm, dtype=np.uint16)
|
| 177 |
+
n = scales.shape[0]
|
| 178 |
+
out = np.empty((n, 84), dtype=np.uint8)
|
| 179 |
+
out[:, :16] = scales
|
| 180 |
+
out[:, 16:80] = qs
|
| 181 |
+
out[:, 80:82] = np.ascontiguousarray(dm[:, 0]).view(np.uint8).reshape(n, 2)
|
| 182 |
+
out[:, 82:84] = np.ascontiguousarray(dm[:, 1]).view(np.uint8).reshape(n, 2)
|
| 183 |
+
return out.tobytes()
|
| 184 |
+
|
| 185 |
+
|
| 186 |
def dequantize_blocks(blocks: list[Q2KBlock]) -> np.ndarray:
|
| 187 |
if not blocks:
|
| 188 |
return np.empty(0, dtype=np.float32)
|