lfqian commited on
Commit
5dc3cce
·
verified ·
1 Parent(s): 8b7468c

recipe+sizing artifacts

Browse files
Files changed (1) hide show
  1. _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)