summaryrefslogtreecommitdiff
path: root/ep_run/probe_rhorelax.py
blob: 20acbe5a736316aaa5062024dc5d64eb152806cb (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
"""Replicate the governor's rho meter offline: trainer-faithful NUDGED relax (derive-d via
top CE grad + down-chain vjp, rebuild via up-chain feedforward + d) at fixed ckpts, K sweeps,
fixed val batch. Reports per-sweep residual ratio rho (the exact quantity beta_cap gates on)
+ the per-block residual profile of the dominant mode (localization), per lineage x step.
This is the operator whose contraction collapse killed crown-3; free-state norm audits
(specaudit, statej) could not see it — the growth may live in curvature/alignment."""
import argparse, pickle
import numpy as np, torch, torch.nn as nn, torch.nn.functional as F
from pathlib import Path

ap = argparse.ArgumentParser()
ap.add_argument('--ckpts', default=('plain:150000,plain:185000,plain:195000,plain:200000,'
                                    'plain:210000,plain:230000,cent:150000,cent:185000,'
                                    'cent:195000,cent:200000,cent:210000,cent:230000'))
ap.add_argument('--K', type=int, default=30)
ap.add_argument('--beta', type=float, default=3e-3)
a = ap.parse_args()
dev = 'cuda'
torch.manual_seed(7)
B, T = 8, 256

class RMSNorm(nn.Module):
    def __init__(self, C, eps=1e-6):
        super().__init__(); self.g = nn.Parameter(torch.ones(C)); self.eps = eps
    def forward(self, x):
        return x * torch.rsqrt(x.pow(2).mean(-1, keepdim=True) + self.eps) * self.g

class SwiGLU(nn.Module):
    def __init__(self, C):
        super().__init__()
        h = ((8 * C // 3) + 63) // 64 * 64
        self.w1 = nn.Linear(C, h, bias=False); self.w3 = nn.Linear(C, h, bias=False)
        self.w2 = nn.Linear(h, C, bias=False)
    def forward(self, x):
        return self.w2(F.silu(self.w1(x)) * self.w3(x))

class Olmo2Attn(nn.Module):
    def __init__(self, C, H, T):
        super().__init__()
        self.H, self.hd = H, C // H
        self.qkv = nn.Linear(C, 3 * C, bias=False); self.proj = nn.Linear(C, C, bias=False)
        self.qn, self.kn = RMSNorm(C), RMSNorm(C)
        inv = 1.0 / (500000.0 ** (torch.arange(0, self.hd, 2).float() / self.hd))
        fr = torch.outer(torch.arange(T).float(), inv)
        self.register_buffer('rc', fr.cos(), persistent=False)
        self.register_buffer('rs', fr.sin(), persistent=False)
    def rope(self, x):
        Tn = x.shape[2]
        x1, x2 = x[..., ::2], x[..., 1::2]
        c, s = self.rc[None, None, :Tn].to(x.dtype), self.rs[None, None, :Tn].to(x.dtype)
        return torch.stack((x1 * c - x2 * s, x1 * s + x2 * c), dim=-1).flatten(-2)
    def forward(self, x):
        Bn, Tn, C = x.shape
        q, k, v = self.qkv(x).split(C, dim=2)
        q, k = self.qn(q), self.kn(k)
        q = self.rope(q.view(Bn, Tn, self.H, self.hd).transpose(1, 2))
        k = self.rope(k.view(Bn, Tn, self.H, self.hd).transpose(1, 2))
        v = v.view(Bn, Tn, self.H, self.hd).transpose(1, 2)
        y = F.scaled_dot_product_attention(q, k, v, is_causal=True)
        return self.proj(y.transpose(1, 2).contiguous().view(Bn, Tn, C))

class Olmo2Block(nn.Module):
    def __init__(self, C, H, T):
        super().__init__()
        self.attn = Olmo2Attn(C, H, T); self.ff = SwiGLU(C)
        self.na, self.nf = RMSNorm(C), RMSNorm(C)
    def forward(self, z):
        z = z + self.na(self.attn(z))
        return z + self.nf(self.ff(z))

first = True
for spec in a.ckpts.split(','):
    lineage, step = spec.split(':'); step = int(step)
    p = f'runs/fw72m_{lineage}_s{step}.pt'
    try:
        ck = torch.load(p, map_location='cpu', weights_only=False)
    except FileNotFoundError:
        print(f'{lineage} s{step}: MISSING', flush=True); continue
    cfg = ck['config']; C, H, L = cfg['C'], cfg['H'], cfg['L']
    if first:
        DD = Path('/home/yurenh2/ept/ep_run/data') / cfg.get('data', 'fineweb_edu')
        vocab = pickle.load(open(DD / 'meta.pkl', 'rb'))['vocab_size']
        data = np.memmap(DD / 'val.bin', dtype=np.uint16, mode='r')
        ix = torch.randint(len(data) - T - 1, (B,))
        x = torch.stack([torch.from_numpy(data[i:i + T].astype(np.int64)) for i in ix]).to(dev)
        y = torch.stack([torch.from_numpy(data[i + 1:i + 1 + T].astype(np.int64)) for i in ix]).to(dev)
        first = False
    tok = nn.Embedding(vocab, C).to(dev); tok.load_state_dict(ck['tok'])
    blocks = nn.ModuleList([Olmo2Block(C, H, T) for _ in range(L)]).to(dev)
    blocks.load_state_dict(ck['blocks'], strict=False)
    W_out = ck['wout'].to(dev)
    ln_f = RMSNorm(C).to(dev); ln_f.load_state_dict(ck['lnf'])
    NBT = B * T

    def readout(z): return ln_f(z) @ W_out.t()

    # free feedforward pass (block-wise graphs, EP-style detach between blocks)
    f_ins, f_outs = [], []
    prev = tok(x).detach()
    for b in blocks:
        i = prev.detach().requires_grad_(True)
        o = b(i)
        f_ins.append(i); f_outs.append(o)
        prev = o.detach()
    ins, outs = f_ins, f_outs
    zs = [o.detach().float() for o in f_outs]
    d = [None] * L
    rhos, res_list = [], []
    prof = None
    for k in range(a.K):
        zc = zs[L - 1].detach().requires_grad_(True)
        ce_k = F.cross_entropy(readout(zc).reshape(-1, vocab), y.reshape(-1))
        d[L - 1] = (-a.beta * NBT * torch.autograd.grad(ce_k, zc)[0]).detach().float()
        for l in range(L - 2, -1, -1):
            d[l] = torch.autograd.grad(outs[l + 1], ins[l + 1], grad_outputs=d[l + 1],
                                       retain_graph=True)[0].detach().float()
        prev = tok(x).detach()
        n_ins, n_outs = [], []
        rnum, rden, pblk = 0.0, 0.0, []
        for l in range(L):
            i = prev.detach().requires_grad_(True)
            o = blocks[l](i)
            n_ins.append(i); n_outs.append(o)
            znew = o.detach().float() + d[l]
            dn = float((znew - zs[l]).norm())
            rnum += dn; rden += float(zs[l].norm()); pblk.append(dn)
            zs[l] = znew
            prev = zs[l]
        ins, outs = n_ins, n_outs
        res = rnum / max(rden, 1e-9)
        if res_list: rhos.append(res / max(res_list[-1], 1e-12))
        res_list.append(res)
        prof = pblk
        if not np.isfinite(res) or res > 1e3:
            print(f'{lineage} s{step//1000}k: DIVERGED at sweep {k} (res {res:.2e})', flush=True)
            break
    tail = rhos[-5:] if len(rhos) >= 5 else rhos
    pn = np.array(prof) / (np.sum(prof) + 1e-30)
    print(f'{lineage} s{step//1000}k | res0 {res_list[0]:.4f} resK {res_list[-1]:.2e} | '
          f'rho tail-med {np.median(tail):.4f} max {max(rhos):.4f} | mode blk-profile ' +
          ' '.join(f'{v:.2f}' for v in pn), flush=True)
    del tok, blocks, W_out, ln_f, ins, outs, f_ins, f_outs, zs, d
    torch.cuda.empty_cache()
print('RHORELAX_DONE', flush=True)