Download eval/interference.py from nur-dev/pns-bind-25m: direct link, hf CLI and curl.
- Browser
- Download file 5.68 kB
-
https://huggingface.co/nur-dev/pns-bind-25m/resolve/main/eval/interference.py
- Command line
-
hf download hf://nur-dev/pns-bind-25m/eval/interference.py
-
curl -L -o interference.py https://huggingface.co/nur-dev/pns-bind-25m/resolve/main/eval/interference.py
5.68 kB
| #!/usr/bin/env python3 | |
| """Separate DELAY from INTERFERENCE in the Stage-A positive result. | |
| The preregistered mechanistic signature is: if identity-local writes protect a | |
| binding, accuracy should depend on rewrites of the QUERIED binding, not on how | |
| many unrelated bindings were written in between. The pooled slope conflates | |
| the two because n_unrelated correlates with delay. This computes the 2-D | |
| table acc(delay bucket x unrelated-write bucket). | |
| """ | |
| import json | |
| import sys | |
| from pathlib import Path | |
| import numpy as np | |
| import torch | |
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent / "src")) | |
| sys.path.insert(0, str(Path(__file__).resolve().parent)) | |
| from pns.common import atomic_write_json, eval_root, shards_root # noqa | |
| from pns.checkpoint import load_model # noqa: E402 | |
| from pns.model.modules import enum_legal_mask # noqa: E402 | |
| from pns.train.loader import iter_eval_batches # noqa: E402 | |
| from pns.world.schema import Fam # noqa: E402 | |
| from stage_a import to_dev # noqa: E402 | |
| SEM = (int(Fam.SEM_LATEST), int(Fam.SEM_2HOP)) | |
| def rows_for(run, dev, n_lifetimes=400): | |
| m, _, _ = load_model(run, dev) | |
| out = [] | |
| for b in iter_eval_batches("e3_dev", shards_root(), 24, n_lifetimes): | |
| g = to_dev(b, dev) | |
| B, L = g["etype"].shape | |
| state = m.initial_state(B, dev) | |
| wall = torch.zeros(B, L, device=dev) # cumulative ALL binding writes | |
| wown = {} # cumulative writes per slot | |
| cum = torch.zeros(B, L, m.cfg.n_slots, device=dev) | |
| run_c = torch.zeros(B, m.cfg.n_slots, device=dev) | |
| acc_all = torch.zeros(B, device=dev) | |
| with torch.autocast("cuda", dtype=torch.bfloat16): | |
| for t in range(L): | |
| state, o = m.step(state, g["tok"][:, t], g["etype"][:, t], | |
| g["dt"][:, t], g["bind_write"][:, t], | |
| g["bind_read"][:, t], g["bind_slot_ent"], | |
| g["bind_slot_attr"]) | |
| w = g["bind_write"][:, t] | |
| hit = (w >= 0) | |
| acc_all = acc_all + hit.float() | |
| wall[:, t] = acc_all | |
| if hit.any(): | |
| run_c[torch.arange(B, device=dev), w.clamp(min=0)] += hit.float() | |
| cum[:, t] = run_c | |
| sel = torch.isin(g["family"][:, t], torch.tensor(SEM, device=dev)) | |
| if sel.any(): | |
| legal = enum_legal_mask(g["enum_legal"][sel, t]) | |
| pred = o["enum"][sel].masked_fill(~legal, -1e9).argmax(-1) | |
| ok = (pred == g["enum_gold"][sel, t]).float().cpu().numpy() | |
| idx = torch.nonzero(sel).flatten().tolist() | |
| for j, i in enumerate(idx): | |
| d = int(g["delay"][i, t]) | |
| e0 = max(0, t - d) | |
| rs = int(g["bind_read"][i, t]) | |
| total = float(wall[i, t] - wall[i, e0]) | |
| own = float(cum[i, t, rs] - cum[i, e0, rs]) if rs >= 0 else 0.0 | |
| out.append((float(ok[j]), d, total - own, own)) | |
| return np.array(out) if out else np.zeros((0, 4)) | |
| def table(a, name): | |
| ok, d, unrel, own = a[:, 0], a[:, 1], a[:, 2], a[:, 3] | |
| dq = [0, 16, 64, 10**9] | |
| uq = np.quantile(unrel, [0.33, 0.66]) | |
| lines = [f"### {name}", "", | |
| "| delay \\ unrelated writes | few | mid | many | row slope |", | |
| "| --- | --- | --- | --- | --- |"] | |
| res = {} | |
| for i in range(3): | |
| dm = (d > dq[i]) & (d <= dq[i + 1]) | |
| cells, vals = [], [] | |
| for lo, hi in ((-1, uq[0]), (uq[0], uq[1]), (uq[1], 1e18)): | |
| m = dm & (unrel > lo) & (unrel <= hi) | |
| if m.sum() > 25: | |
| v = float(ok[m].mean()); vals.append(v) | |
| cells.append(f"{v:.3f} (n={int(m.sum())})") | |
| else: | |
| vals.append(np.nan); cells.append("-") | |
| sl = (vals[2] - vals[0]) if not np.isnan(vals[2] + vals[0]) else np.nan | |
| res[f"delay_{dq[i]}_{dq[i+1]}"] = dict(few=vals[0], mid=vals[1], | |
| many=vals[2], slope=sl) | |
| lines.append(f"| d {dq[i]}-{dq[i+1] if i < 2 else 'inf'} | " | |
| + " | ".join(cells) + f" | {sl:+.3f} |") | |
| # dependence on rewrites of the QUERIED binding, holding delay wide | |
| lines += ["", "| own-binding rewrites since evidence | acc |", "| --- | --- |"] | |
| for lo, hi, nm in ((-0.5, 0.5, "0"), (0.5, 1.5, "1"), (1.5, 1e9, "2+")): | |
| m = (own > lo) & (own <= hi) | |
| if m.sum() > 25: | |
| lines.append(f"| {nm} | {float(ok[m].mean()):.3f} (n={int(m.sum())}) |") | |
| res[f"own_{nm}"] = float(ok[m].mean()) | |
| return lines, res | |
| def main(): | |
| dev = "cuda" | |
| runs = sys.argv[1:] or ["E3A_bind_s1", "E3A_unbound_s1"] | |
| rep, lines = {}, ["# Experiment 2 Stage-A: delay vs interference, decomposed", "", | |
| "The pooled interference slope conflates delay with " | |
| "unrelated writes because the two correlate. This holds " | |
| "delay fixed within rows.", ""] | |
| for run in runs: | |
| a = rows_for(run, dev) | |
| L, r = table(a, run) | |
| lines += L + [""] | |
| rep[run] = r | |
| print(run, json.dumps(r, default=str)[:400], flush=True) | |
| out = Path(__file__).resolve().parent.parent / "results" / "reproduced" | |
| out.mkdir(parents=True, exist_ok=True) | |
| (out / "interference.md").write_text("\n".join(lines) + "\n") | |
| atomic_write_json(eval_root() / "E3_INTERFERENCE.json", rep) | |
| print("->", out / "interference.md") | |
| if __name__ == "__main__": | |
| main() | |