wuff-mann commited on
Commit
f62c215
·
verified ·
1 Parent(s): 83dd9b4

Archive CIDM-v2 reproducible b2_r8_equal_exposure_audit.py

Browse files
cidm_v2/source/B2_R8_P0/b2_r8_equal_exposure_audit.py ADDED
@@ -0,0 +1,280 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CIDM-v2 B2-R8/P0: frozen, issue-time, equal-realized-RMS perturbation audit.
2
+
3
+ NO TRAINING. Truth is used only AFTER producing autoregressive forecasts to score
4
+ physical-space MSE. Same per-sample/per-step RMS exposure for Natural and Rotated.
5
+ This diagnostic on B0-preselected validation windows is NOT an independent skill claim.
6
+ """
7
+ from __future__ import annotations
8
+ import csv,hashlib,io,json,math,zipfile
9
+ from pathlib import Path
10
+ import torch
11
+ from benchmark import Config, Windows, UnifiedModel, make_synthetic_cache
12
+ from b2_r1_flux_control import check_zip
13
+ from b2_r6_causal_spatial_controller import (
14
+ sha, make_cache_checked, CausalCoefficientHead, orthogonal_matrix, effect_basis,
15
+ TASK_LIST, SEED, LEADS, RATIO_BUDGET,
16
+ )
17
+ from b2_r7_frozen_controller_gain_audit import load_heads, forecast_scaled, validate_r6, write_json, write_csv
18
+
19
+ # Hash pinned to the user-provided actual real run, not the original Notebook.
20
+ R7_REAL_ZIP_SHA='9c7891b0784ce63c95dad6eb52c073ea69495f2e99adf38a4f187255397f987a'
21
+ R6_REAL_ZIP_SHA='d923eb6af43c294f28ec4f236c707feb0c4bcf1e6e5fed519bf697599730ef89'
22
+ PARENT_SHA='69552b29e169ec22ee85d9c1b1a678fdb188f6f25dc18f33b927b88f4dddffd4'
23
+ # Magnitudes are fractions of the current autoregressive model's own latent RMS.
24
+ BUDGETS=(0.,0.0005,0.001,0.003,0.005,0.01)
25
+ SIGNS=(1,-1)
26
+ PRE_REGISTERED_BUDGET=0.003
27
+
28
+
29
+ def validate_r7(r7zip):
30
+ if sha(r7zip)!=R7_REAL_ZIP_SHA:raise RuntimeError('B2_R7_REAL_REPORT_SHA_MISMATCH')
31
+ with zipfile.ZipFile(r7zip) as z:
32
+ if z.testzip():raise RuntimeError('B2_R7_CRC_FAILED')
33
+ r=json.loads(z.read('report.json'))
34
+ if r['status']!='FROZEN_CONTROLLER_SENSITIVITY_COMPLETED_NO_TRAINING':raise RuntimeError('B2_R7_SCIENTIFIC_STATE_INVALID')
35
+ if {v['task'] for v in r['tasks']}!=set(TASK_LIST):raise RuntimeError('B2_R7_TASK_COVERAGE_MISSING')
36
+ for item in json.loads(z.read('FILE_SHA256_MANIFEST.json')):
37
+ if hashlib.sha256(z.read(item['path'])).hexdigest()!=item['sha256']:
38
+ raise RuntimeError('B2_R7_INNER_MANIFEST_MISMATCH:'+item['path'])
39
+ parsed=list(csv.DictReader(io.StringIO(z.read('gain_lead_curves.csv').decode('utf-8'))))
40
+ assert len(parsed)==285
41
+ ref={(v['task'],v['method'],int(v['lead'])):float(v['mse_physical']) for v in parsed}
42
+ return r,ref
43
+
44
+
45
+ def _norm(t):
46
+ return t.square().mean(dim=(1,2,3),keepdim=True).sqrt()
47
+
48
+
49
+ @torch.no_grad()
50
+ def rollout_budget(core, head, h, *, rotate=False, Q=None, budget=0., sign=1, steps=12):
51
+ mm=core.model; one_d=(h.ndim==4); hh=h.unsqueeze(-2) if one_d else h
52
+ B,L,C,H,W=hh.shape; coeff=mm.coefficients()
53
+ z=mm.encode(hh.reshape(B,L*C,H,W)); prev=hh[:,-1]; initial=prev
54
+ skew=mm.skew-mm.skew.T
55
+ preds=[]; diag={}; min_raw=float('inf')
56
+ for lead in range(1,steps+1):
57
+ f=coeff['flux']*mm.flux(z)
58
+ l=coeff['local']*torch.tanh(mm.local(z))
59
+ s=.035*torch.einsum('ij,bjhw->bihw',skew,z)
60
+ base=z+f+l+s
61
+ if head is None or float(budget)==0.:
62
+ change=torch.zeros_like(base); realized=base.new_zeros((B,))
63
+ raw_rms=base.new_zeros((B,)); gates=base.new_zeros((B,)); inactive=base.new_zeros((B,))
64
+ else:
65
+ basis,amps=effect_basis(f,l,s,Q if rotate else None)
66
+ coeff_local=head(base,prev,initial,amps,lead)
67
+ raw=(coeff_local.unsqueeze(2)*basis).sum(dim=1)/math.sqrt(3)
68
+ raw_rms=_norm(raw)
69
+ if not torch.isfinite(raw_rms).all():raise RuntimeError('NONFINITE_HEAD_VECTOR')
70
+ # Near-zero controller vectors cannot be safely normalized.
71
+ active=(raw_rms>=1e-9)
72
+ change=sign*float(budget)*_norm(base).clamp_min(1e-4)*raw/raw_rms.clamp_min(1e-9)
73
+ change=torch.where(active,change,torch.zeros_like(change))
74
+ realized=(_norm(change)/_norm(base).clamp_min(1e-4)).flatten()
75
+ raw_rms=raw_rms.flatten()
76
+ gates=coeff_local.abs().mean(dim=(1,2,3))
77
+ inactive=(~active).flatten().float()
78
+ # Assert exact equal exposure before any ground-truth loss is observed.
79
+ if active.any() and (realized[active.flatten()]-float(budget)).abs().max()>max(1e-6,float(budget)*2e-3):
80
+ raise RuntimeError('EXPOSURE_MATCH_CONTRACT_FAIL')
81
+ z=base+change
82
+ out=prev+coeff['out']*torch.tanh(mm.decode(z));prev=out
83
+ preds.append(out.squeeze(-2) if one_d else out)
84
+ if lead in LEADS:
85
+ diag[lead]={'used_rel':realized.cpu(), 'raw_norm':raw_rms.cpu(),
86
+ 'gate_abs':gates.cpu(),'inactive':inactive.cpu()}
87
+ return torch.stack(preds,1),diag
88
+
89
+
90
+ def _name(head,budget,sign):
91
+ if head is None:return 'baseline'
92
+ return f'{head}__b{budget*100:.3f}pct__s{sign:+d}'
93
+
94
+
95
+ @torch.no_grad()
96
+ def evaluate(core,heads,Q,val,indices,device,*,smoke=False):
97
+ # Fully preregistered grid. All methods issue-time; never use truth for tuning.
98
+ budgets=(0.,.001,.003) if smoke else BUDGETS
99
+ specs=[(None,None,0.,1,False)]
100
+ for method in ('operator','rotated'):
101
+ for b in budgets:
102
+ if b==0: continue # baseline is shared and has exact zero correction
103
+ for sign in SIGNS:
104
+ specs.append((method,heads[method],b,sign,method=='rotated'))
105
+ records=[]; sums={_name(n,b,sign):{int(t):[0.,0] for t in LEADS} for n,_,b,sign,_ in specs}
106
+ stats={_name(n,b,sign):{int(t):[0.,0.,0.,0] for t in LEADS} for n,_,b,sign,_ in specs}
107
+ parity=0.
108
+ for pos in range(0,len(indices),2):
109
+ ids=indices[pos:pos+2]
110
+ h=torch.stack([val[int(i)][0] for i in ids]).to(device)
111
+ truth=torch.stack([val[int(i)][1] for i in ids]).to(device)
112
+ scale=val.sd.to(device).reshape(1,-1,*([1]*(truth.ndim-3)))
113
+ # Verify B2-R6 1x head and B2-R7 1x replay on the same issue-time windows.
114
+ prior={}
115
+ for kind in ('operator','rotated'):
116
+ prior[kind],_=forecast_scaled(core,heads[kind],h,rotate=(kind=='rotated'),Q=Q,steps=12,gain=1.,sign=1)
117
+ for typ,head,b,sign,rot in specs:
118
+ name=_name(typ,b,sign)
119
+ pred,d=rollout_budget(core,head,h,rotate=rot,Q=Q,budget=b,sign=sign)
120
+ if typ is None:
121
+ parent_base,_=forecast_scaled(core,None,h,steps=12,gain=0.,sign=1)
122
+ parity=max(parity,float((pred-parent_base).abs().max()))
123
+ for lead in LEADS:
124
+ err=((pred[:,lead-1]-truth[:,lead-1])*scale).square().flatten(1).mean(1)
125
+ sums[name][lead][0]+=float(err.sum());sums[name][lead][1]+=err.numel()
126
+ for idx,loss in zip(ids,err.cpu().tolist()):
127
+ records.append({'window_index':int(idx),'method':name,'lead':int(lead),'mse_physical':float(loss)})
128
+ if head is not None:
129
+ di=d[int(lead)];stat=stats[name][int(lead)]
130
+ stat[0]+=float(di['used_rel'].sum());stat[1]+=float(di['inactive'].sum());
131
+ stat[2]+=float(di['gate_abs'].sum());stat[3]+=len(ids)
132
+ # No time leakage: prior forecasts only scored retrospectively here.
133
+ for kind in ('operator','rotated'):
134
+ for lead in LEADS:
135
+ err=((prior[kind][:,lead-1]-truth[:,lead-1])*scale).square().flatten(1).mean(1)
136
+ key=f'original__{kind}__g1'
137
+ if key not in sums:sums[key]={int(t):[0.,0] for t in LEADS}
138
+ sums[key][int(lead)][0]+=float(err.sum());sums[key][int(lead)][1]+=err.numel()
139
+ if parity>2e-6:raise RuntimeError('ORIGINAL_OUTPUT_PARITY_FAIL')
140
+ curves=[]
141
+ for name,lead_metrics in sums.items():
142
+ for lead,(total,n) in lead_metrics.items():
143
+ ss,ns=sums['baseline'][lead]
144
+ s=stats.get(name,{}).get(lead,[0,0,0,0])
145
+ curves.append({'method':name,'lead':lead,'mse_physical':total/max(n,1),
146
+ 'ratio_to_baseline':(total/max(n,1))/(ss/max(ns,1)),
147
+ 'realized_relative_rms':s[0]/max(s[3],1),
148
+ 'inactive_fraction':s[1]/max(s[3],1),
149
+ 'gate_abs_mean':s[2]/max(s[3],1)})
150
+ return curves,records,parity
151
+
152
+
153
+ def check_replay(curves,ref,task):
154
+ for method in ('baseline','operator__g1__s+1','rotated__g1__s+1'):
155
+ current_name='baseline' if method=='baseline' else 'original__'+method.split('__')[0]+'__g1'
156
+ for lead in LEADS:
157
+ current=next(row['mse_physical'] for row in curves if row['method']==current_name and row['lead']==lead)
158
+ old=ref[(task,method,int(lead))]
159
+ if abs(current-old)>max(1e-8,abs(old)*0.003):
160
+ raise RuntimeError(f'B2_R7_REPLAY_MISMATCH {task} {method} {lead}: {current} vs {old}')
161
+
162
+
163
+ def posthoc_gate(curves,task):
164
+ # A diagnosis gate only, NOT authorization for full training or deployment.
165
+ get=lambda m,lead:next(r for r in curves if r['method']==m and r['lead']==lead)
166
+ bud=PRE_REGISTERED_BUDGET;natural=_name('operator',bud,1);rot=_name('rotated',bud,1)
167
+ rev=_name('operator',bud,-1)
168
+ late=[9,12]
169
+ late_nat=sum(get(natural,l)['mse_physical'] for l in late)
170
+ late_rot=sum(get(rot,l)['mse_physical'] for l in late)
171
+ late_base=sum(get('baseline',l)['mse_physical'] for l in late)
172
+ early=max(get(natural,l)['ratio_to_baseline'] for l in (1,3))
173
+ return {'task':task,'diagnostic_budget_percent':bud*100,
174
+ 'late_vs_baseline_gain_pct':100*(1-late_nat/late_base),
175
+ 'late_vs_rotated_gain_pct':100*(1-late_nat/late_rot),
176
+ 'early_max_ratio_to_baseline':early,
177
+ 'directional_sign_consistency':all(get(natural,l)['mse_physical']<get(rev,l)['mse_physical'] for l in late),
178
+ 'exposure_nat_minus_rot_at_12':get(natural,12)['realized_relative_rms']-get(rot,12)['realized_relative_rms'],
179
+ 'local_diagnostic_signal':bool((late_nat/late_rot)<=0.99 and early<=1.02)}
180
+
181
+
182
+ def sample_risk(samples, budget=PRE_REGISTERED_BUDGET):
183
+ """Posthoc paired risk; zero-error windows have an absolute+relative floor."""
184
+ out={}
185
+ for lead in (1,3,9,12):
186
+ table={}
187
+ for row in samples:
188
+ if row['lead']==lead:
189
+ table.setdefault(row['method'],{})[row['window_index']]=float(row['mse_physical'])
190
+ baseline=table['baseline']
191
+ for typ in ('operator','rotated'):
192
+ key=_name(typ,budget,1)
193
+ active=table[key]
194
+ assert set(active)==set(baseline),'UNPAIRED_SAMPLE_IDS'
195
+ ids=sorted(baseline)
196
+ improv=[active[i]<baseline[i] for i in ids]
197
+ small_floor=max(1e-12,sum(baseline.values())/len(ids)*1e-4)
198
+ ratios=sorted(active[i]/max(baseline[i],small_floor) for i in ids)
199
+ out[f'{typ}_lead{lead}']={
200
+ 'windows':len(ids), 'window_win_fraction':sum(improv)/len(ids),
201
+ 'sample_ratio_p90':ratios[min(len(ids)-1,math.ceil(.9*len(ids))-1)],
202
+ 'regression_over_2pct_fraction':sum(active[i]>baseline[i]*1.02+small_floor for i in ids)/len(ids),
203
+ 'denominator_floor':small_floor,
204
+ }
205
+ return out
206
+
207
+
208
+ def run(parent,r6zip,r7zip,work,output,*,smoke=False,device='cpu'):
209
+ work=Path(work);output=Path(output)
210
+ work.mkdir(parents=True,exist_ok=True);output.mkdir(parents=True,exist_ok=True)
211
+ rr,refs=validate_r7(r7zip)
212
+ if sha(r6zip)!=R6_REAL_ZIP_SHA:raise RuntimeError('R6_WEIGHTS_ARCHIVE_SHA_MISMATCH')
213
+ validate_r6(r6zip)
214
+ all_curves=[];all_samples=[];cases=[]; gates=[]
215
+ if smoke:
216
+ cfg=Config(profile='smoke');cache1=make_synthetic_cache(cfg)
217
+ for split in ('train','val','test'):
218
+ cache1['splits'][split]['h']=cache1['splits'][split]['h'].mean(dim=-2)
219
+ cache1['splits'][split]['y']=cache1['splits'][split]['y'].mean(dim=-2)
220
+ inputs=[('smoke_1d',cache1),('smoke_2d',make_synthetic_cache(cfg))]
221
+ for task,cache in inputs:
222
+ core=UnifiedModel('V2_Ported_R5',1,8,12,4).to(device).eval().requires_grad_(False)
223
+ hch=1
224
+ heads=load_heads(r6zip,'burgers_1d' if task=='smoke_1d' else 'swe_2d',hch,device)
225
+ Q=orthogonal_matrix(int(core.model.skew.shape[0]),32207,device,torch.float32)
226
+ val=Windows(cache['splits']['val']['h'],cache['splits']['val']['y'],cache['mean'],cache['std'])
227
+ curves,samples,parity=evaluate(core,heads,Q,val,[0,1],device,smoke=True)
228
+ for item in curves:item['task']=task
229
+ for item in samples:item['task']=task
230
+ all_curves.extend(curves);all_samples.extend(samples)
231
+ cases.append({'task':task,'forward_parity_max':parity})
232
+ status='PROGRAM_ONLY_SMOKE_NO_SCIENCE'
233
+ else:
234
+ if parent is None or sha(parent)!=PARENT_SHA:raise RuntimeError('PINNED_18_MODEL_PARENT_REQUIRED')
235
+ with zipfile.ZipFile(parent) as z:
236
+ m,contract=check_zip(z);cfg=Config(**m['config'])
237
+ for task in TASK_LIST:
238
+ core,cache,replay=make_cache_checked(task,z,cfg,work,device)
239
+ old=next(x for x in rr['tasks'] if x['task']==task)
240
+ if replay['checkpoint_sha256']!=old['replay']['checkpoint_sha256']:
241
+ raise RuntimeError('CHECKPOINT_SHA_CHANGED:'+task)
242
+ hch=int(cache['splits']['val']['h'].shape[2]);heads=load_heads(r6zip,task,hch,device)
243
+ Q=orthogonal_matrix(int(core.model.skew.shape[0]),32207,device,torch.float32)
244
+ val=Windows(cache['splits']['val']['h'],cache['splits']['val']['y'],cache['mean'],cache['std'])
245
+ ix=old['readout_windows']
246
+ if ix!=list(range(16,32)):raise RuntimeError('AUDIT_WINDOW_CONTRACT_CHANGED')
247
+ curves,samples,parity=evaluate(core,heads,Q,val,ix,device,smoke=False)
248
+ check_replay(curves,refs,task)
249
+ if parity>2e-6:raise RuntimeError('PARENT_FORWARDS_CHANGED')
250
+ diagnostic_gate=posthoc_gate(curves,task)
251
+ diagnostic_gate['paired_window_risk']=sample_risk(samples)
252
+ gates.append(diagnostic_gate)
253
+ cases.append({'task':task,'replay':replay,'baseline_parity_max':parity,
254
+ 'audit_windows':ix,'frozen_controller_only':True})
255
+ for item in curves:item['task']=task
256
+ for item in samples:item['task']=task
257
+ all_curves.extend(curves);all_samples.extend(samples)
258
+ write_csv(output/'equal_rms_lead_curves.csv',all_curves)
259
+ write_csv(output/'sample_mse.csv',all_samples)
260
+ write_json(output/'live_progress.json',{'completed_tasks':[v['task'] for v in cases]})
261
+ status='FROZEN_EQUAL_RMS_AUDIT_COMPLETED_NO_TRAINING'
262
+ write_csv(output/'equal_rms_lead_curves.csv',all_curves)
263
+ write_csv(output/'sample_mse.csv',all_samples)
264
+ write_json(output/'matched_exposure_gate.json',{'status':'DIAGNOSTIC_ONLY','per_task':gates,
265
+ 'does_not_authorize': ['full-training','hyperparameter deployment','outer-holdout claims'],
266
+ 'registered_budget_fraction':PRE_REGISTERED_BUDGET})
267
+ report={'experiment':'CIDM_v2_B2_R8_P0_FrozenEqualRMS_CausalControl_Diagnostic',
268
+ 'status':status,'cases':cases,'training_steps':0,
269
+ 'r6_real_archive_sha256':R6_REAL_ZIP_SHA,'r7_real_archive_sha256':R7_REAL_ZIP_SHA,
270
+ 'expected_parent_sha256':PARENT_SHA,
271
+ 'budgets':list(BUDGETS),'signs':list(SIGNS),'num_curves':len(all_curves),
272
+ 'num_samples':len(all_samples),'registered_decision_budget':PRE_REGISTERED_BUDGET,
273
+ 'no_future_truth_used_in_control':True,'labels_scoring_only':True,
274
+ 'outer_holdout':False,
275
+ 'notes':['Unit-norm controller action is evaluated at equal per-step/per-window latent-RMS exposure.',
276
+ 'Natural vs Rotated comparison is exposure-matched, not trained-policy matched.',
277
+ 'No validation budget maximization or deployment tuning; B0 checkpoint was val-selected.',
278
+ 'Oracle gradient geometry from B2-R5 is not used in causal corrections.']}
279
+ write_json(output/'report.json',report)
280
+ return report