mylab-share-2 / eval_stability.py
pengxiang's picture
Add files using upload-large-folder tool
7b3a667 verified
Raw
History Blame Contribute Delete
5.3 kB
"""Depth-scan harvest policies for one deterministic model.
The flip analysis (d24 vs d96, warm-s2 probe) showed: solved puzzles freeze
(202/211 identical preds), unsolved ones keep wandering (0/45 frozen), and the
d48+ plateau is right->wrong (-9) cancelling wrong->right (+14). This script
harvests that flux with depth-scan policies, all deterministic:
fixed-D : exact/perpos at each depth (reference)
stable-halt : scan depths ascending; lock a puzzle at the first depth whose
prediction equals the previous depth's (converged), else last.
Deployable: per-puzzle early exit on stability.
cell-vote : per-cell majority across depths (temporal self-ensemble).
oracle : right at ANY depth (ceiling for per-puzzle depth selection).
Usage: DISABLE_COMPILE=1 python eval_stability.py checkpoints/fl-b01-L4-warm-s2 \
--step 78120 --depths 6,12,24,36,48,72,96 --max_batches 8
"""
import os, argparse, glob
os.environ.setdefault("DISABLE_COMPILE", "1"); os.environ.setdefault("WANDB_MODE", "offline")
import numpy as np
import torch
from omegaconf import OmegaConf
from pretrain_config import PretrainConfig
from create_model import create_model
from pretrain import create_dataloader, autocast_ctx
from models.losses import IGNORE_LABEL_ID
ap = argparse.ArgumentParser()
ap.add_argument("ckpt_dir"); ap.add_argument("--step", default="")
ap.add_argument("--depths", default="6,12,24,36,48,72,96"); ap.add_argument("--max_batches", type=int, default=8)
a = ap.parse_args()
cfg = OmegaConf.to_container(OmegaConf.load(os.path.join(a.ckpt_dir, "all_config.yaml")), resolve=True)
cfg.update(load_checkpoint=None, resume_from=None, metrics_out=None)
base = PretrainConfig(**cfg)
loader, meta = create_dataloader(base, "test", test_set_mode=True, epochs_per_iter=1,
global_batch_size=base.global_batch_size, rank=0, world_size=1)
model, _, _ = create_model(base, meta, rank=0, world_size=1, strict_load=False)
strip = lambda sd: {k.replace("_orig_mod.", ""): v for k, v in sd.items()}
bl = sorted(glob.glob(os.path.join(a.ckpt_dir, "step_*_train_state.pt")), key=lambda p: int(p.split("step_")[1].split("_")[0]))
b = os.path.join(a.ckpt_dir, f"step_{a.step}_train_state.pt") if a.step else bl[-1]
d = torch.load(b, map_location="cpu", weights_only=False)
model.load_state_dict(strip(d["model"]), strict=False)
model.load_state_dict(strip(d["ema"]), strict=False)
model.eval()
icfg = model.model.inner.config
depths = [int(x) for x in a.depths.split(",") if x]
print(f"{os.path.basename(b)} (EMA) depths={depths} batches={a.max_batches}", flush=True)
preds_by_d, labels = {}, None
for D in depths:
icfg.L_cycles = D; icfg.n_backwards_L = D
ps, ls = [], []
with torch.no_grad():
for bi, (_, batch, _) in enumerate(loader):
if bi >= a.max_batches: break
batch = {k: v.cuda() for k, v in batch.items()}
with torch.device("cuda"): carry = model.initial_carry(batch)
while True:
with autocast_ctx(base):
carry, _, _, _, preds, fin = model(carry=carry, batch=batch, return_keys=["preds"])
if fin: break
ps.append(preds["preds"].short().cpu()); ls.append(batch["labels"].short().cpu())
preds_by_d[D] = torch.cat(ps).numpy()
if labels is None: labels = torch.cat(ls).numpy()
v = labels != IGNORE_LABEL_ID
ex = (((preds_by_d[D] == labels) & v).sum(1) == v.sum(1))
pp = ((preds_by_d[D] == labels) & v).sum() / v.sum()
print(f" fixed d{D:<3d}: exact {ex.mean():.4f} perpos {pp:.4f}", flush=True)
v = labels != IGNORE_LABEL_ID
n = labels.shape[0]
right = {D: (((preds_by_d[D] == labels) & v).sum(1) == v.sum(1)) for D in depths}
# oracle: right at any depth
oracle = np.zeros(n, bool)
for D in depths: oracle |= right[D]
# stable-halt: lock at first depth whose preds == previous depth's preds
final = np.array([preds_by_d[depths[-1]][i] for i in range(n)])
lockD = np.full(n, depths[-1])
locked = final.copy()
done = np.zeros(n, bool)
for i in range(1, len(depths)):
Dp, Dc = depths[i-1], depths[i]
same = ((preds_by_d[Dp] == preds_by_d[Dc]) | ~v).all(1)
take = same & ~done
locked[take] = preds_by_d[Dc][take]; lockD[take] = Dc; done |= take
ex_halt = (((locked == labels) & v).sum(1) == v.sum(1))
pp_halt = ((locked == labels) & v).sum() / v.sum()
# cell-vote: per-cell majority across depths
stack = np.stack([preds_by_d[D] for D in depths]) # [K, N, T]
K = stack.shape[0]
vote = np.zeros_like(stack[0])
for i in range(n):
col = stack[:, i, :] # [K, T]
for t in np.where(v[i])[0]:
vals, cnt = np.unique(col[:, t], return_counts=True)
vote[i, t] = vals[np.argmax(cnt)]
ex_vote = (((vote == labels) & v).sum(1) == v.sum(1))
pp_vote = ((vote == labels) & v).sum() / v.sum()
print(f"\n policy | exact | perpos | note")
print(f" stable-halt | {ex_halt.mean():.4f} | {pp_halt:.4f} | mean lock depth {lockD.mean():.1f} (compute<=d{depths[-1]})")
print(f" cell-vote | {ex_vote.mean():.4f} | {pp_vote:.4f} | majority over {K} depths")
print(f" oracle | {oracle.mean():.4f} | — | right at ANY depth (ceiling)")
print(f" (n={n})", flush=True)