Coverage for transformer_lens/model_bridge/supported_architectures/olmo_hybrid.py: 89%
38 statements
« prev ^ index » next coverage.py v7.10.1, created at 2026-10-23 20:45 +0000
« prev ^ index » next coverage.py v7.10.1, created at 2026-10-23 20:45 +0000
1"""OLMo Hybrid architecture adapter.
3AllenAI's OLMo Hybrid (``OlmoHybridForCausalLM``, Olmo-Hybrid-7B):
4alternating layer types — OLMo2-style full-attention layers (post-norms
5in the residual, full-width QK-norm, NoPE mode when position embeddings
6are withheld) and GatedDeltaNet linear-attention layers (pre-norm, with
7separate q/k/v short convolutions). Attention stays HF-native; the
8OlmoHybrid GatedDeltaNet variant differs from Qwen3Next's (separate
9q/k/v conv states), so it is delegated opaquely rather than through
10GatedDeltaNetBridge's reimplementation. Generation uses the model's own
11OlmoHybridDynamicCache.
12"""
14from typing import Any
16import torch.nn as nn
18from transformer_lens.model_bridge.architecture_adapter import ArchitectureAdapter
19from transformer_lens.model_bridge.generalized_components import (
20 AttentionBridge,
21 BlockBridge,
22 EmbeddingBridge,
23 LinearBridge,
24 RMSNormalizationBridge,
25 UnembeddingBridge,
26)
27from transformer_lens.model_bridge.generalized_components.base import (
28 GeneralizedComponent,
29)
32class _OlmoHybridBlockBridge(BlockBridge):
33 """BlockBridge whose norm keys and hook aliases are chosen per layer type.
35 ``post_attention_layernorm`` is pre-MLP on linear-attention layers, so only those keep ``ln2``.
36 """
38 def __init__(self, *args: Any, **kwargs: Any):
39 super().__init__(*args, **kwargs)
40 if self.hook_aliases is BlockBridge.hook_aliases: 40 ↛ 41line 40 didn't jump to line 41 because the condition on line 40 was never true
41 self.hook_aliases = dict(self.hook_aliases)
42 # Per-layer targets are set at bind time; this fallback covers the
43 # template, which has both ln2 and ln2_post declared.
44 self.hook_aliases["hook_mlp_out"] = ["ln2_post.hook_out", "mlp.hook_out"]
46 def set_original_component(self, original_component: nn.Module) -> None:
47 super().set_original_component(original_component)
48 if self.hook_aliases is BlockBridge.hook_aliases: 48 ↛ 49line 48 didn't jump to line 49 because the condition on line 48 was never true
49 self.hook_aliases = dict(self.hook_aliases)
50 # Runs before setup_submodules binds the norms, so re-keying is safe.
51 if getattr(original_component, "post_feedforward_layernorm", None) is not None:
52 if "ln2" in self.submodules: 52 ↛ 54line 52 didn't jump to line 54 because the condition on line 52 was always true
53 self.submodules["ln1_post"] = self.submodules.pop("ln2")
54 self.hook_aliases["hook_resid_mid"] = "mlp.hook_in"
55 self.hook_aliases["hook_attn_out"] = "ln1_post.hook_out"
56 self.hook_aliases["hook_mlp_out"] = "ln2_post.hook_out"
57 self.mlp_reads_resid_directly = True
58 else:
59 self.hook_aliases["hook_resid_mid"] = "ln2.hook_in"
60 self.hook_aliases["hook_attn_out"] = "linear_attn.hook_out"
61 self.hook_aliases["hook_mlp_out"] = "mlp.hook_out"
62 self.mlp_reads_resid_directly = False
65class OlmoHybridArchitectureAdapter(ArchitectureAdapter):
66 """Architecture adapter for OlmoHybridForCausalLM models."""
68 # Post-norm attention layers and the linear-attention state are not
69 # fold-safe; compatibility-mode weight processing does not apply.
70 supports_fold_ln = False
72 def __init__(self, cfg: Any) -> None:
73 """Initialize the OLMo Hybrid architecture adapter."""
74 super().__init__(cfg)
76 self._set_rms_rotary_defaults()
77 self.cfg.attn_implementation = "eager"
78 self.cfg.is_stateful = True
80 self.weight_processing_conversions = {
81 **self._qkvo_weight_conversions(),
82 }
84 self.component_mapping = {
85 "embed": EmbeddingBridge(name="model.embed_tokens"),
86 "blocks": _OlmoHybridBlockBridge(
87 name="model.layers",
88 submodules={
89 # Linear-attention layers are pre-norm (input_layernorm);
90 # full-attention layers are OLMo2 post-norm and have
91 # post_feedforward_layernorm instead.
92 "ln1": RMSNormalizationBridge(
93 name="input_layernorm", config=self.cfg, optional=True
94 ),
95 "ln2": RMSNormalizationBridge(name="post_attention_layernorm", config=self.cfg),
96 "ln2_post": RMSNormalizationBridge(
97 name="post_feedforward_layernorm", config=self.cfg, optional=True
98 ),
99 "attn": AttentionBridge(
100 name="self_attn",
101 config=self.cfg,
102 submodules={
103 "q": LinearBridge(name="q_proj"),
104 "k": LinearBridge(name="k_proj"),
105 "v": LinearBridge(name="v_proj"),
106 "o": LinearBridge(name="o_proj"),
107 "q_norm": RMSNormalizationBridge(name="q_norm", config=self.cfg),
108 "k_norm": RMSNormalizationBridge(name="k_norm", config=self.cfg),
109 },
110 maintain_native_attention=True,
111 requires_attention_mask=True,
112 optional=True,
113 ),
114 "linear_attn": GeneralizedComponent(name="linear_attn", optional=True),
115 "mlp": self._gated_mlp(),
116 },
117 ),
118 "ln_final": RMSNormalizationBridge(name="model.norm", config=self.cfg),
119 "unembed": UnembeddingBridge(name="lm_head", config=self.cfg),
120 }
122 def create_stateful_cache(
123 self,
124 hf_model: Any,
125 batch_size: int,
126 device: Any,
127 dtype: Any,
128 ) -> Any:
129 """OLMo Hybrid keeps per-layer q/k/v conv states in its own cache class."""
130 from transformers.models.olmo_hybrid.modeling_olmo_hybrid import (
131 OlmoHybridDynamicCache,
132 )
134 return OlmoHybridDynamicCache(config=hf_model.config)