Skip to content

Commit 0b51a7e

Browse files
fabiandegenbryce13950jlarson4
authored
update loading in bert demo to use transformer bridge (#1015)
* updated loading in attribution patching demo to use transformer bridge * updated loading in bert demo to use transformer bridge * Update to allow NSP via bridge * Format and type fixes * Add import * Attribution Patching moved to own branch * Hiding Attribution patching until its own PR --------- Co-authored-by: Bryce Meyer <bryce13950@gmail.com> Co-authored-by: jlarson4 <jonahalarson@comcast.net>
1 parent 1253850 commit 0b51a7e

4 files changed

Lines changed: 167 additions & 50 deletions

File tree

demos/BERT.ipynb

Lines changed: 119 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,66 @@
2828
},
2929
{
3030
"cell_type": "code",
31-
"execution_count": null,
31+
"execution_count": 39,
3232
"metadata": {},
33-
"outputs": [],
34-
"source": "# NBVAL_IGNORE_OUTPUT\nimport os\n\n# Janky code to do different setup when run in a Colab notebook vs VSCode\nDEVELOPMENT_MODE = False\nIN_GITHUB = os.getenv(\"GITHUB_ACTIONS\") == \"true\"\ntry:\n import google.colab\n\n IN_COLAB = True\n print(\"Running as a Colab notebook\")\n\n # PySvelte is an unmaintained visualization library, use it as a backup if circuitsvis isn't working\n # # Install another version of node that makes PySvelte work way faster\n # !curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -; sudo apt-get install -y nodejs\n # %pip install git+https://github.com/neelnanda-io/PySvelte.git\nexcept:\n IN_COLAB = False\n\nif not IN_GITHUB and not IN_COLAB:\n print(\"Running as a Jupyter notebook - intended for development only!\")\n from IPython import get_ipython\n\n ipython = get_ipython()\n # Code to automatically update the HookedTransformer code as its edited without restarting the kernel\n ipython.run_line_magic(\"load_ext\", \"autoreload\")\n ipython.run_line_magic(\"autoreload\", \"2\")\n\nif IN_COLAB:\n %pip install transformer_lens\n %pip install circuitsvis"
33+
"outputs": [
34+
{
35+
"name": "stdout",
36+
"output_type": "stream",
37+
"text": [
38+
"Running as a Jupyter notebook - intended for development only!\n",
39+
"The autoreload extension is already loaded. To reload it, use:\n",
40+
" %reload_ext autoreload\n"
41+
]
42+
}
43+
],
44+
"source": [
45+
"# NBVAL_IGNORE_OUTPUT\n",
46+
"import os\n",
47+
"\n",
48+
"# Janky code to do different setup when run in a Colab notebook vs VSCode\n",
49+
"DEVELOPMENT_MODE = False\n",
50+
"IN_GITHUB = os.getenv(\"GITHUB_ACTIONS\") == \"true\"\n",
51+
"try:\n",
52+
" import google.colab\n",
53+
"\n",
54+
" IN_COLAB = True\n",
55+
" print(\"Running as a Colab notebook\")\n",
56+
"\n",
57+
" # PySvelte is an unmaintained visualization library, use it as a backup if circuitsvis isn't working\n",
58+
" # # Install another version of node that makes PySvelte work way faster\n",
59+
" # !curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -; sudo apt-get install -y nodejs\n",
60+
" # %pip install git+https://github.com/neelnanda-io/PySvelte.git\n",
61+
"except:\n",
62+
" IN_COLAB = False\n",
63+
"\n",
64+
"if not IN_GITHUB and not IN_COLAB:\n",
65+
" print(\"Running as a Jupyter notebook - intended for development only!\")\n",
66+
" from IPython import get_ipython\n",
67+
"\n",
68+
" ipython = get_ipython()\n",
69+
" # Code to automatically update the HookedTransformer code as its edited without restarting the kernel\n",
70+
" ipython.run_line_magic(\"load_ext\", \"autoreload\")\n",
71+
" ipython.run_line_magic(\"autoreload\", \"2\")\n",
72+
"\n",
73+
"if IN_COLAB:\n",
74+
" %pip install transformer_lens\n",
75+
" %pip install circuitsvis"
76+
]
3577
},
3678
{
3779
"cell_type": "code",
38-
"execution_count": null,
80+
"execution_count": 40,
3981
"metadata": {},
40-
"outputs": [],
82+
"outputs": [
83+
{
84+
"name": "stdout",
85+
"output_type": "stream",
86+
"text": [
87+
"Using renderer: colab\n"
88+
]
89+
}
90+
],
4191
"source": [
4292
"# Plotly needs a different renderer for VSCode/Notebooks vs Colab argh\n",
4393
"import plotly.io as pio\n",
@@ -51,27 +101,27 @@
51101
},
52102
{
53103
"cell_type": "code",
54-
"execution_count": 3,
104+
"execution_count": 41,
55105
"metadata": {},
56106
"outputs": [
57107
{
58108
"data": {
59109
"text/html": [
60-
"<div id=\"circuits-vis-8c91db10-74f4\" style=\"margin: 15px 0;\"/>\n",
110+
"<div id=\"circuits-vis-1f660292-2b7d\" style=\"margin: 15px 0;\"/>\n",
61111
" <script crossorigin type=\"module\">\n",
62-
" import { render, Hello } from \"https://unpkg.com/circuitsvis@1.43.2/dist/cdn/esm.js\";\n",
112+
" import { render, Hello } from \"https://unpkg.com/circuitsvis@1.43.3/dist/cdn/esm.js\";\n",
63113
" render(\n",
64-
" \"circuits-vis-8c91db10-74f4\",\n",
114+
" \"circuits-vis-1f660292-2b7d\",\n",
65115
" Hello,\n",
66116
" {\"name\": \"Neel\"}\n",
67117
" )\n",
68118
" </script>"
69119
],
70120
"text/plain": [
71-
"<circuitsvis.utils.render.RenderedHTML at 0x13a9760d0>"
121+
"<circuitsvis.utils.render.RenderedHTML at 0x1647e3b60>"
72122
]
73123
},
74-
"execution_count": 3,
124+
"execution_count": 41,
75125
"metadata": {},
76126
"output_type": "execute_result"
77127
}
@@ -85,7 +135,7 @@
85135
},
86136
{
87137
"cell_type": "code",
88-
"execution_count": 4,
138+
"execution_count": 42,
89139
"metadata": {},
90140
"outputs": [],
91141
"source": [
@@ -94,12 +144,12 @@
94144
"\n",
95145
"from transformers import AutoTokenizer\n",
96146
"\n",
97-
"from transformer_lens import HookedEncoder, BertNextSentencePrediction"
147+
"from transformer_lens.model_bridge import TransformerBridge"
98148
]
99149
},
100150
{
101151
"cell_type": "code",
102-
"execution_count": null,
152+
"execution_count": 43,
103153
"metadata": {},
104154
"outputs": [],
105155
"source": [
@@ -119,30 +169,29 @@
119169
},
120170
{
121171
"cell_type": "code",
122-
"execution_count": 6,
172+
"execution_count": 44,
123173
"metadata": {},
124174
"outputs": [
125175
{
126-
"name": "stderr",
127-
"output_type": "stream",
128-
"text": [
129-
"WARNING:root:Support for BERT in TransformerLens is currently experimental, until such a time when it has feature parity with HookedTransformer and has been tested on real research tasks. Until then, backward compatibility is not guaranteed. Please see the docs for information on the limitations of the current implementation.\n",
130-
"If using BERT for interpretability research, keep in mind that BERT has some significant architectural differences to GPT. For example, LayerNorms are applied *after* the attention and MLP components, meaning that the last LayerNorm in a block cannot be folded.\n"
131-
]
132-
},
133-
{
134-
"name": "stdout",
135-
"output_type": "stream",
136-
"text": [
137-
"Moving model to device: mps\n",
138-
"Loaded pretrained model bert-base-cased into HookedTransformer\n"
139-
]
176+
"data": {
177+
"application/vnd.jupyter.widget-view+json": {
178+
"model_id": "1d4b75dcfcbf488da7196992cde5c9bb",
179+
"version_major": 2,
180+
"version_minor": 0
181+
},
182+
"text/plain": [
183+
"Loading weights: 0%| | 0/202 [00:00<?, ?it/s]"
184+
]
185+
},
186+
"metadata": {},
187+
"output_type": "display_data"
140188
}
141189
],
142190
"source": [
143191
"# NBVAL_IGNORE_OUTPUT\n",
144-
"bert = HookedEncoder.from_pretrained(\"bert-base-cased\")\n",
145-
"tokenizer = AutoTokenizer.from_pretrained(\"bert-base-cased\")"
192+
"bert = TransformerBridge.boot_transformers(\"google-bert/bert-base-cased\")\n",
193+
"bert.enable_compatibility_mode()\n",
194+
"tokenizer = AutoTokenizer.from_pretrained(\"google-bert/bert-base-cased\")"
146195
]
147196
},
148197
{
@@ -158,7 +207,7 @@
158207
},
159208
{
160209
"cell_type": "code",
161-
"execution_count": 7,
210+
"execution_count": 45,
162211
"metadata": {},
163212
"outputs": [
164213
{
@@ -188,15 +237,15 @@
188237
},
189238
{
190239
"cell_type": "code",
191-
"execution_count": 8,
240+
"execution_count": 46,
192241
"metadata": {},
193242
"outputs": [
194243
{
195244
"name": "stdout",
196245
"output_type": "stream",
197246
"text": [
198247
"Prompt: ['The [MASK] is bright today.', 'She [MASK] to the store.', 'The dog [MASK] the ball.']\n",
199-
"Prediction: \"['Prediction 0: sun', 'Prediction 1: went', 'Prediction 2: caught']\"\n"
248+
"Prediction: \"['Prediction 0: sun', 'Prediction 1: returned', 'Prediction 2: has']\"\n"
200249
]
201250
}
202251
],
@@ -214,16 +263,30 @@
214263
"metadata": {},
215264
"source": [
216265
"## Next Sentence Prediction\n",
217-
"To carry out Next Sentence Prediction, you have to use the class BertNextSentencePrediction, and pass a HookedEncoder in its constructor. \n",
218-
"Then, create a list with the two sentences you want to perform NSP on as elements and use that as input to the forward function. \n",
219-
"The model will then predict the probability of the sentence at position 1 following (i.e. being the next sentence) to the sentence at position 0."
266+
"To carry out Next Sentence Prediction, load a `BertForNextSentencePrediction` model via TransformerBridge. \n",
267+
"Then, tokenize a pair of sentences and pass the tokens to the model. \n",
268+
"The model predicts the probability of the second sentence following the first."
220269
]
221270
},
222271
{
223272
"cell_type": "code",
224-
"execution_count": 9,
273+
"execution_count": 47,
225274
"metadata": {},
226275
"outputs": [
276+
{
277+
"data": {
278+
"application/vnd.jupyter.widget-view+json": {
279+
"model_id": "9cea0cab8d47422098f5bcec64a74126",
280+
"version_major": 2,
281+
"version_minor": 0
282+
},
283+
"text/plain": [
284+
"Loading weights: 0%| | 0/201 [00:00<?, ?it/s]"
285+
]
286+
},
287+
"metadata": {},
288+
"output_type": "display_data"
289+
},
227290
{
228291
"name": "stdout",
229292
"output_type": "stream",
@@ -235,13 +298,21 @@
235298
}
236299
],
237300
"source": [
238-
"nsp = BertNextSentencePrediction(bert)\n",
301+
"# NBVAL_IGNORE_OUTPUT\n",
302+
"from transformers import BertForNextSentencePrediction\n",
303+
"\n",
304+
"nsp = TransformerBridge.boot_transformers(\n",
305+
" \"google-bert/bert-base-cased\",\n",
306+
" model_class=BertForNextSentencePrediction,\n",
307+
")\n",
308+
"nsp.enable_compatibility_mode()\n",
309+
"\n",
239310
"sentence_a = \"A man walked into a grocery store.\"\n",
240311
"sentence_b = \"He bought an apple.\"\n",
241312
"\n",
242-
"input = [sentence_a, sentence_b]\n",
243-
"\n",
244-
"predictions = nsp(input, return_type=\"predictions\")\n",
313+
"inputs = tokenizer(sentence_a, sentence_b, return_tensors=\"pt\")\n",
314+
"device = next(nsp.parameters()).device\n",
315+
"predictions = nsp(inputs[\"input_ids\"].to(device), return_type=\"predictions\")\n",
245316
"\n",
246317
"print(f\"Sentence A: {sentence_a}\")\n",
247318
"print(f\"Sentence B: {sentence_b}\")\n",
@@ -258,7 +329,7 @@
258329
},
259330
{
260331
"cell_type": "code",
261-
"execution_count": 10,
332+
"execution_count": 48,
262333
"metadata": {},
263334
"outputs": [
264335
{
@@ -274,6 +345,8 @@
274345
"prompt = \"The [MASK] is bright today.\"\n",
275346
"\n",
276347
"tokens = tokenizer(prompt, return_tensors=\"pt\")[\"input_ids\"]\n",
348+
"device = next(bert.parameters()).device\n",
349+
"tokens = tokens.to(device)\n",
277350
"logits = bert(tokens) # Since we are not specifying return_type, we get the logits\n",
278351
"logprobs = logits[tokens == tokenizer.mask_token_id].log_softmax(dim=-1)\n",
279352
"prediction = tokenizer.decode(logprobs.argmax(dim=-1).item())\n",
@@ -293,7 +366,7 @@
293366
],
294367
"metadata": {
295368
"kernelspec": {
296-
"display_name": "Python 3",
369+
"display_name": "transformer-lens",
297370
"language": "python",
298371
"name": "python3"
299372
},
@@ -307,10 +380,10 @@
307380
"name": "python",
308381
"nbconvert_exporter": "python",
309382
"pygments_lexer": "ipython3",
310-
"version": "3.10.15"
383+
"version": "3.12.12"
311384
},
312385
"orig_nbformat": 4
313386
},
314387
"nbformat": 4,
315388
"nbformat_minor": 2
316-
}
389+
}

transformer_lens/model_bridge/bridge.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ def boot_transformers(
149149
tokenizer: Optional[Any] = None,
150150
load_weights: bool = True,
151151
trust_remote_code: bool = False,
152+
model_class: Optional[type] = None,
152153
) -> "TransformerBridge":
153154
"""Boot a model from HuggingFace (alias for sources.transformers.boot).
154155
@@ -160,6 +161,8 @@ def boot_transformers(
160161
tokenizer: Optional pre-initialized tokenizer to use; if not provided one will be created.
161162
load_weights: If False, load model without weights (on meta device) for config inspection only.
162163
trust_remote_code: Whether to trust remote code for custom model architectures.
164+
model_class: Optional HuggingFace model class to use instead of the default
165+
auto-detected class (e.g., BertForNextSentencePrediction).
163166
164167
Returns:
165168
The bridge to the loaded model.
@@ -174,6 +177,7 @@ def boot_transformers(
174177
tokenizer=tokenizer,
175178
load_weights=load_weights,
176179
trust_remote_code=trust_remote_code,
180+
model_class=model_class,
177181
)
178182

179183
@property
@@ -1206,7 +1210,7 @@ def forward(
12061210
12071211
Args:
12081212
input: Input to the model
1209-
return_type: Type of output to return ('logits', 'loss', 'both', None)
1213+
return_type: Type of output to return ('logits', 'loss', 'both', 'predictions', None)
12101214
loss_per_token: Whether to return loss per token
12111215
prepend_bos: Whether to prepend BOS token
12121216
padding_side: Which side to pad on
@@ -1341,6 +1345,26 @@ def forward(
13411345
), f"Expected logits tensor, got {type(logits)}"
13421346
loss = self.loss_fn(logits, input_ids, per_token=loss_per_token)
13431347
return (logits, loss)
1348+
elif return_type == "predictions":
1349+
assert (
1350+
self.tokenizer is not None
1351+
), "Must have a tokenizer to use return_type='predictions'"
1352+
if logits.shape[-1] == 2:
1353+
# Next Sentence Prediction — 2-class output
1354+
logprobs = logits.log_softmax(dim=-1)
1355+
predictions = [
1356+
"The sentences are sequential",
1357+
"The sentences are NOT sequential",
1358+
]
1359+
return predictions[logprobs.argmax(dim=-1).item()]
1360+
else:
1361+
# Masked Language Modeling — decode [MASK] tokens
1362+
logprobs = logits[input_ids == self.tokenizer.mask_token_id].log_softmax(dim=-1)
1363+
predictions = self.tokenizer.decode(logprobs.argmax(dim=-1))
1364+
if " " in predictions:
1365+
predictions = predictions.split(" ")
1366+
predictions = [f"Prediction {i}: {p}" for i, p in enumerate(predictions)]
1367+
return predictions
13441368
elif return_type is None:
13451369
return None
13461370
else:

0 commit comments

Comments
 (0)