Melikshah's picture
download
raw
15.4 kB
import argparse
import json
import random
import time
import opharm
import numpy as np
import torch
from opharm.analysis import POS, blast_partition, code, eval_split, load_rows, settings
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 unit, unmatched
from opharm.interp.probes import fit_probe
from opharm.models import load_model
from opharm.paths import RESULTS, RUNS
from opharm.run.decision import action_logodds, opener_ids
class Ctx:
def __init__(self, model_key, tag, confirm):
rows, self.acts = load_rows(model_key, tag, confirm)
self.main = [r for r in rows if r["set"] == "main" and r["split"] == eval_split(confirm)]
self.by_code = {(r["skeleton"], code(r)): r for r in self.main}
self.tok, self.model = load_tokenizer(model_key), load_model(model_key)
self.key, self.tag = model_key, tag
self.inputs, self.limit = hooks.has_layer_inputs(self.model), hooks.last_patchable_layer(self.model)
self.opener = opener_ids(self.tok)
st = settings(model_key, tag, confirm)
self.blast, self.l_steer = {k: tuple(v) for k, v in st["blast"].items()}, st["L_steer"]
dev_main = [r for r in rows if r["set"] == "main" and r["split"] == "dev"]
self.dev_main = dev_main
x = np.asarray(self.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])
self.r_blast = {p: unmatched(x[:, i], env, ~env, skel) for p, i in POS.items()}
train, _, splits = blast_partition(dev_main, False)
self.probes = {k: fit_probe(x[train, POS[p], layer], env[train], skel[train], splits=splits)[0]
for k, (p, layer) in self.blast.items()}
self.r_ref = np.load(RUNS / model_key / "refsets" / "directions.npz")["r_ref"]
def render(self, r):
return render(self.tok, r["system"], r["user"], TOOLS)
def run(self, ids, edits=(), at=None):
store, extra = {}, []
if at is not None:
extra = hooks.capture(self.model, sorted({layer for _, layer in self.blast.values()}), at, store)
with torch.inference_mode(), hooks.hooked(*edits, extra):
logits = self.model(input_ids=torch.tensor([ids], device=self.model.device), use_cache=False, logits_to_keep=1).logits[0, -1]
m = action_logodds(logits.float().cpu(), self.opener).item()
if at is None:
return m, None
return m, {k: float(self.probes[k].decision_function(store[layer][0, POS[p]][None].numpy())[0])
for k, (p, layer) in self.blast.items()}
def capture_span(self, ids, layers, positions):
store = {}
with torch.inference_mode(), hooks.hooked(hooks.capture(self.model, layers, positions, store)):
self.model(input_ids=torch.tensor([ids], device=self.model.device), use_cache=False, logits_to_keep=1)
return {layer: store[layer][0] for layer in layers}
def capture_inputs(self, ids):
if not self.inputs:
return None
store = {}
with torch.inference_mode(), hooks.hooked(hooks.capture_layer_inputs(self.model, store)):
self.model(input_ids=torch.tensor([ids], device=self.model.device), use_cache=False, logits_to_keep=1)
return store
def patch_edits(self, layer, positions, values, inputs):
edits = [hooks.patch(self.model, layer, positions, values)]
return edits + ([hooks.patch_layer_inputs(self.model, layer, positions, inputs)] if inputs is not None else [])
def sample_pairs(ctx, n, seed, first, second, pos, policy=""):
rng = random.Random(seed)
cands = [(r, ctx.by_code[(r["skeleton"], code(r)[:pos] + second + code(r)[pos + 1:])])
for r in ctx.main if code(r)[pos] == first and (not policy or r["policy"] == policy)]
rng.shuffle(cands)
return cands[:n]
def patch_panel(ctx, pairs, span, layers, complement=False):
out = []
for a, b in pairs:
for tgt, src in ((a, b), (b, a)):
rt, rs = ctx.render(tgt), ctx.render(src)
positions = [i for s, e in rt.spans[span] for i in range(s, e)]
if complement:
positions = [i for i in range(len(rt.ids)) if i not in set(positions)]
m0, p0 = ctx.run(rt.ids, at=[rt.t_inst, rt.t_post])
ms, ps = ctx.run(rs.ids, at=[rs.t_inst, rs.t_post])
vals, inputs = ctx.capture_span(rs.ids, layers, positions), ctx.capture_inputs(rs.ids)
for layer in layers:
m, p = ctx.run(rt.ids, ctx.patch_edits(layer, positions, vals[layer], inputs), at=[rt.t_inst, rt.t_post])
out.append({"target": tgt["id"], "source": src["id"], "layer": layer, "m_clean": m0, "m_source": ms, "m_patched": m,
"probe": {k: {"clean": p0[k], "source": ps[k], "patched": p[k]} for k in p0}})
return out
def decomposition(ctx, layers):
rows = ctx.dev_main
x = np.asarray(ctx.acts[[r["row"] for r in rows], POS["t_post"]], dtype=np.float32)
skel = np.array([r["skeleton"] for r in rows])
masks = [np.array([f(r) for r in rows]) for f in (lambda r: r["target"] == "D", lambda r: r["rollback"] == "N", lambda r: r["policy"] == "C")]
factors = [unmatched(x, m, ~m, skel) for m in masks]
out = {"r_blast_shared": {}, "r_blast_unique": {}, "pc1": {}}
for l in layers:
span = torch.linalg.qr(torch.tensor(np.stack([f[l] / np.linalg.norm(f[l]) for f in factors], 1), dtype=torch.float32))[0]
u = torch.tensor(unit(ctx.r_blast["t_post"][l]), dtype=torch.float32)
out["r_blast_shared"][l] = span @ (span.T @ u)
out["r_blast_unique"][l] = u - out["r_blast_shared"][l]
h = x[:, l].astype(np.float64)
out["pc1"][l] = torch.tensor(np.linalg.svd(h - h.mean(0), full_matrices=False)[2][0], dtype=torch.float32)
return out
def swap_panel(ctx, pairs, layers, names):
dirs = {"r_blast": {l: torch.tensor(ctx.r_blast["t_inst"][l]) for l in layers},
"r_blast_post": {l: torch.tensor(ctx.r_blast["t_post"][l]) for l in layers}}
dirs["random"] = {l: random_like(dirs["r_blast"][l], 3000 + l) for l in layers}
if {"r_blast_shared", "r_blast_unique", "pc1"} & set(names):
dirs.update(decomposition(ctx, layers))
dirs = {k: dirs[k] for k in names}
out = []
for a, b in pairs:
for tgt, src in ((a, b), (b, a)):
rt, rs = ctx.render(tgt), ctx.render(src)
m0, p0 = ctx.run(rt.ids, at=[rt.t_inst, rt.t_post])
ms, ps = ctx.run(rs.ids, at=[rs.t_inst, rs.t_post])
span = {i for s, e in rt.spans["env"] for i in range(s, e)}
keep = [i for i in range(len(rt.ids)) if i not in span]
vals = ctx.capture_span(rs.ids, layers, keep)
for name, d in dirs.items():
for layer in layers:
u = d[layer] / d[layer].norm()
m, p = ctx.run(rt.ids, [hooks.swap_along(ctx.model, layer, u, vals[layer] @ u, keep)], at=[rt.t_inst, rt.t_post])
out.append({"target": tgt["id"], "source": src["id"], "layer": layer, "direction": name, "m_clean": m0,
"m_source": ms, "m_patched": m,
"probe": {k: {"clean": p0[k], "source": ps[k], "patched": p[k]} for k in p0}})
return out
def gain_panel(ctx, rows, layers, gains, seeds):
staging = np.array([r["row"] for r in ctx.dev_main if r["env"] == "S" and r["policy"] == "N"])
points = [("t_post", layer) for layer in layers]
if ctx.blast["dev_best"][0] == "t_inst":
points.append(("t_inst", ctx.blast["dev_best"][1]))
specs = []
for p_name, layer in points:
u = torch.tensor(unit(ctx.r_blast[p_name][layer]), dtype=torch.float32)
dirs = {"r_blast": u, **{f"rand_{s}": random_like(u, 4000 + 100 * layer + s) for s in range(seeds)}}
proj = np.asarray(ctx.acts[staging, POS[p_name], layer], dtype=np.float32)
for name, v in dirs.items():
v = v / v.norm()
specs.append((p_name, layer, name, v, float((proj @ v.numpy()).mean())))
out = []
for r in rows:
rr = ctx.render(r)
m0, _ = ctx.run(rr.ids)
for p_name, layer, name, v, center in specs:
pos = [rr.t_post if p_name == "t_post" else rr.t_inst]
for g in gains:
m, _ = ctx.run(rr.ids, [hooks.gain(ctx.model, layer, v, center, g, pos)])
out.append({"id": r["id"], "cell": code(r), "position": p_name, "layer": layer, "direction": name,
"gain": g, "m_clean": m0, "m": m})
return out
def patch_check(ctx, pairs, layers):
out = {"layers": layers, "pairs": len(pairs), "per_layer": {}}
for layer in layers:
ident, trans, resid_only = [], [], []
for a, b in pairs:
ra, rb = ctx.render(a), ctx.render(b)
every = list(range(len(ra.ids)))
ma, _ = ctx.run(ra.ids)
mb, _ = ctx.run(rb.ids)
own, src = ctx.capture_span(ra.ids, [layer], every), ctx.capture_span(rb.ids, [layer], every)
own_in, src_in = ctx.capture_inputs(ra.ids), ctx.capture_inputs(rb.ids)
ident.append(abs(ctx.run(ra.ids, ctx.patch_edits(layer, every, own[layer], own_in))[0] - ma))
trans.append(abs(ctx.run(ra.ids, ctx.patch_edits(layer, every, src[layer], src_in))[0] - mb))
resid_only.append(abs(ctx.run(ra.ids, ctx.patch_edits(layer, every, src[layer], None))[0] - mb))
out["per_layer"][layer] = {"identity_max": max(ident), "transplant_max": max(trans), "residual_only_transplant_max": max(resid_only)}
out["valid"] = all(v["identity_max"] < 1e-3 and v["transplant_max"] < 1e-2 for v in out["per_layer"].values())
return out
def random_like(v, seed):
g = torch.Generator().manual_seed(seed)
r = torch.randn(v.shape, generator=g)
u = v / v.norm()
r = r - (r @ u) * u
return r / r.norm() * v.norm()
def steer_panel(ctx, rows, coefs, seeds):
ref = torch.tensor(ctx.r_ref[ctx.l_steer])
u_ref = ref / ref.norm()
blast = torch.tensor(unit(ctx.r_blast["t_post"][ctx.l_steer])) * ref.norm()
perp = blast - (blast @ u_ref) * u_ref
dirs = {"r_ref": ref, "r_blast": blast, "r_blast_perp": perp / perp.norm() * ref.norm(),
"r_blast_inst": torch.tensor(unit(ctx.r_blast["t_inst"][ctx.l_steer])) * ref.norm()}
dirs.update({f"rand_ref_{s}": random_like(ref, s) for s in range(seeds)})
dirs.update({f"rand_blast_{s}": random_like(blast, 1000 + s) for s in range(seeds)})
out = []
for r in rows:
ids = ctx.render(r).ids
m0, _ = ctx.run(ids)
for name, v in dirs.items():
for c in coefs:
m, _ = ctx.run(ids, [hooks.steer(ctx.model, ctx.l_steer, v, c)])
out.append({"id": r["id"], "cell": code(r), "direction": name, "coef": c, "m_clean": m0, "m": m})
return out
def ablate_panel(ctx, rows, seeds):
dirs = {f"r_blast_{k}": torch.tensor(ctx.r_blast[p][layer]) for k, (p, layer) in ctx.blast.items()}
dirs["r_ref"] = torch.tensor(ctx.r_ref[ctx.l_steer])
dirs.update({f"rand_{s}": random_like(dirs["r_blast_registered"], 2000 + s) for s in range(seeds)})
out = []
for r in rows:
ids = ctx.render(r).ids
m0, _ = ctx.run(ids)
for name, v in dirs.items():
m, _ = ctx.run(ids, [hooks.ablate(ctx.model, v)])
out.append({"id": r["id"], "cell": code(r), "direction": name, "m_clean": m0, "m": m})
return out
def main():
ap = argparse.ArgumentParser()
ap.add_argument("test", choices=["c1", "c2", "c3", "c4", "c6", "c7", "c8", "check"])
ap.add_argument("model")
ap.add_argument("--tag", default="grid")
ap.add_argument("--n", type=int, default=60)
ap.add_argument("--seeds", type=int, default=8)
ap.add_argument("--layers", default="")
ap.add_argument("--coefs", default="0.5,1,2")
ap.add_argument("--groups", default="DP,DS,BP")
ap.add_argument("--dirs", default="r_blast,random")
ap.add_argument("--gains", default="3,10,30")
ap.add_argument("--confirm", action="store_true")
ap.add_argument("--suffix", default="")
ap.add_argument("--policy", default="", choices=["", "C", "N"])
args = ap.parse_args()
t0 = time.time()
ctx = Ctx(args.model, args.tag, args.confirm)
n_layers = len(hooks.layers(ctx.model))
layers = [int(v) for v in args.layers.split(",")] if args.layers else sorted({round(1 + i * (n_layers - 3) / 7) for i in range(8)})
if args.test in ("c1", "c4", "c6", "c7", "check") and ctx.limit is not None and max(layers) > ctx.limit:
raise ValueError(f"{args.model} shares key/value states after layer {ctx.limit}; patch layers {layers} go past it")
if args.test == "check":
res = patch_check(ctx, sample_pairs(ctx, args.n, 0, "P", "S", 1, args.policy), layers)
RESULTS.mkdir(exist_ok=True)
(RESULTS / f"patchcheck_{args.model}.json").write_text(json.dumps(res, indent=1))
print(json.dumps(res))
return
if args.test == "c1":
out = patch_panel(ctx, sample_pairs(ctx, args.n, 0, "P", "S", 1, args.policy), "env", layers)
elif args.test == "c8":
rng = random.Random(3)
rows = []
for g in ("DP", "DS", "BP", "BS"):
cand = [r for r in ctx.main if code(r) in {g + "AN", g + "NN"}]
rows += rng.sample(cand, min(args.n, len(cand)))
late = [int(v) for v in args.layers.split(",")] if args.layers else sorted({round(f * n_layers) for f in (0.7, 0.82, 0.94)})
out = gain_panel(ctx, rows, late, [float(g) for g in args.gains.split(",")], args.seeds)
elif args.test == "c7":
out = patch_panel(ctx, sample_pairs(ctx, args.n, 0, "P", "S", 1, args.policy), "env", layers, complement=True)
elif args.test == "c6":
out = swap_panel(ctx, sample_pairs(ctx, args.n, 0, "P", "S", 1, args.policy), layers, args.dirs.split(","))
elif args.test == "c4":
out = patch_panel(ctx, sample_pairs(ctx, args.n, 1, "C", "N", 3), "policy", layers)
elif args.test == "c3":
rng = random.Random(2)
rows = []
for g in args.groups.split(","):
cand = [r for r in ctx.main if code(r) in {g + "AN", g + "NN"}]
rows += rng.sample(cand, min(args.n, len(cand)))
out = steer_panel(ctx, rows, [float(c) for c in args.coefs.split(",")], args.seeds)
else:
rows = [r for r in ctx.main if code(r)[:2] == "DP" and r["policy"] == "C" and final_label(r, args.model, args.tag)["label"] == "ASK"]
out = ablate_panel(ctx, rows[: args.n], args.seeds)
name = f"causal_{args.test}_{args.model}_{args.tag}{'_confirm' if args.confirm else ''}{'_' + args.suffix if args.suffix else ''}"
(RUNS / args.model / args.tag / f"{name}.jsonl").write_text("".join(json.dumps(o) + "\n" for o in out))
print(json.dumps({"test": args.test, "records": len(out), "layers": layers, "blast": ctx.blast, "L_steer": ctx.l_steer,
"seconds": round(time.time() - t0, 1)}))
if __name__ == "__main__":
main()

Xet Storage Details

Size:
15.4 kB
·
Xet hash:
1f304c05e24a511cc59ab27187b4fea975102b7e20d8454867386ed3ce9e4ef8

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