wuff-mann commited on
Commit
975b2a4
·
verified ·
1 Parent(s): 94bf790

Archive CIDM-v2 reproducible b2_r10_gradient_credit_audit.py

Browse files
cidm_v2/source/B2_R10_P0_FIX2/b2_r10_gradient_credit_audit.py ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """B2-R10/P0 FIX2: stable gradient-credit with FP64 accounting and conditioning audit.
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
+ large=torch.full((32,),1e20,dtype=torch.float32)
72
+ assert not torch.isfinite(torch.linalg.vector_norm(large))
73
+ assert math.isfinite(float(torch.linalg.vector_norm(large.double())))
74
+ return {'zero_rms_backward':'PASS','zero_operator_backward':'PASS',
75
+ 'fp32_norm_overflow_regression':'PASS'}
76
+
77
+ def forecast_credit(core,head,h,rotation=None,truncate_horizon=1,steps=12):
78
+ """Faithful R9 forward, but F/L/S + decoder keep differentiable computational graph.
79
+
80
+ Core parameters remain frozen, while gradients propagate through the frozen
81
+ operations to earlier head actions. z/prev are detached ONLY on chunk borders.
82
+ """
83
+ if truncate_horizon not in HORIZONS:raise ValueError('unregistered horizon')
84
+ mm=core.model
85
+ one_d=(h.ndim==4)
86
+ hh=h.unsqueeze(-2) if one_d else h
87
+ B,L,C,H,W=hh.shape
88
+ co=mm.coefficients();z=mm.encode(hh.reshape(B,L*C,H,W));prev=hh[:,-1];initial=prev
89
+ skew=mm.skew-mm.skew.T
90
+ out=[]
91
+ for lead in range(1,steps+1):
92
+ if lead>1 and (lead-1)%truncate_horizon==0:
93
+ z=z.detach();prev=prev.detach()
94
+ f=co['flux']*mm.flux(z)
95
+ l=co['local']*torch.tanh(mm.local(z))
96
+ s=.035*torch.einsum('ij,bjhw->bihw',skew,z)
97
+ base=z+f+l+s
98
+ basis,amps=stable_exact_effect_basis(f,l,s,rotation)
99
+ gates=head(base,prev,initial,amps,lead)
100
+ raw=(gates.unsqueeze(2)*basis).sum(dim=1)/math.sqrt(3)
101
+ rr=stable_exact_rms(raw)
102
+ change=BUDGET*stable_exact_rms(base).clamp_min(1e-4)*raw/rr.clamp_min(1e-9)
103
+ change=torch.where(rr>=1e-9,change,torch.zeros_like(change))
104
+ z=base+change
105
+ prev=prev+co['out']*torch.tanh(mm.decode(z))
106
+ out.append(prev.squeeze(-2) if one_d else prev)
107
+ return torch.stack(out,1)
108
+
109
+
110
+ def original_frozen_rollout(core,head,h,rotation=None):
111
+ # Exact prior B2-R9 implementation; this is forward-parity reference.
112
+ from b2_r9_activation_timing import rollout_timing
113
+ return rollout_timing(core,head,h,Q=rotation,rotate=rotation is not None,
114
+ budget=BUDGET,onset=1,mode='fixed_step')[0]
115
+
116
+
117
+ def frozen_gradient(core,head,h,truth,scale,rotation,horizon):
118
+ head.zero_grad(set_to_none=True)
119
+ device=h.device
120
+ if device.type=='cuda':
121
+ torch.cuda.empty_cache();torch.cuda.reset_peak_memory_stats(device)
122
+ torch.cuda.synchronize(device)
123
+ t0=time.perf_counter()
124
+ pred=forecast_credit(core,head,h,rotation=rotation,truncate_horizon=horizon)
125
+ if not torch.isfinite(pred).all():raise RuntimeError('NONFINITE_FORWARD')
126
+ forward_diff=0.
127
+ with torch.no_grad():
128
+ ref=original_frozen_rollout(core,head,h,rotation=rotation)
129
+ forward_diff=float((ref-pred.detach()).abs().max())
130
+ if forward_diff>2e-6:raise RuntimeError(f'B2R10_FORWARD_PARITY_FAILED {horizon} {forward_diff}')
131
+ # Scale to physical units; identical objective at K1/K3/K12.
132
+ shape=(1,1,scale.numel())+(1,)*(pred.ndim-3)
133
+ w=scale.reshape(shape)
134
+ losses={j:((pred[:,j-1]-truth[:,j-1])*w.squeeze(1)).square().mean() for j in EXPECTED_LEADS}
135
+ loss=0.5*(losses[9]+losses[12])+0.125*(losses[1]+losses[3])
136
+ if not torch.isfinite(loss):raise RuntimeError('NONFINITE_GRADIENT_LOSS')
137
+ loss.backward()
138
+ flat=[]
139
+ for p in head.parameters():
140
+ flat.append(torch.zeros_like(p).flatten() if p.grad is None else p.grad.detach().flatten())
141
+ vec=torch.cat(flat)
142
+ if not torch.isfinite(vec).all():
143
+ details=[]
144
+ for name,param in head.named_parameters():
145
+ if param.grad is None: continue
146
+ bad=(~torch.isfinite(param.grad)).sum().item()
147
+ if bad: details.append({'parameter':name,'nonfinite_elements':int(bad),'total_elements':param.grad.numel()})
148
+ raise RuntimeError('NONFINITE_HEAD_GRADIENT_DETAIL '+json.dumps({'horizon':horizon,'bad':details},ensure_ascii=False))
149
+ # FIX2: float32 norm can overflow even when ALL gradient elements are finite.
150
+ # This is a diagnostic conversion after backward, NOT model or gradient clipping.
151
+ vd=vec.detach().double()
152
+ norm64=float(torch.linalg.vector_norm(vd))
153
+ if not math.isfinite(norm64):
154
+ raise RuntimeError('NONFINITE_FP64_GRADIENT_NORM')
155
+ norm32=float(torch.linalg.vector_norm(vec))
156
+ layer_health=[]
157
+ for name,param in head.named_parameters():
158
+ if param.grad is None:
159
+ layer_health.append({'parameter':name,'gradient_missing':True})
160
+ continue
161
+ gg=param.grad.detach().double()
162
+ layer_health.append({'parameter':name,'gradient_missing':False,
163
+ 'gradient_l2_fp64':float(torch.linalg.vector_norm(gg)),
164
+ 'gradient_max_abs':float(gg.abs().max()),
165
+ 'gradient_rms_fp64':float(gg.square().mean().sqrt()),
166
+ 'gradient_nonfinite_count':int((~torch.isfinite(gg)).sum())})
167
+ if device.type=='cuda':torch.cuda.synchronize(device)
168
+ seconds=time.perf_counter()-t0
169
+ peak=(torch.cuda.max_memory_allocated(device)/2**20) if device.type=='cuda' else None
170
+ return vec.detach().cpu(),{'loss':float(loss.detach()),'forward_parity_max':forward_diff,
171
+ 'gradient_l2':norm64,
172
+ 'gradient_l2_log10':math.log10(norm64) if norm64>0 else None,
173
+ 'gradient_l2_fp32_overflow':not math.isfinite(norm32),
174
+ 'gradient_max_abs':float(vd.abs().max()),
175
+ 'gradient_rms_fp64':float(vd.square().mean().sqrt()),
176
+ 'layer_gradient_health':layer_health,
177
+ 'seconds':seconds,
178
+ 'gpu_peak_allocated_mib':peak,'objective':'0.5*(L9+L12)+0.125*(L1+L3)'}
179
+
180
+
181
+ def correlation(x,y):
182
+ xx=torch.linalg.vector_norm(x.double()).item(); yy=torch.linalg.vector_norm(y.double()).item()
183
+ if xx<1e-16 or yy<1e-16:return None
184
+ return float(torch.dot(x.double(),y.double())/torch.linalg.vector_norm(x.double())/torch.linalg.vector_norm(y.double()))
185
+
186
+
187
+ def one_case(task,core,cache,r6zip,device,smoke=False):
188
+ core.requires_grad_(False).eval()
189
+ ch=int(cache['splits']['train']['h'].shape[2]);d=int(core.model.skew.shape[0])
190
+ heads=load_heads(r6zip,'swe_2d' if task=='smoke_2d' else ('burgers_1d' if task=='smoke_1d' else task),ch,device)
191
+ Q=orthogonal_matrix(d,32207,device,torch.float32)
192
+ train=Windows(cache['splits']['train']['h'],cache['splits']['train']['y'],cache['mean'],cache['std'])
193
+ inds=[0,1] if len(train)>=2 else [0]
194
+ arr=[train[i] for i in inds]
195
+ h=torch.stack([a[0] for a in arr]).to(device);y=torch.stack([a[1] for a in arr]).to(device)
196
+ scale=train.sd.to(device)
197
+ rows=[]
198
+ for typ in ('operator','rotated'):
199
+ head=heads[typ].train()
200
+ for p in head.parameters():p.requires_grad_(True)
201
+ gradients={}
202
+ for horizon in HORIZONS:
203
+ grad,stats=frozen_gradient(core,head,h,y,scale,(Q if typ=='rotated' else None),horizon)
204
+ gradients[horizon]=grad
205
+ rows.append({'task':task,'controller':typ,'horizon':horizon,**stats})
206
+ ref=gradients[12]
207
+ for record in rows[-3:]:
208
+ g=gradients[record['horizon']]
209
+ record['cosine_vs_full12']=correlation(g,ref)
210
+ record['relative_l2_error_vs_full12']=float(torch.linalg.vector_norm((g-ref).double())/torch.linalg.vector_norm(ref.double()).clamp_min(1e-15))
211
+ for p in head.parameters():p.requires_grad_(False)
212
+ if any(x['forward_parity_max']>2e-6 for x in rows):raise RuntimeError('PARITY_ERROR')
213
+ if sum(p.requires_grad for p in core.parameters())!=0:raise RuntimeError('CORE_TRAINABILITY_LEAK')
214
+ return rows,{'task':task,'batch_indices':inds,'examples':len(inds),'core_trainable':0,
215
+ 'heads_updated':0,'reference_full_gradient_is_forecast_skill':False}
216
+
217
+
218
+ def run(parent,r6zip,r9zip,work,output,smoke=False,device='cpu'):
219
+ work=Path(work);output=Path(output);work.mkdir(parents=True,exist_ok=True);output.mkdir(parents=True,exist_ok=True)
220
+ zero_test=zero_singular_backward_test()
221
+ write_json(output/'numerical_preflight.json',zero_test)
222
+ r6=validate_r6(r6zip);r9=validate_r9(r9zip)
223
+ rows=[];cases=[]
224
+ if smoke:
225
+ cfg=Config(profile='smoke')
226
+ a=make_synthetic_cache(cfg);b=make_synthetic_cache(cfg)
227
+ for split in ('train','val','test'):
228
+ a['splits'][split]['h']=a['splits'][split]['h'].mean(dim=-2)
229
+ a['splits'][split]['y']=a['splits'][split]['y'].mean(dim=-2)
230
+ for task,cache in [('smoke_1d',a),('smoke_2d',b)]:
231
+ core=UnifiedModel('V2_Ported_R5',1,8,12,4).to(device)
232
+ r,s=one_case(task,core,cache,r6zip,device,smoke=True)
233
+ rows+=r;cases.append(s)
234
+ status='PROGRAM_ONLY_SMOKE_NO_SCIENCE'
235
+ else:
236
+ if parent is None or sha(parent)!=PARENT_SHA:raise RuntimeError('MISSING_OR_CHANGED_EXACT_B0_PARENT')
237
+ with zipfile.ZipFile(parent) as z:
238
+ manifest,contract=check_zip(z);cfg=Config(**manifest['config'])
239
+ for task in TASK_LIST:
240
+ core,cache,replay=make_cache_checked(task,z,cfg,work,device)
241
+ try:
242
+ r,s=one_case(task,core,cache,r6zip,device)
243
+ except Exception as exc:
244
+ write_json(output/'numeric_failure_case.json',
245
+ {'task':task,'exception':repr(exc),'completed':[v['task'] for v in cases],
246
+ 'status':'BLOCKED_NUMERICAL_GRADIENT_NO_TRAINING'})
247
+ raise
248
+ rows+=r;s['parent_replay']=replay;cases.append(s)
249
+ write_csv(output/'gradient_credit_horizons.csv',[{k:v for k,v in x.items() if k!='layer_gradient_health'} for x in rows])
250
+ write_json(output/'progress.json',{'completed':[v['task'] for v in cases]})
251
+ status='FROZEN_GRADIENT_CREDIT_AUDIT_COMPLETED_NO_TRAINING'
252
+ # Evaluate structural evidence, not a skill result and not an automatic permission to train.
253
+ paired=[]
254
+ for task in sorted(set(r['task'] for r in rows)):
255
+ for typ in ('operator','rotated'):
256
+ item={r['horizon']:r for r in rows if r['task']==task and r['controller']==typ}
257
+ cs1=item[1]['cosine_vs_full12'];cs3=item[3]['cosine_vs_full12']
258
+ paired.append({'task':task,'controller':typ,'cos_k1_full12':cs1,'cos_k3_full12':cs3,
259
+ 'improved_cosine_3_vs_1':None if cs1 is None or cs3 is None else cs3-cs1,
260
+ 'full_gradient_norm':item[12]['gradient_l2'],
261
+ 'full_gradient_gpu_mem_mib':item[12]['gpu_peak_allocated_mib'],
262
+ 'k3_gpu_mem_mib':item[3]['gpu_peak_allocated_mib'],
263
+ 'k12_over_k3_norm_ratio':(item[12]['gradient_l2']/max(item[3]['gradient_l2'],1e-300)),
264
+ 'k12_gradient_fp32_norm_overflow':item[12]['gradient_l2_fp32_overflow'],
265
+ 'k12_gradient_max_abs':item[12]['gradient_max_abs']})
266
+ gate={'status':'DIAGNOSTIC_ONLY_NO_TRAINING','candidate_for_future_3step_pilot':False,
267
+ 'reason':'Only gradient geometry on TRAIN windows; cannot authorize training/performance claims from these diagnostics',
268
+ 'comparisons':paired,'no_full_cidm_training':True,'truth_for_actions':False}
269
+ # FP64 norms, per-parameter health and provenance are saved separately from compressed scalar CSV.
270
+ write_json(output/'parameter_gradient_conditioning.json',[
271
+ {'task':r['task'],'controller':r['controller'],'K':r['horizon'],
272
+ 'l2_fp64':r['gradient_l2'],'norm_fp32_overflow':r['gradient_l2_fp32_overflow'],
273
+ 'layers':r['layer_gradient_health']} for r in rows])
274
+ compact=[{k:v for k,v in r.items() if k!='layer_gradient_health'} for r in rows]
275
+ write_csv(output/'gradient_credit_horizons.csv',compact)
276
+ write_csv(output/'gradient_credit_pairs.csv',paired)
277
+ # Numerical interpretation is deliberately stricter than merely finite elements.
278
+ # Thresholds label scale risks, never authorize or tune a deployment policy.
279
+ high=[r for r in paired if r['full_gradient_norm']>1e6 or r['k12_over_k3_norm_ratio']>1e6
280
+ or r['k12_gradient_fp32_norm_overflow']]
281
+ gate['numerical_status']='SEVERE_GRADIENT_AMPLIFICATION_NO_TRAINING' if high else 'NO_EXTREME_NORM_OBSERVED_NO_TRAINING'
282
+ gate['high_gradient_risk_pairs']=[{'task':r['task'],'controller':r['controller'],
283
+ 'norm_k12':r['full_gradient_norm'],'norm_ratio_k12_k3':r['k12_over_k3_norm_ratio']} for r in high]
284
+ gate['prohibits_long_training']=True
285
+ write_json(output/'gradient_qualification_gate.json',gate)
286
+ report={'experiment':'CIDM_v2_B2_R10_P0_GradientCreditHorizon','status':status,
287
+ 'training_steps':0,'data_used_for_qualification':'TRAIN only','rows':len(rows),
288
+ 'cases':cases,'gradient_horizons':list(HORIZONS),'official_full_pde_replay':not smoke,'numerical_fix':'sqrt pre-clamp preserved + FP64 post-backward gradient-norm accounting, no clipping',
289
+ 'r9_evidence_sha256':sha(r9zip),'r6_evidence_sha256':sha(r6zip),
290
+ 'original_parent_sha256':PARENT_SHA,'no_outer_holdout':True,
291
+ 'explanation':'K=12 is an exact autograd reference for the same 12-step loss; it is not a trained model improvement.'}
292
+ write_json(output/'report.json',report)
293
+ print('[B2-R10]',status,'cases',len(cases),'records',len(rows),flush=True)
294
+ 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)
295
+ return report