File size: 7,505 Bytes
4ef8b0b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | """deviant_bed.py — trains the DEVIANT ROSTER candidates on the certified byte
bed. #TAG:deviant_bed #TAG:loss_campaign
One arm per gate-cleared candidate (inventory/DEVIANT_ROSTER.md) + the
gate-refused confidence penalty as the DESIGNATED CONTROL (tests whether gate
refusals predict training reality) + the sparsemax coupling-axis probe on the
addr_head collapse configuration. Certified operating point throughout:
wikitext-2 bytes, block 256, batch 32, 2000 steps, pure Adam 3e-4 wd=0,
fp32/TF32-off, crc32 seeds. Ledgers -> tools/deviant_runs/*.jsonl.
Baselines on this exact protocol (this session): ce 2.4769 mean bpb (3 seeds),
fac_lsh 4.1285, ce_fixedcode 3.8060, fac_none 3.9547.
Run: python tools/deviant_bed.py --arm <name> --seed N [--steps 2000]
python tools/deviant_bed.py --list
"""
import json
import math
import os
import sys
import time
import zlib
import torch
import torch.nn as nn
import torch.nn.functional as F
def _root():
d = os.path.abspath(os.getcwd())
while True:
if os.path.exists(os.path.join(d, "MANIFEST.md")):
return d
p = os.path.dirname(d)
if p == d:
return os.getcwd()
d = p
ROOT = _root()
if os.path.join(ROOT, "tools") not in sys.path:
sys.path.insert(0, os.path.join(ROOT, "tools"))
import ar_differentiation_bed as bed # noqa: E402
from loss_forms import (dev_geomean_accum, dev_softmax_accum, fac_loss_link,
prim_ce, pwa_weights, sparsemax_loss) # noqa: E402
torch.backends.cuda.matmul.allow_tf32 = False
torch.backends.cudnn.allow_tf32 = False
DEV = "cuda" if torch.cuda.is_available() else "cpu"
if DEV == "cuda":
torch.cuda.set_per_process_memory_fraction(0.73)
DATA_ROOT = os.environ.get("GEOLIP_DATA", "./data")
RUNS = os.path.join(ROOT, "tools", "deviant_runs")
REF_CKPT = os.path.join(DATA_ROOT, "fac_ckpts", "ce_s0_t2000.pt")
def seed_for(name):
return zlib.crc32(name.encode()) & 0x7FFFFFFF
ARMS = ("softmax_accum", "geomean_accum", "label_smooth", "focal",
"sparsemax", "anti_curr", "fac_tanh", "fac_cauchy",
"conf_penalty_CONTROL", "p4_sparsemax")
FAC_ARMS = ("fac_tanh", "fac_cauchy")
def build(arm, seed):
torch.manual_seed(seed_for(f"deviant:{arm}:{seed}"))
model_arm = "addr_head" if arm == "p4_sparsemax" else "addr_msl64"
lm = bed.ByteLM(model_arm).to(DEV)
ctx = {}
if arm in FAC_ARMS:
lm.head = nn.Identity() # forward returns feats
gp = torch.Generator().manual_seed(seed_for("deviant-fac-frame"))
ctx["R"] = torch.linalg.qr(
torch.randn(256, 256, generator=gp))[0][:64].to(DEV)
gsm = torch.randn(258, 64, generator=gp) # lsh: box-3 smoothed
sm = (gsm[:-2] + gsm[1:-1] + gsm[2:]) / 3.0
ctx["code"] = ((sm > 0).float() * 2 - 1).to(DEV)
if arm == "anti_curr":
ref = bed.ByteLM("addr_msl64").to(DEV)
sd = torch.load(REF_CKPT, map_location="cpu", weights_only=True)
ref.load_state_dict(sd["state_dict"], strict=True)
ref.eval()
for p_ in ref.parameters():
p_.requires_grad_(False)
ctx["ref"] = ref
return lm, ctx
def loss_of(arm, lm, ctx, x, y):
out = lm(x)
if arm in FAC_ARMS:
link = "tanh" if arm == "fac_tanh" else "cauchy"
return fac_loss_link(out, ctx["R"], ctx["code"][y], link=link).mean()
if arm == "sparsemax" or arm == "p4_sparsemax":
return sparsemax_loss(out.reshape(-1, 256), y.reshape(-1)).mean()
ce_tok = prim_ce(out, y)
if arm == "softmax_accum":
return dev_softmax_accum(ce_tok, T=0.5)
if arm == "geomean_accum":
return dev_geomean_accum(ce_tok)
if arm == "label_smooth":
return F.cross_entropy(out.reshape(-1, 256), y.reshape(-1),
label_smoothing=0.1)
if arm == "focal":
w = (1 - F.softmax(out, -1).gather(-1, y.unsqueeze(-1))
.squeeze(-1).detach()) ** 2
return (w * ce_tok).sum() / w.sum().clamp_min(1e-9)
if arm == "anti_curr":
with torch.no_grad():
pi = F.softmax(ctx["ref"](x), -1).gather(
-1, y.unsqueeze(-1)).squeeze(-1)
m = (pi > 0.6).float()
return (ce_tok * m).sum() / m.sum().clamp_min(1.0)
if arm == "conf_penalty_CONTROL":
p = F.softmax(out, -1)
ent = -(p * p.clamp_min(1e-12).log()).sum(-1).mean()
return ce_tok.mean() - 0.1 * ent
raise ValueError(arm)
@torch.no_grad()
def evaluate(arm, lm, ctx, va, g):
"""bpb-of-record (softmax over available scores), decoded acc, vitals."""
lm.eval()
tot_ce, tot_ok, n = 0.0, 0, 0
for _ in range(8):
x, y = bed._batch(va, 32, 256, DEV, g)
out = lm(x)
if arm in FAC_ARMS:
s = F.normalize(out, dim=-1) @ ctx["R"].t()
scores = s @ ctx["code"].t()
else:
scores = out
lp = F.log_softmax(scores, -1)
tot_ce += float(-lp.gather(-1, y.unsqueeze(-1)).sum())
tot_ok += int((scores.argmax(-1) == y).sum())
n += y.numel()
x, _ = bed._batch(va, 8, 256, DEV, g)
_ = lm(x)
v = lm.head_addr.vitals(lm.head_proj(lm._last_h).view(
*lm._last_h.shape[:-1], lm.n_slots, 4)) \
if getattr(lm, "n_slots", 0) else lm.head_addr.vitals(lm._last_h)
lm.train()
return (tot_ce / n) / math.log(2), tot_ok / n, v
def train_arm(arm, seed, steps=2000):
assert arm in ARMS, f"unknown arm {arm} (see --list)"
os.makedirs(RUNS, exist_ok=True)
tr, va = bed._wikitext_bytes(DATA_ROOT)
lm, ctx = build(arm, seed)
g = torch.Generator().manual_seed(seed_for(f"deviant-data:{arm}:{seed}"))
ge = torch.Generator().manual_seed(seed_for("deviant-eval"))
opt = torch.optim.Adam((p_ for p_ in lm.parameters() if p_.requires_grad),
lr=3e-4, weight_decay=0.0)
t0 = time.time()
for step in range(steps):
x, y = bed._batch(tr, 32, 256, DEV, g)
opt.zero_grad(set_to_none=True)
L = loss_of(arm, lm, ctx, x, y)
L.backward()
opt.step()
if step == 10 and DEV == "cuda":
print(f"[{arm} s{seed}] step10 loss {float(L.detach()):.4f} "
f"peak {torch.cuda.max_memory_allocated()/2**30:.2f}GB",
flush=True)
bpb, acc, vit = evaluate(arm, lm, ctx, va, ge)
rec = {"arm": arm, "seed": seed, "steps": steps, "bpb": round(bpb, 4),
"decoded_acc": round(acc, 4), "vitals": vit,
"wall_s": round(time.time() - t0, 1),
"peak_gb": round(torch.cuda.max_memory_allocated() / 2**30, 2)
if DEV == "cuda" else 0.0}
out = os.path.join(RUNS, f"{arm}_s{seed}_t{steps}.jsonl")
with open(out, "a", encoding="utf-8") as f:
f.write(json.dumps(rec) + "\n")
print(f"[DONE {arm} s{seed}] bpb {bpb:.4f} acc {acc:.4f} "
f"usage_ppl {vit['aliveness']['usage_ppl']:.1f} "
f"win|cos| {vit['win_cos_mean']:.3f} ({rec['wall_s']}s)", flush=True)
return rec
if __name__ == "__main__":
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("--arm", default=None)
ap.add_argument("--seed", type=int, default=0)
ap.add_argument("--steps", type=int, default=2000)
ap.add_argument("--list", action="store_true")
a, _ = ap.parse_known_args()
if a.list or not a.arm:
print("arms:", " ".join(ARMS))
sys.exit(0)
train_arm(a.arm, a.seed, a.steps)
|