Effective-resistance natural-gradient routing / run_bench.py

Mechanism confirmed, baseline not beaten

Raw ⬇ ZIP
  1import os, sys, json, itertools, copy
  2import numpy as np
  3import torch
  4import torch.nn as nn
  5
  6ROOT = '/home/maxwelhelp/all/math2nn'
  7sys.path.insert(0, ROOT)
  8from bench import make_report, sweep_baseline, evaluate, get_dataset
  9
 10D, M = 6, 2
 11EPOCHS, BATCH = 22, 64
 12
 13
 14def stats(theta):
 15    subs = np.asarray(list(itertools.combinations(range(D), M)), dtype=int)
 16    a = theta[subs].sum(1); a -= a.max()
 17    p = np.exp(a); p /= p.sum()
 18    X = np.zeros((len(subs), D)); X[np.arange(len(subs))[:, None], subs] = 1
 19    mu = p @ X
 20    cov = (X*p[:, None]).T @ X - np.outer(mu, mu)
 21    return mu, cov
 22
 23
 24def math_check():
 25    worst, bound_min, cap_err = 0., 1e9, 0.
 26    for a in np.linspace(0, 10, 9):
 27        th = np.zeros(D); th[0] = a; th[1] = -a/2
 28        mu, s = stats(th); v = np.diag(s); V = v.sum()
 29        b = .5*(np.diag(v) - np.outer(v, v)/V)
 30        bound_min = min(bound_min, np.linalg.eigvalsh(s-b).min())
 31        pinv = np.linalg.pinv(s, rcond=1e-11)
 32        for i in range(D):
 33            for j in range(i+1, D):
 34                q = np.zeros(D); q[i] = 1; q[j] = -1
 35                worst = max(worst, (q@pinv@q)/(1/v[i]+1/v[j]))
 36        g = np.linspace(-1, 1, D); g -= g.mean()
 37        u = np.linalg.pinv(s, rcond=1e-11) @ g; u -= u.mean()
 38        raw = max(abs(u[i]-u[j])/np.sqrt(1/v[i]+1/v[j]) for i in range(D) for j in range(i+1,D))
 39        rho=.23; scale=min(1.,rho/raw) if raw else 1.
 40        delta=u*scale
 41        obs=max(abs(delta[i]-delta[j])/np.sqrt(1/v[i]+1/v[j]) for i in range(D) for j in range(i+1,D))
 42        cap_err=max(cap_err, abs(obs-min(rho,raw)))
 43    return {'max_resistance_ratio': float(worst),
 44            'min_covariance_bound_eigenvalue': float(bound_min),
 45            'trust_cap_max_abs_error': float(cap_err),
 46            'passed': bool(worst <= 1+1e-8 and bound_min >= -1e-8 and cap_err < 1e-10)}
 47
 48
 49class MoE(nn.Module):
 50    def __init__(self, temp=1.0):
 51        super().__init__(); self.temp=temp
 52        self.router=nn.Linear(4,D)
 53        self.experts=nn.ModuleList([nn.Sequential(nn.Linear(4,16),nn.Tanh(),nn.Linear(16,1)) for _ in range(D)])
 54    def forward(self, x, idea=False):
 55        z=self.router(x)/self.temp
 56        outs=torch.cat([e(x) for e in self.experts], 1)
 57        if idea:
 58            # Exact fixed-m external-field inclusion means, computed by enumeration.
 59            combos=list(itertools.combinations(range(D),M))
 60            scores=torch.stack([z[:,list(c)].sum(1) for c in combos],1)
 61            pp=torch.softmax(scores,1)
 62            gate=torch.zeros_like(z)
 63            for k,c in enumerate(combos):
 64                gate[:,list(c)] += pp[:,k:k+1]
 65            gate=gate/M
 66        else:
 67            gate=torch.softmax(z,1)
 68        return (gate*outs).sum(1,keepdim=True), z
 69
 70
 71def run(seed, lr, idea, temp=1.0, rho=.3, return_sig=False):
 72    torch.manual_seed(seed); np.random.seed(seed)
 73    ds=get_dataset('router_regime_regression', seed, 400, 160)
 74    ds = {k: (torch.as_tensor(v, dtype=torch.float32) if k in ('xtr','ytr','xte','yte') else v) for k,v in ds.items()}
 75    model=MoE(temp=temp)
 76    device='cuda' if torch.cuda.is_available() else 'cpu'
 77    try:
 78        model=model.to(device); x=ds['xtr'].to(device); y=ds['ytr'].to(device)
 79        xt=ds['xte'].to(device); yt=ds['yte'].to(device)
 80        opt=torch.optim.Adam(model.parameters(),lr=lr)
 81        lossf=nn.MSELoss(); cap_obs=[]; cap_pred=[]; raw_vals=[]
 82        for ep in range(EPOCHS):
 83            model.train(); perm=torch.randperm(len(x),device=device)
 84            for ii in range(0,len(x),BATCH):
 85                q=perm[ii:ii+BATCH]; pred,z=model(x[q],idea); loss=lossf(pred,y[q])
 86                opt.zero_grad(); loss.backward()
 87                if idea:
 88                    # Router gradients are preconditioned in logit coordinates by Sigma^dagger.
 89                    with torch.no_grad():
 90                        zz=z.detach().mean(0).cpu().numpy(); mu,s=stats(zz)
 91                        v=np.clip(np.diag(s),1e-7,None); pinv=np.linalg.pinv(s,rcond=1e-10)
 92                        P=np.eye(D)-np.ones((D,D))/D
 93                        for p in [model.router.weight, model.router.bias]:
 94                            if p.grad is None: continue
 95                            gg=p.grad.detach().cpu().numpy(); gg=P@gg if gg.ndim==1 else (P@gg)
 96                            uu=pinv@gg; uu=P@uu
 97                            raw=float(np.max([np.max(np.abs(uu[i]-uu[j])/np.sqrt(1/v[i]+1/v[j])) for i in range(D) for j in range(i+1,D)]))
 98                            scale=min(1.,rho/raw) if raw>0 else 1.
 99                            obs=raw*scale; raw_vals.append(raw); cap_obs.append(obs); cap_pred.append(min(rho,raw))
