Coverage for transformer_lens/config/transformer_bridge_config.py: 96%

122 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2026-09-21 19:27 +0000

1"""Configuration class for TransformerBridge.""" 

2 

3import warnings 

4import weakref 

5from typing import Any, Optional 

6 

7import numpy as np 

8import torch 

9 

10from transformer_lens.utilities.activation_functions import SOFTCAP_DISABLED 

11 

12from .transformer_lens_config import TransformerLensConfig 

13 

14 

15class TransformerBridgeConfig(TransformerLensConfig): 

16 """ 

17 Configuration for TransformerBridge. 

18 

19 This extends TransformerLensConfig with bridge-specific properties, 

20 particularly architecture information needed for adapter selection. 

21 Also includes all HookedTransformerConfig fields for compatibility. 

22 """ 

23 

24 __slots__ = ("_bridge_ref",) 

25 

26 _BRIDGE_MANAGED_HOOK_FLAGS = frozenset( 

27 { 

28 "use_attn_result", 

29 "use_attn_in", 

30 "use_hook_mlp_in", 

31 "use_split_qkv_input", 

32 } 

33 ) 

34 

35 def __init__( 

36 self, 

37 d_model: int, 

38 d_head: int, 

39 n_layers: int, 

40 n_ctx: int, 

41 n_heads: int = -1, # Add n_heads to signature so it's not filtered out by from_dict 

42 d_vocab: int = -1, 

43 architecture: Optional[str] = None, 

44 tokenizer_prepends_bos: bool = True, 

45 tokenizer_appends_eos: bool = False, 

46 default_padding_side: Optional[str] = None, 

47 # HookedTransformerConfig compatibility fields 

48 model_name: str = "custom", 

49 act_fn: str = "relu", 

50 eps: float = 1e-5, 

51 use_attn_scale: bool = True, 

52 attn_scale: float = -1.0, 

53 use_hook_mlp_in: bool = False, 

54 use_attn_in: bool = False, 

55 use_qk_norm: bool = False, 

56 use_local_attn: bool = False, 

57 original_architecture: Optional[str] = None, 

58 from_checkpoint: bool = False, 

59 checkpoint_index: Optional[int] = None, 

60 checkpoint_label_type: Optional[str] = None, 

61 checkpoint_value: Optional[int] = None, 

62 tokenizer_name: Optional[str] = None, 

63 window_size: Optional[int] = None, 

64 attn_types: Optional[list] = None, 

65 init_mode: str = "gpt2", 

66 normalization_type: Optional[str] = "LN", 

67 n_devices: int = 1, 

68 attention_dir: str = "causal", 

69 attn_only: bool = False, 

70 seed: Optional[int] = None, 

71 initializer_range: float = -1.0, 

72 init_weights: bool = True, 

73 scale_attn_by_inverse_layer_idx: bool = False, 

74 final_rms: bool = False, 

75 d_vocab_out: int = -1, 

76 parallel_attn_mlp: bool = False, 

77 rotary_dim: Optional[int] = None, 

78 n_params: Optional[int] = None, 

79 use_hook_tokens: bool = False, 

80 gated_mlp: bool = False, 

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

82 post_embedding_ln: bool = False, 

83 rotary_base: int | float = 10000, 

84 trust_remote_code: bool = False, 

85 rotary_adjacent_pairs: bool = False, 

86 load_in_4bit: bool = False, 

87 num_experts: Optional[int] = None, 

88 experts_per_token: Optional[int] = None, 

89 n_key_value_heads: Optional[int] = None, 

90 # Heterogeneous attention geometry (e.g. Gemma 4): per-layer values when 

91 # they vary across layers; d_head / n_key_value_heads then hold the 

92 # majority-layer scalar and attention math is delegated to HF. 

93 per_layer_head_dim: Optional[list] = None, 

94 per_layer_num_key_value_heads: Optional[list] = None, 

95 relative_attention_max_distance: Optional[int] = None, 

96 relative_attention_num_buckets: Optional[int] = None, 

97 decoder_start_token_id: Optional[int] = None, 

98 scale_embedding: Optional[bool] = None, 

99 tie_word_embeddings: bool = False, 

100 use_normalization_before_and_after: bool = False, 

101 attn_scores_soft_cap: float = SOFTCAP_DISABLED, 

102 output_logits_soft_cap: float = SOFTCAP_DISABLED, 

103 use_NTK_by_parts_rope: bool = False, 

104 NTK_by_parts_low_freq_factor: float = 1.0, 

105 NTK_by_parts_high_freq_factor: float = 4.0, 

106 NTK_by_parts_factor: float = 8.0, 

107 rmsnorm_uses_offset: bool = False, 

108 attn_implementation: Optional[str] = None, 

109 # Audio model configuration 

110 is_audio_model: bool = False, 

111 # Vision model (ViT, DeiT) configuration 

112 is_visual_model: bool = False, 

113 # Stateful model configuration (e.g., Mamba SSMs use cache_params, 

114 # not past_key_values, so generation delegates to hf_generate) 

115 is_stateful: bool = False, 

116 # Multimodal configuration 

117 is_multimodal: bool = False, 

118 vision_hidden_size: Optional[int] = None, 

119 vision_num_layers: Optional[int] = None, 

120 vision_num_heads: Optional[int] = None, 

121 mm_tokens_per_image: Optional[int] = None, 

122 **kwargs, 

123 ): 

