"""Experiment 1: J-lens readouts. (a) Two-hop reasoning: unspoken intermediate 'spider' visible mid-network. (b) Multilingual: English intermediates during a Chinese task. (c) Directed modulation: 'hold citrus in mind' while copying unrelated text. (d) Layer profile: where in depth the lens carries abstract content. """ import 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, collect_residuals, generate_with_residuals, load_model) RES = Path(os.environ.get("JLENS_RESULTS", "results")) def show_table(jl, tok, ids, layers, positions, topk=6, header=""): """Print top J-lens tokens at (layer, position) grid.""" hs = collect_residuals(jl.model, ids) toks = [tok.decode([t]) for t in ids[0].tolist()] print(f"\n=== {header} ===") print("positions:", {p: repr(toks[p]) for p in positions}) for l in layers: row = [] for p in positions: idx, prob = jl.read(hs[l, p], l, topk=topk) row.append(" ".join( (tok.decode([i]).strip() or "·") for i in idx.tolist()[:topk])) print(f"L{l:>2} | " + " || ".join(row)) def concept_heatmap(jl, tok, ids, words, tag, hs=None): """P(concept tokens) by (layer, position); save tensor + print peak.""" if hs is None: hs = collect_residuals(jl.model, ids) tids = [] for w in words: for v in (w, " " + w, w.capitalize(), " " + w.capitalize()): e = tok.encode(v, add_special_tokens=False) if len(e) == 1: tids.append(e[0]) tids = sorted(set(tids)) L, T, _ = hs.shape out = torch.zeros(L, T) for l in range(L): logits = jl._readout(hs[l].float() @ jl.Jbar[l].T) probs = torch.softmax(logits.float(), dim=-1) out[l] = probs[:, tids].sum(-1).cpu() peak = out.max().item() lmax, pmax = divmod(out.argmax().item(), T) toks = [tok.decode([t]) for t in ids[0].tolist()] if ids is not None else None print(f"[{tag}] words={words} peak P={peak:.3f} at layer {lmax}, " f"pos {pmax}" + (f" ({toks[pmax]!r})" if toks and pmax < len(toks) else "")) RES.mkdir(exist_ok=True) torch.save({"map": out, "words": words, "tokens": [tok.decode([t]) for t in ids[0].tolist()] if ids is not None else None}, RES / f"heat_{tag}.pt") return out 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") L = len(model.model.language_model.layers) mid = range(max(2, L // 6), L - 4, max(2, L // 16)) # (a) two-hop: spider ids = chat_ids(tok, "The animal that spins webs has how many legs? " "Answer with just a number.") print("answer:", jl.generate(ids, 5)) show_table(jl, tok, ids, mid, list(range(ids.shape[1] - 10, ids.shape[1])), header="two-hop spider (last 10 positions)") concept_heatmap(jl, tok, ids, ["spider", "spiders"], "spider") concept_heatmap(jl, tok, ids, ["eight", "8"], "eight") # (b) multilingual: Chinese antonym of small -> big ids = chat_ids(tok, "小的反义词是什么?只用一个字回答。") print("\nanswer:", jl.generate(ids, 5)) show_table(jl, tok, ids, mid, list(range(ids.shape[1] - 8, ids.shape[1])), header="Chinese antonym of 小") concept_heatmap(jl, tok, ids, ["big", "large", "bigger"], "big_en") # (c) directed modulation: think of citrus while copying text copy_text = "The committee will meet on Thursday to review the budget." for cond, instr in [ ("citrus", "While you copy the text, silently think about citrus " "fruits the entire time. Copy this text exactly, output " f"nothing else: \"{copy_text}\""), ("control", f"Copy this text exactly, output nothing else: \"{copy_text}\""), ]: ids = chat_ids(tok, instr) text, all_ids, hs = generate_with_residuals(model, tok, ids, 30) print(f"\n[{cond}] output: {text!r}") # only look at generated positions gen0 = ids.shape[1] heat = concept_heatmap(jl, tok, all_ids.unsqueeze(0), ["citrus", "lemon", "orange", "lime"], f"citrus_{cond}", hs=hs) print(f" citrus P over generated positions: mean " f"{heat[:, gen0:].mean():.4f} max {heat[:, gen0:].max():.4f}") # (d) layer profile: lens entropy + top-token agreement with input/output ids = chat_ids(tok, "Write one sentence about the ocean.") text, all_ids, hs = generate_with_residuals(model, tok, ids, 20) L, T, _ = hs.shape ent, agree_in, agree_next = [], [], [] for l in range(L): logits = jl._readout(hs[l].float() @ jl.Jbar[l].T) probs = torch.softmax(logits.float(), dim=-1) e = -(probs * probs.clamp_min(1e-12).log()).sum(-1).mean() top = probs.argmax(-1) ent.append(e.item()) agree_in.append((top[:-1] == all_ids[:len(top) - 1].cuda()).float().mean().item()) agree_next.append((top[:-1] == all_ids[1:len(top)].cuda()).float().mean().item()) print("\nlayer | lens entropy | top==current tok | top==next tok") for l in range(0, L, 2): print(f"L{l:>2} | {ent[l]:8.2f} | {agree_in[l]:.2f} | {agree_next[l]:.2f}") torch.save({"entropy": ent, "agree_in": agree_in, "agree_next": agree_next}, RES / "layer_profile.pt") if __name__ == "__main__": main()