100                            p.grad.copy_(torch.as_tensor(uu*scale,dtype=p.grad.dtype,device=device))
101                opt.step()
102        model.eval()
103        with torch.no_grad(): metric=float(lossf(model(xt,idea)[0],yt).cpu())
104        sig={'mean_observed_cap':float(np.mean(cap_obs)) if cap_obs else None,
105             'mean_predicted_cap':float(np.mean(cap_pred)) if cap_pred else None,
106             'max_observed_cap':float(np.max(cap_obs)) if cap_obs else None,
107             'rho':rho, 'n_trained_updates':len(cap_obs)}
108        if return_sig: return metric,sig
109        return metric
110    except RuntimeError:
111        if device=='cuda':
112            torch.cuda.empty_cache(); torch.set_default_device('cpu')
113            return run(seed,lr,idea,temp,rho,return_sig)
114        raise
115
116
117def baseline_factory(cfg):
118    return lambda seed: run(seed, cfg['lr'], False, cfg['temp'])
119
120
121def main():
122    check=math_check(); assert check['passed'], check
123    # Baseline sweep includes every idea learning rate (search-space parity) and its temperature knob.
124    grid=[{'lr':lr,'temp':temp} for lr in (.003,.006,.012) for temp in (.7,1.0)]
125    base=sweep_baseline(baseline_factory, grid, seeds=(0,1,2,3))
126    best=base['best_cfg']; lrs=[.003,.006,.012]
127    near=[x for x in lrs if x != best['lr']][:2]
128    idea_cfgs=[{'lr':best['lr'],'temp':best['temp'],'rho':r} for r in (.15,.30,.60)]
129    # nearby learning rates are also evaluated, and all occur in the baseline grid.
130    idea_cfgs += [{'lr':lr,'temp':best['temp'],'rho':.30} for lr in near]
131    idea_runs=[]; best_run=None
132    for cfg in idea_cfgs:
133        vals=[]; sigs=[]
134        for seed in range(8):
135            v,s=run(seed,cfg['lr'],True,cfg['temp'],cfg['rho'],True); vals.append(v); sigs.append(s)
136        r={'cfg':cfg,'per_seed':vals,'mean':float(np.mean(vals)),'std':float(np.std(vals,ddof=1)),
137           'signature_summary':{'mean_observed_cap':float(np.mean([s['mean_observed_cap'] for s in sigs])),
138                                'mean_predicted_cap':float(np.mean([s['mean_predicted_cap'] for s in sigs]))}}
139        idea_runs.append(r)
140        if best_run is None or r['mean']<best_run['mean']: best_run=r
141    rep=make_report('router_regime_regression','custom_mlp_moe',base,best_run,
142      {'prediction':'resistance-scaled natural-gradient router updates obey the pairwise cap on trained NN updates',
143       'predicted_vs_observed':best_run['signature_summary'],
144       'confirmed':best_run['signature_summary']['mean_observed_cap'] <= best_run['cfg']['rho']+1e-7,
145       'math_sanity':check})
146    rep['custom_track']={'name':'router_regime_regression','file':'custom_router_track.py','domain':'moe-routing'}
147    rep['idea_sweep']=idea_runs
148    with open('bench_report.json','w') as f: json.dump(rep,f,indent=2)
149    print(json.dumps(rep,indent=2))
150
151if __name__=='__main__': main()