124 """Initialize TransformerBridgeConfig.""" 

125 object.__setattr__(self, "_bridge_ref", None) 

126 super().__init__( 

127 d_model=d_model, 

128 d_head=d_head, 

129 n_layers=n_layers, 

130 n_ctx=n_ctx, 

131 d_vocab=d_vocab, 

132 n_heads=n_heads, 

133 **kwargs, 

134 ) 

135 

136 # Architecture information for adapter selection 

137 self.architecture = architecture 

138 

139 # Tokenizer configuration 

140 self.tokenizer_prepends_bos = tokenizer_prepends_bos 

141 self.tokenizer_appends_eos = tokenizer_appends_eos 

142 self.default_padding_side = default_padding_side 

143 

144 # Attention weight processing configuration 

145 self.split_attention_weights = False 

146 

147 # HookedTransformerConfig compatibility fields 

148 self.model_name = model_name 

149 self.act_fn = act_fn 

150 self.eps = eps 

151 self.use_attn_scale = use_attn_scale 

152 self.attn_scale = attn_scale 

153 self.use_hook_mlp_in = use_hook_mlp_in 

154 self.use_attn_in = use_attn_in 

155 self.use_qk_norm = use_qk_norm 

156 self.use_local_attn = use_local_attn 

157 self.original_architecture = original_architecture 

158 self.from_checkpoint = from_checkpoint 

159 self.checkpoint_index = checkpoint_index 

160 self.checkpoint_label_type = checkpoint_label_type 

161 self.checkpoint_value = checkpoint_value 

162 self.tokenizer_name = tokenizer_name 

163 self.window_size = window_size 

164 self.attn_types = attn_types 

165 self.init_mode = init_mode 

166 self.normalization_type = normalization_type 

167 self.n_devices = n_devices 

168 self.attention_dir = attention_dir 

169 self.attn_only = attn_only 

170 self.seed = seed 

171 self.initializer_range = initializer_range 

172 self.init_weights = init_weights 

173 self.scale_attn_by_inverse_layer_idx = scale_attn_by_inverse_layer_idx 

174 self.final_rms = final_rms 

175 self.d_vocab_out = d_vocab_out 

176 self.parallel_attn_mlp = parallel_attn_mlp 

177 self.rotary_dim = rotary_dim 

178 self.n_params = n_params 

179 self.use_hook_tokens = use_hook_tokens 

180 self.gated_mlp = gated_mlp 

181 self.dtype = dtype if dtype is not None else torch.float32 

182 self.post_embedding_ln = post_embedding_ln 

183 self.rotary_base = int(rotary_base) 

184 self.trust_remote_code = trust_remote_code 

185 self.rotary_adjacent_pairs = rotary_adjacent_pairs 

186 self.load_in_4bit = load_in_4bit 

187 self.num_experts = num_experts 

188 self.experts_per_token = experts_per_token 

189 self.n_key_value_heads = n_key_value_heads 

190 self.per_layer_head_dim = per_layer_head_dim 

191 self.per_layer_num_key_value_heads = per_layer_num_key_value_heads 

192 self.relative_attention_max_distance = relative_attention_max_distance 

193 self.relative_attention_num_buckets = relative_attention_num_buckets 

194 self.decoder_start_token_id = decoder_start_token_id 

195 # Seq2seq families (Bart, Marian, Pegasus, ...) scale token embeddings 

196 # by sqrt(d_model) in the HF forward when set. 

197 self.scale_embedding = scale_embedding 

198 self.tie_word_embeddings = tie_word_embeddings 

199 self.use_normalization_before_and_after = use_normalization_before_and_after 

200 self.attn_scores_soft_cap = attn_scores_soft_cap 

201 self.output_logits_soft_cap = output_logits_soft_cap 

202 self.use_NTK_by_parts_rope = use_NTK_by_parts_rope 

203 self.NTK_by_parts_low_freq_factor = NTK_by_parts_low_freq_factor 

204 self.NTK_by_parts_high_freq_factor = NTK_by_parts_high_freq_factor 

