wuff-mann commited on
Commit
dc92d87
·
verified ·
1 Parent(s): a512d80

Archive CIDM-v2 reproducible cidm_mhc_a0_qualification.py

Browse files
cidm_v2/source/MHC_A0_FIX1/cidm_mhc_a0_qualification.py ADDED
@@ -0,0 +1,433 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """CIDM-MHC A0: matched-budget, truncated-gradient, Markov hierarchical closure pilot.
2
+
3
+ IMPORTANT: Q0 numerical + immediate causal-gradient prequalification, Q1 small pilot only.
4
+ No architecture-wide finetuning, no external-holdout claim, no full-training permission.
5
+ Historical parent model is frozen and exact B0-R1 official replay must PASS.
6
+ """
7
+ from __future__ import annotations
8
+ import csv, hashlib, json, math, time, copy, traceback, zipfile
9
+ from pathlib import Path
10
+ import numpy as np
11
+ import torch
12
+ from torch import nn
13
+ import torch.nn.functional as F
14
+ from benchmark import Config, Windows, UnifiedModel, make_synthetic_cache
15
+ from b2_r6_causal_spatial_controller import (PARENT_SHA, TASK_LIST, make_cache_checked)
16
+ from b2_r1_flux_control import check_zip
17
+
18
+ LEADS=(1,3,6,9,12)
19
+ ARMS=('stateless_closure','mhc_hierarchical','mhc_no_aux')
20
+ BUDGET=0.003
21
+ SLOW_RATE=0.25 # Persistent coarse state responds at its own timescale; not P(z_t).
22
+ AUX_WEIGHT=0.10
23
+ SEED=20311015
24
+
25
+
26
+ def sha(path):
27
+ h=hashlib.sha256()
28
+ with open(path,'rb') as f:
29
+ for chunk in iter(lambda:f.read(2**20),b''):h.update(chunk)
30
+ return h.hexdigest()
31
+
32
+
33
+ def dump(path,obj):
34
+ path=Path(path);path.parent.mkdir(parents=True,exist_ok=True)
35
+ path.write_text(json.dumps(obj,indent=2,ensure_ascii=False,allow_nan=False,default=str),encoding='utf8')
36
+
37
+
38
+ def write_rows(path,rows):
39
+ if not rows:return
40
+ path=Path(path);path.parent.mkdir(parents=True,exist_ok=True)
41
+ with open(path,'w',encoding='utf-8',newline='') as f:
42
+ w=csv.DictWriter(f,fieldnames=list(dict.fromkeys(k for row in rows for k in row)));w.writeheader();w.writerows(rows)
43
+
44
+
45
+ def lowpass(x):
46
+ if x.ndim !=4:raise RuntimeError('EXPECTED_BCHW')
47
+ kh=1 if x.shape[-2]==1 else 3
48
+ return F.avg_pool2d(x,kernel_size=(kh,3),stride=1,padding=(kh//2,1),count_include_pad=False)
49
+
50
+
51
+ def lowpass_field(x):
52
+ return lowpass(x.unsqueeze(-2)).squeeze(-2) if x.ndim==3 else lowpass(x)
53
+
54
+ def rms(x):
55
+ return torch.sqrt(torch.clamp(x.square().mean(dim=(1,2,3),keepdim=True),min=1e-24))
56
+
57
+
58
+ class MarkovClosureHead(nn.Module):
59
+ """One small shared-architecture controller. No lead ID, no history or future truth."""
60
+ def __init__(self,channels,latent_dim,width=16,projection_dim=8,seed=SEED):
61
+ super().__init__()
62
+ if latent_dim>256:raise RuntimeError('LATENT_CHANNELS_GT_256')
63
+ generator=torch.Generator().manual_seed(seed)
64
+ self.register_buffer('proj',torch.randn(projection_dim,256,generator=generator)/math.sqrt(projection_dim))
65
+ inputs=2*projection_dim+2*channels+2
66
+ self.net=nn.Sequential(nn.Conv2d(inputs,width,3,padding=1),nn.SiLU(),
67
+ nn.Conv2d(width,width,3,padding=1),nn.SiLU(),nn.Conv2d(width,2,1))
68
+ nn.init.zeros_(self.net[-1].weight)
69
+ nn.init.zeros_(self.net[-1].bias)
70
+ self.channels=channels
71
+ self.latent_dim=latent_dim
72
+ def forward(self,slow,fast,prev):
73
+ pp=self.proj[:,:self.latent_dim]
74
+ aa=torch.einsum('kd,bdhw->bkhw',pp,slow.detach())
75
+ bb=torch.einsum('kd,bdhw->bkhw',pp,fast.detach())
76
+ cs=prev.detach()
77
+ x=torch.cat([aa,bb,cs,lowpass(cs),
78
+ torch.log1p(rms(slow.detach())).expand(-1,-1,*cs.shape[-2:]),
79
+ torch.log1p(rms(fast.detach())).expand(-1,-1,*cs.shape[-2:])],dim=1)
80
+ return torch.tanh(self.net(x))
81
+
82
+
83
+ def run_rollout(core,head,h,arm='stateless_closure',steps=12,truncate=True):
84
+ """12-step original core; optional learned correction on persistent slow/fast Markov states.
85
+
86
+ No ground truth or time/round label enters dynamics; only current (slow,fast,p).
87
+ Each update is bounded by <=.3% of latent RMS (actual action can be smaller).
88
+ """
89
+ if arm not in ('baseline',)+ARMS:raise ValueError(arm)
90
+ mm=core.model
91
+ original_1d=(h.ndim==4)
92
+ hh=h.unsqueeze(-2) if original_1d else h
93
+ batch,history,ch,height,width=hh.shape
94
+ coef=mm.coefficients();skew=mm.skew-mm.skew.T
95
+ with torch.no_grad():
96
+ z=mm.encode(hh.reshape(batch,history*ch,height,width))
97
+ prev=hh[:,-1]
98
+ slow=lowpass(z)
99
+ fast=z-slow
100
+ outs=[];effects=[];scales=[]
101
+ for step in range(steps):
102
+ # BPTT(1) only; never differentiate original 12-step dynamical core across time.
103
+ if truncate:
104
+ slow=slow.detach();fast=fast.detach();prev=prev.detach()
105
+ with torch.no_grad():
106
+ # Canonical, original recurrence state is z, NEVER reconstruct from slow+fast.
107
+ # Even ulp-sized changes due to split/recomposition can be amplified for SWE.
108
+ flux=coef['flux']*mm.flux(z)
109
+ local=coef['local']*torch.tanh(mm.local(z))
110
+ skewpart=.035*torch.einsum('ij,bjhw->bihw',skew,z)
111
+ tendency=flux+local+skewpart
112
+ # Match R9's actual FP32 operation order exactly: ((z+flux)+local)+skewpart.
113
+ # Do not algebraically regroup this into z+(flux+local+skewpart).
114
+ base=z+flux+local+skewpart
115
+ if arm=='mhc_hierarchical' or arm=='mhc_no_aux':
116
+ slow_increment=SLOW_RATE*lowpass(tendency)
117
+ slow_base=slow+slow_increment
118
+ fast_base=base-slow_base
119
+ else:
120
+ slow_base=lowpass(base)
121
+ fast_base=base-slow_base
122
+ assert base.shape==slow_base.shape
123
+ # Algebraic zero-control parity: slow_base+fast_base == original base.
124
+ if head is None or arm=='baseline':
125
+ corr=torch.zeros_like(base)
126
+ else:
127
+ gate=head(slow_base,fast_base,prev)
128
+ us=lowpass(tendency)
129
+ uf=tendency-us
130
+ # This is a spatially structured tendency-direction control, not a truth oracle.
131
+ norm_s=us/(rms(us).detach().clamp_min(1e-6))
132
+ norm_f=uf/(rms(uf).detach().clamp_min(1e-6))
133
+ raw=(lowpass(gate[:,0:1]*norm_s)+(gate[:,1:2]*norm_f-lowpass(gate[:,1:2]*norm_f)))/math.sqrt(2)
134
+ # No sqrt(0) backward singularity. Smooth denominator for near-zero action.
135
+ target=BUDGET*rms(base).detach().clamp_min(1e-4)
136
+ corr=target*raw/(rms(raw).detach()+0.05)
137
+ corr=corr*torch.clamp(target/(rms(corr).detach()+1e-12),max=1.0)
138
+ # The maximum RMS correction contract is strict and batchwise.
139
+ exposure=(rms(corr)/rms(base).detach().clamp_min(1e-4)).detach()
140
+ if bool((exposure>BUDGET+1e-6).any()):raise RuntimeError('LATENT_RMS_EXPOSURE_EXCEEDED')
141
+ zn=base+corr
142
+ if arm in ('mhc_hierarchical','mhc_no_aux'):
143
+ cs=SLOW_RATE*lowpass(corr)
144
+ slow=slow_base+cs
145
+ fast=zn-slow
146
+ else:
147
+ slow=lowpass(zn)
148
+ fast=zn-slow
149
+ z=zn # authoritative recurrent state; slow/fast are persistent controller representations
150
+ # Frozen decoder weights, but input gradient is essential for head learning.
151
+ pnew=prev+coef['out']*torch.tanh(mm.decode(zn))
152
+ outs.append(pnew.squeeze(-2) if original_1d else pnew)
153
+ effects.append(rms(corr).detach().flatten()/rms(base).detach().flatten().clamp_min(1e-4))
154
+ scales.append((rms(slow).detach(),rms(fast).detach()))
155
+ prev=pnew
156
+ return torch.stack(outs,dim=1),torch.stack(effects,dim=1),scales
157
+
158
+
159
+ def physical_scale(window,field):
160
+ return window.sd.to(field.device).reshape(1,1,-1,*([1]*(field.ndim-3)))
161
+
162
+
163
+ def criterion(pred,truth,window,arm):
164
+ # In physical units, with same fixed objective for stateless/MHC/full vs no_aux.
165
+ scale=physical_scale(window,pred)
166
+ diff=(pred-truth)*scale
167
+ losses=[]
168
+ for lead in LEADS:
169
+ losses.append(diff[:,lead-1].square().mean())
170
+ loss=sum(losses)/len(losses)
171
+ if arm!='mhc_no_aux' and arm!='baseline':
172
+ # Fixed, physically interpretable hierarchical target representations.
173
+ # Stop-gradient on truth (not learnable encoder) and horizons fixed before testing.
174
+ target=truth.detach()*scale
175
+ physical=pred*scale
176
+ slo=[3,6,9,12];shi=[1,3]
177
+ ll=sum((lowpass_field(physical[:,l-1])-lowpass_field(target[:,l-1])).square().mean() for l in slo)/len(slo)
178
+ lh=sum(((physical[:,l-1]-lowpass_field(physical[:,l-1]))-
179
+ (target[:,l-1]-lowpass_field(target[:,l-1]))).square().mean() for l in shi)/len(shi)
180
+ loss=loss+AUX_WEIGHT*(ll+lh)*0.5
181
+ return loss
182
+
183
+
184
+ def get_data(cache,split):
185
+ return Windows(cache['splits'][split]['h'],cache['splits'][split]['y'],cache['mean'],cache['std'])
186
+
187
+
188
+ def get_batch(ds,ids,device):
189
+ samples=[ds[int(i)] for i in ids]
190
+ return torch.stack([v[0] for v in samples]).to(device),torch.stack([v[1] for v in samples]).to(device)
191
+
192
+
193
+ @torch.no_grad()
194
+ def evaluate(core,heads,cache,split,ids,device,task):
195
+ ds=get_data(cache,split);records=[]
196
+ for ix in ids:
197
+ x,y=get_batch(ds,[ix],device)
198
+ for arm in ('baseline',)+ARMS:
199
+ p,a,_=run_rollout(core,None if arm=='baseline' else heads[arm],x,arm=arm,truncate=True)
200
+ scale=physical_scale(ds,p)
201
+ for lead in LEADS:
202
+ err=((p[:,lead-1]-y[:,lead-1])*scale[:,0]).square().flatten(1).mean(1)
203
+ records.append({'task':task,'split':split,'window':int(ix),'arm':arm,'lead':lead,
204
+ 'mse_physical':float(err.mean()),'rel_action_mean':float(a[:,lead-1].mean())})
205
+ return records
206
+
207
+
208
+ def aggregate(rows,arm,leads=(9,12)):
209
+ # pooled identical number of observations on each lead and window.
210
+ vals=[r['mse_physical'] for r in rows if r['arm']==arm and r['lead'] in leads]
211
+ return sum(vals)/max(1,len(vals))
212
+
213
+
214
+ def make_arms(ch,latent_dim,device):
215
+ torch.manual_seed(SEED)
216
+ first=MarkovClosureHead(ch,latent_dim).to(device)
217
+ heads={a:copy.deepcopy(first).to(device) for a in ARMS}
218
+ sizes={a:sum(p.numel() for p in heads[a].parameters() if p.requires_grad) for a in ARMS}
219
+ assert len(set(sizes.values()))==1
220
+ return heads,sizes
221
+
222
+
223
+ def q0(core,heads,cache,task,device,output,smoke=False):
224
+ train=get_data(cache,'train');val=get_data(cache,'val')
225
+ x,y=get_batch(train,[0],device)
226
+ with torch.no_grad():
227
+ ref=run_rollout(core,None,x,arm='baseline')[0]
228
+ # Confirm original no-correction path exactly matches original causal R9 recurrence.
229
+ from b2_r9_activation_timing import rollout_timing
230
+ old=rollout_timing(core,None,x)[0]
231
+ baseline_parity=float((ref-old).abs().max())
232
+ assert baseline_parity<=2e-6,('BASELINE_R9_PARITY_FAIL',baseline_parity)
233
+ # zero initialized controller must preserve baseline both arms exactly.
234
+ zpar={arm:float((run_rollout(core,head,x,arm=arm)[0]-ref).abs().max()) for arm,head in heads.items()}
235
+ if max(zpar.values())>2e-6:raise RuntimeError('ZERO_GATE_BASELINE_PARITY_FAIL')
236
+ qrecord={'task':task,'baseline_parity_max':baseline_parity,'zero_gate_parity':zpar,'arms':{},'passed':False}
237
+ flags=[]
238
+ for arm,head in heads.items():
239
+ head.train()
240
+ # Q0 originally tested just one AdamW step. We retain that recorded check
241
+ # and add a separate, bounded TRAIN-only negative-gradient feasibility probe.
242
+ original={name:param.detach().clone() for name,param in head.named_parameters()}
243
+ before,act,_=run_rollout(core,head,x,arm=arm)
244
+ l0=criterion(before,y,train,arm)
245
+ head.zero_grad(set_to_none=True);l0.backward()
246
+ grad_by_name={name:param.grad.detach().clone() for name,param in head.named_parameters() if param.grad is not None}
247
+ grads=list(grad_by_name.values())
248
+ gn=math.sqrt(sum(float(g.detach().double().square().sum()) for g in grads))
249
+ finite=bool(math.isfinite(gn) and gn>1e-13 and all(bool(torch.isfinite(g).all()) for g in grads))
250
+ l0=float(l0.detach())
251
+ trust_tol=max(1e-11,abs(l0)*1e-5)
252
+ trials=[];chosen=None;down=False;mag=0.;l1=l0
253
+ if finite:
254
+ # Do not use optimizer state from a different arm; first reproduce old AdamW one-step trial.
255
+ opt=torch.optim.AdamW(head.parameters(),lr=3e-4,weight_decay=0.)
256
+ for name,p in head.named_parameters():
257
+ p.grad=grad_by_name.get(name,None)
258
+ opt.step()
259
+ with torch.no_grad():
260
+ pnew,newact,_=run_rollout(core,head,x,arm=arm)
261
+ adam_loss=float(criterion(pnew,y,train,arm));adam_mag=float(newact.mean())
262
+ trials.append({'method':'original_adamw','step_size':3e-4,'loss_after':adam_loss,'improved':bool(adam_loss<l0-trust_tol)})
263
+ if math.isfinite(adam_loss) and adam_loss<l0-trust_tol and 0.<adam_mag<BUDGET+1e-6:
264
+ chosen='original_adamw';down=True;l1=adam_loss;mag=adam_mag
265
+ else:
266
+ # Undo failed optimizer update before testing a deterministic, normalized
267
+ # negative gradient direction. This is a Q0 feasibility witness on TRAIN only,
268
+ # not validation tuning or permission to alter later Q1 optimizer contract.
269
+ for radius in (3e-4,1e-4,3e-5,1e-5,3e-6,1e-6):
270
+ with torch.no_grad():
271
+ for name,p in head.named_parameters():
272
+ p.copy_(original[name])
273
+ if name in grad_by_name:p.add_(grad_by_name[name],alpha=-radius/max(gn,1e-20))
274
+ pnew,newact,_=run_rollout(core,head,x,arm=arm)
275
+ candidate_loss=float(criterion(pnew,y,train,arm));candidate_mag=float(newact.mean())
276
+ accepted=bool(math.isfinite(candidate_loss) and candidate_loss<l0-trust_tol and 0.<candidate_mag<BUDGET+1e-6)
277
+ trials.append({'method':'normalized_sgd_direction','step_size':radius,'loss_after':candidate_loss,'improved':accepted})
278
+ if accepted:
279
+ chosen=f'normalized_sgd_{radius:.0e}';down=True;l1=candidate_loss;mag=candidate_mag;break
280
+ if not down:
281
+ with torch.no_grad():
282
+ for name,p in head.named_parameters():p.copy_(original[name])
283
+ l1=l0;mag=0.
284
+ else:
285
+ for name,p in head.named_parameters():p.data.copy_(original[name])
286
+ qrecord['arms'][arm]={'loss_before':l0,'loss_after_one_step':l1,
287
+ 'relative_train_objective_change':(l1/l0-1) if l0>0 else None,
288
+ 'gradient_l2':gn,'finite_and_nonzero_gradient':finite,
289
+ 'immediate_descent':down,'q0_descent_witness':chosen,
290
+ 'q0_backtracking_trials':trials,'strict_descent_tolerance':trust_tol,
291
+ 'actual_action_mean':mag,
292
+ 'parameter_count':sum(p.numel() for p in head.parameters() if p.requires_grad)}
293
+ flags.append(finite and down and 0.<mag<BUDGET+1e-6)
294
+ qrecord['passed']=all(flags)
295
+ dump(output/f'{task}_q0_gate.json',qrecord)
296
+ return qrecord
297
+
298
+
299
+ def train_small(core,heads,cache,task,output,device,updates=24,smoke=False):
300
+ """PREQUALIFIED mini-pilot only. Identical updates/cost, NO full architecture optimization."""
301
+ train=get_data(cache,'train');val=get_data(cache,'val')
302
+ opt={a:torch.optim.AdamW(heads[a].parameters(),lr=3e-4,weight_decay=0.) for a in ARMS}
303
+ progress=[];schedule=[0,8,16,24] if not smoke else [0,1,2,3]
304
+ # Selection evaluation is reporting only: all arms use last step, never choose using audit.
305
+ def snap(step):
306
+ with torch.no_grad():
307
+ rows=evaluate(core,heads,cache,'val',list(range(min(4,len(val)))),device,task)
308
+ for arm in ('baseline',)+ARMS:
309
+ progress.append({'task':task,'step':step,'arm':arm,'selection_late_mse':aggregate(rows,arm)})
310
+ write_rows(output/f'{task}_progress.csv',progress)
311
+ snap(0)
312
+ for step in range(1,updates+1):
313
+ for arm,head in heads.items():
314
+ head.train()
315
+ ix=((step-1)*13+SEED)%len(train)
316
+ h,y=get_batch(train,[ix],device)
317
+ p,e,_=run_rollout(core,head,h,arm=arm,truncate=True)
318
+ loss=criterion(p,y,train,arm)
319
+ if not bool(torch.isfinite(loss)):raise RuntimeError(f'NONFINITE_Q1_LOSS {task} {arm} {step}')
320
+ opt[arm].zero_grad(set_to_none=True);loss.backward()
321
+ norm=torch.sqrt(sum(p.grad.detach().double().square().sum() for p in head.parameters() if p.grad is not None))
322
+ # No gradient clipping: nonfinite or extreme gradients FAIL CLOSED.
323
+ if not bool(torch.isfinite(norm)) or float(norm)>1e5:raise RuntimeError(f'UNSAFE_Q1_GRADIENT {task} {arm} {step}: {float(norm)}')
324
+ opt[arm].step()
325
+ if step%4==0 or step==1:
326
+ progress.append({'task':task,'step':step,'arm':arm,'train_objective':float(loss.detach()),
327
+ 'train_grad_l2_unclipped':float(norm.detach()),'actual_action_mean':float(e.mean())})
328
+ write_rows(output/f'{task}_progress.csv',progress)
329
+ if step in schedule:snap(step)
330
+ if device.startswith('cuda') and step%4==0:torch.cuda.empty_cache()
331
+ for arm,head in heads.items():
332
+ torch.save({'arm':arm,'task':task,'steps':updates,'state_dict':{k:v.detach().cpu() for k,v in head.state_dict().items()}},output/f'{task}_{arm}_mini_pilot.pt')
333
+ audit_ix=list(range(16,min(32,len(val))))
334
+ if smoke:audit_ix=list(range(2,min(4,len(val))))
335
+ if len(audit_ix)<2:raise RuntimeError('INSUFFICIENT_DEFERRED_VALIDATION_WINDOWS')
336
+ audit=evaluate(core,heads,cache,'val',audit_ix,device,task)
337
+ write_rows(output/f'{task}_audit_per_window.csv',audit)
338
+ return audit,progress
339
+
340
+
341
+ def gate(audits,smoke=False):
342
+ result={'status':'DIAGNOSTIC_ONLY_NO_FULL_TRAINING','train_allowed':False,'tasks':{},
343
+ 'early_regression_cap':.005,'late_gain_target':.01,'max_severe_window_fraction':.20,
344
+ 'warnings':['selection and audit both derive from original B0 validation split; NOT unseen outer holdout',
345
+ 'seed17 only and 24 update small pilot','full long-BPTT remains unqualified','state sufficiency is operational/model-defined, not physical state sufficiency']}
346
+ for task,rows in audits.items():
347
+ baseline=aggregate(rows,'baseline');early=aggregate(rows,'baseline',(1,3))
348
+ n={}
349
+ for arm in ARMS:
350
+ late=aggregate(rows,arm)
351
+ er=aggregate(rows,arm,(1,3))
352
+ # A severe regression is >2% on at least one late lead for the same window.
353
+ sub={(r['window'],r['lead']):r['mse_physical'] for r in rows if r['arm']==arm}
354
+ ref={(r['window'],r['lead']):r['mse_physical'] for r in rows if r['arm']=='baseline'}
355
+ ids=sorted({r['window'] for r in rows})
356
+ severe=sum(any(sub[(i,l)]>1.02*ref[(i,l)] for l in (9,12)) for i in ids)
357
+ n[arm]={'late_vs_baseline_ratio':late/baseline,
358
+ 'early_vs_baseline_ratio':er/early,
359
+ 'late_severe_window_fraction':severe/len(ids),
360
+ 'pass_diagnostic':bool(late<=baseline*.99 and er<=early*1.005 and severe/len(ids)<=.20)}
361
+ n['mhc_vs_stateless_late_ratio']=aggregate(rows,'mhc_hierarchical')/aggregate(rows,'stateless_closure')
362
+ n['mhc_vs_no_aux_late_ratio']=aggregate(rows,'mhc_hierarchical')/aggregate(rows,'mhc_no_aux')
363
+ result['tasks'][task]=n
364
+ success=sum(r['mhc_hierarchical']['pass_diagnostic'] and r['mhc_vs_stateless_late_ratio']<=.99 for r in result['tasks'].values())
365
+ result['diagnostic_hits']=success
366
+ result['status']=('MHC_FIX1_SMALL_PILOT_SIGNAL_NEEDS_MULTI_SEED_AND_OUTER_DATA' if success>=2 else
367
+ 'MHC_FIX1_SMALL_PILOT_GATE_FAIL_NO_FORMAL_TRAINING')
368
+ if smoke:result['status']='PROGRAM_ONLY_SMOKE_NO_SCIENCE'
369
+ return result
370
+
371
+
372
+ def smoke_case():
373
+ cfg=Config(profile='smoke')
374
+ out=[]
375
+ for tag in ('smoke_1d','smoke_2d'):
376
+ cache=make_synthetic_cache(cfg)
377
+ for split in ('train','val','test'):
378
+ if tag=='smoke_1d':
379
+ cache['splits'][split]['h']=cache['splits'][split]['h'].mean(dim=-2)[...,:4]
380
+ cache['splits'][split]['y']=cache['splits'][split]['y'].mean(dim=-2)[...,:4]
381
+ else:
382
+ cache['splits'][split]['h']=cache['splits'][split]['h'][...,:2,:4]
383
+ cache['splits'][split]['y']=cache['splits'][split]['y'][...,:2,:4]
384
+ core=UnifiedModel('V2_Ported_R5',1,8,12,4).cpu().eval().requires_grad_(False)
385
+ out.append((tag,core,cache))
386
+ return out
387
+
388
+
389
+ def run(parent,work,output,smoke=False,device=None):
390
+ work=Path(work);output=Path(output);work.mkdir(parents=True,exist_ok=True);output.mkdir(parents=True,exist_ok=True)
391
+ torch.set_num_threads(min(4,torch.get_num_threads()))
392
+ device='cpu' if smoke else (device or ('cuda' if torch.cuda.is_available() else 'cpu'))
393
+ torch.manual_seed(SEED);np.random.seed(SEED)
394
+ cases=[];progress=[];audits={};groups=[]
395
+ try:
396
+ if smoke:cases=smoke_case()
397
+ else:
398
+ if parent is None or sha(parent)!=PARENT_SHA:raise RuntimeError('MISSING_OR_WRONG_PARENT')
399
+ with zipfile.ZipFile(parent) as z:
400
+ manifest,_=check_zip(z);cfg=Config(**manifest['config'])
401
+ for task in TASK_LIST:
402
+ core,cache,replay=make_cache_checked(task,z,cfg,work/'official_runtime','cpu')
403
+ dump(output/f'{task}_parent_replay.json',replay)
404
+ cases.append((task,core,cache))
405
+ for task,core,cache in cases:
406
+ core=core.to(device).eval().requires_grad_(False)
407
+ ch=int(cache['splits']['train']['h'].shape[2]);latent_dim=int(core.model.skew.shape[0])
408
+ heads,sizes=make_arms(ch,latent_dim,device)
409
+ initial_head_states={arm:copy.deepcopy(head.state_dict()) for arm,head in heads.items()}
410
+ precert=q0(core,heads,cache,task,device,output,smoke=smoke)
411
+ groups.append({'task':task,'q0':precert['passed'],'head_params':sizes,'device':device})
412
+ dump(output/'progress_summary.json',groups)
413
+ if not precert['passed']:
414
+ # All three tasks are separately Q0-gated. Never silently claim a trained result.
415
+ continue
416
+ for arm in ARMS: heads[arm].load_state_dict(initial_head_states[arm],strict=True)
417
+ audit,_=train_small(core,heads,cache,task,output,device,updates=3 if smoke else 24,smoke=smoke)
418
+ audits[task]=audit
419
+ dump(output/'progress_summary.json',groups)
420
+ g=gate(audits,smoke=smoke)
421
+ if not smoke and len(audits)!=3:
422
+ g['status']='MHC_FIX1_Q0_OR_RESOURCE_GATE_FAIL_NO_FORMAL_TRAINING'
423
+ dump(output/'mhc_gate.json',g)
424
+ report={'status':g['status'],'mode':'smoke' if smoke else 'official_small_pilot',
425
+ 'task_groups':groups,'completed_pilot_tasks':list(audits),
426
+ 'updates_per_head':3 if smoke else 24,'full_model_updates':0,'q0_witness_reset_before_q1':True,'train_allowed':False,
427
+ 'data_contract':'original B0 official parent and original split; no test-set pilot tuning; authoritative canonical z and TRAIN-only Q0 line-search'}
428
+ dump(output/'report.json',report)
429
+ return report
430
+ except Exception as exc:
431
+ dump(output/'failure_details.json',{'type':type(exc).__name__,'exception':str(exc),
432
+ 'traceback':traceback.format_exc()[-16000:],'progress':groups,'train_allowed':False})
433
+ raise