File size: 7,324 Bytes
28c70af | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | #!/usr/bin/env python3
# SPDX-License-Identifier: MIT OR Apache-2.0
"""The oracle of `bankml convert` (O5): llama.cpp b11192's own converter on the same safetensors directory.
convert_oracle.py --record DIR OUT.gguf llama.cpp's `convert_hf_to_gguf.py DIR --outtype f16 --outfile OUT.gguf`
(needs the b11192 source tree in $BANKML_LLAMA_SRC and its Python
requirements: torch CPU, transformers, numpy, safetensors)
convert_oracle.py --bankml DIR ORACLE.gguf `bankml convert DIR` and the sha256 of both files: identical or not
convert_oracle.py --compare A.gguf B.gguf where two GGUFs differ: header, each key (order, type, value), each
tensor (name, order, type, shape, offset, sha256 of its data)
convert_oracle.py --chkhsh DIR llama.cpp's pre-tokenizer hash of DIR's tokenizer (its get_vocab_base_pre
rule, through transformers) and bankml's vocabulary+merges sha256: the
evidence a KNOWN_TOKENIZERS entry in convert.rs needs
convert_oracle.py --names OUT.jsonl gguf-py's name heuristics on a corpus of model ids, for the Rust test
oracle_name_heuristics (BANKML_NAMES_ORACLE=OUT.jsonl)
The directory's name matters: llama.cpp names the model from it (general.name, basename, finetune, size label), so
record and compare the same directory. Byte identity of the two files is the bar; --compare explains a miss.
"""
import ast, hashlib, json, os, re, subprocess, sys
from pathlib import Path
HERE = Path(__file__).resolve().parent
ROOT = HERE.parent
SRC = Path(os.environ.get("BANKML_LLAMA_SRC", "upstream/llama.cpp"))
def sha256(p):
h = hashlib.sha256()
with open(p, "rb") as f:
for b in iter(lambda: f.read(1 << 20), b""):
h.update(b)
return h.hexdigest()
def gguf_mod():
sys.path.insert(0, str(SRC / "gguf-py"))
import gguf # noqa: E402
return gguf
def record(d, out):
conv = SRC / "convert_hf_to_gguf.py"
if not conv.is_file():
sys.exit(f"no {conv}: set BANKML_LLAMA_SRC to a llama.cpp b11192 source tree")
subprocess.run([sys.executable, str(conv), d, "--outtype", "f16", "--outfile", out], check=True)
print(f"{sha256(out)} {out} (llama.cpp convert_hf_to_gguf.py --outtype f16)")
def bankml(d, oracle):
out = str(Path(oracle).with_suffix(".bankml.gguf"))
exe = os.environ.get("BANKML", str(ROOT / "target/release/bankml"))
subprocess.run([exe, "convert", d, "-o", out], check=True)
a, b = sha256(out), sha256(oracle)
print(f"bankml {a}\nllama.cpp {b}\n{'IDENTICAL' if a == b else 'DIFFERENT (run --compare)'}")
if a == b:
os.remove(out)
sys.exit(0 if a == b else 1)
def compare(a, b):
g = gguf_mod()
ra, rb = g.GGUFReader(a), g.GGUFReader(b)
def kv(r):
out = []
for f in r.fields.values():
v = [bytes(f.parts[i]).hex() for i in f.data]
out.append((f.name, [int(t) for t in f.types], hashlib.sha256("".join(v).encode()).hexdigest()[:16]))
return out
ka, kb = kv(ra), kv(rb)
bad = 0
for i in range(max(len(ka), len(kb))):
x = ka[i] if i < len(ka) else None
y = kb[i] if i < len(kb) else None
if x != y:
bad += 1
print(f"key #{i}: {x} != {y}")
for i in range(max(len(ra.tensors), len(rb.tensors))):
x = ra.tensors[i] if i < len(ra.tensors) else None
y = rb.tensors[i] if i < len(rb.tensors) else None
d = lambda t: None if t is None else (t.name, int(t.tensor_type), [int(s) for s in t.shape], int(t.data_offset), hashlib.sha256(bytes(t.data)).hexdigest()[:16])
if d(x) != d(y):
bad += 1
print(f"tensor #{i}: {d(x)} != {d(y)}")
print(f"{len(ka)} / {len(kb)} keys, {len(ra.tensors)} / {len(rb.tensors)} tensors, {bad} differences")
sys.exit(1 if bad else 0)
def chkhsh(d):
src = (SRC / "conversion" / "base.py").read_text()
chktxt = ast.literal_eval(re.search(r"\n chktxt = (.*)\n", src).group(1))
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained(d)
h = hashlib.sha256(str(tok.encode(chktxt)).encode()).hexdigest()
names = re.findall(r'if chkhsh == "([0-9a-f]{64})":\n\s+# ref: (\S+)\n\s+res = "([^"]+)"', src)
hit = [n for n in names if n[0] == h]
t = json.load(open(Path(d) / "tokenizer.json"))
m = t["model"]
inv = {v: k for k, v in m["vocab"].items()}
for a in t.get("added_tokens", []):
inv[a["id"]] = a["content"]
n = json.load(open(Path(d) / "config.json")).get("vocab_size", 32000)
vm = hashlib.sha256()
for i in range(n):
vm.update((inv.get(i, f"[PAD{i}]")).encode() + b"\n")
vm.update(b"\n")
for x in m["merges"]:
x = x if isinstance(x, str) else " ".join("".join(chr(ord(c) + 256) if c == " " else c for c in p) for p in x)
vm.update((x + "\n").encode())
print(json.dumps({"dir": d, "chkhsh": h, "llama.cpp pre": hit[0][2] if hit else None, "ref": hit[0][1] if hit else None,
"vocab_merges_sha256": vm.hexdigest(), "pre_tokenizer": t.get("pre_tokenizer")}, indent=1))
NAMES = """smollm2-135m-instruct mindx-gen39 Mixtral-8x7B-Instruct-v0.1 meta-llama/Meta-Llama-3-8B bigscience/bloom-7b1-petals
Qwen/Qwen2.5-0.5B-Instruct ./my-model ../x/model-7B HuggingFaceTB/SmolLM2-135M-Instruct HuggingFaceTB/SmolLM-135M
prism-ml/Bonsai-8B-gguf Qwen/Qwen3-8B Qwen/Qwen3-0.6B-Base microsoft/Phi-3-mini-4k-instruct google/gemma-2-2b-it
mistralai/Mistral-7B-Instruct-v0.3 deepseek-ai/DeepSeek-V2-Lite-Chat tiiuae/falcon-7b EleutherAI/pythia-1.4b-deduped
TheBloke/Llama-2-13B-chat-GPTQ stabilityai/stablelm-2-1_6b openai-community/gpt2-xl allenai/OLMo-7B-0724-Instruct-hf
ibm-granite/granite-3.0-2b-instruct unsloth/Llama-3.2-1B-Instruct mistralai/Mixtral-8x22B-v0.1 Qwen/Qwen1.5-MoE-A2.7B
model-32k-chat merged checkpoint gen39 mindx-gen40-v2 mindX-Gen41-135M-lora Llama-3-70B-Instruct-AWQ q4_k_m-model
some-model-F16 Model-bf16-v1.2.3 tiny-llama-1.1b-chat-v1.0 x-small-mini-medium iter12-model A3B-thing model-7b1""".split()
def names(out):
g = gguf_mod()
from gguf.metadata import Metadata
with open(out, "w") as f:
for i in NAMES:
for total in (0, 134_515_008, 8_190_000_000, -2_000_000):
parts = Metadata.get_model_id_components(i, total)
f.write(json.dumps({"id": i, "total": total, "parts": list(parts), "title": Metadata.id_to_title(i),
"size": g.size_label(abs(total), abs(total), 0, 0) if total else None}) + "\n")
print(f"{len(NAMES) * 4} cases → {out}")
if __name__ == "__main__":
a = sys.argv[1:]
if len(a) == 3 and a[0] == "--record":
record(a[1], a[2])
elif len(a) == 3 and a[0] == "--bankml":
bankml(a[1], a[2])
elif len(a) == 3 and a[0] == "--compare":
compare(a[1], a[2])
elif len(a) == 2 and a[0] == "--chkhsh":
chkhsh(a[1])
elif len(a) == 2 and a[0] == "--names":
names(a[1])
else:
sys.exit(__doc__)
|