Coverage for transformer_lens/HookedEncoderDecoder.py: 70%

277 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2026-09-01 16:23 +0000

1"""Hooked EncoderDecoder 

2 

3Contains a T5 style model. This is separate from :class:`transformer_lens.HookedTransformer` 

4because it has a significantly different architecture to e.g. GPT style transformers. 

5""" 

6 

7from __future__ import annotations 

8 

9import logging 

10import os 

11from itertools import chain 

12from pathlib import Path 

13from typing import ( 

14 Any, 

15 Dict, 

16 List, 

17 Optional, 

18 Tuple, 

19 Type, 

20 TypeVar, 

21 Union, 

22 cast, 

23 overload, 

24) 

25 

26import torch 

27import tqdm 

28from einops import repeat 

29from jaxtyping import Float, Int 

30from transformers import AutoTokenizer, PreTrainedTokenizerBase 

31from typing_extensions import Literal 

32 

33import transformer_lens.loading_from_pretrained as loading 

34from transformer_lens.ActivationCache import ActivationCache 

35from transformer_lens.components import MLP, Embed, GatedMLP, RMSNorm, T5Block, Unembed 

36from transformer_lens.config.hooked_transformer_config import HookedTransformerConfig 

37from transformer_lens.FactoredMatrix import FactoredMatrix 

38from transformer_lens.hook_points import HookPoint 

39from transformer_lens.HookedRootModule import HookedRootModule 

40from transformer_lens.utilities import TypedModuleList, sample_logits, warn_if_mps 

41from transformer_lens.utilities.multi_gpu import get_device_for_block_index 

42 

43T = TypeVar("T", bound="HookedEncoderDecoder") 

44 

45 

46class HookedEncoderDecoder(HookedRootModule): 

47 """ 

48 This class implements a T5 encoder-decoder using the components in ./components.py, with HookPoints on every interesting activation. It inherits from HookedRootModule. 

49 

50 Limitations: 

51 - Also note that model does not include dropouts, which may lead to inconsistent results from training or fine-tuning. 

52 

53 Like HookedTransformer, it can have a pretrained Transformer's weights loaded via `.from_pretrained`. There are a few features you might know from HookedTransformer which are not yet supported: 

54 - There is no preprocessing (e.g. LayerNorm folding) when loading a pretrained model 

55 - The model only accepts tokens as inputs, and not strings, or lists of strings 

56 """ 

57 

58 tokenizer: Optional[PreTrainedTokenizerBase] 

59 encoder: TypedModuleList[T5Block] 

60 decoder: TypedModuleList[T5Block] 

61 

62 def __init__( 

63 self, 

64 cfg: Union[HookedTransformerConfig, Dict], 

65 tokenizer: Optional[PreTrainedTokenizerBase] = None, 

66 move_to_device: bool = True, 

67 **kwargs: Any, 

68 ): 

69 super().__init__() 

70 if isinstance(cfg, Dict): 70 ↛ 71line 70 didn't jump to line 71 because the condition on line 70 was never true

71 cfg = HookedTransformerConfig(**cfg) 

72 elif isinstance(cfg, str): 72 ↛ 73line 72 didn't jump to line 73 because the condition on line 72 was never true

73 raise ValueError( 

74 "Please pass in a config dictionary or HookedTransformerConfig object. If you want to load a pretrained model, use HookedEncoderDecoder.from_pretrained() instead." 

75 ) 

76 self.cfg: HookedTransformerConfig = cfg 

77 

78 if self.cfg.n_devices != 1: 78 ↛ 79line 78 didn't jump to line 79 because the condition on line 78 was never true

79 raise ValueError("Multiple devices not supported for HookedEncoderDecoder") 

80 if tokenizer is not None: 80 ↛ 81line 80 didn't jump to line 81 because the condition on line 80 was never true

81 self.tokenizer = tokenizer 

82 elif self.cfg.tokenizer_name is not None: 82 ↛ 89line 82 didn't jump to line 89 because the condition on line 82 was always true

83 huggingface_token = os.environ.get("HF_TOKEN", "") 

84 self.tokenizer = AutoTokenizer.from_pretrained( 

85 self.cfg.tokenizer_name, 

86 token=huggingface_token if len(huggingface_token) > 0 else None, 

87 ) 

88 else: 

89 self.tokenizer = None 

90 

91 if self.cfg.d_vocab == -1: 91 ↛ 93line 91 didn't jump to line 93 because the condition on line 91 was never true

92 # If we have a tokenizer, vocab size can be inferred from it. 

93 if self.tokenizer is None: 

94 raise ValueError("Must provide a tokenizer if d_vocab is not provided") 

