Scrambled Sobol Diffusion Ensembles / sobol_bench.py

Failed on benchmark

Raw ⬇ ZIP
  1import sys, json, math, random
  2from pathlib import Path
  3import numpy as np
  4import torch
  5import torch.nn as nn
  6from scipy.stats import qmc, norm
  7sys.path.insert(0, '/home/maxwelhelp/all/math2nn')
  8from bench import get_dataset, make_model, sweep_baseline, evaluate, make_report
  9
 10SEED=2108
 11TRACK='conditional_multitoken_diffusion'
 12MODEL='mlp_tiny'
 13LRS=[1e-3,3e-3,1e-2]
 14EPOCHS=12
 15BATCH=64
 16
 17def seed_all(s):
 18    random.seed(s); np.random.seed(s); torch.manual_seed(s)
 19    if torch.cuda.is_available(): torch.cuda.manual_seed_all(s)
 20
 21def gaussian_corruption(n,d,seed,kind):
 22    if kind=='iid':
 23        z=np.random.RandomState(seed).standard_normal((n,d)).astype('float32')
 24    else:
 25        # Independent Owen scrambles per epoch/seed; inverse-CDF maps cube to N(0,1).
 26        p=qmc.Sobol(d=d, scramble=True, seed=int(seed)).random(n)
 27        z=norm.ppf(np.clip(p,1e-6,1-1e-6)).astype('float32')
 28    return torch.from_numpy(z)
 29
 30def run_one(seed,lr,kind,return_model=False):
 31    seed_all(seed)
 32    d=get_dataset(TRACK,seed,n_train=400,n_test=200)
 33    # The custom track exposes an 8-token target; adapt the harness' generic
 34    # regression reshape back to the declared multi-token output dimension.
 35    d['ytr']=d['ytr'].reshape(-1,8); d['yte']=d['yte'].reshape(-1,8)
 36    dev='cuda' if torch.cuda.is_available() else 'cpu'
 37    try:
 38        net=make_model(MODEL,d['input_shape'],d['out_dim']).to(dev)
 39        opt=torch.optim.Adam(net.parameters(),lr=lr)
 40        loss=nn.MSELoss()
 41        x=d['xtr'].to(dev); y=d['ytr'].to(dev)
 42        n=len(x)
 43        for ep in range(EPOCHS):
 44            # Same examples and targets; only diffusion perturbation differs.
 45            perm=torch.randperm(n,device=dev)
 46            xep=x[perm]; yep=y[perm]
 47            z=gaussian_corruption(n,8,seed*1000+ep,kind).to(dev)
 48            # Mild additional terminal-noise transport, with observed sigma retained.
 49            xin=xep.clone(); sig=xin[:,9:10].clamp(.04,1.)
 50            xin[:,:8]=xin[:,:8] + .18*sig*z
 51            for j in range(0,n,BATCH):
 52                opt.zero_grad(set_to_none=True)
 53                pred=net(xin[j:j+BATCH]); l=loss(pred,yep[j:j+BATCH])
 54                l.backward(); opt.step()
 55        with torch.no_grad():
 56            pred=net(d['xte'].to(dev)); metric=float(loss(pred,d['yte'].to(dev)).cpu())
 57        if return_model: return metric, net, d
 58        return metric
 59    except RuntimeError:
 60        # Explicit CPU fallback for constrained shared GPU slots.
 61        torch.cuda.empty_cache()
 62        osdev=torch.device('cpu')
 63        seed_all(seed)
 64        d=get_dataset(TRACK,seed,n_train=400,n_test=200)
 65        d['ytr']=d['ytr'].reshape(-1,8); d['yte']=d['yte'].reshape(-1,8)
 66        net=make_model(MODEL,d['input_shape'],d['out_dim']).to(osdev)
 67        opt=torch.optim.Adam(net.parameters(),lr=lr); loss=nn.MSELoss()
 68        x=d['xtr']; y=d['ytr']; n=len(x)
 69        for ep in range(EPOCHS):
 70            perm=torch.randperm(n); xin=x[perm].clone(); yep=y[perm]
 71            z=gaussian_corruption(n,8,seed*1000+ep,kind); xin[:,:8]+=0.18*xin[:,9:10].clamp(.04,1.)*z
 72            for j in range(0,n,BATCH):
 73                opt.zero_grad(); l=loss(net(xin[j:j+BATCH]),yep[j:j+BATCH]); l.backward(); opt.step()
 74        with torch.no_grad(): return float(loss(net(d['xte']),d['yte']))
 75
 76def fn(kind,lr): return lambda s: run_one(s,lr,kind)
 77
 78def mechanism_signature():
 79    # Re-test the stage-1 prediction on outputs of trained systems: batch-mean
 80    # variance should decay faster for scrambled Sobol than IID perturbations.
 81    seed=77; lr=3e-3; metric,net,d=run_one(seed,lr,'sobol',True)
 82    dev=next(net.parameters()).device; x=d['xte'][:128].to(dev)
 83    vals={}
 84    for kind in ('iid','sobol'):
 85        means=[]
 86        for r in range(16):
 87            xx=x.clone(); z=gaussian_corruption(len(x),8,90000+r,kind).to(dev)
 88            xx[:,:8]+=0.18*xx[:,9:10].clamp(.04,1.)*z
 89            with torch.no_grad(): means.append(float(net(xx).mean().cpu()))
 90        vals[kind]=float(np.std(means,ddof=1))
 91    # A second size check uses prefixes of power-of-two Sobol sets and IID sets.
 92    slopes={}
 93    for kind in ('iid','sobol'):
 94        ns=[16,32,64,128]; sds=[]
 95        for n in ns:
 96            mm=[]
 97            for r in range(12):
 98                xx=x[:n].clone(); z=gaussian_corruption(n,8,120000+r,kind).to(dev)
 99                xx[:,:8]+=0.18*xx[:,9:10].clamp(.04,1.)*z
