Coverage for transformer_lens/model_bridge/supported_architectures/opt.py: 92%
77 statements
« prev ^ index » next coverage.py v7.10.1, created at 2026-09-01 16:23 +0000
« prev ^ index » next coverage.py v7.10.1, created at 2026-09-01 16:23 +0000
1"""OPT architecture adapter."""
3from typing import Any, Iterator
5import torch
7from transformer_lens.conversion_utils.conversion_steps import RearrangeTensorConversion
8from transformer_lens.conversion_utils.conversion_steps.base_tensor_conversion import (
9 BaseTensorConversion,
10)
11from transformer_lens.conversion_utils.param_processing_conversion import (
12 ParamProcessingConversion,
13)
14from transformer_lens.model_bridge.architecture_adapter import ArchitectureAdapter
15from transformer_lens.model_bridge.generalized_components import (
16 AttentionBridge,
17 BlockBridge,
18 EmbeddingBridge,
19 LinearBridge,
20 MLPBridge,
21 NormalizationBridge,
22 PosEmbedBridge,
23 UnembeddingBridge,
24)
27class _UnflattenTokens(BaseTensorConversion):
28 """Restore [batch, seq, d] on hooks inside OPT's flattened region.
30 OPTDecoderLayer reshapes hidden states to [batch*seq, d] before
31 final_layer_norm/fc1/fc2 and back only after the residual add, so the MLP
32 and ln2 hooks would otherwise fire 2D — silently wrong for
33 position-indexed patching, crashing for `b s d` einops. The block stamps
34 the live (batch, seq) at each forward entry; runs only while user hooks
35 are attached.
36 """
38 def __init__(self) -> None:
39 super().__init__()
40 self.batch_seq: tuple[int, int] | None = None
42 def handle_conversion(self, input_value, *full_context):
43 bs = self.batch_seq
44 if ( 44 ↛ 51line 44 didn't jump to line 51 because the condition on line 44 was always true
45 bs is not None
46 and isinstance(input_value, torch.Tensor)
47 and input_value.dim() == 2
48 and input_value.shape[0] == bs[0] * bs[1]
49 ):
50 return input_value.view(bs[0], bs[1], input_value.shape[-1])
51 return input_value
53 def revert(self, input_value, *full_context):
54 bs = self.batch_seq
55 if ( 55 ↛ 63line 55 didn't jump to line 63 because the condition on line 55 was always true
56 bs is not None
57 and isinstance(input_value, torch.Tensor)
58 and input_value.dim() == 3
59 and tuple(input_value.shape[:2]) == bs
60 ):
61 # reshape (not view) — hooks may return non-contiguous tensors
62 return input_value.reshape(-1, input_value.shape[-1])
63 return input_value
66class _OptBlockBridge(BlockBridge):
67 """BlockBridge that stamps (batch, seq) onto the unflatten conversions."""
69 def __init__(self, *args: Any, **kwargs: Any) -> None:
70 super().__init__(*args, **kwargs)
71 # hook_mlp_in is the block's own HookPoint, fired from a ln2 pre-hook —
72 # inside OPT's flattened region, so it needs the conversion too.
73 self.hook_mlp_in.hook_conversion = _UnflattenTokens()
75 def forward(self, *args: Any, **kwargs: Any) -> Any:
76 hidden = args[0] if args else kwargs.get("hidden_states")
77 if isinstance(hidden, torch.Tensor) and hidden.dim() == 3: 77 ↛ 81line 77 didn't jump to line 81 because the condition on line 77 was always true
78 batch_seq = (hidden.shape[0], hidden.shape[1])
79 for conversion in self._unflatten_conversions():
80 conversion.batch_seq = batch_seq
81 return super().forward(*args, **kwargs)
83 def _unflatten_conversions(self) -> Iterator[_UnflattenTokens]:
84 block_conversion = getattr(self.hook_mlp_in, "hook_conversion", None)
85 if isinstance(block_conversion, _UnflattenTokens): 85 ↛ 87line 85 didn't jump to line 87 because the condition on line 85 was always true
86 yield block_conversion
87 for key in ("mlp", "ln2"):
88 component = self.submodules.get(key)
89 if component is None: 89 ↛ 90line 89 didn't jump to line 90 because the condition on line 89 was never true
90 continue
91 members = [component, *getattr(component, "submodules", {}).values()]
92 for member in members:
93 for hook_name in ("hook_in", "hook_out"):
94 hook_point = getattr(member, hook_name, None)
95 conversion = getattr(hook_point, "hook_conversion", None)
96 if isinstance(conversion, _UnflattenTokens): 96 ↛ 93line 96 didn't jump to line 93 because the condition on line 96 was always true
97 yield conversion
100class OptArchitectureAdapter(ArchitectureAdapter):
101 """Architecture adapter for OPT models."""
103 @staticmethod
104 def _with_unflatten(component: Any) -> Any:
105 """Attach _UnflattenTokens to the component's (and submodules') hooks."""
106 members = [component, *getattr(component, "submodules", {}).values()]
107 for member in members:
108 for hook_name in ("hook_in", "hook_out"):
109 hook_point = getattr(member, hook_name, None)
110 if hook_point is not None and hook_point.hook_conversion is None: 110 ↛ 108line 110 didn't jump to line 108 because the condition on line 110 was always true
111 hook_point.hook_conversion = _UnflattenTokens()
112 return component
114 def __init__(self, cfg: Any) -> None:
115 """Initialize the OPT architecture adapter."""
116 super().__init__(cfg)
118 # Set config variables for weight processing
119 self.cfg.normalization_type = "LN"
120 self.cfg.positional_embedding_type = "standard"
121 self.cfg.final_rms = False
122 self.cfg.gated_mlp = False
123 self.cfg.attn_only = False
125 # OPT models were trained with BOS tokens (inherits default_prepend_bos = True)
127 # Post-norm: disable fold_ln and center_writing_weights (pre-norm only).
128 is_post_norm = not getattr(self.cfg, "do_layer_norm_before", True)
129 if is_post_norm:
130 self.supports_fold_ln = False
131 self.supports_center_writing_weights = False
133 self.weight_processing_conversions = {
134 "blocks.{i}.attn.q.weight": ParamProcessingConversion(
135 tensor_conversion=RearrangeTensorConversion("(n h) m -> n m h", n=self.cfg.n_heads),
136 ),
137 "blocks.{i}.attn.k.weight": ParamProcessingConversion(
138 tensor_conversion=RearrangeTensorConversion("(n h) m -> n m h", n=self.cfg.n_heads),
139 ),
140 "blocks.{i}.attn.v.weight": ParamProcessingConversion(
141 tensor_conversion=RearrangeTensorConversion("(n h) m -> n m h", n=self.cfg.n_heads),
142 ),
143 "blocks.{i}.attn.o.weight": ParamProcessingConversion(
144 tensor_conversion=RearrangeTensorConversion("m (n h) -> n h m", n=self.cfg.n_heads),
145 ),
146 }
148 # OPT-350m is uniquely the only OPT size where word_embed_proj_dim (512)
149 # != hidden_size (1024). It uses project_in/project_out linear layers
150 # instead of a final_layer_norm. Detect this and conditionally include
151 # ln_final only when the model actually has one.
152 word_embed_proj_dim = getattr(self.cfg, "word_embed_proj_dim", self.cfg.d_model)
153 has_final_layer_norm = word_embed_proj_dim == self.cfg.d_model
155 self.component_mapping = {
156 "embed": EmbeddingBridge(name="model.decoder.embed_tokens"),
157 "pos_embed": PosEmbedBridge(name="model.decoder.embed_positions"),
158 "blocks": _OptBlockBridge(
159 name="model.decoder.layers",
160 # fc2 IS the mlp output (no container fires hook_out). No
161 # hook_mlp_in override: pre-norm, the block already provides it.
162 hook_alias_overrides={"hook_mlp_out": "mlp.out.hook_out"},
163 submodules={
164 "ln1": NormalizationBridge(
165 name="self_attn_layer_norm",
166 config=self.cfg,
167 use_native_layernorm_autograd=True,
168 ),
169 "attn": AttentionBridge(
170 name="self_attn",
171 config=self.cfg,
172 requires_attention_mask=True, # OPT requires attention_mask
173 attention_mask_4d=True, # OPT expects 4D mask [batch, 1, tgt_len, src_len]
174 submodules={
175 "q": LinearBridge(name="q_proj"),
176 "k": LinearBridge(name="k_proj"),
177 "v": LinearBridge(name="v_proj"),
178 "o": LinearBridge(name="out_proj"),
179 },
180 ),
181 "ln2": self._with_unflatten(
182 NormalizationBridge(
183 name="final_layer_norm",
184 config=self.cfg,
185 use_native_layernorm_autograd=True,
186 )
187 ),
188 # Containerless fc1/fc2, as BERT. ln2/fc1/fc2 run inside
189 # HF's [batch*seq, d] region — hence the unflatten wrap.
190 "mlp": self._with_unflatten(
191 MLPBridge(
192 name=None,
193 config=self.cfg,
194 submodules={
195 "in": LinearBridge(name="fc1"),
196 "out": LinearBridge(name="fc2"),
197 },
198 )
199 ),
200 },
201 ),
202 "unembed": UnembeddingBridge(name="lm_head"),
203 }
204 if has_final_layer_norm:
205 self.component_mapping["ln_final"] = NormalizationBridge(
206 name="model.decoder.final_layer_norm",
207 config=self.cfg,
208 use_native_layernorm_autograd=True,
209 )
210 # project_in/project_out bridge word_embed_proj_dim <-> hidden_size.
211 if not has_final_layer_norm:
212 self.component_mapping["project_in"] = LinearBridge(name="model.decoder.project_in")
213 self.component_mapping["project_out"] = LinearBridge(name="model.decoder.project_out")