95 

96 self.cfg.d_vocab = len(self.tokenizer) 

97 if self.cfg.d_vocab_out == -1: 97 ↛ 98line 97 didn't jump to line 98 because the condition on line 97 was never true

98 self.cfg.d_vocab_out = self.cfg.d_vocab 

99 

100 self.embed = Embed(self.cfg) 

101 self.encoder = TypedModuleList( 

102 [ 

103 T5Block(self.cfg, num_layer, is_decoder=False) 

104 for num_layer in range(self.cfg.n_layers) 

105 ] 

106 ) 

107 self.encoder_final_ln = RMSNorm(self.cfg) 

108 self.decoder = TypedModuleList( 

109 [ 

110 T5Block(self.cfg, num_layer, is_decoder=True) 

111 for num_layer in range(self.cfg.n_layers) 

112 ] 

113 ) 

114 self.decoder_final_ln = RMSNorm(self.cfg) 

115 self.unembed = Unembed(self.cfg) 

116 

117 self.hook_embed = HookPoint() 

118 

119 if move_to_device and self.cfg.device is not None: 119 ↛ 120line 119 didn't jump to line 120 because the condition on line 119 was never true

120 self.to(self.cfg.device) 

121 

122 self.setup() 

123 

124 def to_tokens( 

125 self, 

126 input: Union[str, List[str]], 

127 move_to_device: bool = True, 

128 truncate: bool = True, 

129 ) -> Tuple[Int[torch.Tensor, "batch pos"], Int[torch.Tensor, "batch pos"]]: 

130 """Converts a string to a tensor of tokens. 

131 Taken mostly from the HookedTransformer implementation, but does not support default padding 

132 sides or prepend_bos. 

133 

134 Args: 

135 input (Union[str, List[str]]): The input to tokenize. 

136 move_to_device (bool): Whether to move the output tensor of tokens to the device the 

137 model lives on. Defaults to True 

138 truncate (bool): If the output tokens are too long, whether to truncate the output 

139 tokens to the model's max context window. Does nothing for shorter inputs. 

140 Defaults to True. 

141 """ 

142 

143 assert self.tokenizer is not None, "Cannot use to_tokens without a tokenizer" 

144 

145 encodings = self.tokenizer( 

146 input, 

147 return_tensors="pt", 

148 padding=True, 

149 truncation=truncate, 

150 max_length=self.cfg.n_ctx if truncate else None, 

151 ) 

152 

153 tokens = encodings.input_ids 

154 attention_mask = encodings.attention_mask 

155 

156 if move_to_device: 156 ↛ 159line 156 didn't jump to line 159 because the condition on line 156 was always true

157 tokens = tokens.to(self.cfg.device) 

158 attention_mask = attention_mask.to(self.cfg.device) 

159 return tokens, attention_mask 

160 

161 @overload 

162 def forward( 

163 self, 

164 input: Union[str, List[str], Int[torch.Tensor, "batch pos"]], 

165 decoder_input: Optional[Int[torch.Tensor, "batch decoder_pos"]] = None, 

166 return_type: Literal["logits"] = "logits", 

167 one_zero_attention_mask: Optional[Int[torch.Tensor, "batch pos"]] = None, 

168 ) -> Float[torch.Tensor, "batch pos d_vocab"]: 

169 ... 

170 

171 @overload 

172 def forward( 

173 self, 

174 input: Union[str, List[str], Int[torch.Tensor, "batch pos"]], 

175 decoder_input: Optional[Int[torch.Tensor, "batch decoder_pos"]] = None, 

176 return_type: Optional[Literal[None]] = None, 

177 one_zero_attention_mask: Optional[Int[torch.Tensor, "batch pos"]] = None, 

178 ) -> Optional[Float[torch.Tensor, "batch pos d_vocab"]]: 

179 ... 

180 

181 def forward( 

182 self, 

183 input: Union[str, List[str], Int[torch.Tensor, "batch pos"]], 

184 decoder_input: Optional[Int[torch.Tensor, "batch decoder_pos"]] = None, 

185 return_type: Optional[str] = "logits", 

186 one_zero_attention_mask: Optional[Int[torch.Tensor, "batch pos"]] = None, 

187 ) -> Optional[Float[torch.Tensor, "batch decoder_pos d_vocab"]]: 