100                with torch.no_grad(): mm.append(float(net(xx).mean().cpu()))
101            sds.append(np.std(mm,ddof=1))
102        slopes[kind]=float(np.polyfit(np.log2(ns),np.log2(np.maximum(sds,1e-12)),1)[0])
103    confirmed=vals['sobol'] < vals['iid'] and slopes['sobol'] < slopes['iid']
104    return {'prediction':'scrambled Sobol batch-mean variance lower and slope closer to -1 than IID',
105            'trained_model_batch_mean_sd':vals,'observed_log2N_slopes':slopes,
106            'variance_ratio_sobol_over_iid':vals['sobol']/vals['iid'], 'confirmed':bool(confirmed)}
107
108def main():
109    grid=[{'lr':v} for v in LRS]
110    base=sweep_baseline(lambda c: fn('iid',c['lr']),grid)
111    # Same union of learning rates; select best idea setting on the same 4-seed sweep.
112    idea_trials=[]
113    for cfg in grid:
114        r=evaluate(fn('sobol',cfg['lr']))
115        idea_trials.append({'cfg':cfg,'mean':r['mean'],'std':r['std']})
116    best=min(idea_trials,key=lambda x:x['mean'])['cfg']
117    idea=evaluate(fn('sobol',best['lr']))
118    rep=make_report(TRACK,MODEL,base,idea,{'idea_sweep':idea_trials,'selected_idea_cfg':best,
119                                           'mechanism_signature':mechanism_signature(),
120                                           'custom_track':{'name':TRACK,'file':'bench/custom_tracks/conditional_multitoken_diffusion.py','domain':'diffusion-sampling'}})
121    Path('bench_report.json').write_text(json.dumps(rep,indent=2))
122    print(json.dumps(rep,indent=2))
123if __name__=='__main__': main()