Coverage for transformer_lens/model_bridge/supported_architectures/gemma3.py: 46%
31 statements
« prev ^ index » next coverage.py v7.10.1, created at 2026-10-10 05:10 +0000
« prev ^ index » next coverage.py v7.10.1, created at 2026-10-10 05:10 +0000
1"""Gemma3 architecture adapter."""
4from typing import Any
6from transformer_lens.conversion_utils.conversion_steps import TransposeTensorConversion
7from transformer_lens.conversion_utils.param_processing_conversion import (
8 ParamProcessingConversion,
9)
10from transformer_lens.model_bridge.architecture_adapter import ArchitectureAdapter
11from transformer_lens.model_bridge.generalized_components import (
12 BlockBridge,
13 EmbeddingBridge,
14 LinearBridge,
15 RMSNormalizationBridge,
16 RotaryEmbeddingBridge,
17 UnembeddingBridge,
18)
19from transformer_lens.model_bridge.generalized_components.position_embeddings_attention import (
20 PositionEmbeddingsAttentionBridge,
21)
24def _enable_native_qk_norm_autograd(bridge_model: Any) -> None:
25 """Component tests grad-check through the wrapped Q/K norms; delegate them
26 to HF's native layernorm autograd (shared by text and multimodal Gemma-3)."""
27 if bridge_model is None or not hasattr(bridge_model, "blocks"):
28 return
29 for block in bridge_model.blocks:
30 hf_attn = getattr(getattr(block, "attn", None), "original_component", None)
31 if hf_attn is None:
32 continue
33 if hasattr(hf_attn, "q_norm"):
34 hf_attn.q_norm.use_native_layernorm_autograd = True
35 if hasattr(hf_attn, "k_norm"):
36 hf_attn.k_norm.use_native_layernorm_autograd = True
39class Gemma3ArchitectureAdapter(ArchitectureAdapter):
40 """Architecture adapter for Gemma3 models."""
42 def __init__(self, cfg: Any) -> None:
43 """Initialize the Gemma3 architecture adapter."""
44 super().__init__(cfg)
46 self.cfg.gated_mlp = True
48 self.cfg.uses_rms_norm = True
49 self.cfg.normalization_type = "RMS"
50 # Gemma models use (1.0 + weight) in RMSNorm instead of just weight
51 # See: https://github.com/huggingface/transformers/pull/29402
52 self.cfg.rmsnorm_uses_offset = True
54 # Gemma 3 uses rotary positional embeddings (dual RoPE)
55 self.cfg.positional_embedding_type = "rotary"
57 # Use eager attention to support output_attentions for hook_attn_scores and hook_pattern
58 # SDPA doesn't support output_attentions, which is required for HookedTransformer compatibility
59 self.cfg.attn_implementation = "eager"
61 self.weight_processing_conversions = {
62 # Note: Gemma3TextScaledWordEmbedding scales by sqrt(d_model) inside
63 # its own forward(). Bridge.embed wraps that layer, so embed.hook_out
64 # already captures the scaled value — no weight pre-scaling and no
65 # hook_conversion needed (setup_hook_compatibility is a no-op).
66 #
67 # Q/K/V weight conversions
68 **self._qkvo_weight_conversions(),
69 # Norm weights (incl. q_norm/k_norm) carry no conversion entry:
70 # cfg.rmsnorm_uses_offset is the only offset signal — weight processing
71 # and the runtime norm read it directly and apply (1 + w) themselves.
72 # MLP weight conversions - transpose from [out, in] to [in, out]
73 "blocks.{i}.mlp.gate.weight": ParamProcessingConversion(
74 tensor_conversion=TransposeTensorConversion(),
75 ),
76 "blocks.{i}.mlp.in.weight": ParamProcessingConversion(
77 tensor_conversion=TransposeTensorConversion(),
78 ),
79 "blocks.{i}.mlp.out.weight": ParamProcessingConversion(
80 tensor_conversion=TransposeTensorConversion(),
81 ),
82 # Unembed weight conversion - transpose from [vocab, d_model] to [d_model, vocab]
83 "unembed.weight": ParamProcessingConversion(
84 tensor_conversion=TransposeTensorConversion(),
85 ),
86 # Note: Gemma-3 does NOT have biases on attention projections (q/k/v/o_proj.bias are all None)
87 # No bias conversions needed
88 }
90 # Set up component mapping with actual bridge instances
91 self.component_mapping = {
92 "embed": EmbeddingBridge(name="model.embed_tokens"),
93 "rotary_emb": RotaryEmbeddingBridge(name="model.rotary_emb"),
94 "blocks": BlockBridge(
95 name="model.layers",
96 submodules={
97 # All Gemma-3 normalizations use simple RMSNorm pass-through
98 "ln1": RMSNormalizationBridge(name="input_layernorm", config=self.cfg),
99 "ln1_post": RMSNormalizationBridge(
100 name="post_attention_layernorm", config=self.cfg
101 ),
102 "ln2": RMSNormalizationBridge(
103 name="pre_feedforward_layernorm", config=self.cfg
104 ),
105 "ln2_post": RMSNormalizationBridge(
106 name="post_feedforward_layernorm", config=self.cfg
107 ),
108 "attn": PositionEmbeddingsAttentionBridge(
109 name="self_attn",
110 config=self.cfg,
111 submodules={
112 "q": LinearBridge(name="q_proj"),
113 "k": LinearBridge(name="k_proj"),
114 "v": LinearBridge(name="v_proj"),
115 "o": LinearBridge(name="o_proj"),
116 "q_norm": RMSNormalizationBridge(name="q_norm", config=self.cfg),
117 "k_norm": RMSNormalizationBridge(name="k_norm", config=self.cfg),
118 },
119 ),
120 "mlp": self._gated_mlp(),
121 },
122 ),
123 "ln_final": RMSNormalizationBridge(name="model.norm", config=self.cfg),
124 "unembed": UnembeddingBridge(name="lm_head"),
125 }
127 def setup_component_testing(self, hf_model: Any, bridge_model: Any = None) -> None:
128 """Wire local RoPE + eager attention; q/k norms delegate to HF autograd.
130 Gemma-3 uses dual RoPE (global + local); component tests share the local
131 instance across all layers (layers on global RoPE accept the tradeoff).
132 """
133 super().setup_component_testing(hf_model, bridge_model)
134 _enable_native_qk_norm_autograd(bridge_model)