Download src/audit.py from GOVINDFROM/parallel-consistent-masked-diffusion: direct link, hf CLI and curl.
- Browser
- Download file 12.5 kB
-
https://huggingface.co/GOVINDFROM/parallel-consistent-masked-diffusion/resolve/main/src/audit.py
- Command line
-
hf download hf://GOVINDFROM/parallel-consistent-masked-diffusion/src/audit.py
-
curl -L -o audit.py https://huggingface.co/GOVINDFROM/parallel-consistent-masked-diffusion/resolve/main/src/audit.py
12.5 kB
| """Numerical audit. Checks the things that would silently invalidate results. | |
| Written because two earlier projects produced confident, wrong numbers: an | |
| answer-length leak that handed the model free information, and a learning-rate | |
| artifact that made a working method look broken. Both were invisible in the | |
| accuracy tables. | |
| """ | |
| import glob, json, os, sys | |
| import numpy as np | |
| import torch | |
| sys.path.insert(0, os.path.dirname(__file__)) | |
| from data import build_addition, build_probe | |
| from sigmorphon import load_language | |
| import diffusion as dfn | |
| from model import Transformer | |
| FAIL = [] | |
| def check(name, ok, detail=""): | |
| print(f" [{'PASS' if ok else 'FAIL'}] {name}" + (f" — {detail}" if detail else ""), flush=True) | |
| if not ok: | |
| FAIL.append(name) | |
| print("\n=== 1. data integrity ===") | |
| for d in (4, 8, 12, 16): | |
| X, A, tok = build_addition(300, d, 3 * d + 4, seed=5, exact=True) | |
| bad = 0 | |
| for i in range(len(X)): | |
| s = [tok.itos[t] for t in X[i] if t != tok.pad] | |
| a = int("".join(s[: s.index("+")])) | |
| b = int("".join(s[s.index("+") + 1: s.index("=")])) | |
| got = int("".join(reversed(s[s.index("=") + 1:]))) | |
| bad += (a + b != got) | |
| check(f"{d}-digit addition arithmetic", bad == 0, f"{bad}/300 wrong") | |
| # the answer region must not reveal its own length | |
| check(f"{d}-digit answer mask is fixed-width", | |
| len({int(x) for x in A.sum(1)}) <= 2, | |
| f"{len({int(x) for x in A.sum(1)})} distinct widths") | |
| print("\n=== 2. the prompt is never masked (no label leakage) ===") | |
| X, A, tok = build_addition(200, 8, 28, seed=7, exact=True) | |
| Xt, At = torch.from_numpy(X), torch.from_numpy(A) | |
| xt = torch.where(At, torch.full_like(Xt, tok.mask), Xt) | |
| prompt_ok = ((xt != tok.mask) | At).all() | |
| check("only answer positions are masked at decode time", bool(prompt_ok)) | |
| eq_visible = all((xt[i] == tok.stoi["="]).any() for i in range(len(xt))) | |
| check("the '=' separator stays visible", eq_visible) | |
| print("\n=== 3. metric cannot be gamed ===") | |
| gold = Xt[:50] | |
| am = At[:50] | |
| perfect = dfn.cond_decode # placeholder to keep import used | |
| from train_add import exact_match | |
| check("identical prediction scores 1.0", exact_match(gold, gold, am, tok) == 1.0) | |
| shifted = gold.clone() | |
| shifted[am] = (shifted[am] + 1) % 10 + 4 | |
| check("all-wrong prediction scores 0.0", exact_match(shifted, gold, am, tok) == 0.0) | |
| const = gold.clone() | |
| const[am] = tok.stoi["0"] | |
| acc_const = exact_match(const, gold, am, tok) | |
| check("constant-output prediction is not rewarded", acc_const < 0.05, f"{acc_const:.3f}") | |
| print("\n=== 4. probe tasks: ground truth valid, degenerate output rejected ===") | |
| from train import probe_accuracy | |
| from data import CharTokenizer | |
| for task in ("copy", "agree", "parity"): | |
| Xp, tk = build_probe(200, 64, 4, seed=1, task=task) | |
| gt = probe_accuracy(Xp, task, 4, tk) | |
| cst = probe_accuracy(np.full_like(Xp, 5), task, 4, tk) | |
| check(f"{task}: ground truth valid", gt > 0.99, f"{gt:.2f}") | |
| if task != "copy": | |
| check(f"{task}: constant output rejected", cst < 0.01, f"{cst:.2f}") | |
| print("\n=== 5. inflection: no train/dev overlap ===") | |
| try: | |
| (Xtr, Atr), (Xdv, Adv), tok2 = load_language( | |
| "english", os.environ.get("SIGROOT", "data/sig"), "medium", 48) | |
| tr = {tuple(int(v) for v in r) for r in Xtr} | |
| dv = {tuple(int(v) for v in r) for r in Xdv} | |
| check("english train/dev disjoint", len(tr & dv) == 0, f"{len(tr & dv)} shared") | |
| check("dev set is non-trivial", len(Xdv) > 200, f"{len(Xdv)} items") | |
| except Exception as e: | |
| check("inflection loader", False, f"{type(e).__name__}: {e}") | |
| print("\n=== 6. reported numbers reproduce from raw run files ===") | |
| runs = os.environ.get("RUNS", "runs") | |
| groups = {} | |
| for f in glob.glob(f"{runs}/g11-*/result.json"): | |
| r = json.load(open(f)) | |
| a = r["args"] | |
| groups.setdefault((a["digits"], a["mode"]), []).append(r["accuracy_by_passes"]["1"]) | |
| bad = [k for k, v in groups.items() if len(v) < 2] | |
| check("every reported group has >=2 seeds", not bad, f"thin groups: {bad[:3]}") | |
| oor = [] | |
| for f in glob.glob(f"{runs}/*/result.json"): | |
| r = json.load(open(f)) | |
| for k, v in (r.get("accuracy_by_passes") or {}).items(): | |
| if not (0.0 <= v <= 1.0): | |
| oor.append((os.path.basename(os.path.dirname(f)), k, v)) | |
| check("all accuracies within [0,1]", not oor, str(oor[:3])) | |
| print("\n=== 7. decoders: adaptive commitment rules behave as specified ===") | |
| # These three rules produce the headline text8 numbers, so their contract is | |
| # checked directly rather than trusted: every masked position must end up | |
| # filled, decoding must always make progress (no stall), and the entropy budget | |
| # must actually behave monotonically in the budget - otherwise the | |
| # quality/compute curve it traces would be meaningless. | |
| try: | |
| from text8 import Text8Tokenizer | |
| t8 = Text8Tokenizer() | |
| torch.manual_seed(0) | |
| L, B = 32, 8 | |
| dm = Transformer(len(t8), 64, 2, 2, "rope", causal=False, max_len=L + 8) | |
| dm.eval() | |
| blank = torch.full((B, L), t8.mask, dtype=torch.long) | |
| for name, fn in [ | |
| ("fixed-K K=1", lambda b: dfn.fixed_k_decode(dm, b, t8, "cpu", 1)), | |
| ("fixed-K K=8", lambda b: dfn.fixed_k_decode(dm, b, t8, "cpu", 8)), | |
| ("confidence tau=0.99", | |
| lambda b: dfn.confidence_threshold_decode(dm, b, t8, "cpu", 0.99)), | |
| ("entropy-budget B=0.1", | |
| lambda b: dfn.entropy_budget_decode(dm, b, t8, "cpu", 0.1)), | |
| ("entropy-budget B=inf", | |
| lambda b: dfn.entropy_budget_decode(dm, b, t8, "cpu", 1e9))]: | |
| x, nfe = fn(blank) | |
| check(f"{name}: no [MASK] survives", not bool((x == t8.mask).any()), | |
| f"{int((x == t8.mask).sum())} left, nfe={nfe}") | |
| check(f"{name}: emits only real symbols", bool((x < len(t8) - 1).all())) | |
| _, n1 = dfn.fixed_k_decode(dm, blank, t8, "cpu", 1) | |
| check("fixed-K K=1 uses exactly one forward pass", n1 == 1, f"nfe={n1}") | |
| _, nb = dfn.entropy_budget_decode(dm, blank, t8, "cpu", 1e9) | |
| check("unbounded entropy budget commits in one pass", nb == 1, f"nfe={nb}") | |
| # a tighter budget must never be cheaper than a looser one | |
| nfes = [dfn.entropy_budget_decode(dm, blank, t8, "cpu", b)[1] | |
| for b in (0.01, 0.5, 2.0, 8.0, 1e9)] | |
| check("entropy budget is monotone: tighter budget costs >= passes", | |
| all(nfes[i] >= nfes[i + 1] for i in range(len(nfes) - 1)), str(nfes)) | |
| # partial contexts must be left alone: only `fillable` positions may change | |
| part = blank.clone() | |
| part[:, :L // 2] = 3 | |
| fill = torch.zeros_like(part, dtype=torch.bool) | |
| fill[:, L // 2:] = True | |
| y, _ = dfn.entropy_budget_decode(dm, part, t8, "cpu", 0.5, fillable=fill) | |
| check("decoding never overwrites the given context", | |
| bool((y[:, :L // 2] == 3).all())) | |
| except Exception as e: | |
| check("decoder contract", False, f"{type(e).__name__}: {e}") | |
| print("\n=== 8. text8 splits are disjoint and standard ===") | |
| try: | |
| from text8 import load_text8, N_TRAIN, N_VALID, N_TEST | |
| root = os.environ.get("T8ROOT", "data/text8") | |
| tr8, va8, te8, tk8 = load_text8(root, 256) | |
| check("standard 90M/5M/5M split", (N_TRAIN, N_VALID, N_TEST) == | |
| (90_000_000, 5_000_000, 5_000_000)) | |
| check("vocabulary is 27 symbols + [MASK]", len(tk8) == 28, f"{len(tk8)}") | |
| check("no window straddles a split boundary", | |
| tr8.size + va8.size + te8.size <= 100_000_000, | |
| f"{tr8.size + va8.size + te8.size} chars covered") | |
| seen = {bytes(r) for r in tr8[::997]} | |
| dup = sum(bytes(r) in seen for r in te8[::997]) | |
| check("no test window appears in the training sample", dup == 0, f"{dup} shared") | |
| except FileNotFoundError: | |
| print(" [skip] text8 not downloaded here") | |
| except Exception as e: | |
| check("text8 loader", False, f"{type(e).__name__}: {e}") | |
| print("\n=== 9. graph families: enumeration matches known combinatorics ===") | |
| # These families have closed-form counts, so the enumerator can be checked | |
| # against mathematics rather than against itself. If enumeration is wrong then | |
| # every marginal, every entropy and V* are wrong, and the central proposition | |
| # would be "verified" against a fiction. | |
| try: | |
| from graphs import enumerate_family, exact_stats, sample, is_valid, n_edges | |
| # Cayley: labelled spanning trees on n nodes = n^(n-2) | |
| for n in (5, 6, 7): | |
| got = len(enumerate_family("tree", n)) | |
| check(f"spanning trees on {n} nodes = {n}^{{{n}-2}}", got == n ** (n - 2), | |
| f"{got} vs {n ** (n - 2)}") | |
| # perfect matchings on 2m nodes = (2m-1)!! | |
| for n, want in ((4, 3), (6, 15)): | |
| got = len(enumerate_family("matching", n)) | |
| check(f"perfect matchings on {n} nodes = {want}", got == want, f"{got}") | |
| # 2-regular on 6 labelled vertices: one 6-cycle (5!/2=60) + two 3-cycles (10) | |
| got = len(enumerate_family("2regular", 6)) | |
| check("2-regular graphs on 6 nodes = 70", got == 70, f"{got}") | |
| # the proposition itself, on every family | |
| bad = [] | |
| for n in (6, 7): | |
| for fam in ("matching", "2regular", "tree", "trianglefree", "bipartite"): | |
| try: | |
| st = exact_stats(fam, n) | |
| except ValueError: | |
| continue | |
| V, tc = st["one_pass_ceiling"], st["total_correlation_bits"] | |
| if V < 2 ** (-tc) - 1e-9: | |
| bad.append((fam, n, V, 2 ** (-tc))) | |
| if tc < -1e-9: | |
| bad.append((fam, n, "negative TC", tc)) | |
| check("V* >= 2^-TC on every family (the proposition)", not bad, str(bad[:2])) | |
| # sampled graphs must actually belong to the family they were drawn from | |
| for fam in ("matching", "tree", "bipartite"): | |
| X = sample(fam, 6, 200, seed=3) | |
| check(f"{fam}: sampled graphs are all in the family", | |
| bool(is_valid(X, fam, 6).all())) | |
| check(f"{fam}: sample width = n_edges", X.shape[1] == n_edges(6)) | |
| except Exception as e: | |
| check("graph families", False, f"{type(e).__name__}: {e}") | |
| print("\n=== 10. molecules: the encoding is lossless ===") | |
| # Storing aromatic bond types loses per-ATOM aromaticity, so real molecules | |
| # failed to rebuild and generated-sample validity would have been scored against | |
| # a target the representation could not reach. Round-trip on REAL data is the | |
| # check that catches it. | |
| try: | |
| from qm9 import MolTokenizer, SEQ_LEN, MAX_ATOMS, load_qm9, validity | |
| mt = MolTokenizer() | |
| am = mt.allowed_mask() | |
| check("node slots forbid bond symbols", | |
| not am[:MAX_ATOMS][:, mt.bond_ids].any()) | |
| check("edge slots forbid atom symbols", | |
| not am[MAX_ATOMS:][:, mt.atom_ids].any()) | |
| check("no position may emit [MASK]", not am[:, mt.mask].any()) | |
| root = os.environ.get("QM9ROOT", "data/qm9") | |
| Xm, mtok, smis = load_qm9(root, limit=1500) | |
| v, _ = validity(Xm, mtok) | |
| check("round-trip validity on REAL molecules is ~100%", v > 0.999, | |
| f"{v*100:.2f}%") | |
| check("sequence width = 9 atoms + 36 bonds", Xm.shape[1] == SEQ_LEN, | |
| f"{Xm.shape[1]} vs {SEQ_LEN}") | |
| except FileNotFoundError: | |
| print(" [skip] qm9.csv not downloaded here") | |
| except Exception as e: | |
| check("molecule encoding", False, f"{type(e).__name__}: {e}") | |
| print("\n=== 11. budget conditioning ===") | |
| try: | |
| from data import build_addition as _ba | |
| _, _, tk = _ba(2, 4, 16, seed=0, exact=True) | |
| m0 = Transformer(len(tk), 32, 2, 2, "rope", causal=False, max_len=24) | |
| m1 = Transformer(len(tk), 32, 2, 2, "rope", causal=False, max_len=24, | |
| budget_bins=8) | |
| check("budget_bins=0 adds no parameters", | |
| m0.n_params() == m1.n_params() - 8 * 32, | |
| f"{m0.n_params()} vs {m1.n_params()}") | |
| # a K=1 decode only ever sees the fully-masked state, so t must be exactly 1 | |
| t1 = dfn._t_for_budget(torch.ones(256)) | |
| check("K=1 always draws t=1", bool((t1 == 1.0).all()), f"max dev {float((t1-1).abs().max()):.2e}") | |
| t4 = dfn._t_for_budget(torch.full((4096,), 4.0)) | |
| vals = sorted({round(float(v), 4) for v in t4}) | |
| check("K=4 draws only {0.25,0.5,0.75,1.0}", | |
| vals == [0.25, 0.5, 0.75, 1.0], str(vals)) | |
| check("t is never zero", bool((dfn._t_for_budget(torch.full((4096,), 64.0)) > 0).all())) | |
| b = Transformer.budget_bin(torch.tensor([1., 2., 4., 8., 1024.]), 8, "cpu") | |
| check("budget bins are log2 and clamped", b.tolist() == [0, 1, 2, 3, 7], | |
| str(b.tolist())) | |
| except Exception as e: | |
| check("budget conditioning", False, f"{type(e).__name__}: {e}") | |
| print(f"\n=== AUDIT: {len(FAIL)} failure(s) ===") | |
| for f in FAIL: | |
| print(" !", f) | |
| sys.exit(1 if FAIL else 0) | |