Download code/probe_gate_fixed.py from Avra98/Sudoku_superposition: direct link, hf CLI and curl.
- Browser
- Download file 9.96 kB
-
https://huggingface.co/Avra98/Sudoku_superposition/resolve/main/code/probe_gate_fixed.py
- Command line
-
hf download hf://Avra98/Sudoku_superposition/code/probe_gate_fixed.py
-
curl -L -o probe_gate_fixed.py https://huggingface.co/Avra98/Sudoku_superposition/resolve/main/code/probe_gate_fixed.py
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|)") | |