mLattice / experiments /polish_capacity_weakkeys.py
Ex0bit's picture
Docker Space: actual demo server, 3 checkpoints, readable dark text
2f06744 verified
Raw History Blame Contribute Delete
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()