pns-bind-25m / eval /state_stats.py
nur-dev's picture
PNS-Bind-25M: implementation, configs, eval, results, reproduction
f930dac verified
Raw History Blame Contribute Delete
3.16 kB
#!/usr/bin/env python3
"""Direct test of the mechanism behind the null interventions: does Sigma
carry lifetime-specific information at all?
If Sigma_t were a useful state it would differ between lifetimes at the same
event index. If it has collapsed to a learned constant, the between-lifetime
spread will be negligible against the state's own scale.
"""
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.train.loader import iter_eval_batches # noqa
from evaluate import load_model, to_gpu # noqa
dev = "cuda"
model, _ = load_model(sys.argv[1] if len(sys.argv) > 1 else "PNSR_K4_fin", dev)
d = model.cfg.d
PROBE_T = [31, 63, 127, 191, 255]
snaps = {t: [] for t in PROBE_T}
with torch.no_grad():
for b in iter_eval_batches("val", shards_root(), 32, 32):
g = to_gpu(b, dev)
B, L = g["etype"].shape
state = model.initial_state(B, dev)
with torch.autocast("cuda", dtype=torch.bfloat16):
h = model.recenc.static(g["rec_val_toks"], g["rec_key_toks"], g["rec_store"],
g["rec_kind"], g["rec_key"], g["rec_ent"])
for t in range(L):
live = g["live"][:, t]; mask = live >= 0; rows = live.clamp(min=0)
bank = torch.gather(h, 1, rows.unsqueeze(-1).expand(-1, -1, d))
bank = model.recenc.finalize(
bank, (t - torch.gather(g["rec_birth"], 1, rows)).clamp(min=0))
state, _ = model.step(state, g["tok"][:, t], g["etype"][:, t],
g["dt"][:, t], bank * mask.unsqueeze(-1), mask)
if t in snaps:
snaps[t].append(state.float().cpu())
break
rep = {}
for t, lst in snaps.items():
S = torch.cat(lst) # [B, 120, d]
mean = S.mean(0, keepdim=True)
between = (S - mean).norm(dim=-1).mean().item() # spread across lifetimes
scale = S.norm(dim=-1).mean().item() # typical slot norm
flat = S.flatten(1)
fn = torch.nn.functional.normalize(flat, dim=-1)
cos = (fn @ fn.T)
off = cos[~torch.eye(len(fn), dtype=torch.bool)].mean().item()
rep[str(t)] = dict(slot_norm=round(scale, 4),
between_lifetime_spread=round(between, 4),
spread_over_norm=round(between / max(scale, 1e-9), 4),
mean_pairwise_cosine=round(off, 4))
print(t, rep[str(t)], flush=True)
# drift of the mean state over time: does it even change with t?
ms = {t: torch.cat(lst).mean(0).flatten() for t, lst in snaps.items()}
ks = sorted(ms)
rep["mean_state_cosine_t31_vs_t255"] = round(float(
torch.nn.functional.cosine_similarity(ms[ks[0]], ms[ks[-1]], dim=0)), 4)
run_name = sys.argv[1] if len(sys.argv) > 1 else "PNSR_K4_fin"
atomic_write_json(eval_root() / f"SIGMA_COLLAPSE_{run_name}.json", rep)
print("mean-state cosine t=31 vs t=255:", rep["mean_state_cosine_t31_vs_t255"])