188 """Forward pass of the T5 model. 

189 

190 Args: 

191 input: Input to be processed. Can be one of: 

192 - str: A single string input 

193 - List[str]: A batch of string inputs 

194 - Int[torch.Tensor, "batch pos"]: A batch of token IDs 

195 decoder_input: Tensor of shape (batch, decoder_pos) containing the decoder input sequence. 

196 If None and input is of type str or List[str], starts with batch of beginning-of-sequence (BOS) tokens. 

197 return_type: Specifies the model output type: 

198 - "logits": Return logits tensor 

199 - None: Returns nothing 

200 one_zero_attention_mask: A binary mask which indicates 

201 which tokens should be attended to (1) and which should be ignored (0). 

202 Primarily used for padding variable-length sentences in a batch. 

203 For instance, in a batch with sentences of differing lengths, shorter 

204 sentences are padded with 0s on the right. If not provided, the model 

205 assumes all tokens should be attended to. 

206 This parameter gets inferred from the tokenizer if input is a string or list of strings. 

207 Shape is (batch_size, sequence_length). 

208 

209 Returns: 

210 Optional[Float[torch.Tensor, "batch decoder_pos d_vocab"]]: 

211 If return_type="logits": Returns logits tensor of shape (batch, decoder_pos, vocab_size) 

212 If return_type=None: Returns None 

213 """ 

214 

215 if isinstance(input, (str, list)): 

216 tokens, attention_mask = self.to_tokens(input) 

217 

218 # If attention mask is not provided, use the ones from the tokenizer 

219 one_zero_attention_mask = ( 

220 attention_mask if one_zero_attention_mask is None else one_zero_attention_mask 

221 ) 

222 

223 # If decoder_input is not provided, start with tensor of PAD tokens of shape (batch, 1) 

224 if decoder_input is None: 224 ↛ 244line 224 didn't jump to line 244 because the condition on line 224 was always true

225 assert self.tokenizer is not None 

226 decoder_input = torch.full( 

227 (tokens.shape[0], 1), 

228 self.tokenizer.pad_token_id, 

229 device=self.cfg.device, 

230 ) 

231 else: 

232 tokens = input 

233 

234 if one_zero_attention_mask is None: 

235 logging.warning( 

236 "No attention mask provided. Assuming all tokens should be attended to." 

237 ) 

238 

239 if decoder_input is None: 239 ↛ 240line 239 didn't jump to line 240 because the condition on line 239 was never true

240 raise ValueError( 

241 "Must provide decoder_input if input is not a string or list of strings" 

242 ) 

243 

244 if tokens.device.type != self.cfg.device: 244 ↛ 245line 244 didn't jump to line 245 because the condition on line 244 was never true

245 tokens = tokens.to(self.cfg.device) 

246 

247 if one_zero_attention_mask is not None: 

248 one_zero_attention_mask = one_zero_attention_mask.to(self.cfg.device) 

249 

250 resid = self.hook_embed(self.embed(tokens)) 

251 

252 if one_zero_attention_mask is not None: 

253 additive_attention_mask = ( 

254 repeat(1 - one_zero_attention_mask, "batch pos -> batch 1 1 pos") 

255 ) * torch.finfo(self.cfg.dtype).min 

256 else: 

257 additive_attention_mask = None 

258 

259 query_len = key_len = tokens.shape[1] 

260 

261 encoder_positional_bias = self.encoder[0].attn.compute_relative_attention_bias( 

262 query_len, key_len, device=self.cfg.device 

263 ) 

264 

265 for encoder_block in self.encoder: 

266 resid = encoder_block( 

267 resid_pre=resid, 

268 additive_attention_mask=additive_attention_mask, 

269 position_bias=encoder_positional_bias, 

270 ) 

271 

272 encoder_resid = self.encoder_final_ln(resid) 

273 

274 if decoder_input is None: 274 ↛ 275line 274 didn't jump to line 275 because the condition on line 274 was never true

275 raise ValueError("decoder_input cannot be None when input is not a string") 

276 decoder_resid = self.embed(decoder_input) 

277 decoder_query_len = decoder_key_len = decoder_input.shape[1] 

278 decoder_positional_bias = self.decoder[0].attn.compute_relative_attention_bias( 

279 decoder_query_len, decoder_key_len, device=self.cfg.device 

280 ) 

281 

282 for decoder_block in self.decoder: 

283 decoder_resid = decoder_block( 

284 resid_pre=decoder_resid, 

285 position_bias=decoder_positional_bias, 

286 encoder_hidden_states=encoder_resid, 

287 encoder_additive_attention_mask=additive_attention_mask, 

288 ) 

289 

290 decoder_resid = self.decoder_final_ln(decoder_resid) 

291 

292 if self.cfg.tie_word_embeddings: 292 ↛ 297line 292 didn't jump to line 297 because the condition on line 292 was always true

293 # Rescale output before projecting on vocab 

