Coverage for transformer_lens/model_bridge/supported_architectures/gemma3n.py: 100%
26 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"""Gemma 3n text-only architecture adapter.
3Bridges the text path of the full tri-modal ``Gemma3nForConditionalGeneration``
4(``model.language_model`` + ``lm_head``); the vision/audio towers stay referenced but
5unbridged (see the vision+audio follow-up). The decoder layers run on a stacked AltUp
64-stream residual, so blocks use ``AltUpBlockBridge`` rather than ``BlockBridge``. All
7math is deferred to HF; submodules are decomposed only for hooks (parity-safe delegation).
8"""
10from typing import Any
12from transformer_lens.model_bridge.architecture_adapter import ArchitectureAdapter
13from transformer_lens.model_bridge.generalized_components import (
14 AltUpBlockBridge,
15 AttentionBridge,
16 EmbeddingBridge,
17 GatedMLPBridge,
18 LinearBridge,
19 RotaryEmbeddingBridge,
20 UnembeddingBridge,
21)
22from transformer_lens.model_bridge.generalized_components.base import (
23 GeneralizedComponent,
24)
27class _SparsityPreservingGatedMLPBridge(GatedMLPBridge):
28 """GatedMLPBridge that always delegates, even under processed weights.
30 Gemma3nTextMLP applies `_gaussian_topk` activation sparsity (0.95 on the
31 first layers of E2B/E4B) before the activation; the functional
32 processed-weights branch computes plain act(gate)*up and inflates those
33 layers' output ~10x. Delegation stays numerically correct in compat mode
34 because set_processed_weights has already written the processed tensors
35 into the wrapped Linears — and the gate/in/out hooks still fire from
36 inside HF's forward, which is all the bridge swap wanted.
37 """
39 def set_processed_weights(self, weights, verbose: bool = False) -> None:
40 super().set_processed_weights(weights, verbose=verbose)
41 self._use_processed_weights = False
44class Gemma3nArchitectureAdapter(ArchitectureAdapter):
45 """Text-only adapter for Gemma 3n (`Gemma3nForConditionalGeneration`)."""
47 _testing_lm_attr = "model.language_model"
48 _testing_wire_rotary = False
50 # The full model includes a timm-based vision tower (TimmWrapperModel), so timm is needed
51 # even for text-only use (the towers stay referenced).
52 required_libraries: list[str] = ["timm"]
53 required_libraries_group: str = "multimodal"
55 # Phase 3 (processed/compatibility mode) folds LN into a single residual stream, which
56 # AltUp's 4-stream residual can't represent. Phases 1 (HF parity), 2 (hooks), and 4 (text
57 # quality) do apply and pass.
58 applicable_phases: list[int] = [1, 2, 4]
60 def __init__(self, cfg: Any) -> None:
61 super().__init__(cfg)
63 self.cfg.is_multimodal = False
64 self.cfg.gated_mlp = True
65 self.cfg.uses_rms_norm = True
66 self.cfg.normalization_type = "RMS"
67 self.cfg.rmsnorm_uses_offset = True # Gemma RMSNorm uses (1.0 + weight)
68 self.cfg.positional_embedding_type = "rotary"
69 self.cfg.attn_implementation = "eager"
70 # AltUp + per-layer-embedding residual topology isn't fold-safe.
71 self.supports_fold_ln = False
72 self.weight_processing_conversions: dict = {}
74 self.component_mapping = {
75 "embed": EmbeddingBridge(name="model.language_model.embed_tokens"),
76 "rotary_emb": RotaryEmbeddingBridge(name="model.language_model.rotary_emb"),
77 "blocks": AltUpBlockBridge(
78 name="model.language_model.layers",
79 config=self.cfg,
80 submodules={
81 "input_layernorm": GeneralizedComponent(name="input_layernorm"),
82 "post_attention_layernorm": GeneralizedComponent(
83 name="post_attention_layernorm"
84 ),
85 "pre_feedforward_layernorm": GeneralizedComponent(
86 name="pre_feedforward_layernorm"
87 ),
88 "post_feedforward_layernorm": GeneralizedComponent(
89 name="post_feedforward_layernorm"
90 ),
91 "post_per_layer_input_norm": GeneralizedComponent(
92 name="post_per_layer_input_norm"
93 ),
94 "altup": GeneralizedComponent(name="altup"),
95 "laurel": GeneralizedComponent(name="laurel"),
96 "per_layer_input_gate": GeneralizedComponent(name="per_layer_input_gate"),
97 "per_layer_projection": GeneralizedComponent(name="per_layer_projection"),
98 # AttentionBridge for the per-head hook surface a bare
99 # component lacks. Delegated semantics: pattern AND scores
100 # both fire HF's post-softmax weights (no pre-softmax tensor
101 # exists, unlike gemma1/2/3); writes to them are inert.
102 "self_attn": AttentionBridge(
103 name="self_attn",
104 config=self.cfg,
105 maintain_native_attention=True,
106 submodules={
107 "q": LinearBridge(name="q_proj"),
108 # The last num_kv_shared_layers layers reuse earlier KV and
109 # drop their own k/v projections and norms.
110 "k": LinearBridge(name="k_proj", optional=True),
111 "v": LinearBridge(name="v_proj", optional=True),
112 "o": LinearBridge(name="o_proj"),
113 "q_norm": GeneralizedComponent(name="q_norm"),
114 "k_norm": GeneralizedComponent(name="k_norm", optional=True),
115 "v_norm": GeneralizedComponent(name="v_norm", optional=True),
116 },
117 ),
118 "mlp": _SparsityPreservingGatedMLPBridge(
119 name="mlp",
120 config=self.cfg,
121 submodules={
122 "gate": LinearBridge(name="gate_proj"),
123 "in": LinearBridge(name="up_proj"),
124 "out": LinearBridge(name="down_proj"),
125 },
126 ),
127 },
128 ),
129 "ln_final": GeneralizedComponent(name="model.language_model.norm"),
130 "unembed": UnembeddingBridge(name="lm_head"),
131 }