GOVINDFROM's picture
Recurrent masked diffusion: results, code, card
ae02605 verified
Raw History Blame Contribute Delete
3.15 kB
"""Does deep supervision make the recurrence EXTRAPOLATE past its training depth?
The claim behind supervising every recurrence is that it forces each application
of the shared block to be a valid one-step refinement. If that holds, the map is
iterable beyond where it was fit, and running more recurrences at inference than
at training should keep helping - or at least not collapse.
This is free: no retraining, only re-evaluating trained checkpoints at different
recurrence counts. A model whose accuracy peaks exactly at its training depth and
degrades past it has learned one entangled R-step function rather than a
repeatable refinement step.
"""
import argparse, glob, json, os, sys
from collections import defaultdict
import numpy as np
import torch
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from model import RecurrentDenoiser
import diffusion as dfn
from sudoku import CELLS, SudokuTokenizer
p = argparse.ArgumentParser()
p.add_argument("--hard", default="data/sudoku_hard")
p.add_argument("--runs", default="runs")
p.add_argument("--pattern", default="rec1-*")
p.add_argument("--passes", type=int, default=16)
p.add_argument("--eval_bs", type=int, default=250)
p.add_argument("--out", default="figures/sudoku_rsweep.json")
a = p.parse_args()
dev = "cuda" if torch.cuda.is_available() else "cpu"
tok = SudokuTokenizer()
P = np.load(os.path.join(a.hard, "test_puz.npy"))
S = np.load(os.path.join(a.hard, "test_sol.npy"))
Pt, St = torch.from_numpy(P), torch.from_numpy(S - 1)
blank = Pt == 0
res = defaultdict(dict)
for d in sorted(glob.glob(os.path.join(a.runs, a.pattern))):
rf = os.path.join(d, "result.json")
if not (os.path.exists(rf) and os.path.exists(os.path.join(d, "model.pt"))):
continue
cfg = json.load(open(rf))["args"]
if not cfg.get("recurrent"):
continue
m = RecurrentDenoiser(len(tok), cfg["d"], cfg["layers"], cfg["heads"], cfg["pe"],
max_len=CELLS + 8, recurrences=cfg["R"]).to(dev)
m.load_state_dict(torch.load(os.path.join(d, "model.pt"), map_location=dev))
m.eval()
name = os.path.basename(d)
trained_R = cfg["R"]
print(f"\n--- {name} (trained with R={trained_R})", flush=True)
print(f" {'R at inference':>16}{'board':>10}", flush=True)
for R in (1, 2, 4, 8, 16, 32, 64, 128):
m.recurrences = R
preds = []
with torch.no_grad():
for i in range(0, len(Pt), a.eval_bs):
gold = St[i:i + a.eval_bs].to(dev)
bl = blank[i:i + a.eval_bs].to(dev)
x = torch.where(bl, torch.full_like(gold, tok.mask), gold)
y, _ = dfn.fixed_k_decode(m, x, tok, dev, a.passes, fillable=bl)
preds.append(y.cpu().numpy() + 1)
acc = float((np.concatenate(preds) == S).all(axis=1).mean())
res[name][R] = acc
flag = " <- trained here" if R == trained_R else ""
print(f" {R:>16}{acc*100:>9.2f}%{flag}", flush=True)
del m
torch.cuda.empty_cache()
os.makedirs(os.path.dirname(a.out) or ".", exist_ok=True)
json.dump(res, open(a.out, "w"), indent=2)
print(f"\nwrote {a.out}")