294 # See https://github.com/tensorflow/mesh/blob/fa19d69eafc9a482aff0b59ddd96b025c0cb207d/mesh_tensorflow/transformer/transformer.py#L586 

295 decoder_resid *= self.cfg.d_model**-0.5 

296 

297 logits = self.unembed(decoder_resid) 

298 if return_type is None: 298 ↛ 299line 298 didn't jump to line 299 because the condition on line 298 was never true

299 return None 

300 return logits 

301 

302 @torch.inference_mode() 

303 def generate( 

304 self, 

305 input: Union[str, Int[torch.Tensor, "batch pos"]] = "", 

306 one_zero_attention_mask: Optional[Int[torch.Tensor, "batch pos"]] = None, 

307 max_new_tokens: int = 10, 

308 stop_at_eos: bool = True, 

309 eos_token_id: Optional[Union[int, List[int]]] = None, 

310 do_sample: bool = True, 

311 top_k: Optional[int] = None, 

312 top_p: Optional[float] = None, 

313 temperature: float = 1.0, 

314 freq_penalty: float = 0.0, 

315 return_type: Optional[str] = "input", 

316 verbose: bool = True, 

317 ) -> Union[Int[torch.Tensor, "batch new_tokens"], str]: 

318 """Sample tokens from the T5 encoder-decoder model. 

319 

320 Sample tokens from the model until the model outputs eos_token or max_new_tokens is reached. 

321 This function is primarily taken from HookedTransformer but adjusted for the HookedEncoderDecoder 

322 architecture. 

323 This function does not support key value caching and no default padding sides or prepend_bos. 

324 

325 To avoid fiddling with ragged tensors, if we input a batch of text and some sequences finish 

326 (by producing an EOT token), we keep running the model on the entire batch, but throw away 

327 the output for a finished sequence and just keep adding EOTs to pad. 

328 

329 This supports entering a single string, but not a list of strings - if the strings don't 

330 tokenize to exactly the same length, this gets messy. If that functionality is needed, 

331 convert them to a batch of tokens and input that instead. 

332 

333 Args: 

334 input (Union[str, Int[torch.Tensor, "batch pos"])]): Either a batch of tokens ([batch, 

335 pos]) or a text string (this will be converted to a batch of tokens with batch size 

336 1). 

337 max_new_tokens (int): Maximum number of tokens to generate. 

338 stop_at_eos (bool): If True, stop generating tokens when the model outputs eos_token. 

339 eos_token_id (Optional[Union[int, Sequence]]): The token ID to use for end 

340 of sentence. If None, use the tokenizer's eos_token_id - required if using 

341 stop_at_eos. It's also possible to provide a list of token IDs (not just the 

342 eos_token_id), in which case the generation will stop when any of them are output 

343 (useful e.g. for stable_lm). 

344 do_sample (bool): If True, sample from the model's output distribution. Otherwise, use 

345 greedy search (take the max logit each time). 

346 top_k (int): Number of tokens to sample from. If None, sample from all tokens. 

347 top_p (float): Probability mass to sample from. If 1.0, sample from all tokens. If <1.0, 

348 we take the top tokens with cumulative probability >= top_p. 

349 temperature (float): Temperature for sampling. Higher values will make the model more 

350 random (limit of temp -> 0 is just taking the top token, limit of temp -> inf is 

351 sampling from a uniform distribution). 

352 freq_penalty (float): Frequency penalty for sampling - how much to penalise previous 

353 tokens. Higher values will make the model more random. 

354 return_type (Optional[str]): The type of the output to return - either a string (str), 

355 a tensor of tokens (tensor) or whatever the format of the input was (input). 

356 verbose (bool): If True, show tqdm progress bars for generation. 

357 

358 Returns: 

359 outputs (torch.Tensor): [batch, new_tokens], generated sequence of new tokens 

360 (by default returns same type as input). 

361 """ 

362 

363 if isinstance(input, str): 363 ↛ 375line 363 didn't jump to line 375 because the condition on line 363 was always true

364 # If text, convert to tokens (batch_size=1) 

365 assert ( 

366 self.tokenizer is not None 

367 ), "Must provide a tokenizer if passing a string to the model" 

368 encoder_input, attention_mask = self.to_tokens(input) 

369 

370 # If attention mask is not provided, use the one from the tokenizer 

371 one_zero_attention_mask = ( 

372 attention_mask if one_zero_attention_mask is None else one_zero_attention_mask 

373 ) 

374 else: 

375 assert isinstance(input, torch.Tensor) # keep mypy happy 

376 encoder_input = input 

377 

