bankml / testing /convert_oracle.py
Gregory-L's picture
bankML: the whole source (github.com/cryptoAGI/bankml @ 12ae409) and its page, with the bankML persona; the live engine (Dockerfile, hf/start.sh) ready for Docker hardware
28c70af verified
Raw History Blame Contribute Delete
7.32 kB
#!/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__)