Coverage for transformer_lens/model_bridge/supported_architectures/granite.py: 100%
28 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"""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 AttentionBridge,
14 EmbeddingBridge,
15 GatedMLPBridge,
16 MoEBridge,
17 MoERouterBridge,
18 RMSNormalizationBridge,
19 RotaryEmbeddingBridge,
20 ScaledResidualBlockBridge,
21 UnembeddingBridge,
22)
25class GraniteArchitectureAdapter(ArchitectureAdapter):
26 """Architecture adapter for IBM Granite models (dense).
28 Granite is a Llama-like architecture with RMSNorm, rotary position embeddings
29 (RoPE), GQA, and a gated MLP (SiLU activation). Granite-specific scaling
30 multipliers are applied by the HF model's native forward pass;
31 ScaledResidualBlockBridge accounts for residual_multiplier so
32 hook_attn_out / hook_mlp_out expose the scaled residual contributions.
33 EmbeddingBridge similarly exposes embedding_multiplier at hook_embed,
34 while per-head hook_result includes the residual multiplier.
36 Optional Parameters (may not exist in state_dict):
37 -------------------------------------------------
38 Granite models do NOT have biases on attention and MLP projections:
40 - blocks.{i}.attn.b_Q/b_K/b_V/b_O - No bias on attention projections
41 - blocks.{i}.mlp.b_in/b_gate/b_out - No bias on MLP projections
42 - blocks.{i}.ln1.b, blocks.{i}.ln2.b, ln_final.b - RMSNorm has no bias
43 """
45 _testing_hybrid = True
46 _testing_eager = None
48 def __init__(self, cfg: Any) -> None:
49 """Initialize the Granite architecture adapter."""
50 super().__init__(cfg)
52 self._setup_common_config(cfg)
53 self.weight_processing_conversions = {**self._qkvo_weight_conversions()}
54 self.component_mapping = self._build_component_mapping()
56 def _setup_common_config(self, cfg: Any) -> None:
57 """Set up config variables shared across all Granite variants."""
58 self._set_rms_rotary_defaults()
59 self.cfg.default_prepend_bos = False
61 def _build_attention_bridge(self, optional: bool = False) -> AttentionBridge:
62 """Build the standard Granite attention bridge (GraniteMoeHybrid passes optional)."""
63 return self._qkvo_attention_bridge(optional=optional)
65 def _build_embedding_bridge(self) -> EmbeddingBridge:
66 """Expose the embedding's scaled residual contribution at hook_out."""
67 return EmbeddingBridge(
68 name="model.embed_tokens",
69 output_contribution_scale=getattr(self.cfg, "embedding_multiplier", 1.0),
70 )
72 def _build_mlp_bridge(self) -> GatedMLPBridge:
73 """Build the dense gated MLP bridge."""
74 return self._gated_mlp()
76 def _build_moe_bridge(self) -> MoEBridge:
77 """Sparse MoE block with a hookable router.
79 logits_index=-1: GraniteMoe-family routers return
80 (top_k_index, top_k_weights, router_logits) — index 0 is an int64
81 index tensor, so the default would hook the wrong element.
82 """
83 return MoEBridge(
84 name="block_sparse_moe",
85 config=self.cfg,
86 submodules={
87 "gate": MoERouterBridge(
88 name="router", logits_index=-1, indices_index=0, weights_index=1
89 )
90 },
91 )
93 def _build_component_mapping(self) -> dict:
94 """Build the full component mapping for dense Granite."""
95 return {
96 "embed": self._build_embedding_bridge(),
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)