E1 halting-gate machinery (adapter, trainer, gated eval) + pre-registration item 18
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,120 @@
|
||||
"""E1 trainer (PLAN_SELFPACED): halting-gate merge on MBPP prompt-only loop.
|
||||
|
||||
Same data/recipe as train_merge_code.py (STaR-verified code supervision,
|
||||
prompt-only loop_mask), but NO difficulty->depth curriculum: every batch
|
||||
mixes difficulties and runs the soft-halting loop to k_max — the GATE must
|
||||
learn the allocation the curriculum used to hand-code.
|
||||
|
||||
Loss: CE(code tokens | s_mix) + lam * mean(E[iters]).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import math
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from halting_common import HaltingMergeAdapter, halting_loop_logits
|
||||
from loop_common import BandLooper
|
||||
from prep_mbpp import DIRECT_SUFFIX, mbpp_prompt
|
||||
from train_merge_code import build_code_batch, MAX_TOK
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
||||
from jlens.core import load_model # noqa: E402
|
||||
|
||||
OUT = Path(os.environ.get("LOOP_OUT",
|
||||
Path(__file__).resolve().parent.parent / "results-loop"))
|
||||
STEPS = 600
|
||||
BATCH = 4
|
||||
WARMUP = 20
|
||||
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--seed", type=int, default=0)
|
||||
ap.add_argument("--lam", type=float, default=1e-3,
|
||||
help="compute penalty weight on E[iterations]")
|
||||
ap.add_argument("--kmax", type=int, default=4)
|
||||
ap.add_argument("--lr", type=float, default=1e-3)
|
||||
ap.add_argument("--lam-warmup", type=int, default=100,
|
||||
help="steps before the penalty ramps in (anti-collapse)")
|
||||
ARGS = ap.parse_args()
|
||||
LR = ARGS.lr
|
||||
TAG = (f"gate_l{ARGS.lam:g}" + (f"_s{ARGS.seed}" if ARGS.seed else ""))
|
||||
|
||||
|
||||
def lr_at(step):
|
||||
if step < WARMUP:
|
||||
return LR * (step + 1) / WARMUP
|
||||
t = (step - WARMUP) / max(1, STEPS - WARMUP)
|
||||
return 1e-4 + 0.5 * (LR - 1e-4) * (1 + math.cos(math.pi * t))
|
||||
|
||||
|
||||
def lam_at(step):
|
||||
return ARGS.lam * min(1.0, max(0.0, (step - ARGS.lam_warmup) / 100))
|
||||
|
||||
|
||||
def main():
|
||||
rng = random.Random(ARGS.seed)
|
||||
torch.manual_seed(ARGS.seed)
|
||||
data = json.load(open(OUT / "mbpp_data.json"))
|
||||
model, tok = load_model(dtype=torch.bfloat16)
|
||||
for p in model.parameters():
|
||||
p.requires_grad_(False)
|
||||
looper = BandLooper(model)
|
||||
adapter = HaltingMergeAdapter(
|
||||
d=model.config.get_text_config().hidden_size).cuda()
|
||||
opt = torch.optim.AdamW(adapter.parameters(), lr=LR, weight_decay=0.01)
|
||||
|
||||
train = [it for it in data if it["split"] == "train"
|
||||
and it["label"] != "drop" and it["sol_code"]]
|
||||
train = [it for it in train
|
||||
if len(tok(mbpp_prompt(tok, it, DIRECT_SUFFIX))["input_ids"])
|
||||
+ len(tok(it["sol_code"])["input_ids"]) + 12 <= MAX_TOK]
|
||||
val = train[:24]
|
||||
pool = train[24:]
|
||||
print(f"pool={len(pool)} val={len(val)} lam={ARGS.lam} kmax={ARGS.kmax}",
|
||||
flush=True)
|
||||
|
||||
log = []
|
||||
t0 = time.time()
|
||||
for step in range(STEPS):
|
||||
batch = rng.sample(pool, BATCH)
|
||||
ids, msk, lab, lmask = build_code_batch(tok, batch)
|
||||
# last prompt position = last position where label == -100 & mask==1
|
||||
pl = (lmask.long().cumsum(-1).argmax(-1))
|
||||
for g in opt.param_groups:
|
||||
g["lr"] = lr_at(step)
|
||||
logits, q, ei = halting_loop_logits(
|
||||
looper, adapter, ids, ARGS.kmax, msk, pl, loop_mask=lmask)
|
||||
ce = F.cross_entropy(logits[:, :-1].flatten(0, 1).float(),
|
||||
lab[:, 1:].flatten(), ignore_index=-100)
|
||||
loss = ce + lam_at(step) * ei.mean()
|
||||
opt.zero_grad(set_to_none=True)
|
||||
loss.backward()
|
||||
torch.nn.utils.clip_grad_norm_(adapter.parameters(), 1.0)
|
||||
opt.step()
|
||||
hard_b = [it["label"] == "hard" for it in batch]
|
||||
log.append({"step": step, "ce": ce.item(), "ei": ei.mean().item(),
|
||||
"ei_hard": (ei[torch.tensor(hard_b)].mean().item()
|
||||
if any(hard_b) else None),
|
||||
"ei_easy": (ei[~torch.tensor(hard_b)].mean().item()
|
||||
if not all(hard_b) else None)})
|
||||
if step % 10 == 0:
|
||||
r = log[-1]
|
||||
print(f"step {step:4d} ce={r['ce']:.4f} E[k]={r['ei']:.2f} "
|
||||
f"(hard {r['ei_hard']} / easy {r['ei_easy']}) "
|
||||
f"({(time.time()-t0)/(step+1):.1f}s/step)", flush=True)
|
||||
if step % 100 == 99 or step == STEPS - 1:
|
||||
torch.save(adapter.state_dict(),
|
||||
OUT / f"adapter_{TAG}_e{step+1}.pt")
|
||||
json.dump(log, open(OUT / f"train_{TAG}_log.json", "w"))
|
||||
print("done", flush=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user