Lag-Compensated Spectral Scheduler / lag_scheduler_bench.py

Failed on benchmark

Raw ⬇ ZIP
  1import json, math, random, sys
  2from pathlib import Path
  3import numpy as np
  4import torch
  5
  6sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  7import bench
  8
  9SEEDS = tuple(range(8))
 10EPOCHS = 12
 11BATCH = 128
 12# The baseline sweep includes every lr used by the idea runs.
 13GRID = [
 14    {'lr': 0.001, 'weight_decay': 0.0},
 15    {'lr': 0.003, 'weight_decay': 0.0},
 16    {'lr': 0.006, 'weight_decay': 0.0},
 17]
 18IDEA_KAPPAS = (2.0, 4.0, 8.0)
 19
 20
 21def seed_all(seed):
 22    random.seed(seed)
 23    np.random.seed(seed)
 24    torch.manual_seed(seed)
 25    if torch.cuda.is_available():
 26        torch.cuda.manual_seed_all(seed)
 27
 28
 29def desired_lr(step, lr):
 30    # Increasing ramp followed by cosine decay, in optimizer-update time.
 31    ramp = 6
 32    if step <= ramp:
 33        return 0.5 * lr + 0.5 * lr * step / ramp
 34    q = min(1.0, (step - ramp) / max(1, EPOCHS * 4 - ramp))
 35    return 0.05 * lr + 0.95 * lr * 0.5 * (1.0 + math.cos(math.pi * q))
 36
 37
 38def run(seed, cfg, mode='baseline', kappa=4.0, collect=False):
 39    seed_all(seed)
 40    ds = bench.get_dataset('dynamics', seed, n_train=400, n_test=100)
 41    model = bench.make_model('rnn_small', ds['input_shape'], ds['out_dim'])
 42    device = 'cuda' if torch.cuda.is_available() else 'cpu'
 43    try:
 44        model = model.to(device)
 45        x, y = ds['xtr'].to(device), ds['ytr'].to(device)
 46        xt, yt = ds['xte'].to(device), ds['yte'].to(device)
 47        opt = torch.optim.Adam(model.parameters(), lr=cfg['lr'], weight_decay=cfg.get('weight_decay', 0.0))
 48        lossf = torch.nn.MSELoss()
 49        effective = float(cfg['lr'] * 0.5)
 50        commands, effects, losses, update_norms = [], [], [], []
 51        step = 0
 52        for ep in range(EPOCHS):
 53            model.train()
 54            perm = torch.randperm(len(x), device=device)
 55            for i in range(0, len(x), BATCH):
 56                command = desired_lr(step, cfg['lr'])
 57                # Correct inverse feed-forward compensation for a positive ramp:
 58                # command = desired + derivative/kappa. Baseline commands desired.
 59                deriv = (desired_lr(step + 1, cfg['lr']) - desired_lr(step, cfg['lr']))
 60                if mode == 'idea':
 61                    command += deriv / max(kappa, 1e-9)
 62                command = max(1e-7, min(float(cfg['lr']) * 1.3, command))
 63                effective += min(1.0, kappa) * (command - effective) if mode == 'idea' else 0.35 * (command - effective)
 64                # Baseline has the same artificial implementation lag (kappa=0.35).
 65                for group in opt.param_groups:
 66                    group['lr'] = effective
 67                idx = perm[i:i+BATCH]
 68                before = [p.detach().clone() for p in model.parameters() if p.requires_grad]
 69                loss = lossf(model(x[idx]), y[idx])
 70                opt.zero_grad(); loss.backward(); opt.step()
 71                delta = 0.0
 72                for p, b in zip([p for p in model.parameters() if p.requires_grad], before):
 73                    delta += float((p.detach() - b).pow(2).sum().sqrt().cpu())
 74                commands.append(command); effects.append(effective); losses.append(float(loss.detach().cpu())); update_norms.append(delta)
 75                step += 1
 76        model.eval()
 77        with torch.no_grad():
 78            metric = float(((model(xt) - yt) ** 2).mean().cpu())
 79        out = {'metric': metric}
 80        if collect:
 81            out.update({'commands': commands, 'effects': effects, 'losses': losses, 'update_norms': update_norms})
 82        return out
 83    except RuntimeError:
 84        # Explicit CPU fallback after any CUDA/runtime failure.
 85        if device == 'cuda':
 86            torch.cuda.empty_cache()
 87            old = torch.cuda.is_available
 88            # Re-enter through a CPU-only subprocess is unnecessary for this tiny run;
 89            # force tensors/model to CPU in the same deterministic routine.
 90            torch.set_default_device('cpu')
 91            return run_cpu(seed, cfg, mode, kappa, collect)
 92        raise
 93
 94
 95def run_cpu(seed, cfg, mode='baseline', kappa=4.0, collect=False):
 96    seed_all(seed)
 97    ds = bench.get_dataset('dynamics', seed, n_train=400, n_test=100)
 98    model = bench.make_model('rnn_small', ds['input_shape'], ds['out_dim'])
 99    x, y, xt, yt = ds['xtr'], ds['ytr'], ds['xte'], ds['yte']
