Shapequant / shapequant /sigma.py
Angshul's picture
Upload 17 files
c675b8d verified
Raw History Blame Contribute Delete
5.2 kB
"""Model-implied input second moments, built from the weights only and streamed layer by layer.
Residual stream: Sig_0 = E_{t~pi}[e_t e_t^T] (+ positional embeddings), pi = Zipf prior on token id
(BPE ids are assigned in merge order, i.e. by frequency); then Sig += W^T W for every writer
(attention output and MLP output) in order.
Norm outputs: LayerNorm: C = P Sig P / (tr/n), cov = g C g, mean = beta, M2 = cov + beta beta^T.
RMSNorm: C = Sig / (tr/n), M2 = g C g, mean = g m / s, cov = M2 - mean mean^T.
MLP 2nd input: Monte Carlo of the block's own nonlinearity on x ~ N(mean, cov).
Attention output: E[o o^T] = mu_o mu_o^T + kappa * blockdiag_heads(C_v of the head's KV group).
Every S is normalised to trace n. Design H = I + lam * S.
"""
import math
import torch
F64 = torch.float64
class ResidualModel:
def __init__(self, arch, reader, device, ctx=1024, zipf_shift=10.0, chunk=8192):
self.a, self.r, self.dev = arch, reader, device
E = reader.get(arch.embed, device=device, dtype=torch.float32)
V = E.shape[0]
pi = 1.0 / (torch.arange(V, device=device, dtype=F64) + zipf_shift); pi /= pi.sum()
Sig = torch.zeros((arch.d, arch.d), dtype=F64, device=device); m = torch.zeros(arch.d, dtype=F64, device=device)
for s in range(0, V, chunk):
e = E[s:s + chunk].to(F64); w = pi[s:s + chunk]
Sig += (e * w[:, None]).T @ e; m += w @ e
del E
if arch.pos_embed is not None and reader.has(arch.pos_embed):
P = reader.get(arch.pos_embed, device=device, dtype=F64)
T = min(ctx, P.shape[0] - arch.pos_offset)
P = P[arch.pos_offset: arch.pos_offset + T]
pm = P.mean(0)
Sig += P.T @ P / T + torch.outer(m, pm) + torch.outer(pm, m); m = m + pm
self.Sig, self.m = Sig, m
def add_writer(self, W_out_in):
"""W in torch layout (out=d, in); the writer adds W h with isotropic h."""
W = W_out_in.to(device=self.dev, dtype=F64)
self.Sig += W @ W.T
def norm_moments(self, L, which):
a = self.a; n = a.d
gname, bname = a.norm_names(L, which)
g = self.r.get(gname, device=self.dev, dtype=F64)
if a.norm == "ln":
Sg = self.Sig - self.Sig.mean(0, keepdim=True)
Sg = Sg - Sg.mean(1, keepdim=True) # P Sig P
C = Sg / (torch.trace(Sg) / n)
cov = g[:, None] * C * g[None, :]
b = self.r.get(bname, device=self.dev, dtype=F64) if bname and self.r.has(bname) else torch.zeros(n, dtype=F64, device=self.dev)
return b, cov, cov + torch.outer(b, b)
s2 = torch.trace(self.Sig) / n
M2 = g[:, None] * (self.Sig / s2) * g[None, :]
mu = g * self.m / torch.sqrt(s2)
w, Q = torch.linalg.eigh(M2 - torch.outer(mu, mu))
cov = (Q * w.clamp(min=0)) @ Q.T
return mu, cov, M2
def normalise(M):
return M.mul_(M.shape[0] / torch.trace(M))
def S_norm(moments):
return normalise(moments[2].clone())
def S_act(arch, moments, W_up, b_up, W_gate, n_samples=16384, batch=2048, seed=0, device="cpu"):
"""W_up/W_gate: torch layout (inter, d). Returns (inter, inter) float32."""
mu, cov, _ = moments
w, Q = torch.linalg.eigh(cov)
R = (Q * w.clamp(min=0).sqrt()).to(torch.float32)
mu = mu.to(torch.float32)
Wu = W_up.to(device=device, dtype=torch.float32).T
Wg = None if W_gate is None else W_gate.to(device=device, dtype=torch.float32).T
bu = None if b_up is None else b_up.to(device=device, dtype=torch.float32)
gen = torch.Generator(device=device).manual_seed(seed)
M = torch.zeros((Wu.shape[1], Wu.shape[1]), dtype=torch.float32, device=device); done = 0
while done < n_samples:
b = min(batch, n_samples - done)
x = mu + torch.randn((b, R.shape[0]), generator=gen, device=device) @ R.T
u = x @ Wu
if bu is not None:
u = u + bu
if arch.act == "swiglu":
z = x @ Wg; h = z * torch.sigmoid(z) * u
elif arch.act == "relu":
h = torch.relu(u)
elif arch.act == "gelu_new":
h = 0.5 * u * (1 + torch.tanh(math.sqrt(2 / math.pi) * (u + 0.044715 * u ** 3)))
elif arch.act == "gelu":
h = torch.nn.functional.gelu(u)
else:
raise NotImplementedError(arch.act)
M += h.T @ h; done += b
return normalise(M.div_(done))
def S_attn(arch, moments, W_v, b_v, kappa=1.0):
"""W_v: torch layout (n_kv*head_dim, d). Returns (n_head*head_dim)^2 float64."""
mu, cov, _ = moments
Wv = W_v.to(device=cov.device, dtype=F64)
mv = Wv @ mu + (0 if b_v is None else b_v.to(device=cov.device, dtype=F64))
Cv = Wv @ cov @ Wv.T
H, dh = arch.n_head, arch.head_dim; per = H // arch.n_kv
n = H * dh
mo = torch.zeros(n, dtype=F64, device=cov.device); M = torch.zeros((n, n), dtype=F64, device=cov.device)
for h in range(H):
gk = h // per; s, t = slice(h * dh, (h + 1) * dh), slice(gk * dh, (gk + 1) * dh)
mo[s] = mv[t]; M[s, s] = kappa * Cv[t, t]
return normalise(M + torch.outer(mo, mo))