Coverage for transformer_lens/pretrained/weight_conversions/olmo3.py: 100%
42 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"""Weight conversion functions for OLMo 3/3.1 models.
3OLMo 3/3.1 architecture features:
4- Q/K normalization (RMSNorm on queries/keys before attention)
5- Grouped Query Attention (GQA) with n_key_value_heads < n_heads
6- Sliding window attention + full attention layers (mixed via layer_types)
7- RMSNorm throughout (no +1 modification unlike Gemma)
8- Rotary Position Embeddings (RoPE) with YARN scaling
9- Gated MLP (SwiGLU-style)
10- Post-normalization pattern (RMSNorm after attention and MLP)
11"""
13from typing import cast
15import einops
16import torch
18from transformer_lens.config.hooked_transformer_config import HookedTransformerConfig
21def convert_olmo3_weights(olmo3, cfg: HookedTransformerConfig):
22 state_dict = {}
24 # Must match TransformerBlock's attention choice, which selects
25 # GroupedQueryAttention (underscore-prefixed params) whenever
26 # n_key_value_heads is set — including n_key_value_heads == n_heads.
27 using_gqa = cfg.n_key_value_heads is not None
28 gqa_uscore = "_" if using_gqa else ""
29 n_kv_heads = cast(int, cfg.n_key_value_heads if using_gqa else cfg.n_heads)
31 assert cfg.d_mlp is not None # keep mypy happy
33 base_model = olmo3.model
34 state_dict["embed.W_E"] = base_model.embed_tokens.weight
36 for l in range(cfg.n_layers):
37 state_dict[f"blocks.{l}.ln1.w"] = base_model.layers[l].post_attention_layernorm.weight
39 W_Q = base_model.layers[l].self_attn.q_proj.weight
40 W_K = base_model.layers[l].self_attn.k_proj.weight
41 W_V = base_model.layers[l].self_attn.v_proj.weight
43 W_Q = einops.rearrange(W_Q, "(n h) m->n m h", n=cfg.n_heads)
44 W_K = einops.rearrange(W_K, "(n h) m->n m h", n=n_kv_heads)
45 W_V = einops.rearrange(W_V, "(n h) m->n m h", n=n_kv_heads)
47 state_dict[f"blocks.{l}.attn.W_Q"] = W_Q
48 state_dict[f"blocks.{l}.attn.{gqa_uscore}W_K"] = W_K
49 state_dict[f"blocks.{l}.attn.{gqa_uscore}W_V"] = W_V
51 # OLMo 3 always has Q/K norms (applied on full projected vectors)
52 state_dict[f"blocks.{l}.attn.q_norm.w"] = base_model.layers[l].self_attn.q_norm.weight
53 state_dict[f"blocks.{l}.attn.k_norm.w"] = base_model.layers[l].self_attn.k_norm.weight
55 state_dict[f"blocks.{l}.attn.b_Q"] = torch.zeros(
56 cfg.n_heads, cfg.d_head, dtype=cfg.dtype, device=W_Q.device
57 )
58 state_dict[f"blocks.{l}.attn.{gqa_uscore}b_K"] = torch.zeros(
59 n_kv_heads, cfg.d_head, dtype=cfg.dtype, device=W_K.device
60 )
61 state_dict[f"blocks.{l}.attn.{gqa_uscore}b_V"] = torch.zeros(
62 n_kv_heads, cfg.d_head, dtype=cfg.dtype, device=W_V.device
63 )
65 W_O = base_model.layers[l].self_attn.o_proj.weight
66 W_O = einops.rearrange(W_O, "m (n h)->n h m", n=cfg.n_heads)
67 state_dict[f"blocks.{l}.attn.W_O"] = W_O
69 state_dict[f"blocks.{l}.attn.b_O"] = torch.zeros(
70 cfg.d_model, dtype=cfg.dtype, device=W_O.device
71 )
73 state_dict[f"blocks.{l}.ln2.w"] = base_model.layers[l].post_feedforward_layernorm.weight
74 state_dict[f"blocks.{l}.mlp.W_in"] = base_model.layers[l].mlp.up_proj.weight.T
75 state_dict[f"blocks.{l}.mlp.W_gate"] = base_model.layers[l].mlp.gate_proj.weight.T
76 state_dict[f"blocks.{l}.mlp.b_in"] = torch.zeros(
77 cfg.d_mlp, dtype=cfg.dtype, device=base_model.layers[l].mlp.up_proj.weight.device
78 )
79 state_dict[f"blocks.{l}.mlp.W_out"] = base_model.layers[l].mlp.down_proj.weight.T
80 state_dict[f"blocks.{l}.mlp.b_out"] = torch.zeros(
81 cfg.d_model, dtype=cfg.dtype, device=base_model.layers[l].mlp.down_proj.weight.device
82 )
84 state_dict["ln_final.w"] = base_model.norm.weight
86 state_dict["unembed.W_U"] = olmo3.lm_head.weight.T
87 state_dict["unembed.b_U"] = torch.zeros(
88 cfg.d_vocab, dtype=cfg.dtype, device=olmo3.lm_head.weight.device
89 )
91 return state_dict