shayekh's picture
download
raw
6.99 kB
#!/usr/bin/env python
"""
Claim 1: MFA decomposes activations into a discrete region assignment (responsibilities
over centroids) plus a continuous within-region offset (posterior-mean loadings),
formalized in Eqs 5-13.
We verify the released MFA code implements Eqs 5-13 exactly and that the decomposition
x ~= A b(x) (Eq 11-13) reconstructs real Gemma-2-2B residual-stream activations.
Checks:
(a) Responsibilities R_k(x) (Eq 8) are a valid posterior: nonneg, sum to 1.
(b) Posterior-mean latent z_hat_k (Eqs 9-10) equals the closed-form Z_k (x - mu_k).
(c) Dictionary A = [mu_1 | W_1 | ... | mu_K | W_K] (Eq 12) and coefficients
b(x) = [R_k, R_k z_hat_k] (Eq 13) give recon = A b(x) (Eq 11).
(d) Two-segment structure: centroid term explains region-level, offset refines it.
Report relative reconstruction error and R^2 vs. the model's own reconstruct().
"""
import os, sys, json, time, argparse
import torch
import numpy as np
sys.path.insert(0, os.environ.get("MFA_REPO", os.path.join(os.path.dirname(__file__), "..", "repo")))
from modeling.model_checkpointing import load_mfa
from modeling.mfa import MFAEncoderDecoder
def get_activations(model_name, layer, n_tokens, device, seq_len=64):
"""Extract residual-stream activations (resid_post) at `layer` from real text."""
from transformer_lens import HookedTransformer
from datasets import load_dataset
tl = HookedTransformer.from_pretrained(model_name, device=device, dtype=torch.float32)
hook = f"blocks.{layer}.hook_resid_post"
ds = load_dataset("NeelNanda/pile-10k", split="train", streaming=True)
acts = []
for ex in ds:
toks = tl.to_tokens(ex["text"])[:, :seq_len]
with torch.no_grad():
_, cache = tl.run_with_cache(toks, names_filter=hook, return_type=None)
a = cache[hook].reshape(-1, tl.cfg.d_model).float().cpu()
# drop BOS position per example
acts.append(a[1:])
if sum(x.shape[0] for x in acts) >= n_tokens:
break
X = torch.cat(acts, 0)[:n_tokens]
del tl
torch.cuda.empty_cache()
return X
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--ckpt", required=True)
ap.add_argument("--model", default="gemma-2-2b")
ap.add_argument("--layer", type=int, default=18)
ap.add_argument("--n", type=int, default=4096)
ap.add_argument("--out", default="outputs/claim1_decomposition.json")
args = ap.parse_args()
device = "cuda" if torch.cuda.is_available() else "cpu"
t0 = time.time()
mfa = load_mfa(args.ckpt, map_location=device).eval()
print(f"Loaded MFA: K={mfa.K} D={mfa.D} q={mfa.q}")
cache = f"outputs/acts_{args.model}_l{args.layer}_{args.n}.pt"
if os.path.exists(cache):
X = torch.load(cache).to(device)
print(f"Loaded cached activations {tuple(X.shape)}")
else:
X = get_activations(args.model, args.layer, args.n, device).to(device)
os.makedirs("outputs", exist_ok=True)
torch.save(X.cpu(), cache)
print(f"Activations: {tuple(X.shape)} from {args.model} L{args.layer}")
with torch.no_grad():
# (a) responsibilities
R = mfa.responsibilities(X) # (B,K) Eq 8
resp_sum = R.sum(1)
resp_min = R.min().item()
assign = R.argmax(1)
used = assign.unique().numel()
# (b) posterior mean z_hat via model, and closed-form Z_k(x-mu_k) (Eqs 9-10)
Ez, _ = mfa.component_posterior(X) # (B,K,q)
# closed form for each token's assigned component
W = mfa.W # (K,D,q)
psi = mfa._psi() # (K,D)
idx = assign
Wk = W[idx] # (B,D,q)
psik = psi[idx] # (B,D)
muk = mfa.mu[idx] # (B,D)
Iq = torch.eye(mfa.q, device=device)
# Z_k = (I + W^T Psi^-1 W)^-1 W^T Psi^-1
WtPinv = torch.einsum("bdq,bd->bqd", Wk, 1.0/psik) # (B,q,D)
M = Iq[None] + torch.einsum("bqd,bdr->bqr", WtPinv, Wk) # (B,q,q)
zclosed = torch.linalg.solve(M, torch.einsum("bqd,bd->bq", WtPinv, (X-muk)))
zmodel = Ez[torch.arange(X.shape[0]), idx] # (B,q)
z_err = (zclosed - zmodel).norm() / zmodel.norm().clamp_min(1e-9)
# (c) full dictionary decomposition Eqs 11-13, done in token-chunks to bound memory.
# encode() assembles A = [mu_1|W_1|...|mu_K|W_K] (Eq 12) and
# b(x) = [R_k, R_k z_k] (Eq 13) then recon = A b(x) = coeffs @ A^T (Eq 11).
ed = MFAEncoderDecoder(mfa)
recon_parts = []
for s in range(0, X.shape[0], 512):
recon_parts.append(ed.encode(X[s:s+512]).recon)
recon = torch.cat(recon_parts, 0) # (B,D) = A b(x)
# relative reconstruction error of the Eq 11-13 dictionary reconstruction
rel_err_mix = ((recon - X).norm(dim=1) / X.norm(dim=1)).mean().item()
# single-component (hard-assigned) two-segment reconstruction:
# segment 1: centroid only ; segment 1+2: centroid + W z
cent = muk # region term R_k mu_k (hard)
offset = torch.einsum("bdq,bq->bd", Wk, zmodel) # within-region offset
err_centroid = ((cent - X).norm(dim=1)/X.norm(dim=1)).mean().item()
err_two = ((cent + offset - X).norm(dim=1)/X.norm(dim=1)).mean().item()
# variance explained by hard two-segment recon
ss_res = ((cent+offset - X)**2).sum().item()
ss_tot = ((X - X.mean(0))**2).sum().item()
r2_two = 1 - ss_res/ss_tot
# dictionary sparsity: nonzero coeff blocks per token (should be ~all K responsibilities
# but effective support is small -> measure mass in top block)
Rsort = R.sort(1, descending=True).values
top1_mass = Rsort[:, 0].mean().item()
top5_mass = Rsort[:, :5].sum(1).mean().item()
out = {
"ckpt": os.path.basename(args.ckpt),
"model": args.model, "layer": args.layer,
"K": mfa.K, "D": mfa.D, "q": mfa.q,
"n_tokens": int(X.shape[0]),
"resp_sum_mean": float(resp_sum.mean()),
"resp_sum_max_dev": float((resp_sum-1).abs().max()),
"resp_min": float(resp_min),
"components_used": int(used),
"z_closedform_rel_err": float(z_err), # (b) should be ~0
"recon_rel_err_mixture": rel_err_mix, # (c)
"err_centroid_only": err_centroid, # (d) segment 1
"err_centroid_plus_offset": err_two, # (d) segment 1+2
"r2_two_segment": r2_two,
"top1_responsibility_mass": top1_mass,
"top5_responsibility_mass": top5_mass,
"wall_s": time.time()-t0,
}
os.makedirs(os.path.dirname(args.out), exist_ok=True)
json.dump(out, open(args.out, "w"), indent=2)
print(json.dumps(out, indent=2))
if __name__ == "__main__":
main()

Xet Storage Details

Size:
6.99 kB
·
Xet hash:
5270fb0b0e2c08c5f1de590a05775706bd9fdb4b5815ae034783afeb49227377

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.