Coverage for transformer_lens/model_bridge/generalized_components/unembedding.py: 63%
55 statements
« prev ^ index » next coverage.py v7.10.1, created at 2026-09-21 19:27 +0000
« prev ^ index » next coverage.py v7.10.1, created at 2026-09-21 19:27 +0000
1"""Unembedding bridge component.
3This module contains the bridge component for unembedding layers.
4"""
5from typing import Any, Dict, Optional
7import torch
8from accelerate.utils import align_module_device
10from transformer_lens.model_bridge.generalized_components.base import (
11 GeneralizedComponent,
12)
15class UnembeddingBridge(GeneralizedComponent):
16 """Unembedding bridge that wraps transformer unembedding layers.
18 This component provides standardized input/output hooks.
19 """
21 property_aliases = {"W_U": "u.weight"}
23 def __init__(
24 self,
25 name: str,
26 config: Optional[Any] = None,
27 submodules: Optional[Dict[str, GeneralizedComponent]] = {},
28 ):
29 """Initialize the unembedding bridge.
31 Args:
32 name: The name of this component
33 config: Optional configuration (unused for UnembeddingBridge)
34 submodules: Dictionary of GeneralizedComponent submodules to register
35 """
36 super().__init__(name, config, submodules=submodules)
38 def set_original_component(self, original_component: torch.nn.Module) -> None:
39 """Set the original component and ensure it has bias enabled.
41 Args:
42 original_component: The original transformer component to wrap
43 """
44 # If this is a Linear layer without bias, enable it
45 if isinstance(original_component, torch.nn.Linear) and original_component.bias is None:
46 # Get the output features (vocab size)
47 vocab_size = original_component.weight.shape[0] # shape is safe on a meta tensor too
48 dtype = original_component.weight.dtype # dtype is also safe on a meta tensor
50 # .device is NOT safe the same way: under Accelerate offload this
51 # weight is a meta placeholder outside of a materialized window, so
52 # reading .device unguarded would build a meta-device (i.e. fake,
53 # data-less) bias instead of real zeros. align_module_device gives
54 # the real execution device for this one read.
55 with align_module_device(original_component):
56 device = original_component.weight.device
58 original_component.bias = torch.nn.Parameter(
59 torch.zeros(vocab_size, device=device, dtype=dtype)
60 )
62 super().set_original_component(original_component)
64 @property
65 def W_U(self) -> torch.Tensor:
66 """Return the unembedding weight matrix in TL format [d_model, d_vocab]."""
67 if "_processed_W_U" in self._parameters: 67 ↛ 68line 67 didn't jump to line 68 because the condition on line 67 was never true
68 processed_W_U = self._parameters["_processed_W_U"]
69 if processed_W_U is not None:
70 # Processed weights are in HF format [vocab, d_model]
71 # Transpose to TL format [d_model, d_vocab]
72 return processed_W_U.T
73 if self.original_component is None: 73 ↛ 74line 73 didn't jump to line 74 because the condition on line 73 was never true
74 raise RuntimeError(f"Original component not set for {self.name}")
75 assert hasattr(
76 self.original_component, "weight"
77 ), f"Component {self.name} has no weight attribute"
78 weight = self.original_component.weight
79 assert isinstance(weight, torch.Tensor), f"Weight is not a tensor for {self.name}"
80 # HF format is [d_vocab, d_model], transpose to TL format [d_model, d_vocab]
81 return weight.T
83 def forward(self, hidden_states: torch.Tensor, **kwargs: Any) -> torch.Tensor:
84 """Forward pass through the unembedding bridge.
86 Args:
87 hidden_states: Input hidden states
88 **kwargs: Additional arguments to pass to the original component
90 Returns:
91 Unembedded output (logits)
92 """
93 if self.original_component is None: 93 ↛ 94line 93 didn't jump to line 94 because the condition on line 93 was never true
94 raise RuntimeError(
95 f"Original component not set for {self.name}. Call set_original_component() first."
96 )
97 hidden_states = self.hook_in(hidden_states)
98 output = self.original_component(hidden_states, **kwargs)
100 output = self.hook_out(output)
101 return output
103 @property
104 def b_U(self) -> torch.Tensor:
105 """Access the unembedding bias vector."""
106 if "_b_U" in self._parameters: 106 ↛ 107line 106 didn't jump to line 107 because the condition on line 106 was never true
107 param = self._parameters["_b_U"]
108 if param is not None:
109 return param
110 if self.original_component is None: 110 ↛ 111line 110 didn't jump to line 111 because the condition on line 110 was never true
111 raise RuntimeError(f"Original component not set for {self.name}")
112 if hasattr(self.original_component, "bias") and self.original_component.bias is not None: 112 ↛ 117line 112 didn't jump to line 117 because the condition on line 112 was always true
113 bias = self.original_component.bias
114 assert isinstance(bias, torch.Tensor), f"Bias is not a tensor for {self.name}"
115 return bias
116 else:
117 assert hasattr(
118 self.original_component, "weight"
119 ), f"Component {self.name} has no weight attribute"
120 weight = self.original_component.weight
121 assert isinstance(weight, torch.Tensor), f"Weight is not a tensor for {self.name}"
122 dtype = weight.dtype # safe on a meta tensor
123 vocab_size: int = int(weight.shape[0]) # safe on a meta tensor
124 # .device is not safe the same way under offload - see
125 # set_original_component's identical guard above.
126 with align_module_device(self.original_component):
127 device = weight.device
128 return torch.zeros(vocab_size, device=device, dtype=dtype)