378 # If tokens are provided, user should be aware that attention mask will not be inferred 

379 if one_zero_attention_mask is None: 

380 logging.warning( 

381 "No attention mask provided. Assuming all tokens should be attended to." 

382 ) 

383 

384 if return_type == "input": 384 ↛ 390line 384 didn't jump to line 390 because the condition on line 384 was always true

385 if isinstance(input, str): 385 ↛ 388line 385 didn't jump to line 388 because the condition on line 385 was always true

386 return_type = "str" 

387 else: 

388 return_type = "tensor" 

389 

390 assert isinstance(encoder_input, torch.Tensor) 

391 batch_size = encoder_input.shape[0] 

392 device = get_device_for_block_index(0, self.cfg) 

393 

394 # For the decoder input, we start with a tensor of PAD tokens of shape (batch, 1) 

395 assert self.tokenizer is not None 

396 decoder_input = torch.full((batch_size, 1), self.tokenizer.pad_token_id).to(device) 

397 

398 stop_tokens: List[int] = [] 

399 eos_token_for_padding = 0 

400 if stop_at_eos: 400 ↛ 426line 400 didn't jump to line 426 because the condition on line 400 was always true

401 tokenizer_has_eos_token = self.tokenizer.eos_token_id is not None 

402 

403 local_eos_token_id: Optional[Union[int, List[int]]] = eos_token_id 

404 if local_eos_token_id is None: 404 ↛ 411line 404 didn't jump to line 411 because the condition on line 404 was always true

405 assert ( 

406 tokenizer_has_eos_token 

407 ), "Must pass a eos_token_id if stop_at_eos is True and tokenizer is None or has no eos_token_id" 

408 

409 local_eos_token_id = self.tokenizer.eos_token_id 

410 

411 if isinstance(local_eos_token_id, int): 411 ↛ 416line 411 didn't jump to line 416 because the condition on line 411 was always true

412 stop_tokens = [local_eos_token_id] 

413 eos_token_for_padding = local_eos_token_id 

414 else: 

415 # eos_token_id is a Sequence (e.g. list or tuple) 

416 if local_eos_token_id is None: 

417 raise ValueError("eos_token_id cannot be None here") 

418 stop_tokens = local_eos_token_id 

419 eos_token_for_padding = ( 

420 self.tokenizer.eos_token_id 

421 if tokenizer_has_eos_token 

422 else local_eos_token_id[0] 

423 ) 

424 

425 # An array to track which sequences in the batch have finished. 

426 finished_sequences = torch.zeros(batch_size, dtype=torch.bool, device=self.cfg.device) 

427 

428 # Currently nothing in HookedTransformer changes with eval, but this is here in case 

429 # that changes in the future. 

430 self.eval() 

431 for _ in tqdm.tqdm(range(max_new_tokens), disable=not verbose): 431 ↛ 478line 431 didn't jump to line 478 because the loop on line 431 didn't complete

432 # While generating, we keep generating logits, throw away all but the final logits, 

433 # and then use those logits to sample from the distribution We keep adding the 

434 # sampled tokens to the end of tokens. 

435 # We input the entire sequence, as a [batch, pos] tensor, since we aren't using 

436 # the cache. 

437 

438 # Encoder input will be the same for all iterations 

439 # Decoder input will be appended with the new token each iteration 

440 logits = self.forward( 

441 encoder_input, 

442 decoder_input=decoder_input, 

443 one_zero_attention_mask=one_zero_attention_mask, 

444 ) 

445 assert logits is not None 

446 final_logits = logits[:, -1, :] 

447 

448 if do_sample: 448 ↛ 449line 448 didn't jump to line 449 because the condition on line 448 was never true

449 sampled_tokens = sample_logits( 

450 final_logits, 

451 top_k=top_k, 

452 top_p=top_p, 

453 temperature=temperature, 

454 freq_penalty=freq_penalty, 

455 tokens=decoder_input, 

456 ).to(get_device_for_block_index(0, self.cfg)) 

457 else: 

458 sampled_tokens = final_logits.argmax(-1).to(get_device_for_block_index(0, self.cfg)) 

459 

460 if stop_at_eos: 460 ↛ 473line 460 didn't jump to line 473 because the condition on line 460 was always true

461 # For all unfinished sequences, add on the next token. If a sequence was 

462 # finished, throw away the generated token and add eos_token_for_padding 

463 # instead. 

464 sampled_tokens[finished_sequences] = eos_token_for_padding 

465 finished_sequences.logical_or_( 

466 torch.isin( 

467 sampled_tokens.to(self.cfg.device), 

468 torch.tensor(stop_tokens).to(self.cfg.device), 

469 ) 

470 ) 

471 

472 # Append new token to the decoder input 

473 decoder_input = torch.cat([decoder_input, sampled_tokens.unsqueeze(-1)], dim=-1) 

474 

475 if stop_at_eos and finished_sequences.all(): 

476 break 

477 

478 if return_type == "str": 478 ↛ 484line 478 didn't jump to line 484 because the condition on line 478 was always true

479 assert self.tokenizer is not None 

480 # Convert tokens to string 

481 return cast(str, self.tokenizer.decode(decoder_input[0], skip_special_tokens=True)) 

482 

483 else: 

484 return decoder_input 

485 

486 @overload # type: ignore[overload-overlap] 

487 def run_with_cache( 

488 self, *model_args: Any, return_cache_object: Literal[True] = True, **kwargs: Any 

489 ) -> Tuple[Float[torch.Tensor, "batch pos d_vocab"], ActivationCache]: 

490 ... 

491 

492 @overload # type: ignore[overload-overlap] 

493 def run_with_cache( 

494 self, *model_args: Any, return_cache_object: Literal[False] = False, **kwargs: Any 

495 ) -> Tuple[Float[torch.Tensor, "batch pos d_vocab"], Dict[str, torch.Tensor]]: 

496 ... 

497 

498 def run_with_cache( 

499 self, 

500 *model_args: Any, 

501 return_cache_object: bool = True, 

502 remove_batch_dim: bool = False, 

503 **kwargs: Any, 

504 ) -> Tuple[ 

505 Float[torch.Tensor, "batch pos d_vocab"], 

506 Union[ActivationCache, Dict[str, torch.Tensor]], 

507 ]: 

508 """ 

509 Wrapper around run_with_cache in HookedRootModule. If return_cache_object is True, this will return an ActivationCache object, with a bunch of useful HookedTransformer specific methods, otherwise it will return a dictionary of activations as in HookedRootModule. This function was copied directly from HookedTransformer. 

510 """ 

511 out, cache_dict = super().run_with_cache( 

512 *model_args, remove_batch_dim=remove_batch_dim, **kwargs 

513 ) 

514 if return_cache_object: 514 ↛ 518line 514 didn't jump to line 518 because the condition on line 514 was always true

515 cache = ActivationCache(cache_dict, self, has_batch_dim=not remove_batch_dim) 

516 return out, cache 

517 else: 

518 return out, cache_dict 

519 

520 def to(self: T, *args: Any, **kwargs: Any) -> T: 

521 return super().to(*args, **kwargs) 

522 

523 def cuda(self: T, device: Optional[Union[int, torch.device]] = None) -> T: 

524 if isinstance(device, int): 

525 return self.to(f"cuda:{device}") 

526 elif device is None: 

527 return self.to("cuda") 

528 else: 

529 return self.to(device) 

530 

531 def cpu(self: T) -> T: 

532 return self.to("cpu") 

533 

534 def mps(self: T) -> T: 

535 """Warning: MPS may produce silently incorrect results. See #1178.""" 

536 warn_if_mps("mps") 

537 return self.to(torch.device("mps")) 

538 

539 @classmethod 

540 def from_pretrained( 

541 cls: Type[T], 

542 model_name: str, 

543 checkpoint_index: Optional[int] = None, 

544 checkpoint_value: Optional[int] = None, 

545 hf_model: Optional[Any] = None, 

546 device: Optional[str] = None, 

547 tokenizer: Optional[Any] = None, 

548 move_to_device: bool = True, 

549 dtype: Optional[torch.dtype] = torch.float32, 

550 **from_pretrained_kwargs: Any, 

551 ) -> T: 

552 """Loads in the pretrained weights from huggingface. Currently supports loading weight from HuggingFace BertForMaskedLM. Unlike HookedTransformer, this does not yet do any preprocessing on the model.""" 

553 import warnings 

554 

555 warnings.warn( 

556 "HookedEncoderDecoder.from_pretrained is deprecated and will be removed in a " 

557 "future major release. Use TransformerBridge.boot_transformers(...) instead — " 

558 "the bridge supports T5-style encoder-decoder models. See " 

559 "docs/source/content/migrating_to_v3.md.", 

560 DeprecationWarning, 

561 stacklevel=2, 

562 ) 

563 

564 logging.warning( 

565 "Support for T5 in TransformerLens is currently experimental, until such a time when it has feature " 

566 "parity with HookedTransformer and has been tested on real research tasks. Until then, backward " 

567 "compatibility is not guaranteed. Please see the docs for information on the limitations of the current " 

568 "implementation." 

569 "\n" 

570 "If using T5 for interpretability research, keep in mind that T5 has some significant architectural " 

571 "differences to GPT. The major one is that T5 is an Encoder-Decoder model" 

572 "Also, it uses relative positional embeddings, different types of Attention (without bias) and LayerNorm" 

573 ) 

574 

575 if from_pretrained_kwargs.get("load_in_8bit", False) or from_pretrained_kwargs.get( 575 ↛ 578line 575 didn't jump to line 578 because the condition on line 575 was never true

576 "load_in_4bit", False 

577 ): 

578 raise ValueError("Quantization not supported") 

579 

580 if "torch_dtype" in from_pretrained_kwargs: 580 ↛ 581line 580 didn't jump to line 581 because the condition on line 580 was never true

581 dtype = from_pretrained_kwargs["torch_dtype"] 

582 

583 if dtype is None: 583 ↛ 584line 583 didn't jump to line 584 because the condition on line 583 was never true

584 dtype = torch.float32 

585 

586 name_or_path = ( 

587 model_name if Path(model_name).exists() else loading.get_official_model_name(model_name) 

588 ) 

589 

590 cfg = loading.get_pretrained_model_config( 

591 name_or_path, 

592 checkpoint_index=checkpoint_index, 

593 checkpoint_value=checkpoint_value, 

594 fold_ln=False, 

595 device=device, 

596 n_devices=1, 

597 dtype=dtype, 

598 **from_pretrained_kwargs, 

599 ) 

600 

601 state_dict = loading.get_pretrained_state_dict( 

602 name_or_path, cfg, hf_model, dtype=dtype, **from_pretrained_kwargs 

603 ) 

604 

605 model = cls(cfg, tokenizer, move_to_device=False) 

606 

607 model.load_state_dict(state_dict, strict=False) 

608 

609 if move_to_device and cfg.device is not None: 609 ↛ 612line 609 didn't jump to line 612 because the condition on line 609 was always true

610 model.to(cfg.device) 

611 

612 print(f"Loaded pretrained model {model_name} into HookedTransformer") 

613 

614 return model 

615 

616 @property 

617 def W_U(self) -> Float[torch.Tensor, "d_model d_vocab"]: 

618 """ 

619 Convenience to get the unembedding matrix (ie the linear map from the final residual stream to the output logits) 

620 """ 

621 return self.unembed.W_U 

622 

623 @property 

624 def b_U(self) -> Float[torch.Tensor, "d_vocab"]: 

625 """ 

626 Convenience to get the unembedding bias 

627 """ 

628 return self.unembed.b_U 

629 

630 @property 

631 def W_E(self) -> Float[torch.Tensor, "d_vocab d_model"]: 

632 """ 

633 Convenience to get the embedding matrix 

634 """ 

635 return self.embed.W_E 

636 

637 @property 

638 def W_pos(self) -> None: 

639 """ 

640 Convenience function to get the positional embedding. Only works on models with absolute positional embeddings! 

641 """ 

642 raise NotImplementedError( 

643 "T5 does not have absolute positional embeddings. Uses relative positional embeddings instead." 

644 ) 

645 

646 @property 

647 def W_K(self) -> Float[torch.Tensor, "n_layers n_heads d_model d_head"]: 

648 """Stacks the key weights across all layers""" 

649 return torch.stack( 

650 [block.attn.W_K for block in chain(self.encoder, self.decoder)], 

651 dim=0, 

652 ) 

653 

654 @property 

655 def W_Q(self) -> Float[torch.Tensor, "n_layers n_heads d_model d_head"]: 

656 """Stacks the query weights across all layers""" 

657 return torch.stack( 

658 [block.attn.W_Q for block in chain(self.encoder, self.decoder)], 

659 dim=0, 

660 ) 

661 

662 @property 

663 def W_V(self) -> Float[torch.Tensor, "n_layers n_heads d_model d_head"]: 

664 """Stacks the value weights across all layers""" 

665 return torch.stack( 

666 [block.attn.W_V for block in chain(self.encoder, self.decoder)], 

667 dim=0, 

668 ) 

669 

670 @property 

671 def W_O(self) -> Float[torch.Tensor, "n_layers n_heads d_head d_model"]: 

672 """Stacks the attn output weights across all layers""" 

673 return torch.stack( 

674 [block.attn.W_O for block in chain(self.encoder, self.decoder)], 

675 dim=0, 

676 ) 

677 

678 @property 

679 def W_in(self) -> Float[torch.Tensor, "n_layers d_model d_mlp"]: 

680 """Stacks the MLP input weights across all layers""" 

681 weights: List[torch.Tensor] = [] 

