recipe+sizing artifacts
Browse files- _recipe/year_branch_train.py +345 -0
_recipe/year_branch_train.py
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""FY2001 additive year-plugin SCAFFOLD (plan §2, r=512). Skeleton + DRY mode.
|
| 2 |
+
AdditiveYearBranch = independent bottleneck FFN E_t (d->r->d, r=512) + scalar gate
|
| 3 |
+
g_t=σ(w_t·h + v_t·c_t), hooked at insertion layers. Frozen backbone; ONLY E_t/g_t train.
|
| 4 |
+
Hard year-key isolation (this-year tokens only) + cutoff g:=0 -> byte-exact backbone.
|
| 5 |
+
DRY: wire onto frozen base, train a few steps on tiny FY2001 set, prove (a) branch reduces
|
| 6 |
+
NLL, (b) backbone params byte-unchanged (base_equal). Real training gated on Manager review.
|
| 7 |
+
"""
|
| 8 |
+
import os, sys, json, math, urllib.request, random
|
| 9 |
+
|
| 10 |
+
BLUE="/blue/fire-finai/lq62.yale/annulus"; CODE_V11=f"{BLUE}/code_v11"
|
| 11 |
+
BASE_CKPT=f"{BLUE}/checkpoints/train/v11-33e-2001/weights"
|
| 12 |
+
TOK_PATH=f"{BLUE}/checkpoints/train/v11-33e-2001/context/qwen2-annulus-v5f"
|
| 13 |
+
EDGAR=f"{BLUE}/data/edgar_multiyear_v2"; OUT_DIR=f"{BLUE}/residprep_uf"
|
| 14 |
+
DISCORD_CHAN="1516845719955439636"; MANAGER_UID="1518372899575169215"
|
| 15 |
+
MAXTOK=512; R=int(os.environ.get("BRANCH_R") or "128"); SEED=1234
|
| 16 |
+
INSERT_LAYERS=[int(x) for x in (os.environ.get("INSERT_LAYERS") or "12,23").split(",")] # plan §1: early+mid
|
| 17 |
+
DRY=os.environ.get("DRY","1")=="1"; DRY_DOCS=8; DRY_STEPS=int(os.environ.get("DRY_STEPS") or "400")
|
| 18 |
+
LR=float(os.environ.get('LR') or '3e-4'); FISCAL_YEAR=2001
|
| 19 |
+
DATA_YEAR=int(os.environ.get("DATA_YEAR") or "2001")
|
| 20 |
+
DATA_N=int(os.environ.get("DATA_N") or "3000")
|
| 21 |
+
REAL_STEPS=int(os.environ.get("REAL_STEPS") or "3000")
|
| 22 |
+
FILTERED_DIR=os.environ.get("FILTERED_DIR") or "/blue/fire-finai/lq62.yale/annulus/data/01_filtered/parts"
|
| 23 |
+
|
| 24 |
+
os.environ["ANNULUS_ROUTED_EXPERTS"]="33"; os.environ["ANNULUS_SHARED_EXPERTS"]="1"
|
| 25 |
+
os.environ["ANNULUS_SHARED_FFN"]="2048"; os.environ["ANNULUS_LAYERS"]="24"
|
| 26 |
+
os.environ["ANNULUS_TOPK"]="8"; os.environ["ANNULUS_GROUPED_GEMM"]="0"; os.environ["ANNULUS_GROUP_AUX"]="1"
|
| 27 |
+
os.environ["RANK"]="0"; os.environ["WORLD_SIZE"]="1"; os.environ["LOCAL_RANK"]="0"
|
| 28 |
+
os.environ.setdefault("MASTER_ADDR","localhost")
|
| 29 |
+
os.environ["MASTER_PORT"]=str(20000+(int(os.environ.get("SLURM_JOB_ID","0") or 0) or os.getpid())%40000)
|
| 30 |
+
for p in [f"{CODE_V11}/nemo/src", f"{BLUE}/.pylibs", CODE_V11, f"{CODE_V11}/eval"]:
|
| 31 |
+
try: sys.path.remove(p)
|
| 32 |
+
except ValueError: pass
|
| 33 |
+
sys.path.insert(0,p)
|
| 34 |
+
|
| 35 |
+
def discord_post(msg):
|
| 36 |
+
t=os.environ.get("DISCORD_BOT_TOKEN","")
|
| 37 |
+
if not t: print(f"[discord_post] no token — {msg}"); return
|
| 38 |
+
try:
|
| 39 |
+
urllib.request.urlopen(urllib.request.Request(
|
| 40 |
+
f"https://discord.com/api/v10/channels/{DISCORD_CHAN}/messages",
|
| 41 |
+
data=json.dumps({"content":msg}).encode(),
|
| 42 |
+
headers={"Authorization":f"Bot {t}","Content-Type":"application/json"},method="POST"),timeout=15)
|
| 43 |
+
except Exception as e: print(f"[discord_post] failed: {e}")
|
| 44 |
+
|
| 45 |
+
import torch
|
| 46 |
+
torch.manual_seed(SEED); random.seed(SEED)
|
| 47 |
+
|
| 48 |
+
class AdditiveYearBranch(torch.nn.Module):
|
| 49 |
+
"""E_t + scalar content-gate. Init to no-op (fc2=0) => backbone byte-exact until trained."""
|
| 50 |
+
def __init__(self, d, r, c_t):
|
| 51 |
+
super().__init__()
|
| 52 |
+
self.fc1=torch.nn.Linear(d, r); self.fc2=torch.nn.Linear(r, d, bias=False)
|
| 53 |
+
torch.nn.init.zeros_(self.fc2.weight)
|
| 54 |
+
self.w=torch.nn.Parameter(torch.zeros(d)); self.v=torch.nn.Parameter(torch.zeros(d))
|
| 55 |
+
self.register_buffer("c_t", c_t.detach().float()) # frozen year-context vector
|
| 56 |
+
self.enabled=True # cutoff: set False -> g:=0 -> h_out=h_frozen byte-exact
|
| 57 |
+
def forward(self, h):
|
| 58 |
+
if not self.enabled: return h
|
| 59 |
+
wd=self.fc1.weight.dtype
|
| 60 |
+
g=torch.sigmoid((h.to(wd)*self.w).sum(-1,keepdim=True) + (self.c_t*self.v).sum()) # σ(w·h+v·c_t)
|
| 61 |
+
e=self.fc2(torch.nn.functional.silu(self.fc1(h.to(wd))))
|
| 62 |
+
return h + (g*e).to(h.dtype)
|
| 63 |
+
|
| 64 |
+
print(f"[branch] loading base v11-32e (mask e32), R={R}, insert L{INSERT_LAYERS}, DRY={DRY}", flush=True)
|
| 65 |
+
import icl_eval_v5 as V
|
| 66 |
+
core, tok = V.build_v5_model_and_tokenizer(BASE_CKPT, TOK_PATH)
|
| 67 |
+
nm=0
|
| 68 |
+
for name,param in core.named_parameters():
|
| 69 |
+
if ("mlp.experts.linear_fc" in name) and name.endswith("weight32"): param.data.zero_(); nm+=1
|
| 70 |
+
assert nm>0
|
| 71 |
+
for p in core.parameters(): p.requires_grad=False # FREEZE backbone
|
| 72 |
+
core.eval()
|
| 73 |
+
# snapshot backbone for base_equal (byte-exact freeze proof)
|
| 74 |
+
snap={n:p.detach().clone() for n,p in core.named_parameters()}
|
| 75 |
+
|
| 76 |
+
# year-context c_t = [Y{yr}] token embedding (real year anchor)
|
| 77 |
+
d_model=1024
|
| 78 |
+
YEAR_TOK_ID=tok(f"[Y{FISCAL_YEAR}]",add_special_tokens=False)["input_ids"]
|
| 79 |
+
assert len(YEAR_TOK_ID)==1, f"[Y{FISCAL_YEAR}] not single token: {YEAR_TOK_ID}"
|
| 80 |
+
YEAR_TOK_ID=YEAR_TOK_ID[0]
|
| 81 |
+
_emb=None
|
| 82 |
+
for _n,_p in core.named_parameters():
|
| 83 |
+
if _p.dim()==2 and _p.shape[1]==d_model and _p.shape[0]>=150000 and ("embed" in _n.lower() or "word" in _n.lower()):
|
| 84 |
+
_emb=_p; break
|
| 85 |
+
if _emb is None:
|
| 86 |
+
for _n,_p in core.named_parameters():
|
| 87 |
+
if _p.dim()==2 and _p.shape[1]==d_model and _p.shape[0]>=150000: _emb=_p; break
|
| 88 |
+
assert _emb is not None, "word embedding [vocab,d_model] not found"
|
| 89 |
+
c_t=_emb[YEAR_TOK_ID].detach().float().cuda()
|
| 90 |
+
_ctnorm=float(c_t.norm())
|
| 91 |
+
print(f"[branch] special_token_added=True YEAR_TOK[Y{FISCAL_YEAR}]={YEAR_TOK_ID} c_t_norm={_ctnorm:.4f} emb={tuple(_emb.shape)}",flush=True)
|
| 92 |
+
discord_post(f"\U0001f9e9 <@{MANAGER_UID}> FY2001 anchor: [Y{FISCAL_YEAR}]token=YES(id={YEAR_TOK_ID}, prepended to每篇), c_t=emb[{YEAR_TOK_ID}] norm={_ctnorm:.4f} "+("YEAR-ANCHOR-OK" if _ctnorm>1e-6 else "c_t=0!"))
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
tf=[m for n,m in core.named_modules() if m.__class__.__name__=="TransformerLayer"]
|
| 96 |
+
branches=[]; hooks=[]
|
| 97 |
+
for li in INSERT_LAYERS:
|
| 98 |
+
br=AdditiveYearBranch(d_model, R, c_t).cuda(); branches.append(br)
|
| 99 |
+
def mk(b):
|
| 100 |
+
def hook(mod,inp,out):
|
| 101 |
+
hs=out[0] if isinstance(out,tuple) else out; m=b(hs)
|
| 102 |
+
return (m,)+out[1:] if isinstance(out,tuple) else m
|
| 103 |
+
return hook
|
| 104 |
+
hooks.append(tf[li].register_forward_hook(mk(br)))
|
| 105 |
+
n_branch_params=sum(p.numel() for b in branches for p in b.parameters())
|
| 106 |
+
print(f"[branch] {len(branches)} branches, {n_branch_params} trainable params (~{n_branch_params/1e6:.2f}M)", flush=True)
|
| 107 |
+
# ③ cutoff invariant HARD GATE (module-level, noise-free): enabled=False => branch(x)==x bit-for-bit
|
| 108 |
+
_b=branches[0]; _b.enabled=False
|
| 109 |
+
for _dt in (torch.bfloat16, torch.float32):
|
| 110 |
+
_x=torch.randn(5,1,d_model,device="cuda",dtype=_dt)
|
| 111 |
+
assert torch.equal(_b(_x),_x), f"CUTOFF ③ FAIL dt={_dt}: branch(x)!=x when disabled"
|
| 112 |
+
_b.enabled=True
|
| 113 |
+
print("[branch] ③ CUTOFF_INVARIANT_PASS (enabled=False -> bit-exact)", flush=True)
|
| 114 |
+
|
| 115 |
+
docs=[]; n2001=0
|
| 116 |
+
import glob as _g3
|
| 117 |
+
import pyarrow.parquet as _pq3
|
| 118 |
+
_cand=sorted(_g3.glob(f"{FILTERED_DIR}/year_{FISCAL_YEAR}_*.parquet"))
|
| 119 |
+
_yearsplit=bool(_cand)
|
| 120 |
+
if not _cand: _cand=sorted(_g3.glob(f"{FILTERED_DIR}/*.parquet"))
|
| 121 |
+
assert _cand, f"no parquet in {FILTERED_DIR}"
|
| 122 |
+
_sch=_pq3.ParquetFile(_cand[0]).schema_arrow
|
| 123 |
+
_tx=next((x for x in ("text","content","body","document","output","input","raw") if x in _sch.names),None)
|
| 124 |
+
_dc=next((x for x in ("date","date_filed","fiscal_year","filing_year","year","time") if x in _sch.names),None)
|
| 125 |
+
assert _tx, f"cols={list(_sch.names)} no text col"
|
| 126 |
+
for _p in _cand:
|
| 127 |
+
if len(docs)>=DATA_N: break
|
| 128 |
+
_cols=[_tx]+([_dc] if _dc and not _yearsplit else [])
|
| 129 |
+
_t=_pq3.read_table(_p,columns=_cols)
|
| 130 |
+
_tl=_t.column(_tx).to_pylist()
|
| 131 |
+
_dl=_t.column(_dc).to_pylist() if (_dc and not _yearsplit) else [None]*len(_tl)
|
| 132 |
+
for _tv,_dv in zip(_tl,_dl):
|
| 133 |
+
if len(docs)>=DATA_N: break
|
| 134 |
+
if _yearsplit or (not _dc) or str(_dv)[:4]==str(FISCAL_YEAR):
|
| 135 |
+
n2001+=1
|
| 136 |
+
if _tv and len(_tv)>1500:
|
| 137 |
+
ids=[YEAR_TOK_ID]+tok(_tv,add_special_tokens=False)["input_ids"][:MAXTOK-1]
|
| 138 |
+
if len(ids)>=16: docs.append(ids)
|
| 139 |
+
print(f"[branch] parts={len(_cand)} yearsplit={_yearsplit} text={_tx} date={_dc} FY{FISCAL_YEAR}_rows={n2001} usable={len(docs)}",flush=True)
|
| 140 |
+
discord_post(f"\U0001f9e9 <@{MANAGER_UID}> 01_filtered 落地读通:parts={len(_cand)} yearsplit={_yearsplit} FY{FISCAL_YEAR}行={n2001} 可用docs={len(docs)}")
|
| 141 |
+
assert len(docs)>=8, f"only {len(docs)} usable (rows={n2001}) — STOP ask Lingfei"
|
| 142 |
+
|
| 143 |
+
def nll(ds):
|
| 144 |
+
tot,nt=0.0,0
|
| 145 |
+
with torch.no_grad():
|
| 146 |
+
for ids in ds:
|
| 147 |
+
inp=torch.tensor([ids],device="cuda"); pos=torch.arange(len(ids),device="cuda").unsqueeze(0)
|
| 148 |
+
out=core(input_ids=inp,position_ids=pos,attention_mask=None)
|
| 149 |
+
lg=(out[0] if isinstance(out,tuple) else out).float()
|
| 150 |
+
lp=torch.log_softmax(lg[0,:-1],dim=-1); tgt=torch.tensor(ids[1:],device="cuda")
|
| 151 |
+
tot+=float((-lp.gather(1,tgt[:,None]).squeeze(1)).mean()); nt+=1
|
| 152 |
+
return tot/nt
|
| 153 |
+
|
| 154 |
+
# ---- DIAG mode: load trained BEST plugin, compare ON/OFF NLL in train() vs eval(), dump c_t/gate ----
|
| 155 |
+
if os.environ.get("DIAG"):
|
| 156 |
+
import glob as _gd
|
| 157 |
+
_cks=sorted(_gd.glob(f"{OUT_DIR}/plugin_FY{DATA_YEAR}_r{R}_*BEST.pt")) # FIX: 带R精确匹配,别把r32/r106混选
|
| 158 |
+
assert _cks, f"no BEST ckpt in {OUT_DIR}"
|
| 159 |
+
_sd=torch.load(_cks[-1],map_location="cuda")
|
| 160 |
+
for _j,_b in enumerate(branches): _b.load_state_dict(_sd[f"branch{_j}"])
|
| 161 |
+
print(f"[diag] loaded {_cks[-1]}",flush=True)
|
| 162 |
+
_msg=[f"loaded {_cks[-1].split('/')[-1]}"]
|
| 163 |
+
_msg.append(f"c_t.norm(emb[{YEAR_TOK_ID}])={float(branches[0].c_t.norm()):.4f}")
|
| 164 |
+
for _j,_b in enumerate(branches[:2]):
|
| 165 |
+
print(f"[diag] branch{_j} w={float(_b.w.norm()):.3f} v={float(_b.v.norm()):.3f} fc2w={float(_b.fc2.weight.norm()):.3f}",flush=True)
|
| 166 |
+
if os.environ.get("NONINT"):
|
| 167 |
+
import json as _nj
|
| 168 |
+
_HF="/blue/fire-finai/lq62.yale/annulus/heldout/eval_heldout.jsonl"
|
| 169 |
+
_pre=[]; _y2000=[]
|
| 170 |
+
for _ln in open(_HF):
|
| 171 |
+
_o=_nj.loads(_ln); _t=_o.get("text") or ""
|
| 172 |
+
try: _yv=int(str(_o.get("year"))[:4])
|
| 173 |
+
except: _yv=None
|
| 174 |
+
if _yv is None or len(_t)<=800: continue
|
| 175 |
+
_bucket=_pre if _yv<2000 else (_y2000 if _yv==2000 else None)
|
| 176 |
+
if _bucket is None: continue
|
| 177 |
+
if len(_bucket)>=80: continue
|
| 178 |
+
_ids=[YEAR_TOK_ID]+tok(_t,add_special_tokens=False)["input_ids"][:MAXTOK-1]
|
| 179 |
+
if len(_ids)>=16: _bucket.append(_ids)
|
| 180 |
+
core.eval()
|
| 181 |
+
_res=[]
|
| 182 |
+
for _nm,_ds in [("pre-2000",_pre),("year2000",_y2000)]:
|
| 183 |
+
if not _ds: continue
|
| 184 |
+
for _b in branches: _b.enabled=False
|
| 185 |
+
_off=nll(_ds)
|
| 186 |
+
for _b in branches: _b.enabled=True
|
| 187 |
+
_on=nll(_ds)
|
| 188 |
+
_res.append(f"{_nm}(n={len(_ds)}): OFF={_off:.4f} ON={_on:.4f} dNLL={_on-_off:+.4f}")
|
| 189 |
+
print(f"[nonint] {_res[-1]}",flush=True)
|
| 190 |
+
try: discord_post("\U0001f9ea <@1329102249854500885> FY2001 BEST插件 非干扰测(修好版,core.eval,ON=强制开插件):\n"+"\n".join(_res)+"\n≈0=非干扰成立(插件对旧文本几乎不动);明显正=还有扰动。注:按cutoff设计旧年插件本应关(bit-exact),这是'强制开'的上界。")
|
| 191 |
+
except Exception: pass
|
| 192 |
+
raise SystemExit(0)
|
| 193 |
+
_dd=docs[:6]
|
| 194 |
+
for _mode in ("train","eval"):
|
| 195 |
+
getattr(core,_mode)()
|
| 196 |
+
for _b in branches: _b.enabled=False
|
| 197 |
+
_off=nll(_dd)
|
| 198 |
+
for _b in branches: _b.enabled=True
|
| 199 |
+
_on=nll(_dd)
|
| 200 |
+
_line=f"mode={_mode}: OFF={_off:.4f} ON={_on:.4f} dNLL={_on-_off:+.4f}"
|
| 201 |
+
print(f"[diag] {_line}",flush=True); _msg.append(_line)
|
| 202 |
+
try:
|
| 203 |
+
discord_post("\U0001f50e <@1518372899575169215> FY2001 插件 DIAG(train vs eval forward):\n"+"\n".join(_msg)+"\n判读:train-mode 负/eval-mode 正 = train/eval forward 不一致(MoE模式)就是根因;两 mode 都正=另查;c_t.norm≈0 = [Y2001] emb 是垃圾。")
|
| 204 |
+
except Exception: pass
|
| 205 |
+
raise SystemExit(0)
|
| 206 |
+
|
| 207 |
+
train=docs[:DRY_DOCS] if DRY else docs
|
| 208 |
+
opt=torch.optim.AdamW([p for b in branches for p in b.parameters()], lr=LR, weight_decay=float(os.environ.get('WD') or '0.01'))
|
| 209 |
+
import math as _math
|
| 210 |
+
WARMUP=int(os.environ.get("WARMUP") or "500")
|
| 211 |
+
def _lr_at(_st,_tot):
|
| 212 |
+
if _st<WARMUP: return LR*(_st+1)/max(1,WARMUP)
|
| 213 |
+
_p=(_st-WARMUP)/max(1,_tot-WARMUP)
|
| 214 |
+
return LR*0.5*(1+_math.cos(_math.pi*min(1.0,_p)))
|
| 215 |
+
core.eval() # global align (base_nll/held-off/loop all eval-mode)
|
| 216 |
+
_trs=train[:300] # fixed train sample for decisive train-set ΔNLL
|
| 217 |
+
base_nll=nll(_trs)
|
| 218 |
+
# ---- held-out early-stop setup (Lingfei same-source heldout_2001) ----
|
| 219 |
+
import json as _json
|
| 220 |
+
_HELD=os.environ.get("HELDOUT_PATH") or "/blue/fire-finai/lq62.yale/annulus/data/rung1_heldout_2001/heldout_2001_samesource.jsonl"
|
| 221 |
+
_hd=None; _held_off=None; _best_delta=1e9; _best_state=None; _best_step=0
|
| 222 |
+
try:
|
| 223 |
+
_tmp=[]
|
| 224 |
+
with open(_HELD) as _hf:
|
| 225 |
+
for _ln in _hf:
|
| 226 |
+
if len(_tmp)>=int(os.environ.get("HELD_N") or "150"): break
|
| 227 |
+
_o=_json.loads(_ln); _tv=_o.get("text") or ""
|
| 228 |
+
if _tv and len(_tv)>1500:
|
| 229 |
+
_hi=[YEAR_TOK_ID]+tok(_tv,add_special_tokens=False)["input_ids"][:MAXTOK-1]
|
| 230 |
+
if len(_hi)>=16: _tmp.append(_hi)
|
| 231 |
+
assert len(_tmp)>=16, f"held-out only {len(_tmp)}"
|
| 232 |
+
_hd=_tmp
|
| 233 |
+
for _b in branches: _b.enabled=False
|
| 234 |
+
_held_off=nll(_hd)
|
| 235 |
+
for _b in branches: _b.enabled=True
|
| 236 |
+
print(f"[held] loaded={len(_hd)} OFF_nll={_held_off:.4f}", flush=True)
|
| 237 |
+
discord_post(f"📐 <@{MANAGER_UID}> FY{DATA_YEAR} 重训 held-out={len(_hd)}篇 OFF-NLL={_held_off:.4f} (早停基线,目标ΔNLL变负)")
|
| 238 |
+
except Exception as _e:
|
| 239 |
+
_hd=None
|
| 240 |
+
try: discord_post(f"⚠ held-out 加载失败: {_e} — 早停关闭")
|
| 241 |
+
except Exception: pass
|
| 242 |
+
core.eval() # FIX(train/eval forward align): backbone in eval-mode so plugin calibrates to deployment forward (dropout=0 but year_fused_loss/router self.training branches differ)
|
| 243 |
+
STEPS=DRY_STEPS if DRY else REAL_STEPS
|
| 244 |
+
ACCUM=1 if DRY else int(os.environ.get("ACCUM") or "16") # grad accumulation -> effective batch
|
| 245 |
+
GCLIP=float(os.environ.get("GCLIP") or "1.0") # grad clip norm
|
| 246 |
+
print(f"[branch] ACCUM={ACCUM} GCLIP={GCLIP}", flush=True)
|
| 247 |
+
for st in range(STEPS):
|
| 248 |
+
for _g in opt.param_groups: _g["lr"]=_lr_at(st,STEPS)
|
| 249 |
+
ids=train[st%len(train)]
|
| 250 |
+
inp=torch.tensor([ids],device="cuda"); pos=torch.arange(len(ids),device="cuda").unsqueeze(0)
|
| 251 |
+
if st%ACCUM==0: opt.zero_grad()
|
| 252 |
+
out=core(input_ids=inp,position_ids=pos,attention_mask=None)
|
| 253 |
+
lg=(out[0] if isinstance(out,tuple) else out).float()
|
| 254 |
+
lp=torch.log_softmax(lg[0,:-1],dim=-1); tgt=torch.tensor(ids[1:],device="cuda")
|
| 255 |
+
loss=(-lp.gather(1,tgt[:,None]).squeeze(1)).mean()
|
| 256 |
+
(loss/ACCUM).backward() # accumulate scaled grad
|
| 257 |
+
if (st+1)%ACCUM==0:
|
| 258 |
+
torch.nn.utils.clip_grad_norm_([p for b in branches for p in b.parameters()], GCLIP)
|
| 259 |
+
opt.step()
|
| 260 |
+
if not DRY and (st+1)%100==0:
|
| 261 |
+
print(f"[train] step {st+1}/{STEPS} loss={loss.item():.4f} lr="+format(LR,".2e"), flush=True)
|
| 262 |
+
_gn=sum((p.grad.norm().item() if p.grad is not None else 0.0) for b in branches for p in b.parameters())
|
| 263 |
+
_ng=sum(1 for b in branches for p in b.parameters() if (p.grad is not None and float(p.grad.abs().max())>0))
|
| 264 |
+
_tot=sum(1 for b in branches for p in b.parameters())
|
| 265 |
+
print(f"[grad] step {st+1} branch_grad_norm={_gn:.4e} nonzero_grad_params={_ng}/{_tot}", flush=True)
|
| 266 |
+
_drift=max((core.state_dict()[n]-snap[n]).abs().max().item() for n in snap if n in core.state_dict())
|
| 267 |
+
# post-train rollback ③ (periodic): trained branch OFF must be bit-exact identity
|
| 268 |
+
_bt=branches[0]; _bt.enabled=False
|
| 269 |
+
_rb=0.0
|
| 270 |
+
for _dt in (torch.bfloat16, torch.float32):
|
| 271 |
+
_xx=torch.randn(4,1,d_model,device="cuda",dtype=_dt); _rb=max(_rb,(_bt(_xx)-_xx).abs().max().item())
|
| 272 |
+
_bt.enabled=True
|
| 273 |
+
try: discord_post(f"📉 FY{DATA_YEAR} r{R} step {st+1}/{STEPS} loss={loss.item():.4f} | base_equal 漂移={_drift:.1e}{' ✅' if _drift<1e-6 else ' ❌骨干被动!'} | 训后rollback③={_rb:.1e}{' ✅' if _rb==0 else ' ❌'}")
|
| 274 |
+
except Exception: pass
|
| 275 |
+
if _drift>=1e-6:
|
| 276 |
+
discord_post(f"🛑 <@{MANAGER_UID}> FY{DATA_YEAR} STOP: base_equal 骨干漂移={_drift:.2e}≠0 (step {st+1}) — backbone 被动了,fail-loud 停训!")
|
| 277 |
+
raise SystemExit(f"BASE_EQUAL_VIOLATION drift={_drift}")
|
| 278 |
+
if _rb!=0:
|
| 279 |
+
discord_post(f"🛑 <@{MANAGER_UID}> FY{DATA_YEAR} STOP: 训后rollback③={_rb:.2e}≠0 (step {st+1}) — cutoff 关不净,fail-loud 停训!")
|
| 280 |
+
raise SystemExit(f"ROLLBACK_VIOLATION={_rb}")
|
| 281 |
+
if not DRY and _hd is not None and (st+1)%int(os.environ.get("HELD_EVERY") or "500")==0:
|
| 282 |
+
core.eval()
|
| 283 |
+
for _b in branches: _b.enabled=True
|
| 284 |
+
_held_on=nll(_hd); _dl=_held_on-_held_off
|
| 285 |
+
core.eval() # FIX(train/eval forward align): backbone in eval-mode so plugin calibrates to deployment forward (dropout=0 but year_fused_loss/router self.training branches differ)
|
| 286 |
+
_new=_dl<_best_delta
|
| 287 |
+
print(f"[held] step {st+1} ON={_held_on:.4f} OFF={_held_off:.4f} dNLL={_dl:+.4f} best={_best_delta:+.4f}", flush=True)
|
| 288 |
+
if _new:
|
| 289 |
+
_best_delta=_dl; _best_step=st+1
|
| 290 |
+
_best_state={f"branch{_j}":{k:v.detach().cpu().clone() for k,v in b.state_dict().items()} for _j,b in enumerate(branches)}
|
| 291 |
+
_tag=(" ↓新最优" if _new else " (best={:+.4f}@{})".format(_best_delta,_best_step))
|
| 292 |
+
try: discord_post("📈 FY{} step {}/{} held-out ΔNLL(ON-OFF)={:+.4f}{}".format(DATA_YEAR,st+1,STEPS,_dl,_tag))
|
| 293 |
+
except Exception: pass
|
| 294 |
+
if not DRY and (st+1)%1000==0:
|
| 295 |
+
_lstr2="-".join(map(str,INSERT_LAYERS)); _ckp=f"{OUT_DIR}/plugin_FY{DATA_YEAR}_r{R}_L{_lstr2}_step{st+1}.pt"
|
| 296 |
+
core.eval(); torch.save({f"branch{_j}":b.state_dict() for _j,b in enumerate(branches)}, _ckp); core.eval() # FIX(train/eval forward align): backbone in eval-mode so plugin calibrates to deployment forward (dropout=0 but year_fused_loss/router self.training branches differ)
|
| 297 |
+
print(f"[ckpt] saved {_ckp}", flush=True)
|
| 298 |
+
core.eval()
|
| 299 |
+
fin_nll=nll(_trs)
|
| 300 |
+
if not DRY and _best_state is not None:
|
| 301 |
+
_lstrB="-".join(map(str,INSERT_LAYERS))
|
| 302 |
+
_ckB=f"{OUT_DIR}/plugin_FY{DATA_YEAR}_r{R}_L{_lstrB}_BEST.pt"
|
| 303 |
+
torch.save(_best_state|{"meta":{"year":DATA_YEAR,"r":R,"layers":INSERT_LAYERS,"params":n_branch_params,"best_step":_best_step,"best_delta_nll":_best_delta,"held_off_nll":_held_off}}, _ckB)
|
| 304 |
+
print(f"[branch] BEST plugin saved -> {_ckB} (step{_best_step} dNLL={_best_delta:+.4f})", flush=True)
|
| 305 |
+
try: discord_post("🏆 <@{}> FY{} 早停最优: step{} held-out ΔNLL={:+.4f} -> {}".format(MANAGER_UID,DATA_YEAR,_best_step,_best_delta,_ckB))
|
| 306 |
+
except Exception: pass
|
| 307 |
+
|
| 308 |
+
if not DRY:
|
| 309 |
+
_lstr="-".join(map(str,INSERT_LAYERS))
|
| 310 |
+
_ck=f"{OUT_DIR}/plugin_FY{DATA_YEAR}_r{R}_L{_lstr}.pt"
|
| 311 |
+
torch.save({f"branch{_i}":b.state_dict() for _i,b in enumerate(branches)}|{"meta":{"year":DATA_YEAR,"r":R,"layers":INSERT_LAYERS,"params":n_branch_params}}, _ck)
|
| 312 |
+
print(f"[branch] plugin saved -> {_ck}", flush=True)
|
| 313 |
+
try: discord_post(f"💾 FY{DATA_YEAR} 插件已存 {_ck} (r={R}, {n_branch_params/1e6:.2f}M, L{INSERT_LAYERS})")
|
| 314 |
+
except Exception: pass
|
| 315 |
+
# base_equal: backbone params byte-unchanged
|
| 316 |
+
max_drift=max((core.state_dict()[n]-snap[n]).abs().max().item() for n in snap if n in core.state_dict())
|
| 317 |
+
|
| 318 |
+
# ── ③ exact-rollback = MODULE-LEVEL unit test (noise-free, no MoE) ──
|
| 319 |
+
# cutoff invariant: enabled=False => branch(x) MUST equal x bit-for-bit.
|
| 320 |
+
_b=branches[0]; _b.enabled=False
|
| 321 |
+
_max=0.0; _ok=True
|
| 322 |
+
for _dt in (torch.bfloat16, torch.float32):
|
| 323 |
+
_x=torch.randn(7,1,1024, device="cuda", dtype=_dt)
|
| 324 |
+
_y=_b(_x); _d=(_y-_x).abs().max().item(); _max=max(_max,_d)
|
| 325 |
+
if not torch.equal(_y,_x): _ok=False
|
| 326 |
+
# and: enabled=True but UNTRAINED-equivalent check — trained branch ON adds nonzero (sanity)
|
| 327 |
+
_b.enabled=True; _xon=torch.randn(7,1,1024,device="cuda",dtype=torch.bfloat16)
|
| 328 |
+
_on_delta=(_b(_xon)-_xon).abs().max().item()
|
| 329 |
+
_b.enabled=False
|
| 330 |
+
rollback_max=_max; rollback_exact=_ok
|
| 331 |
+
# whole-model determinism floor (context only; MoE routing argmax flip -> big, expected)
|
| 332 |
+
def _logits(ids):
|
| 333 |
+
inp=torch.tensor([ids],device="cuda"); pos=torch.arange(len(ids),device="cuda").unsqueeze(0)
|
| 334 |
+
with torch.no_grad(): out=core(input_ids=inp,position_ids=pos,attention_mask=None)
|
| 335 |
+
return (out[0] if isinstance(out,tuple) else out).float()
|
| 336 |
+
_pid=train[0]; _det=(_logits(_pid)-_logits(_pid)).abs().max().item()
|
| 337 |
+
|
| 338 |
+
sig_roll = "✅精确复原(逐位==x)" if rollback_exact else f"❌分支没关净(max={rollback_max:.2e})"
|
| 339 |
+
summary=(f"🧩 <@{MANAGER_UID}> FY2001 加法插件真训 (r={R}, L{INSERT_LAYERS})\n"
|
| 340 |
+
f"branch params={n_branch_params/1e6:.2f}M (只训这些, 骨干冻)\n"
|
| 341 |
+
f"① train-NLL {base_nll:.4f} → {fin_nll:.4f} ({'✅插件在学' if fin_nll<base_nll*0.98 else '⚠️没降'})\n"
|
| 342 |
+
f"② base_equal 骨干漂移={max_drift:.2e} ({'✅byte-frozen' if max_drift<1e-6 else '❌骨干被动'})\n"
|
| 343 |
+
f"③ cutoff 模块级单元测试: enabled=False→branch(x)逐位==x, max_diff={rollback_max:.2e} → {sig_roll}\n"
|
| 344 |
+
f" (旁证:branch ON 对随机x的改动={_on_delta:.3f}≠0=分支有作用;整模型MoE非确定性底={_det:.2f}=路由argmax翻转,与③无关)")
|
| 345 |
+
discord_post(summary); print(summary,flush=True); print("BRANCH_DRY_DONE",flush=True)
|