Download methods/values.py from skyuu72/kibble-pythia-1b: direct link, hf CLI and curl.
- Browser
- Download file 7.43 kB
-
https://huggingface.co/skyuu72/kibble-pythia-1b/resolve/main/methods/values.py
- Command line
-
hf download hf://skyuu72/kibble-pythia-1b/methods/values.py
-
curl -L -o values.py https://huggingface.co/skyuu72/kibble-pythia-1b/resolve/main/methods/values.py
7.43 kB
| # keys, current residuals, and the gradient-optimised target vectors ("z*") that ROME/MEMIT use. | |
| # z* is the only place a baseline backprops through the model. our own methods get it only as an oracle. | |
| import os, sys | |
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) | |
| from common import * | |
| # a few throwaway lead-ins so keys/values aren't glued to "subject is the first word" | |
| PREFIXES = ["", "The weather was nice. ", "I think that ", "According to the report, ", "Yesterday someone said: ", "In other news, "] | |
| def req(f): | |
| """what an editor is allowed to see. no paraphrases, no hops, no neighbours.""" | |
| return dict(id=f["id"], subject=f["subject"], target=f["target"], prompt=f["direct"], statement=f["statement"], rel=f["rel"]) | |
| def _hid(o): return o[0] if isinstance(o, tuple) else o | |
| def POS_SUBJ(tok, p, r): return subj_pos(tok, p, r["subject"]) # last token of the subject | |
| def POS_LAST(tok, p, r): return len(enc(tok, p)) - 1 # last token of the prompt | |
| def keys_at(model, tok, reqs, layers, prefixes=PREFIXES, bs=24, pos_fn=POS_SUBJ): | |
| """mean over prefixes of the mlp key (input of dense_4h_to_h) at the last subject token. dict l -> [N, 8192]""" | |
| acc = None | |
| for pre in prefixes: | |
| P = [pre + r["prompt"] for r in reqs]; pos = [pos_fn(tok, p, r) for p, r in zip(P, reqs)] | |
| got = capture(model, tok, P, pos, {l: (W_out(model, l), "in") for l in layers}, bs=bs) | |
| acc = got if acc is None else {l: acc[l] + got[l] for l in layers} | |
| return {l: acc[l] / len(prefixes) for l in layers} | |
| def resid_at(model, tok, reqs, L, prefixes=PREFIXES, bs=24, pos_fn=POS_SUBJ): | |
| """mean over prefixes of block-L output at the last subject token. [N, 2048]""" | |
| acc = 0 | |
| for pre in prefixes: | |
| P = [pre + r["prompt"] for r in reqs]; pos = [pos_fn(tok, p, r) for p, r in zip(P, reqs)] | |
| acc = acc + capture(model, tok, P, pos, {"h": (blocks(model)[L], "out")}, bs=bs)["h"] | |
| return acc / len(prefixes) | |
| def compute_z(model, tok, reqs, L, steps=20, lr=0.5, clamp=0.75, klf=0.0625, wd=0.5, scale=256.0): | |
| """MEMIT's compute_z, several facts at once (each fact has its own delta, the losses don't mix). | |
| returns absolute targets z [G, 2048] for block-L output at the last subject token.""" | |
| seqs, own, spos, tinfo, bare = [], [], [], [], [] | |
| for g, r in enumerate(reqs): | |
| for k, pre in enumerate(PREFIXES): | |
| pi, ti = split_target(tok, pre + r["prompt"], r["target"]) | |
| if k == 0: bare.append(len(seqs)) | |
| seqs.append(pi + ti[:-1]); own.append(g); spos.append(subj_pos(tok, pre + r["prompt"], r["subject"])); tinfo.append((len(pi) - 1, ti)) | |
| klp = r["subject"] + " is a" | |
| seqs.append(enc(tok, klp)); own.append(g); spos.append(subj_pos(tok, klp, r["subject"])); tinfo.append(None) | |
| x, m = pad(seqs); n = len(seqs); rows = torch.arange(n, device=DEV) | |
| spos_t = torch.tensor(spos, device=DEV); own_t = torch.tensor(own, device=DEV) | |
| G = len(reqs); d = model.config.hidden_size | |
| delta = torch.zeros(G, d, device=DEV, requires_grad=True); opt = torch.optim.Adam([delta], lr=lr) | |
| st = dict(init=None) | |
| def hook(mod, inp, out): | |
| h = _hid(out) | |
| if st["init"] is None: st["init"] = h[rows, spos_t].detach().float() | |
| h = h.clone(); h[rows, spos_t] = h[rows, spos_t] + delta[own_t].to(h.dtype) | |
| return (h,) + tuple(out[1:]) if isinstance(out, tuple) else h | |
| # flat index lists for the target-token log-probs | |
| r_i, p_i, t_i, w_i, g_i = [], [], [], [], [] | |
| for s, info in enumerate(tinfo): | |
| if info is None: continue | |
| a, ti = info | |
| for j, t in enumerate(ti): | |
| r_i.append(s); p_i.append(a + j); t_i.append(t); w_i.append(1.0 / (len(ti) * len(PREFIXES))); g_i.append(own[s]) | |
| r_i, p_i, t_i, g_i = (torch.tensor(v, device=DEV) for v in (r_i, p_i, t_i, g_i)); w_i = torch.tensor(w_i, device=DEV) | |
| kl_rows = torch.tensor([s for s, info in enumerate(tinfo) if info is None], device=DEV) | |
| kl_pos = torch.tensor([len(seqs[s]) - 1 for s in kl_rows.tolist()], device=DEV) | |
| kl_init, init_norm = None, None | |
| with torch.enable_grad(), hooks([(blocks(model)[L], hook, "fwd")]): | |
| for step in range(steps): | |
| logits = model(input_ids=x, attention_mask=m).logits | |
| lp = torch.log_softmax(logits[r_i, p_i].float(), -1) | |
| nll = torch.zeros(G, device=DEV).index_add(0, g_i, -lp.gather(1, t_i[:, None])[:, 0] * w_i) | |
| klp = torch.log_softmax(logits[kl_rows, kl_pos].float(), -1) | |
| if kl_init is None: | |
| kl_init = klp.detach(); init_norm = st["init"][torch.tensor(bare, device=DEV)].norm(dim=1) | |
| kl = (kl_init.exp() * (kl_init - klp)).sum(-1) | |
| loss = (nll + klf * kl + wd * (delta.norm(dim=1) / init_norm) ** 2).sum() | |
| opt.zero_grad(); (loss * scale).backward() | |
| if not torch.isfinite(delta.grad).all(): | |
| delta.grad = torch.nan_to_num(delta.grad, nan=0.0, posinf=0.0, neginf=0.0) | |
| opt.step() | |
| with torch.no_grad(): | |
| nrm = delta.norm(dim=1, keepdim=True); mx = (clamp * init_norm)[:, None] | |
| delta.mul_(torch.clamp(mx / nrm.clamp_min(1e-8), max=1.0)) | |
| z = st["init"][torch.tensor(bare, device=DEV)] + delta.detach() | |
| return z, nll.detach() | |
| def z_cached(model, tok, reqs, L, group=8, tag="", **kw): | |
| """z* per fact id, cached on disk. MUST be called on the unedited model (the cache assumes it).""" | |
| path = os.path.join(CK, f"z_L{L}{tag}.pt") | |
| cache = torch.load(path) if os.path.exists(path) else {} | |
| todo = [r for r in reqs if r["id"] not in cache] | |
| t0 = time.time() | |
| for a in range(0, len(todo), group): | |
| g = todo[a:a + group] | |
| while True: | |
| try: | |
| z, nll = compute_z(model, tok, g, L, **kw); break | |
| except torch.OutOfMemoryError: | |
| free(); assert len(g) > 1; g = g[:len(g) // 2]; print("[oom] z group ->", len(g), flush=True) | |
| if len(g) < len(todo[a:a + group]): # finish the rest of this slice one small group at a time | |
| rest = todo[a + len(g):a + group] | |
| z2 = [compute_z(model, tok, rest[i:i + len(g)], L, **kw) for i in range(0, len(rest), len(g))] | |
| z = torch.cat([z] + [q[0] for q in z2]); nll = torch.cat([nll] + [q[1] for q in z2]); g = todo[a:a + group] | |
| for r, zz, nl in zip(g, z, nll): cache[r["id"]] = dict(z=zz.cpu(), nll=nl.item()) | |
| if (a // group) % 10 == 0: | |
| torch.save(cache, path); print(f" z* {a + len(g)}/{len(todo)} nll~{nll.mean().item():.3f} {time.time() - t0:.0f}s", flush=True) | |
| if todo: torch.save(cache, path) | |
| return torch.stack([cache[r["id"]]["z"] for r in reqs]).to(DEV), [cache[r["id"]]["nll"] for r in reqs] | |
| _CINV = {} | |
| def cov_inv(l, kind="k", ridge=0.0): | |
| """inverse of the cached second-moment matrix for layer l (float64, on gpu). kind 'k' = mlp keys, 'x' = mlp inputs.""" | |
| key = (l, kind, ridge) | |
| if key not in _CINV: # parked on cpu (64GB of ram, 8 of vram), shipped to the gpu per use | |
| C = torch.load(os.path.join(CK, f"cov_{kind}_L{l}.pt"))["C"].to(DEV).double() | |
| if ridge: C = C + ridge * C.diagonal().mean() * torch.eye(C.shape[0], device=DEV, dtype=torch.float64) | |
| _CINV[key] = torch.linalg.inv(C).cpu() | |
| del C; free() | |
| return _CINV[key].to(DEV) | |
| def drop_cov_cache(): | |
| _CINV.clear(); free() | |