Coverage for transformer_lens/model_bridge/generalized_components/clip_vision_encoder.py: 52%

50 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2026-10-10 05:10 +0000

1"""CLIP Vision Encoder bridge component. 

2 

3This module contains the bridge component for CLIP vision encoder layers 

4used in multimodal models like LLava. 

5""" 

6 

7from typing import Any, Dict, Optional 

8 

9import torch 

10 

11from transformer_lens.hook_points import HookPoint 

12from transformer_lens.model_bridge.generalized_components.attention import ( 

13 AttentionBridge, 

14) 

15from transformer_lens.model_bridge.generalized_components.base import ( 

16 GeneralizedComponent, 

17) 

18from transformer_lens.model_bridge.generalized_components.linear import LinearBridge 

19from transformer_lens.model_bridge.generalized_components.mlp import MLPBridge 

20from transformer_lens.model_bridge.generalized_components.normalization import ( 

21 NormalizationBridge, 

22) 

23from transformer_lens.model_bridge.generalized_components.vision_encoder import ( 

24 vision_attention_config, 

25) 

26 

27 

28class CLIPVisionEncoderLayerBridge(GeneralizedComponent): 

29 """Bridge for a single CLIP encoder layer. 

30 

31 CLIP encoder layers have: 

32 - layer_norm1: LayerNorm 

33 - self_attn: CLIPAttention 

34 - layer_norm2: LayerNorm 

35 - mlp: CLIPMLP 

36 """ 

37 

38 is_list_item: bool = True 

39 hook_aliases = { 

40 "hook_resid_pre": "hook_in", 

41 "hook_resid_post": "hook_out", 

42 "hook_attn_in": "attn.hook_in", 

43 "hook_attn_out": "attn.hook_out", 

44 "hook_mlp_in": "mlp.hook_in", 

45 "hook_mlp_out": "mlp.hook_out", 

46 } 

47 

48 def __init__( 

49 self, 

50 name: str, 

51 config: Optional[Any] = None, 

52 submodules: Optional[Dict[str, GeneralizedComponent]] = None, 

53 ): 

54 """Initialize the CLIP encoder layer bridge. 

55 

56 Args: 

57 name: The name of this component (e.g., "encoder.layers") 

58 config: Optional configuration object 

59 submodules: Dictionary of submodules to register 

60 """ 

61 default_submodules: Dict[str, GeneralizedComponent] = { 

62 "ln1": NormalizationBridge( 

63 name="layer_norm1", config=config, use_native_layernorm_autograd=True 

64 ), 

65 "attn": AttentionBridge( 

66 name="self_attn", 

67 config=vision_attention_config(config), 

68 submodules={ 

69 "q": LinearBridge(name="q_proj"), 

70 "k": LinearBridge(name="k_proj"), 

71 "v": LinearBridge(name="v_proj"), 

72 "o": LinearBridge(name="out_proj"), 

73 }, 

74 ), 

75 "ln2": NormalizationBridge( 

76 name="layer_norm2", config=config, use_native_layernorm_autograd=True 

77 ), 

78 "mlp": MLPBridge( 

79 name="mlp", 

80 config=config, 

81 submodules={ 

82 "in": LinearBridge(name="fc1"), 

83 "out": LinearBridge(name="fc2"), 

84 }, 

85 ), 

86 } 

87 if submodules: 87 ↛ 88line 87 didn't jump to line 88 because the condition on line 87 was never true

88 default_submodules.update(submodules) 

89 super().__init__(name, config, submodules=default_submodules) 

90 

91 def forward( 

92 self, 

93 hidden_states: torch.Tensor, 

94 attention_mask: Optional[torch.Tensor] = None, 

95 causal_attention_mask: Optional[torch.Tensor] = None, 

96 **kwargs: Any, 

97 ) -> torch.Tensor: 

98 """Forward pass through the vision encoder layer. 

99 

100 Args: 

101 hidden_states: Input hidden states from previous layer 

102 attention_mask: Optional attention mask 

103 causal_attention_mask: Optional causal attention mask (used by CLIP encoder) 

104 **kwargs: Additional arguments 

105 

106 Returns: 

107 Output hidden states 

108 """ 

109 if self.original_component is None: 

110 raise RuntimeError( 

111 f"Original component not set for {self.name}. Call set_original_component() first." 

112 ) 

113 

114 hidden_states = self.hook_in(hidden_states) 

115 output = self.original_component( 

116 hidden_states, 

117 attention_mask=attention_mask, 

118 causal_attention_mask=causal_attention_mask, 

119 **kwargs, 

120 ) 

