Download code/scripts/p2/build_valid.py from viai957/align: direct link, hf CLI and curl.
- Browser
- Download file 7.95 kB
-
https://huggingface.co/viai957/align/resolve/main/code/scripts/p2/build_valid.py
- Command line
-
hf download hf://viai957/align/code/scripts/p2/build_valid.py
-
curl -L -o build_valid.py https://huggingface.co/viai957/align/resolve/main/code/scripts/p2/build_valid.py
7.95 kB
| """Build a miniF2F VALIDATION problem file for model selection. | |
| WHY. Round selection, the weight-averaging coefficient and the self-distillation stop rule are | |
| all tuned by looking at scores. Tuning them on miniF2F-test makes the final test number partly | |
| a tuning artefact (adaptive overfitting to 244 problems). miniF2F-valid (244 different problems, | |
| same distribution) is the standard dev split; every development decision is taken on it and the | |
| test split is touched only by the pre-registered final evaluation. | |
| Source: cat-searcher/minif2f-lean4 `valid.jsonl`, pinned revision below. Statements are rewritten | |
| into the SAME envelope as our test file (Goedel's header, `:= by sorry`), so prompts and the | |
| verifier see identical structure. Decontamination against miniF2F-test, MOBench and the training | |
| pool is by signature sha (statement_sha), renaming-invariant sha (alpha_sha), theorem name and | |
| informal text (build_problems' 13-gram / 8-gram layers); any overlap is dropped and counted. Elaboration under the pinned Mathlib is checked with <= 2 | |
| Lean workers (not a heavy job), and failures are dropped and listed. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import hashlib | |
| import importlib.util | |
| import json | |
| import os | |
| import sys | |
| from pathlib import Path | |
| REPO = Path(__file__).resolve().parents[2] | |
| sys.path.insert(0, str(REPO)) | |
| def _load_sibling(name: str): | |
| """Load scripts/p2/<name>.py by path (the repo idiom; keeps the dependency firewall's import scan clean). | |
| A cached module is reused only if it IS this sibling file, and a failed import is not left cached.""" | |
| path = Path(__file__).with_name(f"{name}.py").resolve() | |
| cached = sys.modules.get(name) | |
| if cached is not None and Path(getattr(cached, "__file__", "") or "").resolve() == path: | |
| return cached | |
| spec = importlib.util.spec_from_file_location(name, path) | |
| mod = importlib.util.module_from_spec(spec) | |
| sys.modules[name] = mod | |
| try: | |
| spec.loader.exec_module(mod) | |
| except BaseException: | |
| sys.modules.pop(name, None) | |
| raise | |
| return mod | |
| C = _load_sibling("p2_common") | |
| SRC_REPO = "cat-searcher/minif2f-lean4" | |
| SRC_REV = "70a1249ce240667f6bcdd1ccd62f847f0e065d57" | |
| HEADER = "import Mathlib\nimport Aesop\n\nset_option maxHeartbeats 0\n\nopen BigOperators Real Nat Topology Rat\n\n" | |
| def to_problem(row: dict) -> dict: | |
| stmt = row["formal_statement"].rstrip() | |
| if stmt.endswith(":= sorry"): | |
| stmt = stmt[: -len(":= sorry")] + ":= by sorry" | |
| elif not stmt.endswith(":= by sorry"): | |
| raise C.ContractError(f"{row['id']}: statement does not end in a sorry proof") | |
| informal = (row.get("informal_stmt") or "").strip() | |
| prefix = f"/-- {informal}-/\n" if informal else "" | |
| full = HEADER + prefix + stmt | |
| return { | |
| "problem_id": row["id"], | |
| "source": "minif2f", | |
| "split": "valid", | |
| "formal_statement": full, | |
| "informal_prefix": prefix or None, | |
| "statement_sha": C.statement_sha(full), | |
| } | |
| def elaborate(pool, kept: list, timeout_s: float) -> tuple[list, list]: | |
| """Elaboration check with the grader ceiling-tested FIRST (known-good must pass, known-bad must | |
| fail), as build_problems.run_check does. Without it a broken toolchain or header would mark every | |
| statement as an elaboration failure and silently produce an empty dev split. `pool.map` returns | |
| exceptions in place; any exception stops the build instead of becoming a "failure" row.""" | |
| bp = _load_sibling("build_problems") | |
| probe = pool.map(lambda w, s: bp.check_elaborates(w, s, timeout_s), [bp.SELF_TEST_GOOD, bp.SELF_TEST_BAD]) | |
| for what, res, want in (("known-good", probe[0], True), ("known-bad", probe[1], False)): | |
| if isinstance(res, BaseException): | |
| raise res | |
| if res[0] is not want: | |
| raise RuntimeError(f"elaboration instrument failed its ceiling test: {what} statement gave {res}") | |
| res = pool.map(lambda w, rec: bp.check_elaborates(w, rec["formal_statement"], timeout_s), kept) | |
| good, bad = [], [] | |
| for p, r in zip(kept, res): | |
| if isinstance(r, BaseException): | |
| raise r | |
| ok, why = r | |
| if ok: | |
| good.append(p) | |
| else: | |
| bad.append({"id": p["problem_id"], "reason": why}) | |
| return good, bad | |
| def main(argv=None) -> int: | |
| ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) | |
| ap.add_argument("--src", required=True, help="the pinned valid.jsonl") | |
| ap.add_argument("--problems-dir", required=True, help="dir holding minif2f.jsonl, mobench.jsonl, train_pool.jsonl") | |
| ap.add_argument("--out", required=True) | |
| ap.add_argument("--workers", type=int, default=2) | |
| ap.add_argument("--elab-timeout", type=float, default=60.0) | |
| ap.add_argument("--no-elab", action="store_true", help="skip the Lean elaboration check (tests)") | |
| a = ap.parse_args(argv) | |
| if not 1 <= a.workers <= 2: | |
| raise SystemExit("--workers must be 1 or 2 (this is not a heavy job)") | |
| pdir = Path(a.problems_dir) | |
| shas, alphas, names, guarded = set(), set(), set(), [] | |
| for f in ("minif2f.jsonl", "mobench.jsonl", "train_pool.jsonl"): | |
| for r in C.iter_jsonl(pdir / f): | |
| shas.add(r["statement_sha"]) | |
| alphas.add(C.alpha_sha(r["formal_statement"])) | |
| names.add(r["problem_id"]) | |
| guarded.append(r) | |
| # informal-text layers (13-gram / three 8-grams): "same informal problem, different formalisation" is the realistic leak, and | |
| # build_problems applies it to the pool against test+MOBench only, never against this dev split | |
| informal = _load_sibling("build_problems").Decontaminator(guarded) | |
| kept, dropped = [], [] | |
| for row in C.iter_jsonl(a.src): | |
| try: | |
| p = to_problem(row) | |
| except C.ContractError as e: | |
| dropped.append({"id": row.get("id"), "reason": f"unsupported: {e}"}) | |
| continue | |
| if p["statement_sha"] in shas or C.alpha_sha(p["formal_statement"]) in alphas or p["problem_id"] in names: | |
| dropped.append({"id": p["problem_id"], "reason": "overlaps test/MOBench/pool"}) | |
| continue | |
| why = informal.check_informal(p["informal_prefix"]) | |
| if why is not None: | |
| dropped.append({"id": p["problem_id"], "reason": f"informal text overlaps test/MOBench/pool ({why})"}) | |
| continue | |
| kept.append(p) | |
| elab_bad = [] | |
| if not a.no_elab and kept: | |
| from align.utils import guard | |
| from align.verify.lean import ReplPool, find_toolchain | |
| from align.verify.lean.repl_pool import ReplLimits | |
| guard.preflight_host_check(strict=True) | |
| # scratch on /apps (repo .tmp), never $TMPDIR: /apps/CLAUDE.md rule 5 (no writes to / or /tmp) | |
| pool = ReplPool.from_toolchain(find_toolchain(), n_workers=a.workers, limits=ReplLimits(), preflight=True, | |
| scratch_dir=str(REPO / ".tmp" / "p2_valid_pool")) | |
| try: | |
| kept, elab_bad = elaborate(pool, kept, a.elab_timeout) | |
| finally: | |
| pool.close() | |
| # split first, then its manifest, both atomic. The manifest records the split's sha256, so a kill | |
| # between the two writes (new split, old manifest) is DETECTABLE by any reader, never silent. | |
| C.write_jsonl_atomic(a.out, kept) | |
| manifest = {"source": SRC_REPO, "revision": SRC_REV, "kept": len(kept), "dropped": dropped, | |
| "elaboration_failures": elab_bad, "header": HEADER, | |
| "split_sha256": hashlib.sha256(Path(a.out).read_bytes()).hexdigest()} | |
| mpath = Path(a.out + ".manifest.json") | |
| mtmp = mpath.with_name(mpath.name + f".tmp{os.getpid()}") | |
| mtmp.write_text(json.dumps(manifest, indent=1)) | |
| os.replace(mtmp, mpath) | |
| print(json.dumps({"kept": len(kept), "dropped": len(dropped), "elab_failed": len(elab_bad)})) | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |