Download code/scripts/p2/check_seams.py from viai957/align: direct link, hf CLI and curl.
- Browser
- Download file 6.78 kB
-
https://huggingface.co/viai957/align/resolve/main/code/scripts/p2/check_seams.py
- Command line
-
hf download hf://viai957/align/code/scripts/p2/check_seams.py
-
curl -L -o check_seams.py https://huggingface.co/viai957/align/resolve/main/code/scripts/p2/check_seams.py
6.78 kB
| #!/usr/bin/env python | |
| """P2 integrator: assert the seams between the independently built pieces, on real files. | |
| .venv/bin/python scripts/p2/check_seams.py keys --generations G.jsonl --verifications V.jsonl | |
| .venv/bin/python scripts/p2/check_seams.py sft --generations G0 G1 --verifications V0 V1 --shards D/shards | |
| Each check exists because the pieces were written by different hands against a prose contract, | |
| and each failure below is silent when only one side is looked at: | |
| keys generation -> verification. A verification file whose key set differs from the | |
| generation file's (a sample never verified, a verdict for a sample that is not on disk, a | |
| verdict at another cap or round) scores a different experiment than the one generated. | |
| Also checks `code_sha256` against the generation on disk (a stale verdict). | |
| sft verification -> trainer input. The tokens the trainer takes loss on must be EXACTLY the | |
| `completion_ids` vLLM sampled for a sample the verifier ACCEPTED, and the context must be | |
| exactly its `prompt_ids`. This re-derives the labels through the same `ShardDataset` + | |
| `collate_sft` the trainer uses and does not trust `build_sft_data.py`'s own read-back | |
| (an independent implementation of the same property is the only thing that catches a | |
| shared misunderstanding). | |
| Exit status 0 only if every check passes; a report JSON is printed either way. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import importlib.util | |
| import json | |
| import sys | |
| from pathlib import Path | |
| REPO = Path(__file__).resolve().parents[2] | |
| sys.path.insert(0, str(REPO)) | |
| def _common(): | |
| if "p2_common" in sys.modules: | |
| return sys.modules["p2_common"] | |
| spec = importlib.util.spec_from_file_location("p2_common", Path(__file__).with_name("p2_common.py")) | |
| mod = importlib.util.module_from_spec(spec) | |
| sys.modules["p2_common"] = mod | |
| spec.loader.exec_module(mod) | |
| return mod | |
| class SeamError(AssertionError): | |
| pass | |
| def _load(paths, key_fields): | |
| out: dict[tuple, dict] = {} | |
| for p in paths: | |
| for rec in _common().iter_jsonl(p): | |
| k = tuple(rec[f] for f in key_fields) | |
| if k in out: | |
| raise SeamError(f"duplicate key {k} in {p}") | |
| out[k] = rec | |
| return out | |
| def check_keys(gen_paths, ver_paths) -> dict: | |
| """Generation key set == verification key set, and every verdict is for the code on disk.""" | |
| C = _common() | |
| gens = _load(gen_paths, C.KEY_FIELDS) | |
| vers = _load(ver_paths, C.KEY_FIELDS) | |
| missing = sorted(set(gens) - set(vers)) | |
| extra = sorted(set(vers) - set(gens)) | |
| if missing or extra: | |
| raise SeamError(f"key sets differ: {len(missing)} generated but unverified (e.g. {missing[:2]}), " | |
| f"{len(extra)} verified but not generated (e.g. {extra[:2]})") | |
| stale = [k for k, g in gens.items() if vers[k]["code_sha256"] != C.code_sha(g["lean_code"])] | |
| if stale: | |
| raise SeamError(f"{len(stale)} verdicts were computed for different code than the generation holds, e.g. {stale[:2]}") | |
| # a length-capped sample is never a proof, whatever the verdict file says | |
| bad = [k for k, g in gens.items() if g["finish_reason"] == "length" and vers[k]["accepted"]] | |
| if bad: | |
| raise SeamError(f"{len(bad)} length-truncated samples are marked accepted, e.g. {bad[:2]}") | |
| rounds: dict = {} | |
| for k, v in vers.items(): | |
| r = rounds.setdefault(k[2], [0, 0]) | |
| r[0] += 1 | |
| r[1] += int(bool(v["accepted"])) | |
| return {"keys": len(gens), "by_round": {str(r): {"samples": a, "accepted": b} for r, (a, b) in sorted(rounds.items())}} | |
| def check_sft(gen_paths, ver_paths, shards_dir) -> dict: | |
| """Every stored row's loss tokens == completion_ids of an ACCEPTED generation, context == prompt_ids.""" | |
| import numpy as np | |
| from align.data.loader import collate_sft | |
| from align.data.store import ShardDataset | |
| C = _common() | |
| gens = _load(gen_paths, C.KEY_FIELDS) | |
| vers = _load(ver_paths, C.KEY_FIELDS) | |
| rows = 0 | |
| kinds: dict = {} | |
| seen: set = set() | |
| for split in ("train", "held"): | |
| d = Path(shards_dir) / "sft" / split | |
| if not d.is_dir(): | |
| continue | |
| ds = ShardDataset(d) | |
| for i in range(len(ds)): | |
| row = ds[i] | |
| ex = row.extras | |
| key = (row.problem_id, int(ex["sample_idx"]), int(ex["round"]), int(ex["cap_tokens"])) | |
| if key not in gens: | |
| raise SeamError(f"{split}[{i}] {key}: no such generation") | |
| if not (key in vers and vers[key]["accepted"]): | |
| raise SeamError(f"{split}[{i}] {key}: trained on a sample the verifier did not accept") | |
| if key in seen: | |
| raise SeamError(f"{key}: stored twice") | |
| seen.add(key) | |
| g = gens[key] | |
| p = int(ex["prompt_len"]) | |
| ids = np.asarray(row.input_ids) | |
| if p != len(g["prompt_ids"]) or not np.array_equal(ids[:p], np.asarray(g["prompt_ids"])): | |
| raise SeamError(f"{key}: stored context is not the sampled prompt_ids") | |
| lab = collate_sft([row], pad_token_id=0).labels[0].numpy() | |
| trained = lab[lab != -100] | |
| if not np.array_equal(trained, np.asarray(g["completion_ids"], dtype=trained.dtype)): | |
| raise SeamError(f"{key}: tokens with loss ({len(trained)}) != completion_ids ({len(g['completion_ids'])})") | |
| if not np.all(lab[:p] == -100): | |
| raise SeamError(f"{key}: loss leaks into the prompt") | |
| kinds[str(ex["kind"])] = kinds.get(str(ex["kind"]), 0) + 1 | |
| rows += 1 | |
| if rows == 0: | |
| raise SeamError("no SFT rows were found; a vacuous pass") | |
| accepted = sum(1 for v in vers.values() if v["accepted"]) | |
| return {"rows": rows, "by_kind": kinds, "accepted_samples": accepted, "accepted_not_used": accepted - rows} | |
| def main(argv=None) -> int: | |
| ap = argparse.ArgumentParser(description=__doc__.split("\n")[0]) | |
| sub = ap.add_subparsers(dest="cmd", required=True) | |
| for name in ("keys", "sft"): | |
| s = sub.add_parser(name) | |
| s.add_argument("--generations", nargs="+", required=True) | |
| s.add_argument("--verifications", nargs="+", required=True) | |
| if name == "sft": | |
| s.add_argument("--shards", required=True) | |
| a = ap.parse_args(argv) | |
| try: | |
| rep = check_keys(a.generations, a.verifications) if a.cmd == "keys" else check_sft(a.generations, a.verifications, a.shards) | |
| rep["passed"] = True | |
| except SeamError as e: | |
| rep = {"passed": False, "error": str(e)} | |
| print(json.dumps(rep, indent=2)) | |
| return 0 if rep["passed"] else 1 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |