Sudoku_superposition / code /probe_gate_fixed.py
Avra98's picture
push code/probe_gate_fixed.py
fcaf887 verified
Raw History Blame Contribute Delete
9.96 kB
"""The promotion gate, old readout vs corrected readout, on one checkpoint.
Three things were wrong with how CE(q||p) was measured, all of them inflating
it:
prefix data.py applied instance values only when train=True, so eval scored
p on a sequence whose earlier empty cells held the unique solution.
The model eliminates candidates from that -- correctly -- and was
then charged for not matching a q that conditions on nothing.
depth evaluater.py sets k_budget = K, so one full-depth prediction was
compared against every stage's q, including stages training far
fewer slots.
q the stored pool stops at coverage (~5 grids, 1.4 by stage 11) and
deliberately picks rare candidates, so its histogram is flattened
toward uniform. The gate spends log|S| - H(q) as headroom, and that
quantity is meaningless at n = 5: measured H rises from 0.70 at
n = 3 to 0.99 at n = 7 against log|S| = 1.10, still climbing.
Reports the gate statistic under each combination so the contribution of each
correction is visible.
python probe_gate_fixed.py CKPT STAGE [NPUZ] [BIGQ_NPZ]
"""
import functools
import os
import sys
import numpy as np
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
CKPT = sys.argv[1]
S = int(sys.argv[2])
NPUZ = int(sys.argv[3]) if len(sys.argv) > 3 else 300
BIGQ = (sys.argv[4] if len(sys.argv) > 4
else "/egr/research-slim/ghoshavr/ckpt_probe/bigq/test_bigq300.npz")
POP = np.array([bin(i).count("1") for i in range(512)], np.int16)
import jax # noqa: E402
import jax.numpy as jnp # noqa: E402
from jax import random # noqa: E402
from flax.training import checkpoints # noqa: E402
from flax import jax_utils # noqa: E402
from train import main as train_main # noqa: E402
from train import data, model, trainer, evaluater # noqa: E402
config = train_main.get_config()
K = int(config.num_latent_slots)
mc = model.TransformerConfig(
dtype=config.dtype, vocab_size=config.vocab_size, seq_len=config.seq_len,
num_heads=config.num_heads, num_layers=config.num_layers,
emb_dim=config.emb_dim, qkv_dim=config.qkv_dim, mlp_dim=config.mlp_dim,
dropout_rate=config.dropout_rate,
attention_dropout_rate=config.attention_dropout_rate,
deterministic=False, num_latent_slots=K,
inject_latents=bool(int(getattr(config, "recurrent_latent", 1))))
rng = jax.random.PRNGKey(config.seed)
rng, ir, _ = random.split(rng, 3)
rng, dr = jax.random.split(rng)
net = model.TransformerLMHeadModel(mc)
_, iv = jax.jit(net.init_with_output)(
{"params": ir, "dropout": dr},
jnp.ones((config.minibatch_size, config.seq_len), jnp.int32),
jnp.zeros((config.minibatch_size, K, config.emb_dim), mc.dtype),
jnp.zeros((config.minibatch_size, K), jnp.int32),
jnp.zeros((config.minibatch_size, K), bool))
st, _ = trainer.get_state(config, net, iv)
rs = checkpoints.restore_checkpoint(os.path.abspath(CKPT), st)
print(f"restored step {int(rs.step)} stage {S}", flush=True)
state = jax_utils.replicate(rs)
pes = jax.pmap(functools.partial(
evaluater.eval_step, config=mc.replace(deterministic=True)),
axis_name="batch")
cur = data.CurriculumState(stage=S, max_stage=K)
ds = data.SudokuDataset(config, train=False, curriculum=cur)
cm = np.load(config.test_cand_masks_path, mmap_mode="r")
z = np.load(BIGQ)
bigq, biggrid = z["counts"], z["grids"]
sidx = S - 1
BS = config.minibatch_size
nd = jax.local_device_count()
def fwd(sq, lv, act, sp):
def sh(x):
x = np.asarray(x)
return x.reshape((nd, x.shape[0] // nd) + x.shape[1:])
lg, hd, _ = pes(state, sh(sq), sh(lv), sh(sp), sh(act))
lg, hd = np.array(lg), np.array(hd)
return lg.reshape(-1, *lg.shape[2:]), hd.reshape(-1, *hd.shape[2:])
def probs(seqs, si3, depth):
"""Digit probabilities with `depth` latent slots active and built."""
n = len(seqs)
sq = np.stack(seqs)
s3 = np.asarray(si3)
pad = (-n) % BS
if pad:
sq = np.concatenate([sq, np.zeros((pad, config.seq_len), sq.dtype)])
s3 = np.concatenate([s3, np.full(pad, s3[0])])
out = []
for b in range(0, len(sq), BS):
x = sq[b:b + BS].astype(np.int32)
t3 = s3[b:b + BS]
sp = t3[:, None] + np.arange(K)[None, :]
slots = np.arange(K)[None, :]
af = np.ones((len(x), K), bool)
bi = np.arange(len(x))
lv = np.zeros((len(x), K, config.emb_dim), np.float32)
mk = x.copy()
for j in range(len(x)):
mk[j, t3[j] + K:] = 0
for j in range(depth):
_, hd = fwd(mk, lv, af & (slots < j), sp)
lv[:, j] = hd[bi, t3 - 1 + j]
lg, _ = fwd(x, lv, af & (slots < depth), sp)
zz = lg - lg.max(-1, keepdims=True)
p = np.exp(zz)
p /= p.sum(-1, keepdims=True)
out.append(p[:, :, 1:10].astype(np.float64))
return np.concatenate(out)[:n]
# ---- Build every sequence variant once -------------------------------------
R = int(biggrid.shape[2])
rows, meta, sel = [], [], []
for idx in range(min(NPUZ, bigq.shape[0])):
n5 = int(ds.inst_counts[idx, sidx])
base = np.asarray(ds.eval_inputs[idx])
si = int(ds.eval_start_index[idx, 0])
rows.append(ds.insert_latent_slots(base, si))
meta.append((idx, "sol", 0))
for r in range(R):
g = biggrid[idx, sidx, r]
rows.append(ds.insert_latent_slots(ds._apply_values(base, g), si))
meta.append((idx, "big", r))
lo = int(ds.inst_starts[idx, sidx])
n5c = max(n5, 0)
for r in range(min(n5c, 4)):
rows.append(ds.insert_latent_slots(
ds.apply_instance(base, idx, sidx, inst_row=lo + r), si))
meta.append((idx, "pool", r))
sel.append((idx, si, min(n5c, 4)))
si3 = np.array([3 * int(ds.eval_start_index[m[0], 0]) for m in meta])
store = {}
for depth, tag in ((S, "d"), (K, "K")):
P = probs(rows, si3, depth)
for i, (idx, kind, r) in enumerate(meta):
store.setdefault((tag, idx, kind), {})[r] = P[i]
# ---- Score ------------------------------------------------------------------
# Each combination names (which q, which prefix, which depth).
COMBO = [
("OLD gate as it runs today", "pool5", "sol", "K"),
(" + stage depth only", "pool5", "sol", "d"),
(" + instance prefix", "pool5", "big", "d"),
("NEW + large-sample q", "big", "big", "d"),
(" (large q, solution prefix)", "big", "sol", "d"),
]
agg = {c[0]: dict(ce=0.0, lk=0.0, hq=0.0, n=0.0, pt=0.0, un=0.0, spr=0.0,
mass=0.0, qt=0.0, puz=[]) for c in COMBO}
for idx, si, n5c in sel:
seq = np.asarray(ds.insert_latent_slots(ds.eval_inputs[idx], si))
bits = np.asarray(cm[idx, sidx], np.int64)
tb = np.asarray(cm[idx, -1], np.int64)
q5 = np.zeros((81, 9), np.float64)
lo = int(ds.inst_starts[idx, sidx])
n5 = int(ds.inst_counts[idx, sidx])
if n5 > 0:
A = np.asarray(ds.instances[lo:lo + n5], np.int64)
for c in range(81):
col = A[:, c]
v = (col >= 1) & (col <= 9)
np.add.at(q5[c], col[v] - 1, 1)
qb = bigq[idx, sidx].astype(np.float64)
per = {c[0]: dict(ce=0.0, lk=0.0, n=0.0) for c in COMBO}
base0 = 3 * si + K
for t in range(81 - si):
vp = base0 + 3 * t + 2
if vp >= config.seq_len:
break
r_, c_ = int(seq[vp - 2]), int(seq[vp - 1])
if not (0 <= r_ < 9 and 0 <= c_ < 9):
continue
cell = r_ * 9 + c_
k = int(POP[int(bits[cell])])
if k < 2:
continue
inS = np.array([(int(bits[cell]) >> d) & 1 for d in range(9)], bool)
true = int(tb[cell]).bit_length()
lk = np.log(k)
for name, qsrc, psrc, tag in COMBO:
qc = q5[cell] if qsrc == "pool5" else qb[cell]
tot = qc.sum()
if tot <= 0:
continue
q = qc / tot
if psrc == "sol":
dig = store[(tag, idx, "sol")][0][vp - 1]
else:
src = "big" if psrc == "big" else "pool"
d = store[(tag, idx, src)]
dig = np.mean([d[r] for r in sorted(d)], axis=0)[vp - 1]
ce = -float((q * np.log(np.maximum(dig, 1e-12))).sum())
qn = q[q > 0]
T = agg[name]
T["ce"] += ce
T["lk"] += lk
T["hq"] += -float((qn * np.log(qn)).sum())
T["n"] += 1
T["pt"] += float(dig[true - 1])
T["qt"] += float(q[true - 1])
T["un"] += 1.0 / k
m = float(dig[inS].sum())
pin = dig[inS] / max(m, 1e-30)
T["mass"] += m
T["spr"] += float(-(pin * np.log(np.maximum(pin, 1e-30))).sum()) / lk
per[name]["ce"] += ce
per[name]["lk"] += lk
per[name]["n"] += 1
for name in per:
if per[name]["n"] > 0:
agg[name]["puz"].append(max(
0.0, per[name]["ce"] / per[name]["n"]
- per[name]["lk"] / per[name]["n"]))
BAR = 0.05
print(f"\n{len(sel)} held-out puzzles, stage {S}, bar hinge <= {BAR}")
print(f"{'readout':34s} {'q(true)':>8s} {'p(true)':>8s} {'1/|S|':>7s} "
f"{'spread':>7s} {'H(q)':>7s} {'log|S|':>7s} {'CE':>7s} {'hinge':>7s} verdict")
for name, _, _, _ in COMBO:
T = agg[name]
n = max(T["n"], 1)
v = np.array(T["puz"])
h = v.mean() if len(v) else -1.0
print(f"{name:34s} {T['qt']/n:8.3f} {T['pt']/n:8.3f} {T['un']/n:7.3f} "
f"{T['spr']/n:7.3f} {T['hq']/n:7.3f} {T['lk']/n:7.3f} "
f"{T['ce']/n:7.3f} {h:7.4f} "
f"{'PASS' if 0 <= h <= BAR else 'FAIL'}")
print("\nhinge = mean over puzzles of max(0, mean CE(q||p) - mean log|S|)")