CoolFace
Modelpublic

FluidInference/verdict-coreml

sourceHugging Faceapache-2.0updated 2d agoView on Hugging Face
0likes42downloads
verify-compression.py166 linesDownload Raw Back to root
1"""Measure Verdict LUT8 against FP16 on fixed Decision Index requests without using gold labels."""2 3from __future__ import annotations4 5import argparse6import gzip7import json8import platform9import statistics10import subprocess11import time12from collections import Counter13from pathlib import Path14 15import coremltools as ct16import numpy as np17from transformers import AutoTokenizer18 19from assets import ROOT, sha256, verify_assets20from decision_index_engine import adapt_question, as_text21from native_reference import build_request, decode, load_calibrator22from preprocessing import prepare23 24 25def selected_requests(rows_path: Path, tokenizer, limit: int, length: int):26    """First ten eligible rows per family, before inspecting either model's output."""27    family_counts = Counter()28    skipped = Counter()29    selected = []30    with gzip.open(rows_path, "rt") as stream:31        for line in stream:32            row = json.loads(line)33            family = row["family"]34            if family_counts[family] >= 10:35                continue36            if len(row["questions"]) != 1:37                skipped["multiple_questions"] += 138                continue39            question = next(iter(row["questions"].values()))40            if question["type"] not in ("choice", "noul"):41                skipped["unsupported_question_type"] += 142                continue43            try:44                request = build_request(as_text(row["state"]), adapt_question(question))45            except ValueError:46                skipped["invalid_or_overcapacity"] += 147                continue48            tokens = len(tokenizer(request.text, truncation=False)["input_ids"])49            if tokens > length:50                skipped["overlength"] += 151                continue52            selected.append((row["id"], family, request, tokens))53            family_counts[family] += 154            if len(selected) >= limit:55                break56    return selected, family_counts, skipped57 58 59def main() -> None:60    parser = argparse.ArgumentParser(description=__doc__)61    parser.add_argument("--rows", type=Path, required=True, help="pinned public Decision Index selected-rows.jsonl.gz")62    parser.add_argument("--limit", type=int, default=100)63    parser.add_argument("--length", type=int, default=128)64    parser.add_argument("--repeats", type=int, default=5)65    args = parser.parse_args()66    if args.length != 128:67        raise ValueError("the predeclared LUT8 validation protocol is L128 only")68    if args.limit != 100:69        raise ValueError("the predeclared validation manifest uses exactly 100 requests")70    source = verify_assets(required=("config.json", "tokenizer.json", "tokenizer_config.json", "calibrator.json"))71    tokenizer = AutoTokenizer.from_pretrained(source)72    config = json.loads((source / "config.json").read_text())73    calibrator = load_calibrator(source)74    selected, families, skipped = selected_requests(args.rows, tokenizer, args.limit, args.length)75    if len(selected) != args.limit:76        raise ValueError(f"only {len(selected)} eligible fixed requests; expected {args.limit}")77 78    packages = {79        "fp16": ROOT / "build" / "verdict_fp16_L128_candidates25.mlpackage",80        "lut8": ROOT / "build" / "verdict_lut8_kmeans_per_tensor_L128_candidates25.mlpackage",81    }82    models = {name: ct.models.MLModel(str(path), compute_units=ct.ComputeUnit.ALL) for name, path in packages.items()}83    for _, _, request, _ in selected[:2]:84        arrays = prepare(tokenizer, config["class_token_index"], request.text, args.length, 25)85        for model in models.values():86            model.predict(arrays)87 88    rows = []89    timings = {name: [] for name in models}90    for index, (row_id, family, request, tokens) in enumerate(selected):91        arrays = prepare(tokenizer, config["class_token_index"], request.text, args.length, 25)92        results = {}93        for name, model in models.items():94            output = model.predict(arrays)95            results[name] = decode(output["logits"], request, calibrator)96            if index < 20:97                for _ in range(args.repeats):98                    start = time.perf_counter()99                    model.predict(arrays)100                    timings[name].append((time.perf_counter() - start) * 1000)101        reference = results["fp16"]102        compressed = results["lut8"]103        differences = [abs(reference["probabilities"][key] - compressed["probabilities"][key]) for key in request.ids]104        rows.append(105            {106                "id": row_id,107                "family": family,108                "tokens": tokens,109                "candidates": len(request.ids),110                "fp16_selected_id": reference["selected_id"],111                "lut8_selected_id": compressed["selected_id"],112                "selection_agrees": reference["selected_id"] == compressed["selected_id"],113                "abstention_agrees": reference["is_abstention"] == compressed["is_abstention"],114                "max_probability_error": max(differences),115            }116        )117    errors = np.array([row["max_probability_error"] for row in rows])118    selection_agreement = sum(row["selection_agrees"] for row in rows) / len(rows)119    abstention_agreement = sum(row["abstention_agrees"] for row in rows) / len(rows)120    gates = {121        "min_selection_agreement": 0.99,122        "min_abstention_agreement": 0.99,123        "max_p95_probability_error": 0.02,124        "max_worst_probability_error": 0.10,125    }126    report = {127        "suite_file": args.rows.name,128        "suite_sha256": sha256(args.rows),129        "selection_protocol": "First 10 eligible rows per family in suite order, 100 total; no gold labels used",130        "selected_row_ids": [row["id"] for row in rows],131        "families": dict(families),132        "skipped_before_limit": dict(skipped),133        "packages": {name: path.name for name, path in packages.items()},134        "hardware": {135            "chip": subprocess.run(136                ["sysctl", "-n", "machdep.cpu.brand_string"], capture_output=True, text=True137            ).stdout.strip(),138            "macos": platform.mac_ver()[0],139        },140        "questions": len(rows),141        "selection_agreement": selection_agreement,142        "abstention_agreement": abstention_agreement,143        "p95_probability_error": float(np.percentile(errors, 95)),144        "worst_probability_error": float(errors.max()),145        "median_model_call_ms": {name: statistics.median(values) for name, values in timings.items()},146        "gates": gates,147        "rows": rows,148    }149    report["passed"] = (150        selection_agreement >= gates["min_selection_agreement"]151        and abstention_agreement >= gates["min_abstention_agreement"]152        and report["p95_probability_error"] <= gates["max_p95_probability_error"]153        and report["worst_probability_error"] <= gates["max_worst_probability_error"]154    )155    target = ROOT / "reports" / "lut8-L128-suite-parity.json"156    target.write_text(json.dumps(report, indent=2) + "\n")157    print(158        json.dumps({key: value for key, value in report.items() if key not in ("rows", "selected_row_ids")}, indent=2)159    )160    if not report["passed"]:161        raise SystemExit(1)162 163 164if __name__ == "__main__":165    main()166