Download code/eval_ceq_ckpt.py from Avra98/Sudoku_superposition: direct link, hf CLI and curl.
- Browser
- Download file 6.74 kB
-
https://huggingface.co/Avra98/Sudoku_superposition/resolve/main/code/eval_ceq_ckpt.py
- Command line
-
hf download hf://Avra98/Sudoku_superposition/code/eval_ceq_ckpt.py
-
curl -L -o eval_ceq_ckpt.py https://huggingface.co/Avra98/Sudoku_superposition/resolve/main/code/eval_ceq_ckpt.py
6.74 kB
| """Score a saved checkpoint on the CE(q||p) <= log|S| promotion criterion. | |
| Runs the same evaluation the trainer runs, on the same held-out split, but | |
| offline against an arbitrary checkpoint directory, and reports the per-stage | |
| value statistics with the CE criterion first. | |
| The criterion, per cell with |S| >= 2 and headroom: | |
| CE(q||p) <= log|S| | |
| where q is the empirical digit distribution of that puzzle's stored instances at | |
| that stage (post-constraint, what the data teaches) and log|S| is the entropy of | |
| the uniform distribution over the raw pre-constraint candidate set. Cells where | |
| q is already uniform on S are skipped: there the bar equals the floor H(q), so by | |
| Gibbs only p == q exactly could pass. | |
| Usage: | |
| python wavecurriculum_run/eval_ceq_ckpt.py --ckpt_dir DIR [--stage 6] | |
| [--eval_epochs 40] [--label NAME] | |
| DIR is a directory containing a checkpoint_<step> file, as unpacked from the | |
| latest.tar / stageN_step.tar archives on the Hub. | |
| """ | |
| import argparse | |
| import functools | |
| import json | |
| import os | |
| import sys | |
| os.environ.setdefault("SUDOKU_VERBOSE_EVAL", "0") | |
| import numpy as np | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--ckpt_dir", required=True) | |
| ap.add_argument("--stage", type=int, default=0, | |
| help="Curriculum stage to evaluate at; 0 = read it from " | |
| "the checkpoint's sibling latest.json, else max_stage.") | |
| ap.add_argument("--eval_epochs", type=int, default=40) | |
| ap.add_argument("--label", default="") | |
| ap.add_argument("--out_json", default="") | |
| args = ap.parse_args() | |
| # The config is assembled from the environment exactly as the training jobs | |
| # assemble it, so the launcher must export the same SUDOKU_* variables. Only | |
| # the eval-side knobs are overridden here. | |
| os.environ["SUDOKU_EVAL_EPOCHS"] = str(args.eval_epochs) | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| import jax | |
| import jax.numpy as jnp | |
| from jax import random | |
| from flax.training import checkpoints | |
| from flax import jax_utils | |
| from train import main as train_main | |
| from train import data, model, trainer, evaluater | |
| config = train_main.get_config() | |
| K = int(config.num_latent_slots) | |
| stage = args.stage or int(getattr(config, "curriculum_max_stage", 6)) | |
| # Eval iterator only; the curriculum stage matters because the candidate | |
| # masks and instance pool are read per stage. | |
| curriculum = data.CurriculumState( | |
| stage=stage, max_stage=int(getattr(config, "curriculum_max_stage", 6))) | |
| eval_iter = data.create_iter(config, config.minibatch_size, train=False, | |
| curriculum=curriculum) | |
| model_config = 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, init_rng, _ = random.split(rng, num=3) | |
| rng, dropout_rng = jax.random.split(rng) | |
| net = model.TransformerLMHeadModel(model_config) | |
| dummy_latents = jnp.zeros( | |
| (config.minibatch_size, K, config.emb_dim), model_config.dtype) | |
| dummy_positions = jnp.zeros((config.minibatch_size, K), jnp.int32) | |
| dummy_active = jnp.zeros((config.minibatch_size, K), bool) | |
| _, initial_variables = jax.jit(net.init_with_output)( | |
| {"params": init_rng, "dropout": dropout_rng}, | |
| jnp.ones((config.minibatch_size, config.seq_len), jnp.int32), | |
| dummy_latents, dummy_positions, dummy_active) | |
| state, _ = trainer.get_state(config, net, initial_variables) | |
| restored = checkpoints.restore_checkpoint(os.path.abspath(args.ckpt_dir), | |
| state) | |
| step = int(restored.step) | |
| if step == 0: | |
| sys.exit(f"no checkpoint restored from {args.ckpt_dir}") | |
| print(f"restored step {step} from {args.ckpt_dir}", flush=True) | |
| state = jax_utils.replicate(restored) | |
| p_eval_step = jax.pmap( | |
| functools.partial(evaluater.eval_step, | |
| config=model_config.replace(deterministic=True)), | |
| axis_name="batch") | |
| em = evaluater.get_eval_metrics(state, eval_iter, p_eval_step, config) | |
| ceq = em.get("per_stage_val_ceq", {}) | |
| hq = em.get("per_stage_val_hq", {}) | |
| ok = em.get("per_stage_val_ceq_ok", {}) | |
| gap = em.get("per_stage_val_ceq_gap", {}) | |
| puz = em.get("per_stage_val_ceq_puz", {}) | |
| mass = em.get("per_stage_val_mass_multi", {}) | |
| spread = em.get("per_stage_val_spread", {}) | |
| hb = em.get("per_stage_val_hbound", {}) | |
| tag = args.label or os.path.basename(os.path.abspath(args.ckpt_dir)) | |
| print(f"\n==== {tag} step {step} eval_stage {stage} " | |
| f"({args.eval_epochs} eval batches) ====") | |
| print("stage H(q) ceq log|S|* slack ceq_ok ceq_puz" | |
| " mass* spread* hbound") | |
| rows = {} | |
| for s in sorted(ceq): | |
| if ceq[s] < 0: | |
| continue | |
| # log|S| on the counted cells, recovered from ceq + its slack. | |
| lm = ceq[s] + gap[s] | |
| rows[s] = {"h_q": hq[s], "ceq": ceq[s], "log_m": lm, "slack": gap[s], | |
| "ceq_ok": ok[s], "ceq_puz": puz.get(s, -1.0), | |
| "mass_multi": mass.get(s, -1.0), | |
| "spread": spread.get(s, -1.0), "hbound": hb.get(s, -1.0)} | |
| flag = "" if gap[s] >= 0 else " <-- over ceiling" | |
| print(f"{s:>5} {hq[s]:6.4f} {ceq[s]:7.4f} {lm:7.4f} {gap[s]:+7.4f}" | |
| f" {ok[s]:6.3f} {puz.get(s, -1):7.3f}" | |
| f" {mass.get(s, -1):6.3f} {spread.get(s, -1):7.3f}" | |
| f" {hb.get(s, -1):6.3f}{flag}") | |
| if stage in rows: | |
| r = rows[stage] | |
| verdict = ("PROMOTE" if r["ceq_ok"] >= 0.90 else "HOLD") | |
| print(f"\nfrontier stage {stage}: ceq_ok = {r['ceq_ok']:.3f} " | |
| f"vs bar 0.90 -> {verdict}") | |
| print(f" mean CE(q||p) = {r['ceq']:.4f}, floor H(q) = {r['h_q']:.4f}, " | |
| f"ceiling log|S| = {r['log_m']:.4f}") | |
| if args.out_json: | |
| with open(args.out_json, "w") as f: | |
| json.dump({"label": tag, "step": step, "eval_stage": stage, | |
| "eval_epochs": args.eval_epochs, "per_stage": rows}, f, | |
| indent=1) | |
| print(f"wrote {args.out_json}") | |
| if __name__ == "__main__": | |
| main() | |