Download experiments/polish_capacity_weakkeys.py from Ex0bit/mLattice: direct link, hf CLI and curl.
- Browser
- Download file 5.61 kB
-
https://huggingface.co/spaces/Ex0bit/mLattice/resolve/main/experiments/polish_capacity_weakkeys.py
- Command line
-
hf download hf://spaces/Ex0bit/mLattice/experiments/polish_capacity_weakkeys.py
-
curl -L -o polish_capacity_weakkeys.py https://huggingface.co/spaces/Ex0bit/mLattice/resolve/main/experiments/polish_capacity_weakkeys.py
5.61 kB
| #!/usr/bin/env python3 | |
| """Weak-key targeted polish (final 0.2 points). | |
| Census (results/weak_keys.json, 2 seeds x 4,096 queries): ALL residual errors | |
| at the 0.998 plateau come from ~16 distinct key ids out of 16,384 -- 0.1% of | |
| the vocabulary. The full-batch polish cannot move them (0.4% exposure per | |
| step) and uniform training wobbles the optimum instead. | |
| This script: | |
| * alternates small 512-pair batches whose stored keys and query set FORCE the | |
| weak keys (every batch stores AND queries all of them; DFT 1/p weighting | |
| on top) with full 8,192-pair stability batches; | |
| * probes on FRESH census seeds (never trained on) every 100 steps, f=14, | |
| Q=2,048, and saves when two consecutive probes show <= 1 error total. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import sys | |
| import time | |
| import torch | |
| import torch.nn.functional as F | |
| HERE = os.path.dirname(os.path.abspath(__file__)) | |
| ROOT = os.path.join(HERE, "..") | |
| sys.path.insert(0, os.path.join(ROOT, "src")) | |
| from mulattice.protocol import (SynthCfg, build_model, eval_batch, force_fast_scan, logits_at, | |
| spread_loss, synth_batch) | |
| DEVICE = os.environ.get("DEVICE", "cuda") | |
| CKPT_IN = os.environ.get("RESUME", os.path.join(ROOT, "checkpoints", "capacity8k.pt")) | |
| CKPT_OUT = os.environ.get("OUT", os.path.join(ROOT, "checkpoints", "capacity8k.pt")) | |
| MAX_STEPS = int(os.environ.get("STEPS", "500")) | |
| LR = float(os.environ.get("LR", "5e-5")) | |
| CFG = SynthCfg() | |
| def save(m, path, extra=None): | |
| live_scale = float(m.blocks[0].mixer.cfg.addr_scale) | |
| payload = {"model": m.state_dict(), "proto_cfg": { | |
| "NK": CFG.NK, "NV": CFG.NV, "d_model": CFG.d_model, "n_heads": CFG.n_heads, | |
| "d_k": CFG.d_k, "d_v": CFG.d_v, "addr_scale": live_scale, | |
| "retention_init": CFG.retention_init}, "cfg": "weak-key targeted polish"} | |
| if extra: | |
| payload.update(extra) | |
| torch.save(payload, path) | |
| def weighted_ce(logits, gold): | |
| p_gold = torch.softmax(logits, -1).gather(1, gold.unsqueeze(1)).squeeze(1) | |
| w = (1.0 / p_gold.clamp_min(1e-4)).clamp(1.0, 10.0) | |
| ce_vec = F.cross_entropy(logits, gold, reduction="none") | |
| return (w * ce_vec).sum() / w.sum() | |
| def main(): | |
| dev = DEVICE | |
| weak = json.load(open(os.path.join(ROOT, "results", "weak_keys.json"))) | |
| weak_t = torch.tensor(sorted(weak)) | |
| print(f"weak-key polish: {len(weak_t)} keys, lr {LR}, max {MAX_STEPS}", flush=True) | |
| ck = torch.load(CKPT_IN, map_location="cpu", weights_only=False) | |
| cfg = SynthCfg() | |
| if ck.get("proto_cfg", {}).get("addr_scale"): | |
| cfg.addr_scale = float(ck["proto_cfg"]["addr_scale"]) | |
| m = build_model(cfg) | |
| m.load_state_dict(ck["model"]) | |
| m = m.to(dev).train() | |
| force_fast_scan(m) | |
| opt = torch.optim.AdamW(m.parameters(), lr=LR) | |
| g = torch.Generator().manual_seed(21) | |
| t0 = time.time() | |
| good = 0 | |
| probe_seed = 301 | |
| hist = [] | |
| for s in range(MAX_STEPS): | |
| if s % 3 == 2: # stability: full-size batch | |
| b = synth_batch(1, 8192, 64, CFG, filler=14, generator=g) | |
| else: # targeted: small batch with weak keys | |
| # consistent by construction: include_keys guarantees the weak keys | |
| # are stored exactly once; qpos guarantees every weak key is queried | |
| nw = len(weak_t) | |
| qpos = torch.cat([torch.arange(nw), | |
| nw + torch.randperm(512 - nw, generator=g)[: 128 - nw] | |
| ]).unsqueeze(0) | |
| b = synth_batch(1, 512, 128, CFG, filler=14, generator=g, qpos=qpos, | |
| kw={"include_keys": weak_t}) | |
| tok = b["tokens"].to(dev) | |
| logits = logits_at(m, tok, b["ans_pos"].to(dev)) | |
| gold = b["gold"].to(dev).reshape(-1) | |
| ce = weighted_ce(logits.reshape(-1, CFG.V), gold) | |
| sl = spread_loss(m, tok, b["key_pos"].to(dev), CFG, gate_subsample=512) | |
| loss = ce + sl | |
| opt.zero_grad(set_to_none=True) | |
| loss.backward() | |
| torch.nn.utils.clip_grad_norm_(m.parameters(), 1.0) | |
| opt.step() | |
| if s % 50 == 0: | |
| print(f" wk {s:4d} ce {float(ce.detach()):.4f} T{b['L']} {time.time()-t0:6.0f}s", | |
| flush=True) | |
| if (s + 1) % 100 == 0 or s == MAX_STEPS - 1: | |
| errs = 0 | |
| for _ in range(2): # fresh census probes, 2048 queries each | |
| gg = torch.Generator().manual_seed(probe_seed) | |
| probe_seed += 1 | |
| bb = synth_batch(1, 8192, 1024, CFG, filler=14, generator=gg) | |
| m.eval() | |
| with torch.no_grad(): | |
| pred = logits_at(m, bb["tokens"].to(dev), bb["ans_pos"].to(dev)).argmax(-1).cpu() | |
| m.train() | |
| errs += int((pred != bb["gold"]).sum()) | |
| hist.append({"s": s, "errs_2048": errs}) | |
| with open(os.path.join(ROOT, "results", "polish_weakkeys.json"), "w") as fh: | |
| json.dump(hist, fh, indent=2) | |
| print(f" CENSUS probe (2x1024 fresh queries): {errs} errors " | |
| f"[{time.time()-t0:.0f}s]", flush=True) | |
| good = good + 1 if errs <= 1 else 0 | |
| if errs <= 3: # always keep the best-so-far | |
| save(m, CKPT_OUT, {"step": s, "census_errs": errs}) | |
| if good >= 2: | |
| print(f"TARGET: two consecutive censuses <= 1 error at step {s}", flush=True) | |
| save(m, CKPT_OUT, {"step": s, "census_errs": errs}) | |
| return | |
| print("done", flush=True) | |
| if __name__ == "__main__": | |
| main() | |