Coverage for transformer_lens/pretrained/weight_conversions/gemma.py: 96%
54 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
1import einops
2import torch
4from transformer_lens.config.hooked_transformer_config import HookedTransformerConfig
7def convert_gemma_weights(gemma, cfg: HookedTransformerConfig):
8 state_dict = {}
10 assert cfg.n_key_value_heads is not None # keep mypy happy
11 assert cfg.d_mlp is not None # keep mypy happy
13 # Multimodal detection must survive transformers 5.x, which moved
14 # Gemma3ForConditionalGeneration's language_model under .model — the old
15 # top-level probe silently reported text-only there and converted the
16 # multimodal model down the wrong path.
17 language_model = getattr(gemma, "language_model", None)
18 if language_model is None:
19 language_model = getattr(getattr(gemma, "model", None), "language_model", None)
21 if language_model is not None:
22 # Vision tower is skipped entirely; only the text transformer converts.
23 base_model = getattr(language_model, "model", language_model)
24 else:
25 # Text-only Gemma3ForCausalLM has .model wrapper
26 base_model = gemma.model
28 # Gemma Models scale embeddings by multiplying by sqrt(d_model), use hidden state type to match
29 # HF implementation
30 state_dict["embed.W_E"] = base_model.embed_tokens.weight * torch.tensor(
31 cfg.d_model**0.5, dtype=cfg.dtype
32 )
34 # Gemma has no biases anywhere
35 for l in range(cfg.n_layers):
36 # GemmaRMSNorm adds 1 to weights before multiplying by input, keep RMS calcs in float32
37 state_dict[f"blocks.{l}.ln1.w"] = base_model.layers[
38 l
39 ].input_layernorm.weight.float() + torch.ones_like(
40 base_model.layers[l].input_layernorm.weight, dtype=torch.float32
41 )
42 if cfg.use_normalization_before_and_after: 42 ↛ 50line 42 didn't jump to line 50 because the condition on line 42 was always true
43 # Only applies for Gemma 2
44 state_dict[f"blocks.{l}.ln1_post.w"] = base_model.layers[
45 l
46 ].post_attention_layernorm.weight.float() + torch.ones_like(
47 base_model.layers[l].input_layernorm.weight, dtype=torch.float32
48 )
50 W_Q = base_model.layers[l].self_attn.q_proj.weight
51 W_K = base_model.layers[l].self_attn.k_proj.weight
52 W_V = base_model.layers[l].self_attn.v_proj.weight
53 W_Q = einops.rearrange(W_Q, "(n h) m->n m h", n=cfg.n_heads)
54 W_K = einops.rearrange(W_K, "(n h) m->n m h", n=cfg.n_key_value_heads)
55 W_V = einops.rearrange(W_V, "(n h) m->n m h", n=cfg.n_key_value_heads)
56 state_dict[f"blocks.{l}.attn.W_Q"] = W_Q
57 state_dict[f"blocks.{l}.attn._W_K"] = W_K
58 state_dict[f"blocks.{l}.attn._W_V"] = W_V
60 # Load q_norm and k_norm if they exist (Gemma 3)
61 # Gemma3RMSNorm adds 1 to weights in forward(), so we pre-add it here
62 if cfg.use_qk_norm:
63 state_dict[f"blocks.{l}.attn.q_norm.w"] = base_model.layers[
64 l
65 ].self_attn.q_norm.weight.float() + torch.ones_like(
66 base_model.layers[l].self_attn.q_norm.weight, dtype=torch.float32
67 )
68 state_dict[f"blocks.{l}.attn.k_norm.w"] = base_model.layers[
69 l
70 ].self_attn.k_norm.weight.float() + torch.ones_like(
71 base_model.layers[l].self_attn.k_norm.weight, dtype=torch.float32
72 )
74 state_dict[f"blocks.{l}.attn.b_Q"] = torch.zeros(
75 cfg.n_heads, cfg.d_head, dtype=cfg.dtype, device=W_Q.device
76 )
77 state_dict[f"blocks.{l}.attn._b_K"] = torch.zeros(
78 cfg.n_key_value_heads, cfg.d_head, dtype=cfg.dtype, device=W_K.device
79 )
80 state_dict[f"blocks.{l}.attn._b_V"] = torch.zeros(
81 cfg.n_key_value_heads, cfg.d_head, dtype=cfg.dtype, device=W_V.device
82 )
84 W_O = base_model.layers[l].self_attn.o_proj.weight
85 W_O = einops.rearrange(W_O, "m (n h)->n h m", n=cfg.n_heads)
86 state_dict[f"blocks.{l}.attn.W_O"] = W_O
88 state_dict[f"blocks.{l}.attn.b_O"] = torch.zeros(
89 cfg.d_model, dtype=cfg.dtype, device=W_O.device
90 )
92 # GemmaRMSNorm adds 1 to weights before multiplying by input, keep RMS calcs in float32
93 if not cfg.use_normalization_before_and_after: 93 ↛ 95line 93 didn't jump to line 95 because the condition on line 93 was never true
94 # Only applies for Gemma 1. Confusingly post_attention_layernorm is applied to mlp_input in Gemma 1 and attn_out in Gemma 2
95 state_dict[f"blocks.{l}.ln2.w"] = base_model.layers[
96 l
97 ].post_attention_layernorm.weight.float() + torch.ones_like(
98 base_model.norm.weight, dtype=torch.float32
99 )
100 else:
101 # Only applies for Gemma 2
102 state_dict[f"blocks.{l}.ln2.w"] = base_model.layers[
103 l
104 ].pre_feedforward_layernorm.weight.float() + torch.ones_like(
105 base_model.layers[l].pre_feedforward_layernorm.weight, dtype=torch.float32
106 )
107 state_dict[f"blocks.{l}.ln2_post.w"] = base_model.layers[
108 l
109 ].post_feedforward_layernorm.weight.float() + torch.ones_like(
110 base_model.layers[l].post_feedforward_layernorm.weight, dtype=torch.float32
111 )
113 state_dict[f"blocks.{l}.mlp.W_in"] = base_model.layers[l].mlp.up_proj.weight.T
114 state_dict[f"blocks.{l}.mlp.W_gate"] = base_model.layers[l].mlp.gate_proj.weight.T
115 state_dict[f"blocks.{l}.mlp.b_in"] = torch.zeros(
116 cfg.d_mlp, dtype=cfg.dtype, device=base_model.layers[l].mlp.up_proj.weight.device
117 )
119 state_dict[f"blocks.{l}.mlp.W_out"] = base_model.layers[l].mlp.down_proj.weight.T
120 state_dict[f"blocks.{l}.mlp.b_out"] = torch.zeros(
121 cfg.d_model, dtype=cfg.dtype, device=base_model.layers[l].mlp.down_proj.weight.device
122 )
124 # GemmaRMSNorm adds 1 to weights before multiplying by input, keep RMS calcs in float32
125 state_dict["ln_final.w"] = base_model.norm.weight.float() + torch.ones_like(
126 base_model.norm.weight, dtype=torch.float32
127 )
129 # For multimodal models, lm_head might not exist or be tied to embeddings
130 if hasattr(gemma, "lm_head"):
131 state_dict["unembed.W_U"] = gemma.lm_head.weight.T
132 unembed_device = gemma.lm_head.weight.device
133 else:
134 # Multimodal models might use tied embeddings
135 state_dict["unembed.W_U"] = base_model.embed_tokens.weight.T
136 unembed_device = base_model.embed_tokens.weight.device
137 state_dict["unembed.b_U"] = torch.zeros(cfg.d_vocab, dtype=cfg.dtype, device=unembed_device)
139 return state_dict