wuff-mann commited on
Commit
b6ec01b
·
verified ·
1 Parent(s): f009320

Archive CIDM-v2 reproducible b2_r10_gradient_credit_audit.py

Browse files
cidm_v2/source/B2_R10_P0_FIX1/b2_r10_gradient_credit_audit.py ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """B2-R10/P0 FIX1: stable-backward frozen gradient credit-horizon qualification.
2
+
3
+ No optimization. Compare identical 12-step autonomous forecasts under exact
4
+ 1/3/12-step differentiation through trained B2-R6 heads and unchanged CIDM core.
5
+ Labels used only for TRAIN loss; NEVER as rollout/controller inputs.
6
+ """
7
+ from __future__ import annotations
8
+ import time, math, json, copy, zipfile, csv
9
+ from pathlib import Path
10
+ import torch
11
+ from benchmark import Config, Windows, UnifiedModel, make_synthetic_cache
12
+ from b2_r6_causal_spatial_controller import (
13
+ TASK_LIST, LEADS, SEED, PARENT_SHA, make_cache_checked,
14
+ orthogonal_matrix, sha
15
+ )
16
+ from b2_r7_frozen_controller_gain_audit import load_heads, write_csv, write_json
17
+ from b2_r9_activation_timing import validate_r8, schedule_fraction, validate_r6, validate_r7
18
+ from b2_r1_flux_control import check_zip
19
+
20
+ R9_REAL_ZIP_SHA='3b90fded1769d199da2a8ca93e1505acc451d074f4e45e8c720be3a15861389f'
21
+ BUDGET=.003
22
+ HORIZONS=(1,3,12)
23
+ EXPECTED_LEADS=(1,3,9,12)
24
+
25
+
26
+ def validate_r9(path):
27
+ if sha(path)!=R9_REAL_ZIP_SHA:raise RuntimeError('B2R9_EXACT_REPORT_SHA256_MISMATCH')
28
+ with zipfile.ZipFile(path) as z:
29
+ if z.testzip():raise RuntimeError('B2R9_REPORT_CRC_FAIL')
30
+ data=json.loads(z.read('report.json'))
31
+ if data['status']!='FROZEN_ACTIVATION_TIMING_COMPLETED_NO_TRAINING' or data['total_samples']!=5040:
32
+ raise RuntimeError('B2R9_P0_REPORT_STATUS_INCOMPATIBLE')
33
+ manifest=json.loads(z.read('FILE_SHA256_MANIFEST.json'))
34
+ import hashlib
35
+ for entry in manifest:
36
+ if hashlib.sha256(z.read(entry['path'])).hexdigest()!=entry['sha256']:
37
+ raise RuntimeError('B2R9_MANIFEST_FAIL '+entry['path'])
38
+ return data
39
+
40
+
41
+ # FIX1: Preserve the ORIGINAL R9 forward values, but remove a subtle backward
42
+ # singularity. The old sqrt(mean(x^2)).clamp_min(eps) differentiates through
43
+ # sqrt(0) and returns NaN (0 * infinity) even though the forward is zero.
44
+ # Clamp before sqrt and then restore the exact forward zero. This is NOT a
45
+ # gradient clamp/clip and does not change any nonzero forward result.
46
+
47
+ def stable_exact_rms(x, dims=(1,2,3), keepdim=True):
48
+ q=x.square().mean(dim=dims, keepdim=keepdim)
49
+ return q.clamp_min(1e-30).sqrt() * (q>0).to(q.dtype)
50
+
51
+ def stable_exact_effect_basis(Fs,Ls,Ss,rotation=None):
52
+ basis=[Fs,Ls,Ss]
53
+ if rotation is not None:
54
+ basis=[torch.einsum('ij,bjhw->bihw',rotation,b) for b in basis]
55
+ norms=torch.stack([stable_exact_rms(b,dims=1,keepdim=True) for b in basis],dim=1)
56
+ normed=[b / norms[:,k].clamp_min(1e-6) for k,b in enumerate(basis)]
57
+ return torch.stack(normed,dim=1),norms.flatten(1,2)
58
+
59
+ def zero_singular_backward_test():
60
+ # A targeted regression test, NOT a synthetic forecasting performance claim.
61
+ x=torch.zeros((2,4,3,3),requires_grad=True)
62
+ y=stable_exact_rms(x).sum()
63
+ y.backward()
64
+ assert torch.isfinite(x.grad).all() and torch.count_nonzero(x.grad)==0
65
+ f=torch.zeros((2,4,3,3),requires_grad=True)
66
+ l=torch.randn_like(f,requires_grad=True)
67
+ sk=torch.zeros_like(f,requires_grad=True)
68
+ v,a=stable_exact_effect_basis(f,l,sk)
69
+ (v.square().sum()+a.square().sum()).backward()
70
+ assert all(torch.isfinite(q.grad).all() for q in (f,l,sk))
71
+ return {'zero_rms_backward':'PASS','zero_operator_backward':'PASS'}
72
+
73
+ def forecast_credit(core,head,h,rotation=None,truncate_horizon=1,steps=12):
74
+ """Faithful R9 forward, but F/L/S + decoder keep differentiable computational graph.
75
+
76
+ Core parameters remain frozen, while gradients propagate through the frozen
77
+ operations to earlier head actions. z/prev are detached ONLY on chunk borders.
78
+ """
79
+ if truncate_horizon not in HORIZONS:raise ValueError('unregistered horizon')
80
+ mm=core.model
81
+ one_d=(h.ndim==4)
82
+ hh=h.unsqueeze(-2) if one_d else h
83
+ B,L,C,H,W=hh.shape
84
+ co=mm.coefficients();z=mm.encode(hh.reshape(B,L*C,H,W));prev=hh[:,-1];initial=prev
85
+ skew=mm.skew-mm.skew.T
86
+ out=[]
87
+ for lead in range(1,steps+1):
88
+ if lead>1 and (lead-1)%truncate_horizon==0:
89
+ z=z.detach();prev=prev.detach()
90
+ f=co['flux']*mm.flux(z)
91
+ l=co['local']*torch.tanh(mm.local(z))
92
+ s=.035*torch.einsum('ij,bjhw->bihw',skew,z)
93
+ base=z+f+l+s
94
+ basis,amps=stable_exact_effect_basis(f,l,s,rotation)
95
+ gates=head(base,prev,initial,amps,lead)
96
+ raw=(gates.unsqueeze(2)*basis).sum(dim=1)/math.sqrt(3)
97
+ rr=stable_exact_rms(raw)
98
+ change=BUDGET*stable_exact_rms(base).clamp_min(1e-4)*raw/rr.clamp_min(1e-9)
99
+ change=torch.where(rr>=1e-9,change,torch.zeros_like(change))
100
+ z=base+change
101
+ prev=prev+co['out']*torch.tanh(mm.decode(z))
102
+ out.append(prev.squeeze(-2) if one_d else prev)
103
+ return torch.stack(out,1)
104
+
105
+
106
+ def original_frozen_rollout(core,head,h,rotation=None):
107
+ # Exact prior B2-R9 implementation; this is forward-parity reference.
108
+ from b2_r9_activation_timing import rollout_timing
109
+ return rollout_timing(core,head,h,Q=rotation,rotate=rotation is not None,
110
+ budget=BUDGET,onset=1,mode='fixed_step')[0]
111
+
112
+
113
+ def frozen_gradient(core,head,h,truth,scale,rotation,horizon):
114
+ head.zero_grad(set_to_none=True)
115
+ device=h.device
116
+ if device.type=='cuda':
117
+ torch.cuda.empty_cache();torch.cuda.reset_peak_memory_stats(device)
118
+ torch.cuda.synchronize(device)
119
+ t0=time.perf_counter()
120
+ pred=forecast_credit(core,head,h,rotation=rotation,truncate_horizon=horizon)
121
+ if not torch.isfinite(pred).all():raise RuntimeError('NONFINITE_FORWARD')
122
+ forward_diff=0.
123
+ with torch.no_grad():
124
+ ref=original_frozen_rollout(core,head,h,rotation=rotation)
125
+ forward_diff=float((ref-pred.detach()).abs().max())
126
+ if forward_diff>2e-6:raise RuntimeError(f'B2R10_FORWARD_PARITY_FAILED {horizon} {forward_diff}')
127
+ # Scale to physical units; identical objective at K1/K3/K12.
128
+ shape=(1,1,scale.numel())+(1,)*(pred.ndim-3)
129
+ w=scale.reshape(shape)
130
+ losses={j:((pred[:,j-1]-truth[:,j-1])*w.squeeze(1)).square().mean() for j in EXPECTED_LEADS}
131
+ loss=0.5*(losses[9]+losses[12])+0.125*(losses[1]+losses[3])
132
+ if not torch.isfinite(loss):raise RuntimeError('NONFINITE_GRADIENT_LOSS')
133
+ loss.backward()
134
+ flat=[]
135
+ for p in head.parameters():
136
+ flat.append(torch.zeros_like(p).flatten() if p.grad is None else p.grad.detach().flatten())
137
+ vec=torch.cat(flat)
138
+ if not torch.isfinite(vec).all():
139
+ details=[]
140
+ for name,param in head.named_parameters():
141
+ if param.grad is None: continue
142
+ bad=(~torch.isfinite(param.grad)).sum().item()
143
+ if bad: details.append({'parameter':name,'nonfinite_elements':int(bad),'total_elements':param.grad.numel()})
144
+ raise RuntimeError('NONFINITE_HEAD_GRADIENT_DETAIL '+json.dumps({'horizon':horizon,'bad':details},ensure_ascii=False))
145
+ if device.type=='cuda':torch.cuda.synchronize(device)
146
+ seconds=time.perf_counter()-t0
147
+ peak=(torch.cuda.max_memory_allocated(device)/2**20) if device.type=='cuda' else None
148
+ return vec.detach().cpu(),{'loss':float(loss.detach()),'forward_parity_max':forward_diff,
149
+ 'gradient_l2':float(torch.linalg.vector_norm(vec)),'seconds':seconds,
150
+ 'gpu_peak_allocated_mib':peak,'objective':'0.5*(L9+L12)+0.125*(L1+L3)'}
151
+
152
+
153
+ def correlation(x,y):
154
+ xx=torch.linalg.vector_norm(x).item(); yy=torch.linalg.vector_norm(y).item()
155
+ if xx<1e-16 or yy<1e-16:return None
156
+ return float(torch.dot(x.double(),y.double())/torch.linalg.vector_norm(x.double())/torch.linalg.vector_norm(y.double()))
157
+
158
+
159
+ def one_case(task,core,cache,r6zip,device,smoke=False):
160
+ core.requires_grad_(False).eval()
161
+ ch=int(cache['splits']['train']['h'].shape[2]);d=int(core.model.skew.shape[0])
162
+ heads=load_heads(r6zip,'swe_2d' if task=='smoke_2d' else ('burgers_1d' if task=='smoke_1d' else task),ch,device)
163
+ Q=orthogonal_matrix(d,32207,device,torch.float32)
164
+ train=Windows(cache['splits']['train']['h'],cache['splits']['train']['y'],cache['mean'],cache['std'])
165
+ inds=[0,1] if len(train)>=2 else [0]
166
+ arr=[train[i] for i in inds]
167
+ h=torch.stack([a[0] for a in arr]).to(device);y=torch.stack([a[1] for a in arr]).to(device)
168
+ scale=train.sd.to(device)
169
+ rows=[]
170
+ for typ in ('operator','rotated'):
171
+ head=heads[typ].train()
172
+ for p in head.parameters():p.requires_grad_(True)
173
+ gradients={}
174
+ for horizon in HORIZONS:
175
+ grad,stats=frozen_gradient(core,head,h,y,scale,(Q if typ=='rotated' else None),horizon)
176
+ gradients[horizon]=grad
177
+ rows.append({'task':task,'controller':typ,'horizon':horizon,**stats})
178
+ ref=gradients[12]
179
+ for record in rows[-3:]:
180
+ g=gradients[record['horizon']]
181
+ record['cosine_vs_full12']=correlation(g,ref)
182
+ record['relative_l2_error_vs_full12']=float(torch.linalg.vector_norm((g-ref).double())/torch.linalg.vector_norm(ref.double()).clamp_min(1e-15))
183
+ for p in head.parameters():p.requires_grad_(False)
184
+ if any(x['forward_parity_max']>2e-6 for x in rows):raise RuntimeError('PARITY_ERROR')
185
+ if sum(p.requires_grad for p in core.parameters())!=0:raise RuntimeError('CORE_TRAINABILITY_LEAK')
186
+ return rows,{'task':task,'batch_indices':inds,'examples':len(inds),'core_trainable':0,
187
+ 'heads_updated':0,'reference_full_gradient_is_forecast_skill':False}
188
+
189
+
190
+ def run(parent,r6zip,r9zip,work,output,smoke=False,device='cpu'):
191
+ work=Path(work);output=Path(output);work.mkdir(parents=True,exist_ok=True);output.mkdir(parents=True,exist_ok=True)
192
+ zero_test=zero_singular_backward_test()
193
+ write_json(output/'numerical_preflight.json',zero_test)
194
+ r6=validate_r6(r6zip);r9=validate_r9(r9zip)
195
+ rows=[];cases=[]
196
+ if smoke:
197
+ cfg=Config(profile='smoke')
198
+ a=make_synthetic_cache(cfg);b=make_synthetic_cache(cfg)
199
+ for split in ('train','val','test'):
200
+ a['splits'][split]['h']=a['splits'][split]['h'].mean(dim=-2)
201
+ a['splits'][split]['y']=a['splits'][split]['y'].mean(dim=-2)
202
+ for task,cache in [('smoke_1d',a),('smoke_2d',b)]:
203
+ core=UnifiedModel('V2_Ported_R5',1,8,12,4).to(device)
204
+ r,s=one_case(task,core,cache,r6zip,device,smoke=True)
205
+ rows+=r;cases.append(s)
206
+ status='PROGRAM_ONLY_SMOKE_NO_SCIENCE'
207
+ else:
208
+ if parent is None or sha(parent)!=PARENT_SHA:raise RuntimeError('MISSING_OR_CHANGED_EXACT_B0_PARENT')
209
+ with zipfile.ZipFile(parent) as z:
210
+ manifest,contract=check_zip(z);cfg=Config(**manifest['config'])
211
+ for task in TASK_LIST:
212
+ core,cache,replay=make_cache_checked(task,z,cfg,work,device)
213
+ try:
214
+ r,s=one_case(task,core,cache,r6zip,device)
215
+ except Exception as exc:
216
+ write_json(output/'numeric_failure_case.json',
217
+ {'task':task,'exception':repr(exc),'completed':[v['task'] for v in cases],
218
+ 'status':'BLOCKED_NUMERICAL_GRADIENT_NO_TRAINING'})
219
+ raise
220
+ rows+=r;s['parent_replay']=replay;cases.append(s)
221
+ write_csv(output/'gradient_credit_horizons.csv',rows)
222
+ write_json(output/'progress.json',{'completed':[v['task'] for v in cases]})
223
+ status='FROZEN_GRADIENT_CREDIT_AUDIT_COMPLETED_NO_TRAINING'
224
+ # Evaluate structural evidence, not a skill result and not an automatic permission to train.
225
+ paired=[]
226
+ for task in sorted(set(r['task'] for r in rows)):
227
+ for typ in ('operator','rotated'):
228
+ item={r['horizon']:r for r in rows if r['task']==task and r['controller']==typ}
229
+ cs1=item[1]['cosine_vs_full12'];cs3=item[3]['cosine_vs_full12']
230
+ paired.append({'task':task,'controller':typ,'cos_k1_full12':cs1,'cos_k3_full12':cs3,
231
+ 'improved_cosine_3_vs_1':None if cs1 is None or cs3 is None else cs3-cs1,
232
+ 'full_gradient_norm':item[12]['gradient_l2'],
233
+ 'full_gradient_gpu_mem_mib':item[12]['gpu_peak_allocated_mib'],
234
+ 'k3_gpu_mem_mib':item[3]['gpu_peak_allocated_mib']})
235
+ gate={'status':'DIAGNOSTIC_ONLY_NO_TRAINING','candidate_for_future_3step_pilot':False,
236
+ 'reason':'Only gradient geometry on TRAIN windows; cannot authorize training/performance claims from these diagnostics',
237
+ 'comparisons':paired,'no_full_cidm_training':True,'truth_for_actions':False}
238
+ write_csv(output/'gradient_credit_horizons.csv',rows)
239
+ write_csv(output/'gradient_credit_pairs.csv',paired)
240
+ write_json(output/'gradient_qualification_gate.json',gate)
241
+ report={'experiment':'CIDM_v2_B2_R10_P0_GradientCreditHorizon','status':status,
242
+ 'training_steps':0,'data_used_for_qualification':'TRAIN only','rows':len(rows),
243
+ 'cases':cases,'gradient_horizons':list(HORIZONS),'official_full_pde_replay':not smoke,'numerical_fix':'sqrt pre-clamp, zero-forward preserved, no gradient clipping',
244
+ 'r9_evidence_sha256':sha(r9zip),'r6_evidence_sha256':sha(r6zip),
245
+ 'original_parent_sha256':PARENT_SHA,'no_outer_holdout':True,
246
+ 'explanation':'K=12 is an exact autograd reference for the same 12-step loss; it is not a trained model improvement.'}
247
+ write_json(output/'report.json',report)
248
+ print('[B2-R10]',status,'cases',len(cases),'records',len(rows),flush=True)
249
+ for p in paired:print('[GRAD CREDIT]',p['task'],p['controller'],'cos_K1',p['cos_k1_full12'],'cos_K3',p['cos_k3_full12'],flush=True)
250
+ return report