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
|