Coverage for transformer_lens/model_bridge/generalized_components/unembedding.py: 64%
52 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"""Unembedding bridge component.
3This module contains the bridge component for unembedding layers.
4"""
5from typing import Any, Dict, Optional
7import torch
9from transformer_lens.model_bridge.generalized_components.base import (
10 GeneralizedComponent,
11)
14class UnembeddingBridge(GeneralizedComponent):
15 """Unembedding bridge that wraps transformer unembedding layers.
17 This component provides standardized input/output hooks.
18 """
20 property_aliases = {"W_U": "u.weight"}
22 def __init__(
23 self,
24 name: str,
25 config: Optional[Any] = None,
26 submodules: Optional[Dict[str, GeneralizedComponent]] = {},
27 ):
28 """Initialize the unembedding bridge.
30 Args:
31 name: The name of this component
32 config: Optional configuration (unused for UnembeddingBridge)
33 submodules: Dictionary of GeneralizedComponent submodules to register
34 """
35 super().__init__(name, config, submodules=submodules)
37 def set_original_component(self, original_component: torch.nn.Module) -> None:
38 """Set the original component and ensure it has bias enabled.
40 Args:
41 original_component: The original transformer component to wrap
42 """
43 # If this is a Linear layer without bias, enable it
44 if isinstance(original_component, torch.nn.Linear) and original_component.bias is None:
45 # Get the output features (vocab size)
46 vocab_size = original_component.weight.shape[0]
47 device = original_component.weight.device
48 dtype = original_component.weight.dtype
50 original_component.bias = torch.nn.Parameter(
51 torch.zeros(vocab_size, device=device, dtype=dtype)
52 )
54 super().set_original_component(original_component)
56 @property
57 def W_U(self) -> torch.Tensor:
58 """Return the unembedding weight matrix in TL format [d_model, d_vocab]."""
59 if "_processed_W_U" in self._parameters: 59 ↛ 60line 59 didn't jump to line 60 because the condition on line 59 was never true
60 processed_W_U = self._parameters["_processed_W_U"]
61 if processed_W_U is not None:
62 # Processed weights are in HF format [vocab, d_model]
63 # Transpose to TL format [d_model, d_vocab]
64 return processed_W_U.T
65 if self.original_component is None: 65 ↛ 66line 65 didn't jump to line 66 because the condition on line 65 was never true
66 raise RuntimeError(f"Original component not set for {self.name}")
67 assert hasattr(
68 self.original_component, "weight"
69 ), f"Component {self.name} has no weight attribute"
70 weight = self.original_component.weight
71 assert isinstance(weight, torch.Tensor), f"Weight is not a tensor for {self.name}"
72 # HF format is [d_vocab, d_model], transpose to TL format [d_model, d_vocab]
73 return weight.T
75 def forward(self, hidden_states: torch.Tensor, **kwargs: Any) -> torch.Tensor:
76 """Forward pass through the unembedding bridge.
78 Args:
79 hidden_states: Input hidden states
80 **kwargs: Additional arguments to pass to the original component
82 Returns:
83 Unembedded output (logits)
84 """
85 if self.original_component is None: 85 ↛ 86line 85 didn't jump to line 86 because the condition on line 85 was never true
86 raise RuntimeError(
87 f"Original component not set for {self.name}. Call set_original_component() first."
88 )
89 hidden_states = self.hook_in(hidden_states)
90 output = self.original_component(hidden_states, **kwargs)
92 output = self.hook_out(output)
93 return output
95 @property
96 def b_U(self) -> torch.Tensor:
97 """Access the unembedding bias vector."""
98 if "_b_U" in self._parameters: 98 ↛ 99line 98 didn't jump to line 99 because the condition on line 98 was never true
99 param = self._parameters["_b_U"]
100 if param is not None:
101 return param
102 if self.original_component is None: 102 ↛ 103line 102 didn't jump to line 103 because the condition on line 102 was never true
103 raise RuntimeError(f"Original component not set for {self.name}")
104 if hasattr(self.original_component, "bias") and self.original_component.bias is not None: 104 ↛ 109line 104 didn't jump to line 109 because the condition on line 104 was always true
105 bias = self.original_component.bias
106 assert isinstance(bias, torch.Tensor), f"Bias is not a tensor for {self.name}"
107 return bias
108 else:
109 assert hasattr(
110 self.original_component, "weight"
111 ), f"Component {self.name} has no weight attribute"
112 weight = self.original_component.weight
113 assert isinstance(weight, torch.Tensor), f"Weight is not a tensor for {self.name}"
114 device = weight.device
115 dtype = weight.dtype
116 vocab_size: int = int(weight.shape[0])
117 return torch.zeros(vocab_size, device=device, dtype=dtype)