Archive CIDM-v2 reproducible b2_r3_readout_probe.py
Browse files
cidm_v2/source/B2_R3_P1/b2_r3_readout_probe.py
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""CIDM-v2 B2-R3: frozen-backbone information-sufficiency pilot.
|
| 2 |
+
Only frozen original models and SMALL matched trainable probes. This is NOT full model retraining.
|
| 3 |
+
Three science outcomes: latent-specific readout gain, observable-only calibration gain, no reliable gain.
|
| 4 |
+
"""
|
| 5 |
+
from __future__ import annotations
|
| 6 |
+
import os,io,math,json,hashlib,zipfile,random,copy,csv,time
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
from torch import nn
|
| 11 |
+
from torch.nn import functional as F
|
| 12 |
+
from benchmark import Config,Windows,fetch_official,make_cache,load_data,assess as canonical_assess,UnifiedModel,make_synthetic_cache
|
| 13 |
+
from b2_r1_flux_control import get_core,check_zip,assess as fixed_assess
|
| 14 |
+
|
| 15 |
+
PARENT_SHA='69552b29e169ec22ee85d9c1b1a678fdb188f6f25dc18f33b927b88f4dddffd4'
|
| 16 |
+
TASKS=('burgers_1d','reacdiff_2d','swe_2d')
|
| 17 |
+
LEADS=(1,3,6,9,12)
|
| 18 |
+
SEED=17
|
| 19 |
+
|
| 20 |
+
def sha(p):
|
| 21 |
+
h=hashlib.sha256()
|
| 22 |
+
with open(p,'rb') as f:
|
| 23 |
+
for t in iter(lambda:f.read(1<<20),b''):h.update(t)
|
| 24 |
+
return h.hexdigest()
|
| 25 |
+
|
| 26 |
+
def js(p,x):
|
| 27 |
+
Path(p).write_text(json.dumps(x,ensure_ascii=False,indent=2,allow_nan=False,default=str),encoding='utf-8')
|
| 28 |
+
|
| 29 |
+
def rows_csv(p,rows):
|
| 30 |
+
if not rows:return
|
| 31 |
+
with open(p,'w',newline='',encoding='utf-8') as f:
|
| 32 |
+
w=csv.DictWriter(f,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows)
|
| 33 |
+
|
| 34 |
+
def normalize_sample(x):
|
| 35 |
+
scale=x.detach().square().mean(dim=(1,2,3),keepdim=True).sqrt().clamp_min(.1)
|
| 36 |
+
return torch.tanh(x/scale)
|
| 37 |
+
|
| 38 |
+
class ReadoutProbe(nn.Module):
|
| 39 |
+
"""Identical trainable parameters and dimensions for OBS and LATENT; source varies only in frozen features."""
|
| 40 |
+
def __init__(self,channels,width=16,hidden=24):
|
| 41 |
+
super().__init__()
|
| 42 |
+
self.head=nn.Sequential(nn.Conv2d(width+3*channels+1,hidden,3,padding=1),nn.SiLU(),
|
| 43 |
+
nn.Conv2d(hidden,channels,1))
|
| 44 |
+
nn.init.zeros_(self.head[-1].weight);nn.init.zeros_(self.head[-1].bias)
|
| 45 |
+
def forward(self,common,projection):
|
| 46 |
+
correction=.25*torch.tanh(self.head(torch.cat([common,projection],dim=1)))
|
| 47 |
+
return correction
|
| 48 |
+
|
| 49 |
+
class FixedProjection:
|
| 50 |
+
def __init__(self,d,ch,width=16,seed=20261014,device='cpu'):
|
| 51 |
+
g=torch.Generator(device='cpu').manual_seed(seed)
|
| 52 |
+
self.p_lat=(torch.randn(width,d,1,1,generator=g)/math.sqrt(d)).to(device)
|
| 53 |
+
self.p_obs=(torch.randn(width,3*ch,1,1,generator=g)/math.sqrt(3*ch)).to(device)
|
| 54 |
+
def feats(self,z,common,channels):
|
| 55 |
+
raw=common[:,:3*channels]
|
| 56 |
+
lat=F.conv2d(normalize_sample(z),self.p_lat)
|
| 57 |
+
obs=F.conv2d(normalize_sample(raw),self.p_obs)
|
| 58 |
+
return lat.detach(),obs.detach()
|
| 59 |
+
|
| 60 |
+
@torch.no_grad()
|
| 61 |
+
def rollout(core,h,leads=LEADS):
|
| 62 |
+
"""Exact original R5 dynamics, no correction feedback or future truth in prediction path."""
|
| 63 |
+
mm=core.model
|
| 64 |
+
raised=h.ndim==4
|
| 65 |
+
hh=h.unsqueeze(-2) if raised else h
|
| 66 |
+
B,L,C,H,W=hh.shape
|
| 67 |
+
assert L==mm.hist==4
|
| 68 |
+
coef=mm.coefficients();z=mm.encode(hh.reshape(B,L*C,H,W))
|
| 69 |
+
prev=hh[:,-1];initial=prev; skew=mm.skew-mm.skew.T
|
| 70 |
+
states=[]
|
| 71 |
+
for t in range(1,max(leads)+1):
|
| 72 |
+
z=z+coef['flux']*mm.flux(z)+coef['local']*torch.tanh(mm.local(z))+.035*torch.einsum('ij,bjhw->bihw',skew,z)
|
| 73 |
+
inc=coef['out']*torch.tanh(mm.decode(z));x=prev+inc
|
| 74 |
+
if t in leads:states.append((t,z.detach(),x.detach(),inc.detach(),initial.detach()))
|
| 75 |
+
prev=x
|
| 76 |
+
return states
|
| 77 |
+
|
| 78 |
+
def common_features(x,inc,initial,lead):
|
| 79 |
+
# Values are observational, causally available under frozen baseline rollout.
|
| 80 |
+
B,C,H,W=x.shape
|
| 81 |
+
k=x.new_full((B,1,H,W),float(lead)/12.0)
|
| 82 |
+
return torch.cat([initial.clamp(-5,5),x.clamp(-5,5),inc.clamp(-5,5),k],dim=1).detach()
|
| 83 |
+
|
| 84 |
+
def make_features(states,channels,projection):
|
| 85 |
+
for lead,z,x,inc,initial in states:
|
| 86 |
+
common=common_features(x,inc,initial,lead)
|
| 87 |
+
latent,observable=projection.feats(z,common,channels)
|
| 88 |
+
yield lead,x,common,latent,observable
|
| 89 |
+
|
| 90 |
+
@torch.no_grad()
|
| 91 |
+
def original_parity(core,h):
|
| 92 |
+
states=rollout(core,h,tuple(range(1,13)))
|
| 93 |
+
manual=torch.stack([s[2] for s in states],dim=1)
|
| 94 |
+
model=core(h,12,mode='off')
|
| 95 |
+
if h.ndim==4:manual=manual.squeeze(-2)
|
| 96 |
+
delta=(manual-model).abs().max().item()
|
| 97 |
+
if delta>2e-6:raise RuntimeError('FROZEN_FORWARD_PARITY_MISMATCH:'+str(delta))
|
| 98 |
+
return delta
|
| 99 |
+
|
| 100 |
+
@torch.no_grad()
|
| 101 |
+
def eval_heads(core,heads,projection,ds,indices,device,batch,leads=LEADS):
|
| 102 |
+
core.eval();[v.eval() for v in heads.values()]
|
| 103 |
+
C=ds.h.shape[2];ssd=ds.sd.to(device).reshape(1,-1,1,1)**2
|
| 104 |
+
scores={s:{t:[0.0,0] for t in leads} for s in ['baseline',*heads]}
|
| 105 |
+
for i in range(0,len(indices),batch):
|
| 106 |
+
samples=[ds[int(j)] for j in indices[i:i+batch]]
|
| 107 |
+
h=torch.stack([v[0] for v in samples]).to(device);y=torch.stack([v[1] for v in samples]).to(device)
|
| 108 |
+
if h.ndim==4:y=y.unsqueeze(-2)
|
| 109 |
+
states=rollout(core,h,leads)
|
| 110 |
+
for lead,x,common,lat,obs in make_features(states,C,projection):
|
| 111 |
+
target=y[:,lead-1]
|
| 112 |
+
preds={'baseline':x,'observable':x+heads['observable'](common,obs),
|
| 113 |
+
'latent':x+heads['latent'](common,lat)}
|
| 114 |
+
for name,pred in preds.items():
|
| 115 |
+
diff=(pred-target).square()*ssd
|
| 116 |
+
scores[name][lead][0]+=diff.sum().item();scores[name][lead][1]+=diff.numel()
|
| 117 |
+
result={s:{str(t):scores[s][t][0]/max(scores[s][t][1],1) for t in leads} for s in scores}
|
| 118 |
+
for name in result:
|
| 119 |
+
result[name]['late_mse']=(sum(scores[name][t][0] for t in [9,12])/max(sum(scores[name][t][1] for t in [9,12]),1))
|
| 120 |
+
result[name]['all_mse']=(sum(scores[name][t][0] for t in leads)/max(sum(scores[name][t][1] for t in leads),1))
|
| 121 |
+
return result
|
| 122 |
+
|
| 123 |
+
def index_plan(n):
|
| 124 |
+
# fixed disjoint halves: val-selection and val-audit. Never test tuning or train on validation.
|
| 125 |
+
if n<12:raise RuntimeError('INSUFFICIENT_VAL_WINDOWS: require >=12 for independent audit split')
|
| 126 |
+
ids=np.arange(n);cut=n//2
|
| 127 |
+
return ids[:cut].tolist(),ids[cut:].tolist()
|
| 128 |
+
|
| 129 |
+
def q0_check(archive,external_diag):
|
| 130 |
+
"""Read-only integrity and predeclared indication from actual FIX6 results. Q0 does NOT select a fitted model."""
|
| 131 |
+
if sha(archive)!=PARENT_SHA:raise RuntimeError('B0_PARENT_SHA256_MISMATCH')
|
| 132 |
+
with zipfile.ZipFile(archive) as z:
|
| 133 |
+
m,c=check_zip(z)
|
| 134 |
+
if m['gate']['completed_runs']!=18:raise RuntimeError('INCOMPLETE_PARENT_RUNS')
|
| 135 |
+
import pandas as pd
|
| 136 |
+
data=pd.read_csv(external_diag)
|
| 137 |
+
assert len(data)==45 and len(data[['task','seed','lead']].drop_duplicates())==45
|
| 138 |
+
assert set(data.task)==set(TASKS) and set(data.seed)=={17,41,73} and set(data.lead)==set(LEADS)
|
| 139 |
+
if data[['pred_increment_cos_truth','pred_increment_power_ratio','self_reencode_increment_gap_rel']].isna().any().any():
|
| 140 |
+
raise RuntimeError('FIX6_DIAGNOSTIC_MISSING_OR_NONFINITE')
|
| 141 |
+
l=data[data.lead==12].groupby('task').mean(numeric_only=True)
|
| 142 |
+
evidence={'burgers_bad_direction':bool(l.loc['burgers_1d','pred_increment_cos_truth']<.2),
|
| 143 |
+
'swe_underresponse':bool(l.loc['swe_2d','pred_increment_power_ratio']<.6),
|
| 144 |
+
'rd_direction_better_than_burgers':bool(l.loc['reacdiff_2d','pred_increment_cos_truth']>l.loc['burgers_1d','pred_increment_cos_truth']+.2)}
|
| 145 |
+
return {'gate':'PILOT_ALLOWED' if all(evidence.values()) else 'BLOCKED_Q0_MECHANISM_NOT_REPRODUCED',
|
| 146 |
+
'evidence':evidence,'three_task_lead12':l[['pred_increment_cos_truth','pred_increment_power_ratio','self_reencode_increment_gap_rel']].round(6).to_dict(orient='index'),
|
| 147 |
+
'source_sha256':sha(external_diag),'parent_sha256':PARENT_SHA}
|
| 148 |
+
|
| 149 |
+
def pilot_one(task,archive,cfg,root,output,updates=100,device='cpu',smoke=False):
|
| 150 |
+
"""Small matched-probe TRAINING ONLY, frozen backbone. Each has identical initialization and optimizer budget."""
|
| 151 |
+
out=Path(output)/task;out.mkdir(parents=True,exist_ok=True)
|
| 152 |
+
if smoke:
|
| 153 |
+
cache=make_synthetic_cache(cfg)
|
| 154 |
+
# Synthetic 2D or lifted 1D supported by same logic, NO official result.
|
| 155 |
+
core=UnifiedModel('V2_Ported_R5',1,8,12,4).to(device).eval()
|
| 156 |
+
batch=2
|
| 157 |
+
else:
|
| 158 |
+
with zipfile.ZipFile(archive) as zp:
|
| 159 |
+
m,contract=check_zip(zp)
|
| 160 |
+
path=fetch_official(Path(root)/'official_data',task)
|
| 161 |
+
cache=make_cache(task,path,cfg,Path(root)/'window_cache')
|
| 162 |
+
ref=next(v for v in contract['tasks'] if v['task']==task)
|
| 163 |
+
assert cache['signature']['source_md5']==ref['source_md5']
|
| 164 |
+
assert cache['ids']==ref['ids']
|
| 165 |
+
assert all(len(cache['splits'][s]['h'])==ref['windows'][s] for s in ('train','val','test'))
|
| 166 |
+
core,weight_sha=get_core(zp,cache,task,SEED,device)
|
| 167 |
+
bs=cfg.batch_1d if cache['splits']['test']['h'].ndim==4 else cfg.batch_2d
|
| 168 |
+
ref_score=float(json.loads(zp.read(f'{task}__V2_Ported_R5__seed{SEED}.json'))['test']['model_12'])
|
| 169 |
+
replay=fixed_assess(core,'baseline',cache,'test',device,batch=bs)['rmse12']
|
| 170 |
+
if abs(replay-ref_score)>max(1e-5,ref_score*.001):raise RuntimeError(f'BASELINE_REPLAY_FAIL_CLOSED {task} {replay} != {ref_score}')
|
| 171 |
+
js(out/'parent_replay.json',{'task':task,'seed':SEED,'checkpoint_sha256':weight_sha,'archive_test_rmse12':ref_score,'replay_test_rmse12':replay,'pass':True})
|
| 172 |
+
print('[BASELINE REPLAY PASS]',task,SEED,ref_score,flush=True)
|
| 173 |
+
batch=cfg.batch_1d if cache['splits']['train']['h'].ndim==4 else cfg.batch_2d
|
| 174 |
+
core.requires_grad_(False);core.eval()
|
| 175 |
+
train=Windows(cache['splits']['train']['h'],cache['splits']['train']['y'],cache['mean'],cache['std'])
|
| 176 |
+
val=Windows(cache['splits']['val']['h'],cache['splits']['val']['y'],cache['mean'],cache['std'])
|
| 177 |
+
if smoke:sel_ids,audit_ids=[0,1],[2,3]
|
| 178 |
+
else:sel_ids,audit_ids=index_plan(len(val))
|
| 179 |
+
C=int(cache['splits']['train']['h'].shape[2]);d=core.model.flux.net[-1].out_channels
|
| 180 |
+
proj=FixedProjection(d,C,seed=20261014,device=device)
|
| 181 |
+
torch.manual_seed(20261014);head_obs=ReadoutProbe(C).to(device)
|
| 182 |
+
torch.manual_seed(20261014);head_lat=ReadoutProbe(C).to(device)
|
| 183 |
+
heads={'observable':head_obs,'latent':head_lat}
|
| 184 |
+
assert sum(p.numel() for p in head_obs.parameters())==sum(p.numel() for p in head_lat.parameters())
|
| 185 |
+
opts={s:torch.optim.AdamW(heads[s].parameters(),lr=8e-4,weight_decay=1e-4) for s in heads}
|
| 186 |
+
g=torch.Generator(device='cpu').manual_seed(20261014+SEED)
|
| 187 |
+
rows=[];best={'observable':(float('inf'),None,0),'latent':(float('inf'),None,0)}
|
| 188 |
+
with torch.no_grad():
|
| 189 |
+
smoke_x=torch.stack([train[0][0],train[1][0]]).to(device)
|
| 190 |
+
parity=original_parity(core,smoke_x)
|
| 191 |
+
print('[TRAIN PRECHECK]',task,'orig parity',parity,'readout params',sum(p.numel() for p in head_obs.parameters()),flush=True)
|
| 192 |
+
checkpoints=[0,updates] if smoke else sorted(set([0,*range(20,updates+1,20),updates]))
|
| 193 |
+
def evaluate_at(step):
|
| 194 |
+
v=eval_heads(core,heads,proj,val,sel_ids,device,batch)
|
| 195 |
+
for s in heads:
|
| 196 |
+
mse=v[s]['late_mse']
|
| 197 |
+
if mse<best[s][0]:best[s]=(mse,copy.deepcopy(heads[s].state_dict()),step)
|
| 198 |
+
r={'task':task,'step':step,'baseline_late':v['baseline']['late_mse'],
|
| 199 |
+
'observable_late':v['observable']['late_mse'],'latent_late':v['latent']['late_mse'],
|
| 200 |
+
'latent_vs_observable_ratio':v['latent']['late_mse']/max(v['observable']['late_mse'],1e-20)}
|
| 201 |
+
rows.append(r);rows_csv(out/'pilot_curve.csv',rows)
|
| 202 |
+
print('[PILOT]',task,'step',step,'baseline',round(r['baseline_late'],8),'obs',round(r['observable_late'],8),'latent',round(r['latent_late'],8),flush=True)
|
| 203 |
+
evaluate_at(0)
|
| 204 |
+
for step in range(1,updates+1):
|
| 205 |
+
ids=torch.randint(0,len(train),(batch,),generator=g).tolist()
|
| 206 |
+
samples=[train[i] for i in ids]
|
| 207 |
+
h=torch.stack([v[0] for v in samples]).to(device);y=torch.stack([v[1] for v in samples]).to(device)
|
| 208 |
+
if h.ndim==4:y=y.unsqueeze(-2)
|
| 209 |
+
with torch.no_grad():states=rollout(core,h)
|
| 210 |
+
for o in opts.values():o.zero_grad(set_to_none=True)
|
| 211 |
+
loss={s:0.0 for s in heads}
|
| 212 |
+
for lead,x,common,lat,obs in make_features(states,C,proj):
|
| 213 |
+
target=y[:,lead-1]
|
| 214 |
+
for s,feature in (('observable',obs),('latent',lat)):
|
| 215 |
+
estimate=x+heads[s](common,feature)
|
| 216 |
+
loss[s]=loss[s]+(estimate-target).square().mean()/len(LEADS)
|
| 217 |
+
for s in heads:
|
| 218 |
+
if not torch.isfinite(loss[s]):raise RuntimeError('NONFINITE_TRAINING_LOSS:'+task+':'+s)
|
| 219 |
+
loss[s].backward();torch.nn.utils.clip_grad_norm_(heads[s].parameters(),1.)
|
| 220 |
+
opts[s].step()
|
| 221 |
+
if step in checkpoints:
|
| 222 |
+
evaluate_at(step)
|
| 223 |
+
js(out/'progress.json',{'task':task,'updates_completed':step,'best':{k:{'selection_val_mse':v[0],'best_step':v[2]} for k,v in best.items()},'time':time.time()})
|
| 224 |
+
# Independent audit split: never used for step selection or learning.
|
| 225 |
+
for s in heads:
|
| 226 |
+
assert best[s][1] is not None
|
| 227 |
+
heads[s].load_state_dict(best[s][1]);heads[s].eval()
|
| 228 |
+
audit=eval_heads(core,heads,proj,val,audit_ids,device,batch)
|
| 229 |
+
# Readout trained from train only, step selected from first validation half; second half untouched.
|
| 230 |
+
summary={'task':task,'seed':SEED,'updates':updates,'core_frozen':True,'matched_probe_params':sum(p.numel() for p in heads['latent'].parameters()),
|
| 231 |
+
'selection_windows':len(sel_ids),'audit_windows':len(audit_ids),'best_step':{s:best[s][2] for s in best},
|
| 232 |
+
'audit':audit,'late_ratios':{s:audit[s]['late_mse']/max(audit['baseline']['late_mse'],1e-20) for s in heads},
|
| 233 |
+
'latent_vs_observable_late_ratio':audit['latent']['late_mse']/max(audit['observable']['late_mse'],1e-20),
|
| 234 |
+
'qualifier':'SMOKE_NO_SCIENCE' if smoke else 'SINGLE_SEED_READOUT_PILOT_ONLY'}
|
| 235 |
+
js(out/'pilot_summary.json',summary)
|
| 236 |
+
for s in heads:
|
| 237 |
+
torch.save({'head':heads[s].state_dict(),'task':task,'seed':SEED,'probe_type':s,'best_step':best[s][2],
|
| 238 |
+
'original_backbone_frozen':True,'selection_mse':best[s][0],
|
| 239 |
+
'fixed_projection_seed':20261014},out/f'{s}_best_probe.pt')
|
| 240 |
+
return summary
|
| 241 |
+
|
| 242 |
+
def run(parent,diagnostic_csv,root,output,updates=100,smoke=False):
|
| 243 |
+
out=Path(output);out.mkdir(parents=True,exist_ok=True)
|
| 244 |
+
if smoke:
|
| 245 |
+
cfg=Config(profile='smoke',history=4,train_horizon=6,eval_horizon=12)
|
| 246 |
+
results=[pilot_one('smoke_2d',None,cfg,root,out,updates=2,smoke=True)]
|
| 247 |
+
status='PROGRAM_ONLY_SMOKE_NO_SCIENTIFIC_CLAIM'
|
| 248 |
+
q0={'gate':'SMOKE'}
|
| 249 |
+
else:
|
| 250 |
+
q0=q0_check(parent,diagnostic_csv)
|
| 251 |
+
js(out/'Q0_frozen_gate.json',q0)
|
| 252 |
+
if q0['gate']!='PILOT_ALLOWED':
|
| 253 |
+
status='Q0_FAIL_NO_PROBE_TRAINING'; results=[]
|
| 254 |
+
else:
|
| 255 |
+
with zipfile.ZipFile(parent) as zp:
|
| 256 |
+
manifest,_=check_zip(zp)
|
| 257 |
+
cfg=Config(**manifest['config'])
|
| 258 |
+
results=[]
|
| 259 |
+
for task in TASKS:
|
| 260 |
+
results.append(pilot_one(task,parent,cfg,root,out,updates=updates,device='cuda' if torch.cuda.is_available() else 'cpu'))
|
| 261 |
+
status='SINGLE_SEED_PILOT_COMPLETED_NOT_FULL_TRAINING'
|
| 262 |
+
science={'experiment':'CIDM-v2 B2-R3/P1 FROZEN BACKBONE READOUT SUFFICIENCY PILOT',
|
| 263 |
+
'status':status,'parent_hash':PARENT_SHA,'trained_backbone_steps':0,
|
| 264 |
+
'pilot_probe_updates_per_head_per_task':updates if not smoke and results else 0,
|
| 265 |
+
'task_seed_count':len(results),'results':results,'q0':q0,
|
| 266 |
+
'qualification':'NO_CIDM_V2_FULL_MODEL_QUALIFICATION',
|
| 267 |
+
'evidence_limits':['Readout probes are trainable small models; results are not frozen counterfactuals.',
|
| 268 |
+
'Seed17 only; no multi-seed confirmation or formal v1 comparison.',
|
| 269 |
+
'Validation halves used separately for checkpoint selection and independent pilot audit.',
|
| 270 |
+
'Decoder-correction probes do not feed corrections back to backbone, so no closed-loop architecture inference.',
|
| 271 |
+
'Test labels are not used for training/selection; test split only strict baseline model replay.']}
|
| 272 |
+
if not smoke and len(results)==3:
|
| 273 |
+
rr={x['task']:x for x in results}
|
| 274 |
+
def improve(task):
|
| 275 |
+
x=rr[task]
|
| 276 |
+
return x['late_ratios']['latent']<=.97 and x['latent_vs_observable_late_ratio']<=.98
|
| 277 |
+
gate=improve('burgers_1d') and improve('swe_2d') and rr['reacdiff_2d']['late_ratios']['latent']<=1.02
|
| 278 |
+
science['pilot_gate']='CANDIDATE_FOR_THREE_SEED_REPLICATION' if gate else 'PILOT_NO_UNIFIED_LATENT_SPECIFIC_GAIN'
|
| 279 |
+
science['pilot_gate_policy']={'burgers_and_swe_latent_gain_vs_base_min':.03,
|
| 280 |
+
'burgers_and_swe_latent_gain_vs_matched_obs_min':.02,
|
| 281 |
+
'rd_max_regression_vs_base':.02,'pilot_is_not_architecture_gate':True}
|
| 282 |
+
js(out/'report.json',science)
|
| 283 |
+
return science
|