205 self.NTK_by_parts_factor = NTK_by_parts_factor 

206 self.rmsnorm_uses_offset = rmsnorm_uses_offset 

207 self.attn_implementation = attn_implementation 

208 # Audio model configuration 

209 self.is_audio_model = is_audio_model 

210 # Vision model (ViT, DeiT) configuration 

211 self.is_visual_model = is_visual_model 

212 # Stateful model configuration 

213 self.is_stateful = is_stateful 

214 # Multimodal configuration 

215 self.is_multimodal = is_multimodal 

216 self.vision_hidden_size = vision_hidden_size 

217 self.vision_num_layers = vision_num_layers 

218 self.vision_num_heads = vision_num_heads 

219 self.mm_tokens_per_image = mm_tokens_per_image 

220 self.__post_init__() 

221 

222 def __setattr__(self, name: str, value: Any) -> None: 

223 """Route live Bridge hook-flag assignments through their public setters.""" 

224 if name in self._BRIDGE_MANAGED_HOOK_FLAGS: 

225 bridge_ref = getattr(self, "_bridge_ref", None) 

226 bridge = bridge_ref() if bridge_ref is not None else None 

227 if bridge is not None: 

228 getattr(bridge, f"set_{name}")(value) 

229 return 

230 super().__setattr__(name, value) 

231 

232 def __getstate__(self) -> dict[str, Any]: 

233 """Serialize config data without retaining its live Bridge binding.""" 

234 return self.__dict__.copy() 

235 

236 def __setstate__(self, state: dict[str, Any]) -> None: 

237 """Restore an unbound config copy.""" 

238 self.__dict__.update(state) 

239 object.__setattr__(self, "_bridge_ref", None) 

240 

241 def _bind_bridge(self, bridge: Any) -> None: 

242 """Bind runtime hook-flag assignments to a constructed Bridge.""" 

243 bridge_ref = getattr(self, "_bridge_ref", None) 

244 bound_bridge = bridge_ref() if bridge_ref is not None else None 

245 if bound_bridge is None: 

246 object.__setattr__(self, "_bridge_ref", weakref.ref(bridge)) 

247 elif bound_bridge is not bridge: 247 ↛ exitline 247 didn't return from function '_bind_bridge' because the condition on line 247 was always true

248 warnings.warn( 

249 "TransformerBridgeConfig is already bound to another live " 

250 "TransformerBridge; declining to bind it to this instance. " 

251 "Direct assignments to Bridge-managed hook flags will continue " 

252 "to configure the existing TransformerBridge.", 

253 stacklevel=3, 

254 ) 

255 

256 def _set_bridge_managed_hook_flag(self, name: str, value: bool) -> None: 

257 """Set a managed flag without re-entering the Bridge setter.""" 

258 if name not in self._BRIDGE_MANAGED_HOOK_FLAGS: 258 ↛ 259line 258 didn't jump to line 259 because the condition on line 258 was never true

259 raise ValueError(f"Unknown Bridge-managed hook flag: {name}") 

260 object.__setattr__(self, name, value) 

261 

262 def __post_init__(self): 

263 """Post-initialization processing.""" 

264 # dtype is guaranteed to be set at this point 

265 

266 # Validate architecture if provided before calling super() 

267 if ( 267 ↛ 272line 267 didn't jump to line 272 because the condition on line 267 was never true

268 hasattr(self, "architecture") 

269 and self.architecture is not None 

270 and not isinstance(self.architecture, str) 

271 ): 

272 raise ValueError(f"architecture must be a string, got {type(self.architecture)}") 

273 

274 # Resolve the initializer_range sentinel (-1.0 means "not set by the user"). 

275 # Same rule the legacy HookedTransformerConfig applied. 

276 # Guarded with getattr: this method also runs once from the dataclass 

277 # parent's __init__, before self.initializer_range is assigned below. 

278 if getattr(self, "initializer_range", None) is not None: 

279 if self.initializer_range < 0 and self.init_mode == "gpt2": 

280 # Roughly copy the GPT-2 value, but proportional to sqrt(1/d_model) 

281 self.initializer_range = 0.8 / np.sqrt(self.d_model) 

282 if self.initializer_range < 0 and self.init_mode != "gpt2": 

283 # This is the gain parameter for the weight initialisation 

284 self.initializer_range = 1.0 

285 

286 # Call parent's __post_init__ after our validation 

287 if hasattr(super(), "__post_init__"): 287 ↛ exitline 287 didn't return from function '__post_init__' because the condition on line 287 was always true

288 super().__post_init__() 

289 

290 @property 

291 def head_dim(self) -> int: 

292 """Alias for d_head to match HuggingFace config naming convention.""" 

293 return self.d_head