Coverage for transformer_lens/model_bridge/supported_architectures/llama4.py: 85%
31 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"""Llama 4 (text) architecture adapter.
3Meta's Llama 4 text decoder (``Llama4ForCausalLM``): llama-style RMS-norm
4blocks whose attention adds complex-valued interleaved RoPE, NoPE layers
5with temperature tuning, post-RoPE weightless L2 QK-norm, and chunked
6attention masks — so attention stays delegated to HF. The feed-forward is
7either a sparse MoE (batched 3D experts + top-k sigmoid router + shared
8expert) or a dense gated MLP on non-MoE layers.
9"""
11from typing import Any
13import torch
15from transformer_lens.model_bridge.architecture_adapter import ArchitectureAdapter
16from transformer_lens.model_bridge.generalized_components import (
17 AttentionBridge,
18 BlockBridge,
19 EmbeddingBridge,
20 GatedMLPBridge,
21 LinearBridge,
22 MoEBridge,
23 RMSNormalizationBridge,
24 UnembeddingBridge,
25)
26from transformer_lens.model_bridge.generalized_components.base import (
27 CloneOutputUnderGradMixin,
28 GeneralizedComponent,
29)
32class _Llama4SharedExpertBridge(CloneOutputUnderGradMixin, GatedMLPBridge):
33 """Llama4TextMoe accumulates routed output into the shared-expert result
34 with an in-place ``add_``; clone under grad (see mixin)."""
37class _Llama4MoEBridge(MoEBridge):
38 """MoEBridge that fires hook_out in [batch, seq, d_model].
40 Llama4TextMoe flattens to [batch * seq, d_model] internally and the
41 decoder layer views the result back, so hooks are fired on an
42 input-shaped view and the HF-native flat shape is returned.
43 """
45 def forward(self, *args: Any, **kwargs: Any) -> Any:
46 if self.original_component is None: 46 ↛ 47line 46 didn't jump to line 47 because the condition on line 46 was never true
47 raise RuntimeError(
48 f"Original component not set for {self.name}. Call set_original_component() first."
49 )
50 if len(args) > 0: 50 ↛ 54line 50 didn't jump to line 54 because the condition on line 50 was always true
51 hidden = self.hook_in(args[0])
52 args = (hidden,) + args[1:]
53 else:
54 hidden = self.hook_in(kwargs["hidden_states"])
55 kwargs = {**kwargs, "hidden_states": hidden}
56 output = self.original_component(*args, **kwargs)
57 if isinstance(output, tuple):
58 flat = output[0]
59 if len(output) > 1: 59 ↛ 61line 59 didn't jump to line 61 because the condition on line 59 was always true
60 self.hook_router_scores(output[1])
61 hooked = self.hook_out(flat.view(hidden.shape))
62 return (hooked.view(flat.shape),) + output[1:]
63 assert isinstance(output, torch.Tensor)
64 return self.hook_out(output.view(hidden.shape)).view(output.shape)
67class Llama4ArchitectureAdapter(ArchitectureAdapter):
68 """Architecture adapter for Llama4ForCausalLM models."""
70 def __init__(self, cfg: Any) -> None:
71 """Initialize the Llama 4 architecture adapter."""
72 super().__init__(cfg)
74 self._set_rms_rotary_defaults()
75 self.cfg.attn_implementation = "eager"
77 self.weight_processing_conversions = {
78 **self._qkvo_weight_conversions(),
79 }
81 self.component_mapping = {
82 "embed": EmbeddingBridge(name="model.embed_tokens"),
83 "blocks": BlockBridge(
84 name="model.layers",
85 submodules={
86 "ln1": RMSNormalizationBridge(name="input_layernorm", config=self.cfg),
87 "ln2": RMSNormalizationBridge(name="post_attention_layernorm", config=self.cfg),
88 # Complex-tensor RoPE, NoPE temperature tuning, L2 QK-norm,
89 # and chunked masks live in HF's forward; keep it native.
90 "attn": AttentionBridge(
91 name="self_attn",
92 config=self.cfg,
93 submodules={
94 "q": LinearBridge(name="q_proj"),
95 "k": LinearBridge(name="k_proj"),
96 "v": LinearBridge(name="v_proj"),
97 "o": LinearBridge(name="o_proj"),
98 },
99 maintain_native_attention=True,
100 requires_attention_mask=True,
101 ),
102 # The router returns a (scores, logits) tuple, so it stays
103 # unwrapped; MoEBridge.hook_router_scores captures logits.
104 # Non-MoE layers hold a dense gated MLP under the same name;
105 # its projections map as optional dense_* submodules.
106 "mlp": _Llama4MoEBridge(
107 name="feed_forward",
108 config=self.cfg,
109 sparse_required=("router",),
110 submodules={
111 # HF creates the router unconditionally on MoE
112 # layers; mapping it makes a rename loud and gives
113 # the layer real router observability.
114 "router": GeneralizedComponent(name="router", optional=True),
115 # Dense-layer projections (absent on MoE layers).
116 "dense_gate": LinearBridge(name="gate_proj", optional=True),
117 "dense_in": LinearBridge(name="up_proj", optional=True),
118 "dense_out": LinearBridge(name="down_proj", optional=True),
119 "shared_expert": _Llama4SharedExpertBridge(
120 name="shared_expert",
121 config=self.cfg,
122 optional=True,
123 submodules={
124 "gate": LinearBridge(name="gate_proj"),
125 "in": LinearBridge(name="up_proj"),
126 "out": LinearBridge(name="down_proj"),
127 },
128 ),
129 },
130 ),
131 },
132 ),
133 "ln_final": RMSNormalizationBridge(name="model.norm", config=self.cfg),
134 "unembed": UnembeddingBridge(name="lm_head", config=self.cfg),
135 }