Coverage for transformer_lens/model_bridge/supported_architectures/granite.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"""Granite architecture adapter.
3Base adapter for the IBM Granite model family. Provides shared config setup and
4helper methods used by GraniteMoe and GraniteMoeHybrid variants.
5"""
7from typing import Any
9import torch
11from transformer_lens.model_bridge.architecture_adapter import ArchitectureAdapter
12from transformer_lens.model_bridge.generalized_components import (
13 EmbeddingBridge,
14 GatedMLPBridge,
15 LinearBridge,
16 MoEBridge,
17 MoERouterBridge,
18 PositionEmbeddingsAttentionBridge,
19 RMSNormalizationBridge,
20 RotaryEmbeddingBridge,
21 ScaledResidualBlockBridge,
22 UnembeddingBridge,
23)
26class GraniteArchitectureAdapter(ArchitectureAdapter):
27 """Architecture adapter for IBM Granite models (dense).
29 Granite is a Llama-like architecture with RMSNorm, rotary position embeddings
30 (RoPE), GQA, and a gated MLP (SiLU activation). Granite-specific scaling
31 multipliers are applied by the HF model's native forward pass;
32 ScaledResidualBlockBridge accounts for residual_multiplier so
33 hook_attn_out / hook_mlp_out expose the scaled residual contributions.
35 Optional Parameters (may not exist in state_dict):
36 -------------------------------------------------
37 Granite models do NOT have biases on attention and MLP projections:
39 - blocks.{i}.attn.b_Q/b_K/b_V/b_O - No bias on attention projections
40 - blocks.{i}.mlp.b_in/b_gate/b_out - No bias on MLP projections
41 - blocks.{i}.ln1.b, blocks.{i}.ln2.b, ln_final.b - RMSNorm has no bias
42 """
44 _testing_hybrid = True
45 _testing_eager = None
47 def __init__(self, cfg: Any) -> None:
48 """Initialize the Granite architecture adapter."""
49 super().__init__(cfg)
51 self._setup_common_config(cfg)
52 self.weight_processing_conversions = {**self._qkvo_weight_conversions()}
53 self.component_mapping = self._build_component_mapping()
55 def _setup_common_config(self, cfg: Any) -> None:
56 """Set up config variables shared across all Granite variants."""
57 self._set_rms_rotary_defaults()
58 self.cfg.default_prepend_bos = False
60 def _build_attention_bridge(self, optional: bool = False) -> PositionEmbeddingsAttentionBridge:
61 """Build the standard Granite attention bridge."""
62 return PositionEmbeddingsAttentionBridge(
63 name="self_attn",
64 config=self.cfg,
65 optional=optional,
66 submodules={
67 "q": LinearBridge(name="q_proj"),
68 "k": LinearBridge(name="k_proj"),
69 "v": LinearBridge(name="v_proj"),
70 "o": LinearBridge(name="o_proj"),
71 },
72 requires_attention_mask=True,
73 requires_position_embeddings=True,
74 )
76 def _build_mlp_bridge(self) -> GatedMLPBridge:
77 """Build the dense gated MLP bridge."""
78 return self._gated_mlp()
80 def _build_moe_bridge(self) -> MoEBridge:
81 """Sparse MoE block with a hookable router.
83 logits_index=-1: GraniteMoe-family routers return
84 (top_k_index, top_k_weights, router_logits) — index 0 is an int64
85 index tensor, so the default would hook the wrong element.
86 """
87 return MoEBridge(
88 name="block_sparse_moe",
89 config=self.cfg,
90 submodules={"gate": MoERouterBridge(name="router", logits_index=-1)},
91 )
93 def _build_component_mapping(self) -> dict:
94 """Build the full component mapping for dense Granite."""
95 return {
96 "embed": EmbeddingBridge(name="model.embed_tokens"),
97 "rotary_emb": RotaryEmbeddingBridge(name="model.rotary_emb"),
98 # HF multiplies each sublayer output by residual_multiplier before the
99 # residual add, so hook_attn_out / hook_mlp_out must expose the scaled
100 # contribution, not the raw module output.
101 "blocks": ScaledResidualBlockBridge(
102 name="model.layers",
103 submodules={
104 "ln1": RMSNormalizationBridge(name="input_layernorm", config=self.cfg),
105 "ln2": RMSNormalizationBridge(name="post_attention_layernorm", config=self.cfg),
106 "attn": self._build_attention_bridge(),
107 "mlp": self._build_mlp_bridge(),
108 },
109 residual_contribution_scale=getattr(self.cfg, "residual_multiplier", 1.0),
110 ),
111 "ln_final": RMSNormalizationBridge(name="model.norm", config=self.cfg),
112 "unembed": UnembeddingBridge(name="lm_head", config=self.cfg),
113 }
115 def apply_output_logits_transform(self, logits: torch.Tensor) -> torch.Tensor:
116 """Match Granite's ``lm_head / logits_scaling`` output path."""
117 scaling = float(getattr(self.cfg, "logits_scaling", 1.0))
118 return super().apply_output_logits_transform(logits / scaling)