diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index e0bd51233d..47af62b2e3 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -223,8 +223,8 @@ jobs: fail-fast: false matrix: notebook: + - "Attribution_Patching_Demo" - "Activation_Patching_in_TL_Demo" - # - "Attribution_Patching_Demo" - "ARENA_Content" - "BERT" - "Exploratory_Analysis_Demo" diff --git a/demos/Activation_Patching_in_TL_Demo.ipynb b/demos/Activation_Patching_in_TL_Demo.ipynb index a848029de9..d71858f6d1 100644 --- a/demos/Activation_Patching_in_TL_Demo.ipynb +++ b/demos/Activation_Patching_in_TL_Demo.ipynb @@ -562,6 +562,8 @@ } ], "source": [ + "# 2,160 forward passes of GPT-2; too slow for CI without GPU\n", + "# NBVAL_SKIP\n", "ALL_HEAD_LABELS = [f\"L{i}H{j}\" for i in range(model.cfg.n_layers) for j in range(model.cfg.n_heads)]\n", "if DO_SLOW_RUNS:\n", " attn_head_out_act_patch_results = patching.get_act_patch_attn_head_out_by_pos(model, corrupted_tokens, clean_cache, ioi_metric)\n", @@ -1283,4 +1285,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/demos/Attribution_Patching_Demo.ipynb b/demos/Attribution_Patching_Demo.ipynb index bd9b6c7072..f110f59ee7 100644 --- a/demos/Attribution_Patching_Demo.ipynb +++ b/demos/Attribution_Patching_Demo.ipynb @@ -46,7 +46,14 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:17:47.592530Z", + "iopub.status.busy": "2026-03-06T20:17:47.592314Z", + "iopub.status.idle": "2026-03-06T20:17:47.623795Z", + "shell.execute_reply": "2026-03-06T20:17:47.623529Z" + } + }, "outputs": [ { "name": "stdout", @@ -54,19 +61,10 @@ "text": [ "Running as a Jupyter notebook - intended for development only!\n" ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/var/folders/m3/z6c6rcdj1rbb2jh9vqpgvxg40000gn/T/ipykernel_25358/2480103146.py:24: DeprecationWarning: `magic(...)` is deprecated since IPython 0.13 (warning added in 8.1), use run_line_magic(magic_name, parameter_s).\n", - " ipython.magic(\"load_ext autoreload\")\n", - "/var/folders/m3/z6c6rcdj1rbb2jh9vqpgvxg40000gn/T/ipykernel_25358/2480103146.py:25: DeprecationWarning: `magic(...)` is deprecated since IPython 0.13 (warning added in 8.1), use run_line_magic(magic_name, parameter_s).\n", - " ipython.magic(\"autoreload 2\")\n" - ] } ], "source": [ + "# NBVAL_IGNORE_OUTPUT\n", "# Janky code to do different setup when run in a Colab notebook vs VSCode\n", "import os\n", "\n", @@ -100,8 +98,15 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, + "execution_count": 2, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:17:47.641002Z", + "iopub.status.busy": "2026-03-06T20:17:47.640902Z", + "iopub.status.idle": "2026-03-06T20:17:48.042243Z", + "shell.execute_reply": "2026-03-06T20:17:48.041991Z" + } + }, "outputs": [], "source": [ "# Plotly needs a different renderer for VSCode/Notebooks vs Colab argh\n", @@ -117,20 +122,15 @@ { "cell_type": "code", "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "ename": "ModuleNotFoundError", - "evalue": "No module named 'torchtyping'", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", - "Cell \u001b[0;32mIn[3], line 15\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[38;5;28;01mimport\u001b[39;00m \u001b[38;5;21;01mplotly\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mexpress\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m \u001b[38;5;21;01mpx\u001b[39;00m\n\u001b[1;32m 13\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtorch\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mutils\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mdata\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m DataLoader\n\u001b[0;32m---> 15\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtorchtyping\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m TensorType \u001b[38;5;28;01mas\u001b[39;00m TT\n\u001b[1;32m 16\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mtyping\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m List, Union, Optional, Callable\n\u001b[1;32m 17\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;21;01mfunctools\u001b[39;00m \u001b[38;5;28;01mimport\u001b[39;00m partial\n", - "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'torchtyping'" - ] + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:17:48.043320Z", + "iopub.status.busy": "2026-03-06T20:17:48.043256Z", + "iopub.status.idle": "2026-03-06T20:17:49.916214Z", + "shell.execute_reply": "2026-03-06T20:17:49.915938Z" } - ], + }, + "outputs": [], "source": [ "# Import stuff\n", "import torch\n", @@ -146,7 +146,9 @@ "import plotly.express as px\n", "from torch.utils.data import DataLoader\n", "\n", - "from torchtyping import TensorType as TT\n", + "from typing import Any\n", + "class TT: # torchtyping stub\n", + " def __class_getitem__(cls, _): return Any\n", "from typing import List, Union, Optional, Callable\n", "from functools import partial\n", "import copy\n", @@ -156,27 +158,30 @@ "from transformers import AutoModelForCausalLM, AutoConfig, AutoTokenizer\n", "import dataclasses\n", "import datasets\n", - "from IPython.display import HTML, Markdown" + "from IPython.display import HTML, Markdown\n", + "\n", + "import circuitsvis as cv\n" ] }, { "cell_type": "code", - "execution_count": 5, - "metadata": {}, + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:17:49.917420Z", + "iopub.status.busy": "2026-03-06T20:17:49.917312Z", + "iopub.status.idle": "2026-03-06T20:17:50.070729Z", + "shell.execute_reply": "2026-03-06T20:17:50.070436Z" + } + }, "outputs": [], "source": [ "import transformer_lens\n", "import transformer_lens.utils as utils\n", - "from transformer_lens.hook_points import (\n", - " HookedRootModule,\n", - " HookPoint,\n", - ") # Hooking utilities\n", "from transformer_lens import (\n", - " HookedTransformer,\n", - " HookedTransformerConfig,\n", - " FactoredMatrix,\n", " ActivationCache,\n", - ")" + ")\n", + "from transformer_lens.model_bridge import TransformerBridge" ] }, { @@ -188,17 +193,38 @@ }, { "cell_type": "code", - "execution_count": 6, - "metadata": {}, + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:17:50.071943Z", + "iopub.status.busy": "2026-03-06T20:17:50.071881Z", + "iopub.status.idle": "2026-03-06T20:17:50.092786Z", + "shell.execute_reply": "2026-03-06T20:17:50.092534Z" + } + }, "outputs": [], "source": [ - "from neel_plotly import line, imshow, scatter" + "try:\n", + " from neel_plotly import line, imshow, scatter\n", + "except ImportError:\n", + " # neel_plotly is an optional visualization dependency.\n", + " # Define no-op stubs so patching computations still run without it.\n", + " def line(*args, **kwargs): pass\n", + " def imshow(*args, **kwargs): pass\n", + " def scatter(*args, **kwargs): pass\n" ] }, { "cell_type": "code", - "execution_count": 7, - "metadata": {}, + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:17:50.093774Z", + "iopub.status.busy": "2026-03-06T20:17:50.093722Z", + "iopub.status.idle": "2026-03-06T20:17:50.107523Z", + "shell.execute_reply": "2026-03-06T20:17:50.107307Z" + } + }, "outputs": [], "source": [ "import transformer_lens.patching as patching" @@ -214,33 +240,153 @@ }, { "cell_type": "code", - "execution_count": 8, - "metadata": {}, + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:17:50.108499Z", + "iopub.status.busy": "2026-03-06T20:17:50.108449Z", + "iopub.status.idle": "2026-03-06T20:18:11.048982Z", + "shell.execute_reply": "2026-03-06T20:18:11.048621Z" + } + }, "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "85f0ab0d989a42989ffb354440fe312a", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "config.json: 0%| | 0.00/665 [00:00, ?B/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "75b76bb0ff2f491a8e5febeb8166cbd2", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "model.safetensors: 0%| | 0.00/548M [00:00, ?B/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ed2e29a92a38476b92b8921d12824bbc", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Loading weights: 0%| | 0/148 [00:00, ?it/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "f2f7396e6d4e4943b643ec28bd417f65", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "generation_config.json: 0%| | 0.00/124 [00:00, ?B/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, { "name": "stderr", "output_type": "stream", "text": [ - "Using pad_token, but it is not set yet.\n" + "Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n" ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loaded pretrained model gpt2-small into HookedTransformer\n" - ] + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "63c63cf6c0b846099d787f3dc52849de", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "tokenizer_config.json: 0%| | 0.00/26.0 [00:00, ?B/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "a1f007e8fb68491daa3ba444ca49f505", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "vocab.json: 0.00B [00:00, ?B/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "41e0db38a28c4e10aa81cfdae4bea26c", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "merges.txt: 0.00B [00:00, ?B/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "bb91ef19e455404aac3d283f868f9687", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "tokenizer.json: 0.00B [00:00, ?B/s]" + ] + }, + "metadata": {}, + "output_type": "display_data" } ], "source": [ - "model = HookedTransformer.from_pretrained(\"gpt2-small\")\n", + "model = TransformerBridge.boot_transformers(\"gpt2\", device=\"cpu\")\n", + "model.enable_compatibility_mode()\n", "model.set_use_attn_result(True)" ] }, { "cell_type": "code", - "execution_count": 9, - "metadata": {}, + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:18:11.050207Z", + "iopub.status.busy": "2026-03-06T20:18:11.050136Z", + "iopub.status.idle": "2026-03-06T20:18:11.071743Z", + "shell.execute_reply": "2026-03-06T20:18:11.071534Z" + } + }, "outputs": [ { "name": "stdout", @@ -255,7 +401,7 @@ " [ 6035, 15686],\n", " [15686, 6035],\n", " [ 5780, 14235],\n", - " [14235, 5780]], device='cuda:0')\n" + " [14235, 5780]])\n" ] } ], @@ -301,8 +447,15 @@ }, { "cell_type": "code", - "execution_count": 10, - "metadata": {}, + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:18:11.072701Z", + "iopub.status.busy": "2026-03-06T20:18:11.072631Z", + "iopub.status.idle": "2026-03-06T20:18:11.216646Z", + "shell.execute_reply": "2026-03-06T20:18:11.216388Z" + } + }, "outputs": [ { "name": "stdout", @@ -335,8 +488,15 @@ }, { "cell_type": "code", - "execution_count": 11, - "metadata": {}, + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:18:11.217672Z", + "iopub.status.busy": "2026-03-06T20:18:11.217607Z", + "iopub.status.idle": "2026-03-06T20:18:11.235091Z", + "shell.execute_reply": "2026-03-06T20:18:11.234881Z" + } + }, "outputs": [ { "name": "stdout", @@ -372,8 +532,15 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, + "execution_count": 11, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:18:11.236057Z", + "iopub.status.busy": "2026-03-06T20:18:11.236000Z", + "iopub.status.idle": "2026-03-06T20:18:11.248743Z", + "shell.execute_reply": "2026-03-06T20:18:11.248497Z" + } + }, "outputs": [], "source": [ "Metric = Callable[[TT[\"batch_and_pos_dims\", \"d_model\"]], float]" @@ -381,19 +548,26 @@ }, { "cell_type": "code", - "execution_count": 13, - "metadata": {}, + "execution_count": 12, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:18:11.249670Z", + "iopub.status.busy": "2026-03-06T20:18:11.249619Z", + "iopub.status.idle": "2026-03-06T20:18:11.624655Z", + "shell.execute_reply": "2026-03-06T20:18:11.624419Z" + } + }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Clean Value: 1.0\n", - "Clean Activations Cached: 220\n", - "Clean Gradients Cached: 220\n", + "Clean Activations Cached: 517\n", + "Clean Gradients Cached: 563\n", "Corrupted Value: 0.0\n", - "Corrupted Activations Cached: 220\n", - "Corrupted Gradients Cached: 220\n" + "Corrupted Activations Cached: 517\n", + "Corrupted Gradients Cached: 563\n" ] } ], @@ -401,15 +575,56 @@ "filter_not_qkv_input = lambda name: \"_input\" not in name\n", "\n", "\n", - "def get_cache_fwd_and_bwd(model, tokens, metric):\n", - " model.reset_hooks()\n", - " cache = {}\n", + "def _fix_cache_device_and_pos_embed(cache, device):\n", + " \"\"\"Move run_with_cache tensors back to model device (Bug 5 workaround)\n", + " and expand pos_embed to batch dimension (Bug 3 workaround).\"\"\"\n", + " for key in list(cache.cache_dict.keys()):\n", + " val = cache.cache_dict[key]\n", + " if isinstance(val, torch.Tensor):\n", + " cache.cache_dict[key] = val.to(device)\n", + "\n", + " # Expand pos_embed from [1, seq, d_model] to [batch, seq, d_model]\n", + " if \"hook_pos_embed\" in cache.cache_dict:\n", + " pe = cache.cache_dict[\"hook_pos_embed\"]\n", + " if pe.shape[0] == 1:\n", + " # Infer batch size from any other cached activation\n", + " for k, v in cache.cache_dict.items():\n", + " if k != \"hook_pos_embed\" and isinstance(v, torch.Tensor) and v.ndim >= 2 and v.shape[0] > 1:\n", + " cache.cache_dict[\"hook_pos_embed\"] = pe.expand(v.shape[0], -1, -1)\n", + " break\n", + "\n", + "\n", + "def _fix_head_result_cache(cache, model, is_grad=False):\n", + " \"\"\"Fix hook_result shape for TransformerBridge compatibility.\n", "\n", - " def forward_cache_hook(act, hook):\n", - " cache[hook.name] = act.detach()\n", + " TransformerBridge's hook_result captures [batch, pos, d_model] (post-head-sum)\n", + " but stack_head_results expects [batch, pos, n_heads, d_model] (pre-sum).\n", "\n", - " model.add_hook(filter_not_qkv_input, forward_cache_hook, \"fwd\")\n", + " For forward caches: remove entries so compute_head_results recomputes from z + W_O.\n", + " For grad caches: expand gradient from [batch, pos, d_model] to\n", + " [batch, pos, n_heads, d_model] since d(loss)/d(result_h) = d(loss)/d(out) for all h.\n", + " \"\"\"\n", + " n_heads = model.cfg.n_heads\n", + " for layer in range(model.cfg.n_layers):\n", + " key = f\"blocks.{layer}.attn.hook_result\"\n", + " if key not in cache.cache_dict:\n", + " continue\n", + " val = cache.cache_dict[key]\n", + " if not isinstance(val, torch.Tensor) or val.ndim != 3:\n", + " continue # Already correct shape or not a tensor\n", + " if is_grad:\n", + " # Gradient of sum is identity: d(loss)/d(result_h) = d(loss)/d(out)\n", + " cache.cache_dict[key] = val.unsqueeze(-2).expand(\n", + " *val.shape[:-1], n_heads, val.shape[-1]\n", + " )\n", + " else:\n", + " # Remove so compute_head_results can recompute from z + W_O\n", + " del cache.cache_dict[key]\n", "\n", + "\n", + "def get_cache_fwd_and_bwd(model, tokens, metric):\n", + " model.reset_hooks()\n", + " device = model.cfg.device\n", " grad_cache = {}\n", "\n", " def backward_cache_hook(act, hook):\n", @@ -417,13 +632,38 @@ "\n", " model.add_hook(filter_not_qkv_input, backward_cache_hook, \"bwd\")\n", "\n", - " value = metric(model(tokens))\n", + " # run_with_cache handles forward caching with proper alias resolution\n", + " output, fwd_cache = model.run_with_cache(\n", + " tokens, names_filter=filter_not_qkv_input\n", + " )\n", + "\n", + " # Fix forward cache: move from CPU back to model device, expand pos_embed\n", + " _fix_cache_device_and_pos_embed(fwd_cache, device)\n", + "\n", + " value = metric(output)\n", " value.backward()\n", " model.reset_hooks()\n", + "\n", + " # Add alias entries for grad_cache (backward hooks use hook.name, not dict key).\n", + " # Only add aliases that pass our filter to avoid KeyErrors downstream.\n", + " for key, hp in model.hook_dict.items():\n", + " if hp.name != key and filter_not_qkv_input(key):\n", + " if hp.name in grad_cache and key not in grad_cache:\n", + " grad_cache[key] = grad_cache[hp.name]\n", + "\n", + " # Fix grad cache pos_embed batch dimension too\n", + " grad_act_cache = ActivationCache(grad_cache, model)\n", + " _fix_cache_device_and_pos_embed(grad_act_cache, device)\n", + "\n", + " # Fix hook_result shape: TransformerBridge captures [batch, pos, d_model]\n", + " # but stack_head_results expects [batch, pos, n_heads, d_model]\n", + " _fix_head_result_cache(fwd_cache, model, is_grad=False)\n", + " _fix_head_result_cache(grad_act_cache, model, is_grad=True)\n", + "\n", " return (\n", " value.item(),\n", - " ActivationCache(cache, model),\n", - " ActivationCache(grad_cache, model),\n", + " fwd_cache,\n", + " grad_act_cache,\n", " )\n", "\n", "\n", @@ -438,7 +678,7 @@ ")\n", "print(\"Corrupted Value:\", corrupted_value)\n", "print(\"Corrupted Activations Cached:\", len(corrupted_cache))\n", - "print(\"Corrupted Gradients Cached:\", len(corrupted_grad_cache))" + "print(\"Corrupted Gradients Cached:\", len(corrupted_grad_cache))\n" ] }, { @@ -454,8 +694,15 @@ }, { "cell_type": "code", - "execution_count": 14, - "metadata": {}, + "execution_count": 13, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:18:11.625801Z", + "iopub.status.busy": "2026-03-06T20:18:11.625732Z", + "iopub.status.idle": "2026-03-06T20:18:11.643613Z", + "shell.execute_reply": "2026-03-06T20:18:11.643388Z" + } + }, "outputs": [], "source": [ "def create_attention_attr(\n", @@ -480,8 +727,15 @@ }, { "cell_type": "code", - "execution_count": 15, - "metadata": {}, + "execution_count": 14, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:18:11.644629Z", + "iopub.status.busy": "2026-03-06T20:18:11.644574Z", + "iopub.status.idle": "2026-03-06T20:18:11.658123Z", + "shell.execute_reply": "2026-03-06T20:18:11.657927Z" + } + }, "outputs": [ { "name": "stdout", @@ -515,8 +769,15 @@ }, { "cell_type": "code", - "execution_count": 16, - "metadata": {}, + "execution_count": 15, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-06T20:18:11.659129Z", + "iopub.status.busy": "2026-03-06T20:18:11.659081Z", + "iopub.status.idle": "2026-03-06T20:18:12.156846Z", + "shell.execute_reply": "2026-03-06T20:18:12.156025Z" + } + }, "outputs": [ { "data": { @@ -533,96 +794,18 @@ { "data": { "text/html": [ - "\n", - " \n", - "\n", - " \n", - "
\n", - " \n", - " \n", - " " + "\n", + " " ], "text/plain": [ - "