lfm2-5-350m-rlcd-coreml / verify-native.py
alexwengg's picture
Publish validated LFM2.5-350M-RLCD FP16 Core ML conversion
616b1e2 verified
Raw History Blame Contribute Delete
2.82 kB
"""Compare the no-cache Core ML boundary with RLCD's cached native engine."""
from __future__ import annotations
import argparse
import json
import sys
import time
from pathlib import Path
import torch
from decision import CandidateScorer, load_native, source_path
from fixtures import CONTEXT, SCHEMA
from preprocessing import Shape, batch_arrays, prepare_candidates, select_values
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--length", type=int, default=256)
parser.add_argument("--batch", type=int, default=8)
parser.add_argument("--device", choices=["cpu", "mps"], default="cpu")
args = parser.parse_args()
torch.set_num_threads(2)
snapshot = source_path()
sys.path.insert(0, str(snapshot))
from rlcd.engine import Engine # noqa: E402
tokenizer, model = load_native(args.device)
upstream = Engine.__new__(Engine)
upstream.model = model
upstream.tokenizer = tokenizer
upstream.device = args.device
upstream.dtype = "float32"
shape = Shape(length=args.length, candidates=args.batch)
candidates = prepare_candidates(tokenizer, CONTEXT, SCHEMA, shape)
scorer = CandidateScorer(model, shape.length).eval()
started = time.perf_counter()
with torch.inference_mode():
native = upstream.constrained(CONTEXT, SCHEMA)
ours = []
for offset in range(0, len(candidates), shape.candidates):
group = candidates[offset : offset + shape.candidates]
arrays = batch_arrays(tokenizer, group, shape)
tensors = [torch.from_numpy(value).to(args.device) for value in arrays.values()]
ours.extend(scorer(*tensors).float().cpu().tolist()[: len(group)])
expected = [entry["log_likelihood"] for entries in native["scores"].values() for entry in entries]
errors = [abs(left - right) for left, right in zip(ours, expected)]
same_answer = select_values(candidates, ours) == json.loads(native["text"])
report = {
"source_repo": "notnotsamuel/LFM2.5-350M-RLCD",
"source_revision": "deb589d803d141cabd158ef55f6617b128529f36",
"device": args.device,
"shape": vars(shape),
"candidates": len(candidates),
"max_log_likelihood_error": max(errors),
"same_selected_values": same_answer,
"seconds": round(time.perf_counter() - started, 2),
"expected": expected,
"actual": ours,
}
output = Path("build/native-parity.json")
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(json.dumps(report, indent=2) + "\n")
print(json.dumps(report, indent=2), flush=True)
if not same_answer or max(errors) > 1e-2:
raise SystemExit("native cached/no-cache parity failed")
if __name__ == "__main__":
main()