Download code/scripts/p2/backend_agreement.py from viai957/align: direct link, hf CLI and curl.
- Browser
- Download file 19.1 kB
-
https://huggingface.co/viai957/align/resolve/main/code/scripts/p2/backend_agreement.py
- Command line
-
hf download hf://viai957/align/code/scripts/p2/backend_agreement.py
-
curl -L -o backend_agreement.py https://huggingface.co/viai957/align/resolve/main/code/scripts/p2/backend_agreement.py
19.1 kB
| #!/usr/bin/env python | |
| """Agreement + throughput: our strict verifier vs the Kimina Lean server pre-filter. | |
| hostguard.py run --cores 4 --mem-gb 24 --label agree --wait -- \\ | |
| .venv/bin/python scripts/p2/backend_agreement.py --out .tmp/kimina_agreement/run1 | |
| Item sets (all verified by BOTH backends on the same (formal statement, extracted Lean code)): | |
| kg known-good proofs of the ceiling test (150 Lean-Workbook-proofs rows saved by the registered | |
| ceiling run + 30 restated), statements derived exactly as `lean_ceiling_test.py` does | |
| adv the 61 frozen adversarial cases of `lean_ceiling_cases.py` (3 bombs run on a 1 GiB / 30 s | |
| pool for ours and on a second, small Kimina server for the candidate) | |
| sample N random code-bearing records (finish_reason != length) of a P2 generation file | |
| Phases (resumable, JSONL per phase): `ours` (StrictVerifier, the authority), `control` (OUR REPL pool, | |
| compile-only: separates "Kimina server overhead" from "strict-tier overhead"), `kimina` (compile-only | |
| through the server). They run strictly one after another: never two heavy jobs at once. The server(s) | |
| must already be running (toolchains/work/kimina-run/start.sh); this script never starts or stops them. | |
| Semantics of the comparison. Kimina is a PRE-FILTER: its verdict is `reject` / `maybe` / `error`, never | |
| an accept. The "gated" outcome (accept iff strict accepts AND the pre-filter did not reject) is computed | |
| from the two per-item records; the only accept that can exist is the strict verifier's. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import importlib.util | |
| import json | |
| import os | |
| import random | |
| import statistics | |
| import sys | |
| import threading | |
| import time | |
| from concurrent.futures import ThreadPoolExecutor | |
| from pathlib import Path | |
| REPO = Path(__file__).resolve().parents[2] | |
| sys.path.insert(0, str(REPO)) | |
| MAIN_REPO = Path("/apps/development/rlhf/align") # data lives in the main checkout's .tmp, not in worktrees | |
| DEFAULT_GENS = MAIN_REPO / ".tmp/p2/r1/baseline/gen_minif2f_c40960.jsonl" | |
| DEFAULT_PROBLEMS = MAIN_REPO / ".tmp/p2/problems/minif2f.jsonl" | |
| DEFAULT_KG_RAW = MAIN_REPO / ".tmp/lean_ceiling/registered/known_good_raw.jsonl" | |
| CORES = 4 | |
| def _load(name: str, path: Path): | |
| spec = importlib.util.spec_from_file_location(name, path) | |
| mod = importlib.util.module_from_spec(spec) | |
| sys.modules[name] = mod | |
| spec.loader.exec_module(mod) | |
| return mod | |
| LC = _load("lean_ceiling_test", REPO / "scripts" / "lean_ceiling_test.py") | |
| CASES = LC.C | |
| def pctl(xs, q): | |
| s = sorted(xs) | |
| return s[min(len(s) - 1, int(q * len(s)))] if s else float("nan") | |
| # --------------------------------------------------------------------------- items | |
| def build_items(args) -> list[dict]: | |
| from align.verify.lean.reward import extract_lean_code | |
| items: list[dict] = [] | |
| # known-good | |
| cands = [] | |
| for line in open(args.kg_raw): | |
| r = json.loads(line) | |
| cands.append((r["problem_id"], "lean-workbook-proofs", r["full_proof"])) | |
| for rs in CASES.RESTATED: | |
| cands.append((rs["id"], "restated", CASES.HEADER + rs["code"])) | |
| for pid, src, code in cands: | |
| ds = LC.derive_statement(code) | |
| if ds is None: | |
| continue | |
| items.append(dict(id=f"kg:{pid}", set="kg", sub=src, statement=ds[0], code=code, pool="main")) | |
| # adversarial | |
| for c in CASES.ADVERSARIAL: | |
| stmt = CASES.HEADER + CASES.STATEMENTS[c["statement"]] | |
| comp = c["completion"] if c["completion"] is not None else LC.as_completion( | |
| (CASES.HEADER if c["raw_header"] is None else c["raw_header"]) + c["code"]) | |
| items.append(dict(id=f"adv:{c['id']}", set="adv", sub=c["category"], statement=stmt, | |
| code=extract_lean_code(comp), pool=c["pool"])) | |
| # sampled generations | |
| probs = {} | |
| for line in open(args.problems): | |
| r = json.loads(line) | |
| probs[r["problem_id"]] = r["formal_statement"] | |
| pop = [] | |
| for line in open(args.generations): | |
| r = json.loads(line) | |
| if r["finish_reason"] == "length" or not (r["lean_code"] or "").strip() or r["problem_id"] not in probs: | |
| continue | |
| pop.append(r) | |
| rng = random.Random(args.seed) | |
| for r in rng.sample(pop, min(args.sample, len(pop))): | |
| items.append(dict(id=f"gen:{r['problem_id']}#{r['sample_idx']}", set="sample", sub="minif2f", | |
| statement=probs[r["problem_id"]], code=r["lean_code"], pool="main")) | |
| items.append(dict(id="__meta__", set="meta", population=len(pop))) | |
| return items | |
| def done_ids(path: Path) -> dict: | |
| out = {} | |
| if path.exists(): | |
| for line in open(path): | |
| try: | |
| r = json.loads(line) | |
| out[r["id"]] = r | |
| except json.JSONDecodeError: | |
| pass | |
| return out | |
| class Sink: | |
| def __init__(self, path: Path): | |
| self.f = open(path, "a") | |
| self.lock = threading.Lock() | |
| def write(self, rec: dict): | |
| with self.lock: | |
| self.f.write(json.dumps(rec, ensure_ascii=False, default=str) + "\n") | |
| self.f.flush() | |
| # --------------------------------------------------------------------------- phases | |
| def phase_ours(items, out: Path, args) -> dict: | |
| from align.verify.lean import ReplLimits, ReplPool, StrictConfig, StrictVerifier, find_toolchain | |
| tc = find_toolchain() | |
| sink = Sink(out / "ours.jsonl") | |
| have = done_ids(out / "ours.jsonl") | |
| walls = {} | |
| for pool_name, lim, cfg in (("main", ReplLimits(), StrictConfig(proof_timeout_s=args.timeout)), | |
| ("bomb", ReplLimits(anon_cap_gb=1.0), StrictConfig(proof_timeout_s=30))): | |
| todo = [it for it in items if it["set"] != "meta" and it["pool"] == pool_name and it["id"] not in have] | |
| if not todo: | |
| continue | |
| n = args.workers if pool_name == "main" else 1 | |
| pool = ReplPool.from_toolchain(tc, n_workers=n, limits=lim, preflight=True, | |
| scratch_dir=str(tc.root / "tmp" / f"agree_{pool_name}")) | |
| try: | |
| ver = StrictVerifier(pool, cfg) | |
| t0 = time.monotonic() | |
| def one(w, it): | |
| r = ver._verify_on(w, (it["statement"], it["code"])) | |
| sink.write(dict(id=it["id"], set=it["set"], accepted=bool(r.accepted), reasons=list(r.reasons), | |
| stage=r.stage, elapsed_s=r.elapsed_s, undecided=bool(r.undecided))) | |
| pool.map(one, todo) | |
| walls[pool_name] = time.monotonic() - t0 | |
| finally: | |
| pool.close() | |
| return {"walls": walls, "workers": args.workers} | |
| def phase_control(items, out: Path, args) -> dict: | |
| """OUR REPL pool, compile-only (no strictness): the engine-level control for the Kimina server.""" | |
| from align.verify.lean import ReplLimits, ReplPool, find_toolchain | |
| tc = find_toolchain() | |
| sink = Sink(out / "control.jsonl") | |
| have = done_ids(out / "control.jsonl") | |
| todo = [it for it in items if it["set"] in ("kg", "sample") and it["id"] not in have] | |
| if not todo: | |
| return {} | |
| pool = ReplPool.from_toolchain(tc, n_workers=args.workers, limits=ReplLimits(), preflight=True, | |
| scratch_dir=str(tc.root / "tmp" / "agree_control")) | |
| try: | |
| t0 = time.monotonic() | |
| def one(w, it): | |
| t = time.monotonic() | |
| r = LC.compile_only(w, it["statement"], it["code"], args.timeout) if it["code"] else {"ok": False, "why": "no_code"} | |
| sink.write(dict(id=it["id"], set=it["set"], ok=bool(r.get("ok")), clean=bool(r.get("clean")), why=r.get("why", ""), | |
| elapsed_s=time.monotonic() - t)) | |
| pool.map(one, todo) | |
| return {"wall": time.monotonic() - t0, "workers": args.workers} | |
| finally: | |
| pool.close() | |
| def phase_kimina(items, out: Path, args) -> dict: | |
| from align.verify_backends.kimina_server import KiminaClient, KiminaPrefilter | |
| sink = Sink(out / "kimina.jsonl") | |
| have = done_ids(out / "kimina.jsonl") | |
| walls = {} | |
| for pool_name, url, tmo, workers in (("main", args.kimina_url, args.timeout, args.workers), | |
| ("bomb", args.kimina_bomb_url, 30, 1)): | |
| todo = [it for it in items if it["set"] != "meta" and it["pool"] == pool_name and it["id"] not in have] | |
| if not todo: | |
| continue | |
| cl = KiminaClient(url) | |
| if not cl.healthy(): | |
| raise SystemExit(f"Kimina server at {url} is not healthy; start it first") | |
| pf = KiminaPrefilter(cl, tmo) | |
| def one(it): | |
| t = time.monotonic() | |
| v = pf.check_code(it["code"]) | |
| sink.write(dict(id=it["id"], set=it["set"], verdict=v.verdict, reasons=list(v.reasons), detail=v.detail, | |
| elapsed_s=time.monotonic() - t, server_s=v.elapsed_s)) | |
| t0 = time.monotonic() | |
| with ThreadPoolExecutor(workers) as ex: | |
| list(ex.map(one, todo)) | |
| walls[pool_name] = time.monotonic() - t0 | |
| return {"walls": walls, "workers": args.workers} | |
| # --------------------------------------------------------------------------- report | |
| def stats(xs): | |
| return {"n": len(xs), "sum_s": round(sum(xs), 1), "mean": round(statistics.mean(xs), 3) if xs else None, | |
| "p50": round(pctl(xs, .5), 3), "p90": round(pctl(xs, .9), 3), "p99": round(pctl(xs, .99), 3), | |
| "max": round(max(xs), 3) if xs else None} | |
| def report(items, out: Path, phase_meta: dict) -> dict: | |
| ours, ctl, kim = done_ids(out / "ours.jsonl"), done_ids(out / "control.jsonl"), done_ids(out / "kimina.jsonl") | |
| by_id = {it["id"]: it for it in items if it["set"] != "meta"} | |
| rep: dict = {"phase_meta": phase_meta, "matrix": {}, "disagreements": [], "n_items": {}} | |
| def okind(r): # ours: accept / reject / undecided | |
| return "accept" if r["accepted"] else ("undecided" if r["undecided"] else "reject") | |
| for s in ("kg", "adv", "sample", "ALL"): | |
| ids = [i for i in by_id if (s == "ALL" or by_id[i]["set"] == s) and i in ours and i in kim] | |
| m: dict = {} | |
| for i in ids: | |
| m.setdefault(okind(ours[i]), {}).setdefault(kim[i]["verdict"], 0) | |
| m[okind(ours[i])][kim[i]["verdict"]] += 1 | |
| rep["matrix"][s] = {"n": len(ids), "rows(ours)xcols(kimina)": m} | |
| # ceiling truth | |
| kg_ids = [i for i in by_id if by_id[i]["set"] == "kg" and i in ours and i in kim] | |
| adv_ids = [i for i in by_id if by_id[i]["set"] == "adv" and i in ours and i in kim] | |
| rep["ceiling"] = { | |
| "kg_ours_accept": sum(ours[i]["accepted"] for i in kg_ids), "kg_n": len(kg_ids), | |
| "kg_kimina_maybe": sum(kim[i]["verdict"] == "maybe" for i in kg_ids), | |
| "kg_kimina_reject": sum(kim[i]["verdict"] == "reject" for i in kg_ids), | |
| "adv_ours_accept": sum(ours[i]["accepted"] for i in adv_ids), "adv_n": len(adv_ids), | |
| "adv_kimina_maybe(compile-only would pass these on)": sum(kim[i]["verdict"] == "maybe" for i in adv_ids), | |
| "adv_kimina_reject": sum(kim[i]["verdict"] == "reject" for i in adv_ids), | |
| "gated_adv_accepts": sum(ours[i]["accepted"] and kim[i]["verdict"] != "reject" for i in adv_ids), | |
| } | |
| # disagreements: everything that is not (ours accept & kimina maybe) or (ours reject & kimina reject) | |
| for i in by_id: | |
| if i not in ours or i not in kim: | |
| continue | |
| o, k = ours[i], kim[i] | |
| ok = okind(o) | |
| kv = k["verdict"] | |
| if (ok == "accept" and kv == "maybe") or (ok == "reject" and kv == "reject"): | |
| continue | |
| if ok == "accept" and kv == "reject": | |
| klass = "PREFILTER_FALSE_REJECT (recall loss)" | |
| elif ok == "reject" and kv == "maybe": | |
| klass = "STRICT_ONLY_REJECT (expected: policy/audit layer)" | |
| elif ok == "accept" and kv == "error": | |
| klass = "PREFILTER_UNDECIDED_STRICT_ACCEPT" | |
| elif ok == "undecided" or kv == "error": | |
| klass = "UNDECIDED_ON_ONE_SIDE" | |
| else: | |
| klass = "OTHER" | |
| rep["disagreements"].append(dict(id=i, set=by_id[i]["set"], klass=klass, ours=ok, ours_reasons=o["reasons"], | |
| ours_s=round(o["elapsed_s"], 2), kimina=kv, kimina_reasons=k["reasons"], | |
| kimina_detail=k.get("detail"), kimina_s=round(k["elapsed_s"], 2), | |
| control=({"ok": ctl[i]["ok"], "clean": ctl[i]["clean"], "why": ctl[i]["why"]} if i in ctl else None))) | |
| from collections import Counter | |
| rep["disagreement_classes"] = dict(Counter(d["klass"] for d in rep["disagreements"])) | |
| # throughput over the sample set (+ kg) : same items, three engines | |
| for name, rows, key in (("ours_strict", ours, "elapsed_s"), ("ours_compile_only", ctl, "elapsed_s"), ("kimina_compile_only", kim, "elapsed_s")): | |
| for s in ("sample", "kg"): | |
| xs = [rows[i][key] for i in by_id if by_id[i]["set"] == s and i in rows] | |
| rep.setdefault("latency_s", {}).setdefault(name, {})[s] = stats(xs) | |
| # busy-time throughput on the SAME items (kg + sample): n / sum(per-item latency) = proofs per second per | |
| # worker-core, independent of the heavy tail's wall-clock packing and of which extra items a phase ran | |
| same = [i for i in by_id if by_id[i]["set"] in ("sample", "kg") and i in ours and i in kim and i in ctl] | |
| rep["busy_throughput_proofs_per_s_per_core"] = { | |
| "n": len(same), | |
| **{nm: round(len(same) / sum(rows[i]["elapsed_s"] for i in same), 4) | |
| for nm, rows in (("ours_strict", ours), ("ours_compile_only", ctl), ("kimina_compile_only", kim))}} | |
| # gated pipeline, from the per-item records (never re-run) | |
| samp = [i for i in by_id if by_id[i]["set"] in ("sample", "kg") and i in ours and i in kim] | |
| if samp: | |
| rep["gated"] = { | |
| "n": len(samp), | |
| "strict_accepts": sum(ours[i]["accepted"] for i in samp), | |
| "gated_accepts": sum(ours[i]["accepted"] and kim[i]["verdict"] != "reject" for i in samp), | |
| "prefilter_rejects": sum(kim[i]["verdict"] == "reject" for i in samp), | |
| "strict_calls_saved": sum(kim[i]["verdict"] == "reject" for i in samp), | |
| "sum_strict_s_saved": round(sum(ours[i]["elapsed_s"] for i in samp if kim[i]["verdict"] == "reject"), 1), | |
| "sum_kimina_s_spent": round(sum(kim[i]["elapsed_s"] for i in samp), 1), | |
| "sum_strict_s_total": round(sum(ours[i]["elapsed_s"] for i in samp), 1), | |
| "sum_strict_s_on_survivors": round(sum(ours[i]["elapsed_s"] for i in samp if kim[i]["verdict"] != "reject"), 1), | |
| } | |
| # agreement on the sample set between the two COMPILE-ONLY engines (our REPL vs Kimina) | |
| both = [i for i in by_id if i in ctl and i in kim and by_id[i]["set"] in ("sample", "kg")] | |
| if both: | |
| agree = sum((ctl[i]["clean"]) == (kim[i]["verdict"] == "maybe") for i in both if kim[i]["verdict"] != "error" and ctl[i]["why"] != "timeout") | |
| n2 = sum(1 for i in both if kim[i]["verdict"] != "error" and ctl[i]["why"] != "timeout") | |
| rep["engine_control"] = {"n_decided_by_both": n2, "clean_agree": agree, | |
| "diffs": [dict(id=i, ours_compile_clean=ctl[i]["clean"], ours_why=ctl[i]["why"], kimina=kim[i]["verdict"], kimina_reasons=kim[i]["reasons"]) | |
| for i in both if kim[i]["verdict"] != "error" and ctl[i]["why"] != "timeout" and ctl[i]["clean"] != (kim[i]["verdict"] == "maybe")]} | |
| return rep | |
| def throughput(rep, items, out: Path, phase_meta: dict) -> None: | |
| by_set = {} | |
| for it in items: | |
| if it["set"] != "meta": | |
| by_set.setdefault(it["pool"], []).append(it["id"]) | |
| tp = {} | |
| for name, f, wkey in (("ours_strict", "ours.jsonl", "ours"), ("ours_compile_only", "control.jsonl", "control"), ("kimina_compile_only", "kimina.jsonl", "kimina")): | |
| m = phase_meta.get(wkey) or {} | |
| wall = m.get("wall") if "wall" in m else (m.get("walls") or {}).get("main") | |
| n_main = sum(1 for r in done_ids(out / f).values() if by_set.get("main") and r["id"] in set(by_set["main"])) | |
| if wall: | |
| tp[name] = {"n": n_main, "wall_s": round(wall, 1), "proofs_per_s": round(n_main / wall, 4), | |
| "proofs_per_s_per_core": round(n_main / wall / CORES, 4), "cores": CORES} | |
| rep["throughput"] = tp | |
| def main() -> int: | |
| ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) | |
| ap.add_argument("--out", required=True) | |
| ap.add_argument("--phases", default="ours,control,kimina") | |
| ap.add_argument("--workers", type=int, default=CORES) | |
| ap.add_argument("--timeout", type=float, default=180.0) | |
| ap.add_argument("--sample", type=int, default=500) | |
| ap.add_argument("--seed", type=int, default=20261019) | |
| ap.add_argument("--generations", default=str(DEFAULT_GENS)) | |
| ap.add_argument("--problems", default=str(DEFAULT_PROBLEMS)) | |
| ap.add_argument("--kg-raw", default=str(DEFAULT_KG_RAW)) | |
| ap.add_argument("--kimina-url", default="http://127.0.0.1:18000") | |
| ap.add_argument("--kimina-bomb-url", default="http://127.0.0.1:18001") | |
| ap.add_argument("--sets", default="kg,adv,sample", help="comma list; smoke runs use e.g. --sets sample --sample 6") | |
| ap.add_argument("--report-only", action="store_true") | |
| args = ap.parse_args() | |
| if args.workers > CORES: | |
| print(f"host rule: at most {CORES} cores/workers", file=sys.stderr) | |
| return 2 | |
| out = Path(args.out) | |
| out.mkdir(parents=True, exist_ok=True) | |
| items = [i for i in build_items(args) if i["set"] == "meta" or i["set"] in args.sets.split(",")] | |
| meta_it = [i for i in items if i["set"] == "meta"][0] | |
| (out / "items_manifest.json").write_text(json.dumps( | |
| {"n": len(items) - 1, "population_code_bearing": meta_it["population"], "seed": args.seed, | |
| "sets": {s: sum(1 for i in items if i["set"] == s) for s in ("kg", "adv", "sample")}, | |
| "ids": [i["id"] for i in items if i["set"] != "meta"]}, indent=1)) | |
| meta_path = out / "phase_meta.json" | |
| phase_meta = json.loads(meta_path.read_text()) if meta_path.exists() else {} | |
| if not args.report_only: | |
| for ph, fn in (("ours", phase_ours), ("control", phase_control), ("kimina", phase_kimina)): | |
| if ph in args.phases.split(","): | |
| print(f"== phase {ph} ==", flush=True) | |
| t = time.monotonic() | |
| m = fn(items, out, args) | |
| if m: | |
| phase_meta[ph] = m | |
| meta_path.write_text(json.dumps(phase_meta, indent=1)) | |
| print(f" done in {time.monotonic() - t:.0f}s", flush=True) | |
| rep = report(items, out, phase_meta) | |
| throughput(rep, items, out, phase_meta) | |
| (out / "report.json").write_text(json.dumps(rep, indent=1, default=str)) | |
| print(json.dumps({k: rep[k] for k in ("matrix", "ceiling", "disagreement_classes", "throughput", "gated") if k in rep}, indent=1)) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |