wuff-mann commited on
Commit
2008ced
·
verified ·
1 Parent(s): 0567e85

Archive CIDM-v2 reproducible b2_r13_same_state_vjp.py

Browse files
cidm_v2/source/B2_R13_P0/b2_r13_same_state_vjp.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """B2-R13/P0: frozen, SAME-STATE vs OWN-TRAJECTORY FP32/FP64 local VJPs.
2
+
3
+ Full frozen 12-step forward is used only to harvest step-local states and
4
+ loss-conditioned cotangents. All derivative probes below are independent,
5
+ zero-training, step-local Jacobian-vector products with original CIDM-v2 code.
6
+ No BPTT/training Gate, no causal operator ablation, no Jacobian spectral claim.
7
+ """
8
+ from __future__ import annotations
9
+ import csv, copy, gc, json, math, zipfile, hashlib
10
+ from pathlib import Path
11
+ import torch
12
+ import b2_r11_backward_attribution as r11
13
+ import b2_r12_dual_precision_chain as r12
14
+ import b2_r10_gradient_credit_audit as credit
15
+ from b2_r1_flux_control import check_zip
16
+
17
+ R12_FIX1_SHA='267f8d574d98754f864227cc28e49c335035a0697ec5f0a112117218c4c5d28a'
18
+ LEADS=(1,3,6,9,12)
19
+
20
+ def vecnorm(x):
21
+ return float(torch.linalg.vector_norm(x.detach().reshape(-1).double()))
22
+
23
+ def unit_cot(x):
24
+ y=x.detach().double(); val=torch.linalg.vector_norm(y)
25
+ if not torch.isfinite(val) or val<=0:raise RuntimeError('INVALID_ADJOINT_FOR_UNIT_PROBE')
26
+ return (y/val).detach().cpu()
27
+
28
+ def finite(x,label):
29
+ if not bool(torch.isfinite(x).all()):raise RuntimeError('NONFINITE_'+label)
30
+
31
+ def angle(a,b):
32
+ v=(a.flatten().double()*b.flatten().double()).sum(); d=torch.linalg.vector_norm(a.double())*torch.linalg.vector_norm(b.double())
33
+ return float(v/d.clamp_min(1e-300)) if d>0 else 0.
34
+
35
+ def relative(a,b):
36
+ return vecnorm(a.double()-b.double())/max(vecnorm(b),1e-300)
37
+
38
+ def one_step(core,head,z,p,init,Q,lead):
39
+ """Mathematically identical to R11.full_forward for one controlled step."""
40
+ mm=core.model; co=mm.coefficients();skew=mm.skew-mm.skew.T
41
+ f=co['flux']*mm.flux(z)
42
+ l=co['local']*torch.tanh(mm.local(z))
43
+ s=.035*torch.einsum('ij,bjhw->bihw',skew,z)
44
+ base=z+f+l+s
45
+ basis,amps=credit.stable_exact_effect_basis(f,l,s,Q)
46
+ gates=head(base,p,init,amps,lead)
47
+ raw=(gates.unsqueeze(2)*basis).sum(dim=1)/math.sqrt(3.)
48
+ rr=credit.stable_exact_rms(raw)
49
+ change=r11.BUDGET*credit.stable_exact_rms(base).clamp_min(1e-4)*raw/rr.clamp_min(1e-9)
50
+ change=torch.where(rr>=1e-9,change,torch.zeros_like(change))
51
+ zn=base+change
52
+ pn=p+co['out']*torch.tanh(mm.decode(zn))
53
+ return zn,pn,f,l,s
54
+
55
+ def evaluate_step(core,head,z,p,init,Q,lead,cz,cp):
56
+ """Return both independent local VJPs wrt z_old; p held fixed."""
57
+ dt=next(core.parameters()).dtype
58
+ z=z.to(dtype=dt).detach().requires_grad_(True)
59
+ p=p.to(dtype=dt).detach();init=init.to(dtype=dt).detach()
60
+ cz=cz.to(dtype=dt).detach();cp=cp.to(dtype=dt).detach()
61
+ q=Q.to(dtype=dt).detach() if Q is not None else None
62
+ zn,pn,f,l,s=one_step(core,head,z,p,init,q,lead)
63
+ for k,val in [('zn',zn),('pn',pn),('f',f),('l',l),('s',s)]:finite(val,k)
64
+ grad_z=torch.autograd.grad(zn,z,grad_outputs=cz,retain_graph=True)[0]
65
+ grad_p=torch.autograd.grad(pn,z,grad_outputs=cp,retain_graph=False)[0]
66
+ for k,val in [('vjp_z',grad_z),('vjp_p',grad_p)]:finite(val,k)
67
+ return {'latent':grad_z.detach().cpu().double(),'decoder':grad_p.detach().cpu().double(),
68
+ 'new_z':zn.detach().cpu().double(),'new_p':pn.detach().cpu().double()}
69
+
70
+ def states_and_adjoint(core,head,h,y,sd,Q,precision):
71
+ dt=torch.float32 if precision=='fp32' else torch.float64
72
+ h=h.to(dtype=dt);y=y.to(dtype=dt);sd=sd.to(dtype=dt)
73
+ q=Q.to(dtype=dt) if Q is not None else None
74
+ pred,loss,recs=r11.full_forward(core,head,h,y,sd,q)
75
+ finite(pred,'forward_'+precision)
76
+ if precision=='fp32':
77
+ # same real training objective, two independent VJP probes; no parameter update.
78
+ req=[r[k] for r in recs for k in ('z_new','prev_new')]
79
+ g=torch.autograd.grad(loss,req,allow_unused=True,retain_graph=False)
80
+ if any(v is None for v in g):raise RuntimeError('MISSING_FULL_CHAIN_ADJOINT')
81
+ else:g=None
82
+ out=[]
83
+ for t,rec in enumerate(recs):
84
+ d={k:rec[k].detach().cpu().clone() for k in ('z_old','prev_old','initial','z_new','prev_new')}
85
+ if g is not None:
86
+ d['cot_z']=unit_cot(g[t*2]);d['cot_p']=unit_cot(g[t*2+1])
87
+ out.append(d)
88
+ pred=pred.detach().cpu().double()
89
+ del recs,g,loss
90
+ gc.collect()
91
+ return pred,out
92
+
93
+ def case(task,core,cache,r6zip,kind,number_of_windows=2,leads=LEADS):
94
+ train=r11.Windows(cache['splits']['train']['h'],cache['splits']['train']['y'],cache['mean'],cache['std'])
95
+ ch=int(cache['splits']['train']['h'].shape[2]);d=int(core.model.skew.shape[0])
96
+ head_task= 'burgers_1d' if task=='smoke_1d' else ('swe_2d' if task=='smoke_2d' else task)
97
+ head=r11.load_heads(r6zip,head_task,ch,'cpu')[kind].eval().requires_grad_(False)
98
+ Q=r11.orthogonal_matrix(d,32207,'cpu',torch.float32) if kind=='rotated' else None
99
+ c32=copy.deepcopy(core).cpu().float().eval().requires_grad_(False)
100
+ c64=copy.deepcopy(core).cpu().double().eval().requires_grad_(False)
101
+ h32=copy.deepcopy(head).cpu().float().eval().requires_grad_(True)
102
+ h64=copy.deepcopy(head).cpu().double().eval().requires_grad_(True)
103
+ rows=[];windows=[]
104
+ for i in range(min(number_of_windows,len(train))):
105
+ h,y=train[i];h=h.unsqueeze(0).cpu();y=y.unsqueeze(0).cpu();sd=train.sd.cpu()
106
+ p32,s32=states_and_adjoint(c32,h32,h,y,sd,Q,'fp32')
107
+ p64,s64=states_and_adjoint(c64,h64,h,y,sd,Q,'fp64')
108
+ div=relative(p32,p64);maxdiv=float((p32-p64).abs().max())
109
+ windows.append({'task':task,'controller':kind,'window_index':i,'fp32_fp64_forecast_rel_l2':div,'fp32_fp64_forecast_max_abs':maxdiv})
110
+ for t in leads:
111
+ a=s32[t-1];b=s64[t-1]
112
+ # true FP32 full-chain cotangent: normalized and frozen before local VJP
113
+ # random control: independent of future labels, deterministic per window and lead
114
+ rr=torch.Generator(device='cpu').manual_seed(13013+i*101+t*19+(1 if kind=='rotated' else 0))
115
+ rv=torch.randn(a['cot_z'].shape,generator=rr,dtype=torch.float64)
116
+ rv/=torch.linalg.vector_norm(rv).clamp_min(1e-300)
117
+ rp=torch.randn(a['cot_p'].shape,generator=rr,dtype=torch.float64)
118
+ rp/=torch.linalg.vector_norm(rp).clamp_min(1e-300)
119
+ for probe,cz,cp in (('frozen_loss_adjoint',a['cot_z'],a['cot_p']),('random_isotropic',rv,rp)):
120
+ native32=evaluate_step(c32,h32,a['z_old'],a['prev_old'],a['initial'],Q,t,cz,cp)
121
+ shared64=evaluate_step(c64,h64,a['z_old'],a['prev_old'],a['initial'],Q,t,cz,cp)
122
+ own64=evaluate_step(c64,h64,b['z_old'],b['prev_old'],b['initial'],Q,t,cz,cp)
123
+ # Strict same-dtype independent reproduction of the *actual* archived
124
+ # 12-step rollout intermediates before interpreting any VJP.
125
+ parity32=max(float((native32['new_z']-a['z_new'].double()).abs().max()),
126
+ float((native32['new_p']-a['prev_new'].double()).abs().max()))
127
+ parity64=max(float((own64['new_z']-b['z_new'].double()).abs().max()),
128
+ float((own64['new_p']-b['prev_new'].double()).abs().max()))
129
+ if parity32>2e-6 or parity64>2e-11:
130
+ raise RuntimeError(f'STRICT_LOCAL_FORWARD_REPLAY_FAIL {task} {kind} window={i} lead={t} fp32={parity32} fp64={parity64}')
131
+ row={'local_forward_parity_fp32_max_abs':parity32,'local_forward_parity_fp64_max_abs':parity64,'task':task,'controller':kind,'train_window':i,'lead':t,'probe':probe,
132
+ 'relative_state_deviation_fp32_vs_fp64':relative(a['z_old'],b['z_old']),
133
+ 'relative_observable_deviation_fp32_vs_fp64':relative(a['prev_old'],b['prev_old']),
134
+ 'fp32_fp64_forecast_rel_l2':div,
135
+ 'shared_state_one_step_latent_rel_diff':relative(native32['new_z'],shared64['new_z']),
136
+ 'shifted_state_one_step_latent_rel_diff':relative(shared64['new_z'],own64['new_z']),
137
+ 'shared_state_one_step_observable_rel_diff':relative(native32['new_p'],shared64['new_p']),
138
+ 'shifted_state_one_step_observable_rel_diff':relative(shared64['new_p'],own64['new_p'])}
139
+ for branch in ('latent','decoder'):
140
+ x0=native32[branch];xs=shared64[branch];xo=own64[branch]
141
+ row[branch+'_vjp_fp32_l2']=vecnorm(x0)
142
+ row[branch+'_vjp_shared_fp64_l2']=vecnorm(xs)
143
+ row[branch+'_vjp_native_fp64_l2']=vecnorm(xo)
144
+ row[branch+'_same_state_precision_rel_diff']=relative(x0,xs)
145
+ row[branch+'_same_state_precision_cosine']=angle(x0,xs)
146
+ row[branch+'_trajectory_shift_rel_diff']=relative(xs,xo)
147
+ row[branch+'_trajectory_shift_cosine']=angle(xs,xo)
148
+ rows.append(row)
149
+ return rows,windows
150
+
151
+ def write_csv(path,rows):
152
+ if not rows:return
153
+ with open(path,'w',newline='',encoding='utf-8') as fh:
154
+ w=csv.DictWriter(fh,fieldnames=list(rows[0]));w.writeheader();w.writerows(rows)
155
+
156
+ def write_json(path,obj):
157
+ Path(path).write_text(json.dumps(obj,indent=2,ensure_ascii=False,allow_nan=False,default=str),encoding='utf-8')
158
+
159
+ def run(parent,r6zip,r9zip,fix2zip,r11fix1zip,r12zip,work,output,smoke=False):
160
+ work=Path(work);output=Path(output);work.mkdir(parents=True,exist_ok=True);output.mkdir(parents=True,exist_ok=True)
161
+ if r11.sha(r12zip)!=R12_FIX1_SHA:raise RuntimeError('R12_FIXED_OFFICIAL_REPORT_SHA_MISMATCH')
162
+ with zipfile.ZipFile(r12zip) as z:
163
+ if z.testzip():raise RuntimeError('R12_REPORT_CRC_ERROR')
164
+ rep=json.loads(z.read('report.json'));gate=json.loads(z.read('precision_gate.json'))
165
+ if rep['status']!='FROZEN_DUAL_PRECISION_FIX1_COMPLETED_NO_TRAINING' or rep['case_rows']!=12 or rep['time_rows']!=144 or not gate['strict_partition_pass_all']:
166
+ raise RuntimeError('R12_PRECISION_EVIDENCE_CONTRACT_FAILED')
167
+ if r11.sha(r11fix1zip)!=r12.ARCHIVE_FIX1_SHA:raise RuntimeError('R11_EVIDENCE_SHA_FAILED')
168
+ if r11.sha(fix2zip)!=r11.PREV_FIX2_SHA:raise RuntimeError('R10_EVIDENCE_SHA_FAILED')
169
+ r11.credit.validate_r6(r6zip);r11.credit.validate_r9(r9zip)
170
+ rows=[];wrows=[];cases=[]
171
+ try:
172
+ if smoke:
173
+ cfg=r11.Config(profile='smoke')
174
+ for task,kind in [('smoke_1d','operator'),('smoke_2d','rotated')]:
175
+ cache=r11.make_synthetic_cache(cfg)
176
+ for split in ('train','val','test'):
177
+ if task=='smoke_1d':
178
+ cache['splits'][split]['h']=cache['splits'][split]['h'].mean(dim=-2)[...,:4]
179
+ cache['splits'][split]['y']=cache['splits'][split]['y'].mean(dim=-2)[...,:4]
180
+ else:
181
+ cache['splits'][split]['h']=cache['splits'][split]['h'][...,:2,:4]
182
+ cache['splits'][split]['y']=cache['splits'][split]['y'][...,:2,:4]
183
+ core=r11.UnifiedModel('V2_Ported_R5',1,8,12,4).cpu()
184
+ a,b=case(task,core,cache,r6zip,kind,1,(1,6,12));rows.extend(a);wrows.extend(b)
185
+ cases.append({'task':task,'controller':kind,'program_only':True})
186
+ status='PROGRAM_ONLY_SMOKE_NO_SCIENCE'
187
+ else:
188
+ if parent is None or r11.sha(parent)!=r11.PARENT_SHA:raise RuntimeError('ORIGINAL_PARENT_SHA_FAILED')
189
+ with zipfile.ZipFile(parent) as z:
190
+ manifest,_=check_zip(z);cfg=r11.Config(**manifest['config'])
191
+ for task in r11.TASK_LIST:
192
+ core,cache,replay=r11.make_cache_checked(task,z,cfg,work,'cpu')
193
+ for kind in ('operator','rotated'):
194
+ a,b=case(task,core,cache,r6zip,kind)
195
+ rows.extend(a);wrows.extend(b)
196
+ cases.append({'task':task,'controller':kind,'parent_replay':replay})
197
+ write_csv(output/'same_state_vjp.csv',rows)
198
+ write_csv(output/'window_forward_drift.csv',wrows)
199
+ write_json(output/'progress.json',{'completed':cases,'rows':len(rows)})
200
+ if len(rows)!=3*2*2*len(LEADS)*2 or len(cases)!=6:
201
+ raise RuntimeError('INCOMPLETE_OFFICIAL_R13_CASES')
202
+ status='FROZEN_SAME_STATE_VJP_COMPLETED_NO_TRAINING'
203
+ except Exception as e:
204
+ write_csv(output/'same_state_vjp.csv',rows);write_csv(output/'window_forward_drift.csv',wrows)
205
+ write_json(output/'failure_details.json',{'error':repr(e),'groups_completed':cases,'rows':len(rows)})
206
+ raise
207
+ write_csv(output/'same_state_vjp.csv',rows);write_csv(output/'window_forward_drift.csv',wrows)
208
+ summary=[]
209
+ for task in sorted({r['task'] for r in rows}):
210
+ for kind in sorted({r['controller'] for r in rows if r['task']==task}):
211
+ for probe in ('frozen_loss_adjoint','random_isotropic'):
212
+ ar=[r for r in rows if r['task']==task and r['controller']==kind and r['probe']==probe]
213
+ if not ar:continue
214
+ for branch in ('latent','decoder'):
215
+ f=lambda key:sum(float(x[key]) for x in ar)/len(ar)
216
+ summary.append({'task':task,'controller':kind,'probe':probe,'branch':branch,
217
+ 'mean_same_state_precision_rel_diff':f(branch+'_same_state_precision_rel_diff'),
218
+ 'mean_trajectory_shift_rel_diff':f(branch+'_trajectory_shift_rel_diff'),
219
+ 'mean_same_state_precision_cosine':f(branch+'_same_state_precision_cosine'),
220
+ 'mean_trajectory_shift_cosine':f(branch+'_trajectory_shift_cosine'),
221
+ 'row_count':len(ar)})
222
+ write_csv(output/'vjp_summary.csv',summary)
223
+ gate={'status':'DIAGNOSTIC_ONLY_NO_TRAINING','completed_groups':len(cases),
224
+ 'sample_probe_rows':len(rows),'train_allowed':False,
225
+ 'interpretation':'same-state VJP error isolates local precision dependence at fixed FP32 state and cotangent; shifted-state VJP difference captures sensitivity to FP32-vs-FP64 trajectory changes; not causal proof or a Jacobian spectrum bound',
226
+ 'source_report_SHA256':R12_FIX1_SHA}
227
+ write_json(output/'same_state_gate.json',gate)
228
+ report={'experiment':'CIDM_v2_B2_R13_P0_SameStateLocalJacobian','status':status,'cases':cases,
229
+ 'rows':len(rows),'windows':len(wrows),'updates':0,'train_allowed':False,
230
+ 'dataset_note':'official original TRAIN windows 0 and 1; previously used distribution, no fresh outer holdout'}
231
+ write_json(output/'report.json',report)
232
+ return report