Melikshah's picture
download
raw
15.8 kB
import argparse
import json
import random
import time
from collections import defaultdict
import opharm
import numpy as np
import torch
from opharm.analysis import POS, code, load_rows, settings
from opharm.bench.evaluate import evaluate
from opharm.bench.labels import final_label
from opharm.bench.tools import TOOLS
from opharm.chat import load_tokenizer, render
from opharm.interp import hooks
from opharm.interp.directions import outlier_keep, unit, unmatched
from opharm.models import load_model
from opharm.paths import RUNS
from opharm.run.decision import _special, action_logodds, opener_ids
from opharm.run.generate import CLOSERS, end_ids, pad_id
def random_unit(u, seed):
g = torch.Generator().manual_seed(seed)
r = torch.randn(u.shape, generator=g)
r = r - (r @ u) * u
return r / r.norm()
def displace(model, point, pos, delta):
def fn(h):
if h.shape[1] <= pos:
return h
h = h.clone()
v = h[:, pos].float()
h[:, pos] = (v + delta(v)).to(h.dtype)
return h
return [("pre", hooks.resid_modules(model)[point], fn)]
class Runner:
def __init__(self, model_key, max_new):
self.key, self.max_new = model_key, max_new
self.tok, self.model = load_tokenizer(model_key), load_model(model_key)
self.opener = opener_ids(self.tok)
special = _special(self.tok)
self.stops = sorted(end_ids(self.tok) | {self.tok.convert_tokens_to_ids(s) for s in CLOSERS if s in special})
self.ends, self.pad = end_ids(self.tok), pad_id(self.tok)
def render(self, r):
return render(self.tok, r["system"], r["user"], TOOLS)
def generate(self, ids, edits=()):
x = torch.tensor([ids], device=self.model.device)
with torch.inference_mode(), hooks.hooked(*edits):
out = self.model.generate(x, attention_mask=torch.ones_like(x), max_new_tokens=self.max_new, do_sample=False,
eos_token_id=self.stops, pad_token_id=self.pad, output_logits=True, return_dict_in_generate=True)
m = action_logodds(out.logits[0][0].float().cpu(), self.opener).item()
row = out.sequences[0, len(ids):].tolist()
cut = next((k for k, t in enumerate(row) if t in self.ends or t == self.pad), len(row))
return m, self.tok.decode(row[:cut])
def record(self, r, condition, edits=(), **extra):
m, text = self.generate(self.render(r).ids, edits)
return {"id": r["id"], "cell": code(r), "condition": condition, "m": m, "text": text,
"label_v2": evaluate(text, r, self.key)["label"], **extra}
def generate_many(self, id_lists, edits=()):
n = max(len(ids) for ids in id_lists)
x = torch.tensor([[self.pad] * (n - len(ids)) + ids for ids in id_lists], device=self.model.device)
mask = torch.tensor([[0] * (n - len(ids)) + [1] * len(ids) for ids in id_lists], device=self.model.device)
with torch.inference_mode(), hooks.hooked(*edits):
out = self.model.generate(x, attention_mask=mask, max_new_tokens=self.max_new, do_sample=False,
eos_token_id=self.stops, pad_token_id=self.pad, output_logits=True, return_dict_in_generate=True)
res = []
for i in range(len(id_lists)):
row = out.sequences[i, n:].tolist()
cut = next((k for k, t in enumerate(row) if t in self.ends or t == self.pad), len(row))
res.append((action_logodds(out.logits[0][i].float().cpu(), self.opener).item(), self.tok.decode(row[:cut])))
return res
def records(self, rows, condition, edits, batch):
order = sorted(rows, key=lambda r: len(self.render(r).ids))
out = []
for s in range(0, len(order), batch):
part = order[s:s + batch]
for r, (m, text) in zip(part, self.generate_many([self.render(r).ids for r in part], edits)):
out.append({"id": r["id"], "cell": code(r), "condition": condition, "m": m, "text": text,
"label_v2": evaluate(text, r, self.key)["label"]})
return out
def panel(dev_main, n_skeletons, seed):
by_cls = defaultdict(set)
for r in dev_main:
by_cls[r["cls"]].add(r["skeleton"])
rng = random.Random(seed)
per_cls = max(1, n_skeletons // len(by_cls))
return {s for skels in by_cls.values() for s in rng.sample(sorted(skels), min(per_cls, len(skels)))}
def energy(run, rows, dirs):
h = np.asarray(run.acts[[r["row"] for r in rows]], dtype=np.float64)
tot = (h ** 2).sum(-1)
out = {}
for name, v in dirs.items():
u = v.double().numpy() / float(v.double().norm())
share = ((h @ u) ** 2 / tot).mean(0)
out[name] = {pos: [round(float(x), 5) for x in share[i]] for pos, i in POS.items()}
return out
def factor_controls(x, dev_main, st, base):
p, layer = st["blast"]["registered"]
skel = np.array([r["skeleton"] for r in dev_main])
masks = {"f_target": [r["target"] == "D" for r in dev_main], "f_rollback": [r["rollback"] == "N" for r in dev_main],
"f_rule": [r["policy"] == "C" for r in dev_main]}
f = {k: torch.tensor(unmatched(x[:, POS[p]], np.array(m), ~np.array(m), skel)[layer], dtype=torch.float32) for k, m in masks.items()}
span = torch.linalg.qr(torch.stack([v / v.norm() for v in f.values()], 1))[0]
u = base.float() / base.float().norm()
shared = span @ (span.T @ u)
unique = u - shared
scale = base.float().norm()
out = {k: v / v.norm() * scale for k, v in f.items()}
out["r_blast_shared"], out["r_blast_unique"] = shared / shared.norm() * scale, unique / unique.norm() * scale
h = np.asarray(x[:, POS[p], layer], dtype=np.float64)
pc1 = torch.tensor(np.linalg.svd(h - h.mean(0), full_matrices=False)[2][0], dtype=torch.float32)
out["pc1"] = pc1 / pc1.norm() * scale
return out
def shuffled(x, dev_main, st, base, n):
p, layer = st["blast"]["registered"]
env = np.array([r["env"] == "P" for r in dev_main])
skel = np.array([r["skeleton"] for r in dev_main])
out = {}
for s in range(n):
lab = np.random.default_rng(3000 + s).permutation(env)
v = torch.tensor(unmatched(x[:, POS[p]], lab, ~lab, skel)[layer], dtype=torch.float32)
out[f"shuf_{s}"] = v / v.norm() * base.float().norm()
return out
def c2(run, rows, st, n_random, limit=0, split="dev", masked=False, batch=1, tag="grid", skip=frozenset(), random_start=0,
controls_only=False, factors=False, n_shuffled=0, executions=0):
dev_main = [r for r in rows if r["set"] == "main" and r["split"] == "dev"]
pool = [r for r in rows if r["set"] == "main" and r["split"] == split]
x = np.asarray(run.acts[[r["row"] for r in dev_main]])
env = np.array([r["env"] == "P" for r in dev_main])
skel = np.array([r["skeleton"] for r in dev_main])
dirs = {f"r_blast_{k}": torch.tensor(unmatched(x[:, POS[p]], env, ~env, skel)[layer]) for k, (p, layer) in st["blast"].items()}
dirs["r_ref"] = torch.tensor(np.load(RUNS / run.key / "refsets" / "directions.npz")["r_ref"][st["L_steer"]])
base = dirs["r_blast_registered"]
ctrl = {f"rand_{s}": random_unit(base / base.norm(), 2000 + s) * base.norm() for s in range(random_start, random_start + n_random)}
if factors:
ctrl.update(factor_controls(x, dev_main, st, base))
if n_shuffled:
ctrl.update(shuffled(x, dev_main, st, base, n_shuffled))
want = "EXEC_MATCH" if executions else "ASK"
asks = [r for r in pool if code(r)[:2] == "DP" and r["policy"] == "C" and r["id"] not in skip
and final_label(r, run.key, tag)["label"] == want]
if executions:
asks = sorted(asks, key=lambda r: (r["m"], r["id"]))[:executions]
asks = asks[:limit] if limit else asks
earlier = {f"rand_{s}": random_unit(base / base.norm(), 2000 + s) * base.norm() for s in range(random_start)}
run.info = {"energy": energy(run, asks, {**dirs, **earlier, **ctrl}), "targets": want, "asks": len(asks)}
if executions:
run.info["m_range"] = [round(min(r["m"] for r in asks), 4), round(max(r["m"] for r in asks), 4)]
dirs = ctrl if controls_only else {**dirs, **ctrl}
keep = [torch.tensor(k) for k in outlier_keep(run.key)] if masked else None
edit = lambda v: [hooks.ablate_masked(run.model, v, keep) if masked else hooks.ablate(run.model, v)]
if batch > 1:
clean = [] if controls_only else run.records(asks, "clean", [], batch)
return clean + [x for name, v in dirs.items() for x in run.records(asks, name, edit(v), batch)]
out = []
for r in asks:
if not controls_only:
out.append(run.record(r, "clean"))
for name, v in dirs.items():
out.append(run.record(r, name, edit(v)))
return out
def gain(run, rows, layer, gains, n_skeletons, seed, limit=0):
dev_main = [r for r in rows if r["set"] == "main" and r["split"] == "dev"]
keep = panel(dev_main, n_skeletons, seed)
fit = [r for r in dev_main if r["skeleton"] not in keep]
x = np.asarray(run.acts[[r["row"] for r in fit], POS["t_post"], layer], dtype=np.float32)
env = np.array([r["env"] == "P" for r in fit])
u = torch.tensor(unit(unmatched(x, env, ~env, np.array([r["skeleton"] for r in fit]))), dtype=torch.float32)
proj = x @ u.numpy()
center = {p: float(proj[np.array([r["env"] == "S" and r["policy"] == p for r in fit])].mean()) for p in "CN"}
w = random_unit(u, 7000 + layer)
test = sorted([r for r in dev_main if r["skeleton"] in keep], key=lambda r: r["id"])
test = test[:limit] if limit else test
amp = {r["id"]: float(np.asarray(run.acts[r["row"], POS["t_post"], layer], dtype=np.float32) @ u.numpy()) - center[r["policy"]]
for r in test}
rng, shuffled, mean_amp = random.Random(seed), {}, {}
for p in "CN":
ids = [r["id"] for r in test if r["policy"] == p]
perm = ids[:]
while len(ids) > 1 and any(a == b for a, b in zip(ids, perm)):
rng.shuffle(perm)
shuffled.update(zip(ids, perm))
mean_amp[p] = float(np.mean([amp[i] for i in ids]))
out = []
for r in test:
pos, a, c = run.render(r).t_post, amp[r["id"]], center[r["policy"]]
out.append(run.record(r, "baseline", [], amplitude=a, displacement=0.0))
out.append(run.record(r, "identity", [displace(run.model, layer, pos, lambda v: 0.0 * v)], amplitude=a, displacement=0.0))
for g in gains:
k = g - 1
conds = {
"gain": displace(run.model, layer, pos, lambda v, c=c, k=k: k * ((v @ u.to(v.device)) - c).unsqueeze(-1) * u.to(v.device)),
"matched_random": displace(run.model, layer, pos, lambda v, c=c, k=k: k * ((v @ u.to(v.device)) - c).unsqueeze(-1) * w.to(v.device)),
"shuffled_amplitude": displace(run.model, layer, pos, lambda v, b=amp[shuffled[r["id"]]], k=k: k * b * u.to(v.device).expand_as(v)),
"fixed_addition": displace(run.model, layer, pos, lambda v, b=mean_amp[r["policy"]], k=k: k * b * u.to(v.device).expand_as(v)),
}
for name, edits in conds.items():
out.append(run.record(r, f"{name}|{g:g}", [edits], amplitude=a, displacement=abs(k * a)))
return out, {"layer": layer, "gains": gains, "center": center, "panel_skeletons": sorted(keep),
"fit_skeletons": len({r["skeleton"] for r in fit}), "mean_amplitude": mean_amp}
def steer(run, rows, st, coefs, n_random, n, seed, limit=0, split="dev", batch=1):
dev_main = [r for r in rows if r["set"] == "main" and r["split"] == "dev"]
pool = [r for r in rows if r["set"] == "main" and r["split"] == split]
x = np.asarray(run.acts[[r["row"] for r in dev_main], POS["t_post"], st["L_steer"]], dtype=np.float32)
env = np.array([r["env"] == "P" for r in dev_main])
ref = torch.tensor(np.load(RUNS / run.key / "refsets" / "directions.npz")["r_ref"][st["L_steer"]])
blast = torch.tensor(unit(unmatched(x, env, ~env, np.array([r["skeleton"] for r in dev_main])))) * ref.norm()
dirs = {"r_ref": ref, "r_blast": blast, **{f"rand_{s}": random_unit(ref / ref.norm(), s) * ref.norm() for s in range(n_random)}}
rng = random.Random(seed)
picked = []
for grp in ("DP", "DS", "BP"):
cand = [r for r in pool if code(r) in {grp + "AN", grp + "NN"}]
picked += rng.sample(cand, min(limit or n, n, len(cand)))
edit = lambda v, cf: [hooks.steer(run.model, st["L_steer"], v, cf)]
if batch > 1:
return run.records(picked, "clean", [], batch) + [x for name, v in dirs.items() for cf in coefs
for x in run.records(picked, f"{name}|{cf}", edit(v, cf), batch)]
out = []
for r in picked:
out.append(run.record(r, "clean"))
for name, v in dirs.items():
for cf in coefs:
out.append(run.record(r, f"{name}|{cf}", edit(v, cf)))
return out
def main():
ap = argparse.ArgumentParser()
ap.add_argument("mode", choices=["c2", "gain", "steer"])
ap.add_argument("model")
ap.add_argument("--tag", default="grid")
ap.add_argument("--layer", type=int, default=0)
ap.add_argument("--gains", default="3,10")
ap.add_argument("--skeletons", type=int, default=16)
ap.add_argument("--random", type=int, default=3)
ap.add_argument("--coefs", default="0.5,1")
ap.add_argument("--n", type=int, default=20)
ap.add_argument("--max-new", type=int, default=256)
ap.add_argument("--seed", type=int, default=5)
ap.add_argument("--limit", type=int, default=0)
ap.add_argument("--suffix", default="")
ap.add_argument("--split", default="dev", choices=["dev", "heldout"])
ap.add_argument("--masked", action="store_true")
ap.add_argument("--batch", type=int, default=1)
ap.add_argument("--skip-from", default=None)
ap.add_argument("--random-start", type=int, default=0)
ap.add_argument("--controls-only", action="store_true")
ap.add_argument("--factors", action="store_true")
ap.add_argument("--shuffled", type=int, default=0)
ap.add_argument("--executions", type=int, default=0)
args = ap.parse_args()
t0 = time.time()
held = args.split == "heldout"
rows, acts = load_rows(args.model, args.tag, held)
run = Runner(args.model, args.max_new)
run.acts = acts
st = settings(args.model, args.tag, held)
meta = {"model": args.model, "mode": args.mode, "args": vars(args)}
if args.mode == "c2":
skip = frozenset(json.loads(line)["id"] for line in open(RUNS / args.model / args.tag / args.skip_from)) if args.skip_from else frozenset()
out = c2(run, rows, st, args.random, args.limit, args.split, args.masked, args.batch, args.tag, skip, args.random_start,
args.controls_only, args.factors, args.shuffled, args.executions)
meta.update(getattr(run, "info", {}))
elif args.mode == "gain":
layer = args.layer or round(0.82 * len(hooks.layers(run.model)))
out, info = gain(run, rows, layer, [float(v) for v in args.gains.split(",")], args.skeletons, args.seed, args.limit)
meta.update(info)
else:
out = steer(run, rows, st, [float(c) for c in args.coefs.split(",")], args.random, args.n, args.seed, args.limit, args.split, args.batch)
dest = RUNS / args.model / args.tag / f"decoded_{args.mode}{args.suffix}.jsonl"
dest.write_text("".join(json.dumps(o) + "\n" for o in out))
meta.update(records=len(out), seconds=round(time.time() - t0, 1))
(RUNS / args.model / args.tag / f"decoded_{args.mode}{args.suffix}_meta.json").write_text(json.dumps(meta, indent=1))
print(json.dumps({k: meta[k] for k in ("mode", "records", "seconds")}))
if __name__ == "__main__":
main()

Xet Storage Details

Size:
15.8 kB
·
Xet hash:
868366ee2c2ea4b4e98158864a3f2e9d4d627717dd482904721b91ebe100d830

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