121 

122 if isinstance(output, tuple): 

123 output = (self.hook_out(output[0]),) + output[1:] 

124 else: 

125 output = self.hook_out(output) 

126 

127 return output 

128 

129 

130class CLIPVisionEncoderBridge(GeneralizedComponent): 

131 """Bridge for the complete CLIP vision encoder. 

132 

133 The CLIP vision tower consists of: 

134 - vision_model.embeddings: Patch + position + CLS token embeddings 

135 - vision_model.pre_layrnorm: LayerNorm before encoder layers 

136 - vision_model.encoder.layers[]: Stack of encoder layers 

137 - vision_model.post_layernorm: Final layer norm 

138 

139 This bridge wraps the entire vision tower to provide hooks for 

140 interpretability of the vision processing pipeline. 

141 """ 

142 

143 hook_aliases = { 

144 "hook_vision_embed": "embeddings.hook_out", 

145 "hook_vision_out": "hook_out", 

146 } 

147 

148 def __init__( 

149 self, 

150 name: str, 

151 config: Optional[Any] = None, 

152 submodules: Optional[Dict[str, GeneralizedComponent]] = None, 

153 ): 

154 """Initialize the CLIP vision encoder bridge. 

155 

156 Args: 

157 name: The name of this component (e.g., "vision_tower") 

158 config: Optional configuration object 

159 submodules: Dictionary of submodules to register 

160 """ 

161 default_submodules: Dict[str, GeneralizedComponent] = { 

162 "embeddings": GeneralizedComponent(name="vision_model.embeddings"), 

163 # Native-autograd runs the wrapped LayerNorm's own forward, so the 

164 # vision tower's eps applies instead of the LM config's. 

165 "pre_layernorm": NormalizationBridge( 

166 name="vision_model.pre_layrnorm", 

167 config=config, 

168 use_native_layernorm_autograd=True, 

169 ), 

170 "encoder_layers": CLIPVisionEncoderLayerBridge( 

171 name="vision_model.encoder.layers", config=config 

172 ), 

173 "post_layernorm": NormalizationBridge( 

174 name="vision_model.post_layernorm", 

175 config=config, 

176 use_native_layernorm_autograd=True, 

177 ), 

178 } 

179 

180 if submodules: 180 ↛ 181line 180 didn't jump to line 181 because the condition on line 180 was never true

181 default_submodules.update(submodules) 

182 

183 super().__init__(name, config, submodules=default_submodules) 

184 

185 # Additional hooks for vision-specific processing 

186 self.hook_patch_embed = HookPoint() # After patch embedding 

187 self.hook_pos_embed = HookPoint() # After position embedding added 

188 

189 def forward( 

190 self, 

191 pixel_values: torch.Tensor, 

192 **kwargs: Any, 

193 ) -> torch.Tensor: 

194 """Forward pass through the vision encoder. 

195 

196 Args: 

197 pixel_values: Input image tensor [batch, channels, height, width] 

198 **kwargs: Additional arguments 

199 

200 Returns: 

201 Vision embeddings [batch, num_patches, hidden_size] 

202 """ 

203 if self.original_component is None: 

204 raise RuntimeError( 

205 f"Original component not set for {self.name}. Call set_original_component() first." 

206 ) 

207 

208 pixel_values = self.hook_in(pixel_values) 

209 

210 output = self.original_component(pixel_values, **kwargs) 

211 

212 # Handle tuple output (some models return (hidden_states, ...)) 

213 if isinstance(output, tuple): 

214 output = (self.hook_out(output[0]),) + output[1:] 

215 elif hasattr(output, "last_hidden_state"): 

216 # Handle BaseModelOutput-like returns 

217 output.last_hidden_state = self.hook_out(output.last_hidden_state) 

218 else: 

219 output = self.hook_out(output) 

220 

221 return output 

222 

223 def set_original_component(self, original_component: torch.nn.Module) -> None: 

224 """Set the original component that this bridge wraps. 

225 Note that CLIPVisionModel used to wrap a inner object as .vision_model before 

226 transformers version 5.6.0, but after that it must directly be used. 

227 This is a temporary hack to fix that till the transformers version is bumped. 

228 

229 Args: 

230 original_component: The original transformer component to wrap 

231 """ 

232 if not hasattr(original_component, "vision_model"): 

233 # We should bypass any pytorch module registration. 

234 object.__setattr__(original_component, "vision_model", original_component) 

235 super().set_original_component(original_component)