"""Experiment 2: causal swap interventions gated by the J-lens. At each (layer, position) where the J-lens reads the source concept, transfer the activation content source -> target (embedding write basis; see core.py). (a) Two-hop: swap spider<->ant in the workspace -> answer flips 8 -> 6. (b) Broadcast: swap France->China under different templates. (c) Capital-question grid over country pairs. """ import itertools, os, sys from pathlib import Path import torch sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) from jlens.core import JLens, chat_ids, load_model RES = Path(os.environ.get("JLENS_RESULTS", "results")) def main(): model, tok = load_model(dtype=torch.bfloat16) ck = torch.load(sys.argv[1] if len(sys.argv) > 1 else "results/jbar.pt", map_location="cuda") jl = JLens(model, tok, ck["Jbar"].cuda()) print(f"Jbar from {ck.get('n_prompts')} prompts") # (a) spider -> ant SP = [(" spider", " ant"), (" spiders", " ants"), ("spider", "ant")] q = "The animal that spins webs has how many legs? Answer with just a number." ids = chat_ids(tok, q) print("\n--- two-hop swap: spider -> ant (baseline:", repr(jl.generate(ids, 6)), ") ---") for thr in (0.002, 0.005, 0.01): for alpha in (1.0, 1.5, 2.0): out, fired = jl.generate_swapped(ids, SP, thr=thr, alpha=alpha, max_new_tokens=6) print(f"thr={thr} a={alpha}: {out!r} (fired {len(fired)} slots)") # paper write-basis for comparison out, _ = jl.generate_swapped(ids, SP, thr=0.005, alpha=1.0, write="jlens", max_new_tokens=6) print(f"paper jlens-write thr=0.005 a=1.0: {out!r}") # control: unrelated swap out, _ = jl.generate_swapped(ids, [(" spider", " piano")], thr=0.005, alpha=1.0, max_new_tokens=6) print(f"control spider->piano: {out!r}") # reverse: ant prompt -> spider q2 = ("The insect that builds colonies and lifts many times its own " "weight has how many legs? Answer with just a number.") ids2 = chat_ids(tok, q2) print("\nant prompt baseline:", repr(jl.generate(ids2, 6))) for alpha in (1.0, 1.5, 2.0): out, fired = jl.generate_swapped( ids2, [(" ant", " spider"), (" ants", " spiders"), ("ant", "spider")], thr=0.005, alpha=alpha, max_new_tokens=6) print(f"swap ant->spider a={alpha}: {out!r} (fired {len(fired)})") # (b) broadcast France -> China across templates print("\n--- broadcast: France -> China (thr=0.01, a=1.0) ---") FR = [(" France", " China"), ("France", "China"), (" French", " Chinese")] templates = [ ("capital", "What is the capital of France? Answer with just the city name."), ("language", "What language is spoken in France? Answer with one word."), ("continent", "Which continent is France on? Answer with one word."), ("currency", "What currency is used in France? Answer with one word."), ("river", "Name a famous river in France. Answer with one word."), ("food", "Name a famous dish from France. Answer with a short phrase."), ] for tag, qq in templates: ids = chat_ids(tok, qq) base = jl.generate(ids, 8) sw, fired = jl.generate_swapped(ids, FR, thr=0.01, alpha=1.0, max_new_tokens=8) print(f"[{tag:9}] base={base!r:30} swapped={sw!r} ({len(fired)} slots)") # (c) capital grid over country pairs print("\n--- capital-question swap grid (thr=0.01, a=1.0) ---") countries = {"France": ("Paris", "French"), "China": ("Beijing", "Chinese"), "Japan": ("Tokyo", "Japanese"), "Egypt": ("Cairo", "Egyptian"), "Brazil": ("Brasília", "Brazilian"), "Canada": ("Ottawa", "Canadian")} hits = tries = 0 rows = [] for (src, (scap, sadj)), (tgt, (tcap, tadj)) in itertools.permutations( countries.items(), 2): qq = f"What is the capital of {src}? Answer with just the city name." ids = chat_ids(tok, qq) pairs = [(" " + src, " " + tgt), (src, tgt), (" " + sadj, " " + tadj)] sw, fired = jl.generate_swapped(ids, pairs, thr=0.01, alpha=1.0, max_new_tokens=8) ok = tcap.lower().replace("í", "i").split()[0][:5] in \ sw.lower().replace("í", "i") hits += ok tries += 1 rows.append((src, tgt, tcap, sw, ok)) print(f"{src:>7}->{tgt:<7} expect {tcap:<9} got {sw!r} {'OK' if ok else ''}") print(f"\nswap grid success: {hits}/{tries}") torch.save(rows, RES / "swap_grid.pt") if __name__ == "__main__": main()