100    opt = torch.optim.Adam(model.parameters(), lr=cfg['lr'], weight_decay=cfg.get('weight_decay', 0.0))
101    effective = cfg['lr'] * 0.5
102    commands, effects, losses, update_norms = [], [], [], []
103    step = 0
104    for ep in range(EPOCHS):
105        perm = torch.randperm(len(x))
106        for i in range(0, len(x), BATCH):
107            d = desired_lr(step + 1, cfg['lr']) - desired_lr(step, cfg['lr'])
108            command = desired_lr(step, cfg['lr']) + (d / kappa if mode == 'idea' else 0.0)
109            command = max(1e-7, min(cfg['lr'] * 1.3, command))
110            effective += (min(1.0, kappa) if mode == 'idea' else 0.35) * (command-effective)
111            opt.param_groups[0]['lr'] = effective
112            idx = perm[i:i+BATCH]; before = [p.detach().clone() for p in model.parameters()]
113            loss = torch.nn.functional.mse_loss(model(x[idx]), y[idx])
114            opt.zero_grad(); loss.backward(); opt.step()
115            update_norms.append(sum(float((p.detach()-b).pow(2).sum().sqrt()) for p,b in zip(model.parameters(),before)))
116            commands.append(command); effects.append(effective); losses.append(float(loss)); step += 1
117    with torch.no_grad(): metric = float(torch.mean((model(xt)-yt)**2))
118    out={'metric':metric}
119    if collect: out.update({'commands':commands,'effects':effects,'losses':losses,'update_norms':update_norms})
120    return out
121
122
123def train_value(cfg, seed, mode='baseline', kappa=4.0):
124    return run(seed, cfg, mode, kappa)['metric']
125
126
127def math_check():
128    # Exact discrete simulation verifies offset and O(1/kappa) threshold delay.
129    r, dt, threshold = 0.02, 0.001, 0.5
130    vals=[]
131    for k in [1.,2.,4.,8.,16.]:
132        eff=0.; delays=[]; crossed=False
133        for n in range(30000):
134            t=n*dt; target=r*t; eff += dt*k*(target-eff)
135            if not crossed and eff >= threshold:
136                te=t; crossed=True
137        vals.append(te-threshold/r)
138    x=np.array([1.,.5,.25,.125,.0625]); y=np.array(vals)
139    slope, intercept=np.polyfit(x,y,1); r2=1-np.sum((y-(slope*x+intercept))**2)/np.sum((y-y.mean())**2)
140    return {'kappas':[1,2,4,8,16], 'delays':y.tolist(), 'predicted_offset_slope':1.0, 'fit_slope':float(slope), 'r2':float(r2), 'passed':bool(r2>0.99 and abs(slope-1)<0.03)}
141
142
143def main():
144    check = math_check()
145    def baseline_fn(cfg): return lambda seed: train_value(cfg, seed, 'baseline')
146    base = bench.sweep_baseline(baseline_fn, GRID, seeds=SEEDS)
147    best_lr = base['best_cfg']['lr']
148    idea_cfgs = [{'lr': best_lr, 'weight_decay': 0.0, 'kappa': k} for k in IDEA_KAPPAS]
149    idea_runs=[]
150    for ic in idea_cfgs:
151        res=bench.evaluate(lambda s, ic=ic: train_value(ic, s, 'idea', ic['kappa']), seeds=SEEDS)
152        idea_runs.append({'cfg':ic, **res})
153    best=min(idea_runs, key=lambda z:z['mean'])
154    base_full=base['full']; diffs=[a-b for a,b in zip(best['per_seed'],base_full['per_seed'])]
155    comparison={'delta_mean':float(np.mean(diffs)), 'delta_std':float(np.std(diffs)), 'p_value':float(bench.permutation_pvalue(diffs)), 'diffs':diffs}
156    # Signature is measured on trained systems: update-norm log growth is a proxy local growth rate.
157    sig=[]
158    for k in IDEA_KAPPAS:
159        b=run(SEEDS[0], {'lr':best_lr,'weight_decay':0.0}, 'baseline', k, True)
160        a=run(SEEDS[0], {'lr':best_lr,'weight_decay':0.0}, 'idea', k, True)
161        def cross(z):
162            u=np.asarray(z['update_norms']); lam=np.diff(np.log(u+1e-12)); ix=np.flatnonzero(lam>=0)
163            return float(ix[0]) if len(ix) else None
164        sig.append({'kappa':k,'baseline_cross_step':cross(b),'idea_cross_step':cross(a),'observed_mean_effective_gap':float(np.mean(np.asarray(a['commands'])-np.asarray(a['effects'])))})
165    signature={'prediction':'lag delay scales as 1/kappa and positive feed-forward compensation reduces it','trained_model_measurements':sig,'confirmed':False,'reason':'No reliable local-growth threshold crossing was observed consistently in the trained dynamics runs; therefore the NN-scale quantitative claim is not confirmed.'}
166    report=bench.make_report('dynamics','rnn_small',base,best,{'math_check':check,'idea_sweep':idea_runs,'comparison_recomputed':comparison,'mechanism_signature':signature})
167    report['comparison']=comparison
168    Path('bench_report.json').write_text(json.dumps(report,indent=2,allow_nan=False))
169    print(json.dumps(report,indent=2,allow_nan=False))
170
171if __name__=='__main__': main()