682 for block in chain(self.encoder, self.decoder): 

683 mlp = block.mlp 

684 if isinstance(mlp, (MLP, GatedMLP)): 

685 weights.append(mlp.W_in) 

686 else: 

687 raise NotImplementedError( 

688 f"W_in property is not supported for MLP of type {type(mlp).__name__}" 

689 ) 

690 return torch.stack(weights, dim=0) 

691 

692 @property 

693 def W_out(self) -> Float[torch.Tensor, "n_layers d_mlp d_model"]: 

694 """Stacks the MLP output weights across all layers""" 

695 weights: List[torch.Tensor] = [] 

696 for block in chain(self.encoder, self.decoder): 

697 mlp = block.mlp 

698 if isinstance(mlp, (MLP, GatedMLP)): 

699 weights.append(mlp.W_out) 

700 else: 

701 raise NotImplementedError( 

702 f"W_out property is not supported for MLP of type {type(mlp).__name__}" 

703 ) 

704 return torch.stack(weights, dim=0) 

705 

706 @property 

707 def b_K(self) -> Float[torch.Tensor, "n_layers n_heads d_head"]: 

708 """Stacks the key biases across all layers""" 

709 return torch.stack( 

710 [block.attn.b_K for block in chain(self.encoder, self.decoder)], 

711 dim=0, 

712 ) 

713 

714 @property 

715 def b_Q(self) -> Float[torch.Tensor, "n_layers n_heads d_head"]: 

716 """Stacks the query biases across all layers""" 

717 return torch.stack( 

718 [block.attn.b_Q for block in chain(self.encoder, self.decoder)], 

719 dim=0, 

720 ) 

721 

722 @property 

723 def b_V(self) -> Float[torch.Tensor, "n_layers n_heads d_head"]: 

724 """Stacks the value biases across all layers""" 

725 return torch.stack( 

726 [block.attn.b_V for block in chain(self.encoder, self.decoder)], 

727 dim=0, 

728 ) 

729 

730 @property 

731 def b_O(self) -> Float[torch.Tensor, "n_layers d_model"]: 

732 """Stacks the attn output biases across all layers""" 

733 return torch.stack( 

734 [block.attn.b_O for block in chain(self.encoder, self.decoder)], 

735 dim=0, 

736 ) 

737 

738 @property 

739 def b_in(self) -> Float[torch.Tensor, "n_layers d_mlp"]: 

740 """Stacks the MLP input biases across all layers""" 

741 biases: List[torch.Tensor] = [] 

742 for block in chain(self.encoder, self.decoder): 

743 mlp = block.mlp 

744 if isinstance(mlp, (MLP, GatedMLP)): 

745 biases.append(mlp.b_in) 

746 else: 

747 raise NotImplementedError( 

748 f"b_in property is not supported for MLP of type {type(mlp).__name__}" 

749 ) 

750 return torch.stack(biases, dim=0) 

751 

752 @property 

753 def b_out(self) -> Float[torch.Tensor, "n_layers d_model"]: 

754 """Stacks the MLP output biases across all layers""" 

755 biases: List[torch.Tensor] = [] 

756 for block in chain(self.encoder, self.decoder): 

757 mlp = block.mlp 

758 if isinstance(mlp, (MLP, GatedMLP)): 

759 biases.append(mlp.b_out) 

760 else: 

761 raise NotImplementedError( 

762 f"b_out property is not supported for MLP of type {type(mlp).__name__}" 

763 ) 

764 return torch.stack(biases, dim=0) 

765 

766 @property 

767 def QK(self) -> FactoredMatrix: # [n_layers, n_heads, d_model, d_model] 

768 """Returns a FactoredMatrix object with the product of the Q and K matrices for each layer and head. 

769 Useful for visualizing attention patterns.""" 

770 return FactoredMatrix(self.W_Q, self.W_K.transpose(-2, -1)) 

771 

772 @property 

773 def OV(self) -> FactoredMatrix: # [n_layers, n_heads, d_model, d_model] 

774 """Returns a FactoredMatrix object with the product of the O and V matrices for each layer and head.""" 

775 return FactoredMatrix(self.W_V, self.W_O) 

776 

777 def all_head_labels(self) -> List[str]: 

778 """Returns a list of strings with the format "L{l}H{h}", where l is the layer index and h is the head index.""" 

779 return [f"EL{l}H{h}" for l in range(self.cfg.n_layers) for h in range(self.cfg.n_heads)] + [ 

780 f"DL{l}H{h}" for l in range(self.cfg.n_layers) for h in range(self.cfg.n_heads) 

781 ]