import argparse
import hashlib
import json
import os
import struct
import sys
SLICED = {
"tokenizer.ggml.tokens",
"tokenizer.ggml.token_type",
"tokenizer.ggml.scores",
"tokenizer.ggml.merges",
}
SCALAR_SIZE = {0: 1, 1: 1, 2: 2, 3: 2, 4: 4, 5: 4, 6: 4, 7: 1, 10: 8, 11: 8, 12: 8}
STRING, ARRAY = 8, 9
def read_exact(f, n):
b = f.read(n)
if len(b) != n:
sys.exit(f"truncated source: wanted {n} bytes, got {len(b)}")
return b
def read_string_raw(f):
head = read_exact(f, 8)
(n,) = struct.unpack("<Q", head)
return head + read_exact(f, n)
def read_value_raw(f, vtype):
if vtype == STRING:
return read_string_raw(f), None
if vtype == ARRAY:
head = read_exact(f, 12)
etype, n = struct.unpack("<IQ", head)
elems = []
for _ in range(n):
if etype == STRING:
elems.append(read_string_raw(f))
elif etype in SCALAR_SIZE:
elems.append(read_exact(f, SCALAR_SIZE[etype]))
else:
sys.exit(f"unsupported nested array element type {etype}")
return head + b"".join(elems), (etype, elems)
if vtype in SCALAR_SIZE:
return read_exact(f, SCALAR_SIZE[vtype]), None
sys.exit(f"unsupported value type {vtype}")
def main():
ap = argparse.ArgumentParser()
ap.add_argument("source")
ap.add_argument("out")
ap.add_argument("--keep", type=int, required=True)
a = ap.parse_args()
with open(a.source, "rb") as f:
if read_exact(f, 4) != b"GGUF":
sys.exit("source is not a GGUF file")
(version,) = struct.unpack("<I", read_exact(f, 4))
tensor_count, kv_count = struct.unpack("<QQ", read_exact(f, 16))
kvs, sliced = [], {}
for _ in range(kv_count):
key_raw = read_string_raw(f)
key = key_raw[8:].decode("utf-8")
vtype_raw = read_exact(f, 4)
(vtype,) = struct.unpack("<I", vtype_raw)
value_raw, arr = read_value_raw(f, vtype)
if key in SLICED:
if arr is None:
sys.exit(f"{key} is not an array in the source")
etype, elems = arr
keep = elems[: a.keep]
value_raw = struct.pack("<IQ", etype, len(keep)) + b"".join(keep)
sliced[key] = {"source_len": len(elems), "kept": len(keep)}
kvs.append(key_raw + vtype_raw + value_raw)
header_end = f.tell()
f.seek(0)
source_header_sha256 = hashlib.sha256(read_exact(f, header_end)).hexdigest()
out = b"GGUF" + struct.pack("<I", version) + struct.pack("<QQ", 0, len(kvs)) + b"".join(kvs)
with open(a.out, "wb") as g:
g.write(out)
manifest_path = os.path.join(os.path.dirname(os.path.abspath(a.out)), "MANIFEST.json")
manifest = {}
if os.path.exists(manifest_path):
with open(manifest_path) as m:
manifest = json.load(m)
manifest[os.path.basename(a.out)] = {
"source_file": os.path.basename(a.source),
"source_gguf_version": version,
"source_tensor_count": tensor_count,
"source_header_bytes": header_end,
"source_header_sha256": source_header_sha256,
"keys": len(kvs),
"sliced_arrays": sliced,
"keep": a.keep,
"output_bytes": len(out),
"output_sha256": hashlib.sha256(out).hexdigest(),
}
with open(manifest_path, "w") as m:
json.dump(manifest, m, indent=2, sort_keys=True)
m.write("\n")
print(f"{a.out}: {len(out)} bytes, {len(kvs)} keys, sliced {sorted(sliced)}")
if __name__ == "__main__":
main()