File size: 9,011 Bytes
efc0511 | 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 | # train_peer.py - from-scratch pretrain of the TERNARY + PEER DNA controller.
# Reuses the fast DNA machinery: chunk-parallel recurrence (torch.compile), codon
# memory + mutation (ZERO-SHADOW), Muon(2D) + fused AdamW + WSD + MTP aux head.
import argparse, json, math, os, time, threading, queue
from pathlib import Path
import numpy as np, torch, torch.nn.functional as F
import torch.utils.checkpoint as cp
import sys; sys.path.insert(0, '/root/dna')
from model_dna_peer import DnaPeer, peer_counts
ap = argparse.ArgumentParser()
ap.add_argument('--data-dir', default='/root/dna/data')
ap.add_argument('--out', default='/root/dna/ckpt-peer'); ap.add_argument('--resume', default='/root/dna/ckpt-peer/resume.pt')
ap.add_argument('--batch', type=int, default=16); ap.add_argument('--seq', type=int, default=512)
ap.add_argument('--steps', type=int, default=2000000); ap.add_argument('--target-tokens', type=float, default=3e9)
ap.add_argument('--lr', type=float, default=3e-4); ap.add_argument('--muonlr', type=float, default=2e-2)
ap.add_argument('--rowstep', type=float, default=1.0); ap.add_argument('--warmup', type=int, default=2000)
ap.add_argument('--ckpt-every', type=int, default=2000); ap.add_argument('--hf-every', type=int, default=40000)
ap.add_argument('--hf-repo', default='jaivial/dna-diskchat-2b-peer-v1'); ap.add_argument('--log-every', type=int, default=100)
ap.add_argument('--mtp-frac', type=float, default=0.25)
a = ap.parse_args(); dev = 'cuda'; Path(a.out).mkdir(parents=True, exist_ok=True); torch.manual_seed(1)
torch._dynamo.config.cache_size_limit = 64
torch.backends.cuda.matmul.allow_tf32 = True; torch.backends.cudnn.allow_tf32 = True; torch.backends.cudnn.benchmark = True
m = DnaPeer(chunk=int(os.environ.get('CHUNK_C', '16'))).to(dev)
muon_p = []
for blk in m.blocks: muon_p += [blk.proj.weight, blk.o.weight]
mset = set(id(p) for p in muon_p)
adam_p = [p for p in m.parameters() if id(p) not in mset]
for i in range(len(m.blocks)): m.blocks[i] = torch.compile(m.blocks[i])
ctrl, active = peer_counts(m)
print('PEER_CONFIG', json.dumps({'ctrl_params': ctrl, 'active_params_per_token': active,
'codon_table_bytes': m.nrows * m.ncod, 'experts_per_block': m.nk * m.nk, 'batch': a.batch, 'seq': a.seq}), flush=True)
def ns5(Gm, steps=int(os.environ.get('NS_STEPS', '5'))):
x = 3.4445; y = -4.7750; z = 2.0315; X = Gm.bfloat16(); tp = Gm.size(0) > Gm.size(1)
if tp: X = X.t()
X = X / (X.norm() + 1e-7)
for _ in range(steps):
A = X @ X.t(); B = y * A + z * (A @ A); X = x * X + B @ X
if tp: X = X.t()
return X.to(Gm.dtype)
class Muon(torch.optim.Optimizer):
def __init__(self, params, lr=2e-2, momentum=0.95): super().__init__(list(params), dict(lr=lr, momentum=momentum))
@torch.no_grad()
def step(self):
for grp in self.param_groups:
for p in grp['params']:
if p.grad is None: continue
st = self.state[p]
if 'm' not in st: st['m'] = torch.zeros_like(p.grad)
buf = st['m']; buf.mul_(grp['momentum']).add_(p.grad); u = p.grad.add(buf, alpha=grp['momentum'])
u = ns5(u); u = u.mul(max(1.0, p.size(0) / p.size(1)) ** 0.5); p.add_(u, alpha=-grp['lr'])
opt = torch.optim.AdamW(adam_p, lr=a.lr, betas=(.9, .95), weight_decay=.1, fused=True)
muon = Muon(muon_p, lr=a.muonlr); dense = muon_p + adam_p
print('MUON', sum(p.numel() for p in muon_p), 'ADAM', sum(p.numel() for p in adam_p), flush=True)
cur = None; curi = -1
def shard_list(): return sorted([p for p in Path(a.data_dir).glob('shard_*.u16') if not p.with_suffix('.tmp').exists()])
def batch():
global cur, curi
sh = shard_list()
if not sh: raise SystemExit('no shards yet - start stream_data.py first')
i = np.random.randint(0, len(sh))
if i != curi: cur = np.memmap(sh[i], np.uint16, 'r'); curi = i
n = len(cur) - a.seq - 1; ix = np.random.randint(0, n, size=a.batch)
return torch.from_numpy(np.stack([np.asarray(cur[j:j+a.seq+1], np.int64) for j in ix])).to(dev)
def cosf(s):
if s < a.warmup: return s / a.warmup
ds = int(a.steps * 0.8)
if s < ds: return 1.0
p = (s - ds) / max(1, a.steps - ds); return 1.0 - 0.9 * p
def feats(ids):
B, T = ids.shape; x = m.embed(ids)
for blk in m.blocks: x = cp.checkpoint(blk, x, m.chunk, use_reentrant=False)
mem, bal, rows = m.route(x.reshape(B * T, m.d)); return m.norm(x + mem.reshape(B, T, m.d)), bal, rows
def save(step):
tmp = a.resume + '.tmp'
torch.save({'model': m.state_dict(), 'opt': opt.state_dict(), 'muon': muon.state_dict(), 'step': step, 'config': m.config()}, tmp)
os.replace(tmp, a.resume)
def hf_push(step):
try:
from huggingface_hub import HfApi
api = HfApi(token=os.environ.get('HUGGING_FACE')); api.create_repo(a.hf_repo, exist_ok=True)
p = f'{a.out}/ckpt_{step}.pt'
torch.save({'model': m.state_dict(), 'config': m.config(), 'step': step}, p)
api.upload_file(path_or_fileobj=p, path_in_repo=f'checkpoints/ckpt_{step}.pt', repo_id=a.hf_repo)
os.remove(p); print(f'HF_PUSH step {step}', flush=True)
except Exception as e: print('HF_PUSH_FAIL', str(e)[:140], flush=True)
@torch.no_grad()
def valid_loss():
m.eval(); ids = batch()
with torch.autocast('cuda', dtype=torch.bfloat16):
feat, _, _ = feats(ids)
ce = F.cross_entropy(F.linear(feat[:, :-1].reshape(-1, m.d), m.embed.weight), ids[:, 1:].reshape(-1))
m.train(); return ce.item()
start = 0
if os.path.exists(a.resume):
ck = torch.load(a.resume, map_location=dev, weights_only=False)
m.load_state_dict(ck['model']); opt.load_state_dict(ck['opt']); muon.load_state_dict(ck['muon']); start = int(ck['step'])
print('RESUME', start, flush=True)
_bq = queue.Queue(maxsize=4)
def _worker():
while True: _bq.put(batch())
for _ in range(2): threading.Thread(target=_worker, daemon=True).start()
def nextbatch(): return _bq.get()
m.train(); t0 = time.time(); seen = 0; step = start
print('LOOP start', start, 'steps', a.steps, flush=True)
for step in range(start + 1, a.steps + 1):
f = cosf(step)
for g in opt.param_groups: g['lr'] = a.lr * f
for g in muon.param_groups: g['lr'] = a.muonlr * f
ids = nextbatch(); opt.zero_grad(set_to_none=True)
with torch.autocast('cuda', dtype=torch.bfloat16):
feat, bal, rows = feats(ids)
fl = feat[:, :-1].reshape(-1, m.d); tg = ids[:, 1:].reshape(-1)
l1 = F.cross_entropy(F.linear(fl, m.embed.weight), tg)
f2 = feat[:, :-2].reshape(-1, m.d); t2 = ids[:, 2:].reshape(-1)
sel = torch.randint(0, f2.size(0), (int(a.mtp_frac * f2.size(0)),), device=dev)
l2 = F.cross_entropy(F.linear(f2[sel], m.head2.weight), t2[sel])
loss = l1 + 0.5 * l2 + 1e-3 * bal
loss.backward(); torch.nn.utils.clip_grad_norm_(dense, 1.0); opt.step()
if step % int(os.environ.get('MUON_EVERY', '2')) == 0: muon.step()
with torch.no_grad():
leaf = getattr(m, '_leaf', None)
if leaf is not None and leaf.grad is not None:
flat = rows.reshape(-1); g = leaf.grad
gn = g.norm(dim=-1, keepdim=True).clamp_min(1e-8); qstep = m.codebook.detach().std()
rstep = a.rowstep * qstep * f * (m.d ** 0.5); newrows = leaf.detach() - rstep * (g / gn)
uid, inv = torch.unique(flat, return_inverse=True)
acc = torch.zeros(uid.size(0), m.d, device=newrows.device, dtype=newrows.dtype); acc.index_copy_(0, inv, newrows)
if not hasattr(m, '_pu'): m._pu = []; m._pa = []
m._pu.append(uid); m._pa.append(acc)
if step % int(os.environ.get('MUT_EVERY', '16')) == 0:
pu = torch.cat(m._pu); pa = torch.cat(m._pa)
u2, inv2 = torch.unique(pu, return_inverse=True)
a2 = torch.zeros(u2.size(0), m.d, device=pa.device, dtype=pa.dtype); a2.index_copy_(0, inv2, pa)
before = m.codons[u2].clone(); m.mutate_rows(u2, a2)
m._mut = (m.codons[u2] != before).float().mean().item(); m._pu = []; m._pa = []
m._leaf = None
seen += a.batch * (a.seq - 1); toks = step * a.batch * (a.seq - 1)
if step % a.log_every == 0:
torch.cuda.synchronize(); dt = time.time() - t0; ts = seen / dt
print(f'step={step} tokens={toks} ce={l1.item():.4f} ppl={math.exp(min(20, l1.item())):.1f} '
f'bal={bal.item():.3f} mut={getattr(m,"_mut",0):.3f} tok_s={ts:,.0f}', flush=True)
t0 = time.time(); seen = 0
if step % a.ckpt_every == 0:
save(step); vl = valid_loss(); print(f'CKPT step={step} valid_ce={vl:.4f} valid_ppl={math.exp(min(20, vl)):.1f}', flush=True)
if step % a.hf_every == 0: hf_push(step)
if toks >= a.target_tokens:
print(f'TARGET_TOKENS reached {toks}', flush=True); break
save(step); torch.save({'model': m.state_dict(), 'config': m.config()}, Path(a.out) / 'base.pt'); hf_push(step)
print('PEER_BASE_DONE', json.dumps({'valid_ce': valid_loss(), 'steps': step}), flush=True)
|