Coverage for transformer_lens/model_bridge/generalized_components/altup_block.py: 63%
65 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"""Block bridge for AltUp (Alternating Updates) decoder layers."""
2from __future__ import annotations
4import re
5from typing import Any, Dict, Optional
7import torch
9from transformer_lens.hook_points import HookPoint
10from transformer_lens.model_bridge.exceptions import StopAtLayerException
11from transformer_lens.model_bridge.generalized_components.base import (
12 GeneralizedComponent,
13)
16class AltUpBlockBridge(GeneralizedComponent):
17 """Block bridge for a decoder layer that operates on a stacked AltUp residual.
19 Direct GeneralizedComponent subclass (not BlockBridge) because the layer's residual is a
20 stacked ``[num_altup_inputs, batch, seq, d_model]`` tensor, not a single stream.
21 ``hook_in``/``hook_out`` carry the full stack; ``hook_resid_pre``/``hook_resid_post`` expose
22 the active stream (``altup_active_idx``) as a conventional ``[batch, seq, d_model]`` residual
23 and are patchable (written back into the stack).
24 """
26 is_list_item: bool = True
27 hook_aliases = {
28 "hook_attn_out": "self_attn.hook_out",
29 "hook_mlp_out": "mlp.hook_out",
30 }
32 def __init__(
33 self,
34 name: str,
35 config: Optional[Any] = None,
36 submodules: Optional[Dict[str, GeneralizedComponent]] = None,
37 hook_alias_overrides: Optional[Dict[str, str]] = None,
38 ):
39 # Same post-norm redirect as BlockBridge: the residual contribution is the
40 # post-sublayer norm's output, not the raw sublayer output.
41 auto_overrides = {}
42 if submodules is not None:
43 if "ln1_post" in submodules: 43 ↛ 45line 43 didn't jump to line 45 because the condition on line 43 was always true
44 auto_overrides["hook_attn_out"] = "ln1_post.hook_out"
45 if "ln2_post" in submodules: 45 ↛ 47line 45 didn't jump to line 47 because the condition on line 45 was always true
46 auto_overrides["hook_mlp_out"] = "ln2_post.hook_out"
47 merged_overrides = {**auto_overrides, **(hook_alias_overrides or {})}
48 super().__init__(
49 name,
50 config,
51 submodules=submodules if submodules is not None else {},
52 hook_alias_overrides=merged_overrides if merged_overrides else None,
53 )
54 self.altup_active_idx = int(getattr(config, "altup_active_idx", 0) or 0)
55 # Active AltUp stream as a conventional residual (patchable).
56 self.hook_resid_pre = HookPoint()
57 self.hook_resid_post = HookPoint()
59 def forward(self, *args: Any, **kwargs: Any) -> Any:
60 """Delegate to the HF layer, hooking the AltUp stack and the active residual stream."""
61 if self.original_component is None: 61 ↛ 62line 61 didn't jump to line 62 because the condition on line 61 was never true
62 raise RuntimeError(
63 f"Original component not set for {self.name}. Call set_original_component() first."
64 )
66 self._check_stop_at_layer(*args, **kwargs)
67 args, kwargs = self._hook_input(args, kwargs)
68 output = self.original_component(*args, **kwargs)
69 return self._hook_output(output)
71 def _patch_active_stream(self, stack: torch.Tensor, hook: HookPoint) -> torch.Tensor:
72 """Fire ``hook`` on the active AltUp stream, cloning the stack only if it was patched."""
73 if ( 73 ↛ 78line 73 didn't jump to line 78 because the condition on line 73 was never true
74 not isinstance(stack, torch.Tensor)
75 or stack.dim() < 1
76 or stack.shape[0] <= self.altup_active_idx
77 ):
78 return stack
79 # Capture the view once: indexing returns a fresh object each call, so the identity
80 # check must compare against this exact view, not a re-indexed one.
81 active_view = stack[self.altup_active_idx]
82 active = hook(active_view)
83 # Common case (no hooks, or read-only hooks that return their input): nothing changed,
84 # so skip the full [num_altup, batch, seq, d_model] clone.
85 if active is active_view: 85 ↛ 87line 85 didn't jump to line 87 because the condition on line 85 was always true
86 return stack
87 stack = stack.clone()
88 stack[self.altup_active_idx] = active
89 return stack
91 def _hook_input(self, args: tuple, kwargs: dict) -> tuple[tuple, dict]:
92 """Hook the stacked hidden_states then the active residual, positional or by name."""
93 if len(args) > 0 and isinstance(args[0], torch.Tensor): 93 ↛ 96line 93 didn't jump to line 96 because the condition on line 93 was always true
94 hidden = self._patch_active_stream(self.hook_in(args[0]), self.hook_resid_pre)
95 args = (hidden,) + args[1:]
96 elif "hidden_states" in kwargs and isinstance(kwargs["hidden_states"], torch.Tensor):
97 kwargs["hidden_states"] = self._patch_active_stream(
98 self.hook_in(kwargs["hidden_states"]), self.hook_resid_pre
99 )
100 return args, kwargs
102 def _hook_output(self, output: Any) -> Any:
103 """Hook the active residual then the stacked output, preserving any tuple structure."""
104 primary = output[0] if isinstance(output, tuple) and len(output) > 0 else output
105 if isinstance(primary, torch.Tensor): 105 ↛ 107line 105 didn't jump to line 107 because the condition on line 105 was always true
106 primary = self.hook_out(self._patch_active_stream(primary, self.hook_resid_post))
107 if isinstance(output, tuple) and len(output) > 0: 107 ↛ 108line 107 didn't jump to line 108 because the condition on line 107 was never true
108 return (primary,) + output[1:]
109 return primary
111 def _check_stop_at_layer(self, *args: Any, **kwargs: Any) -> None:
112 """Raise StopAtLayerException (carrying the AltUp stack) at the configured stop index."""
113 if getattr(self, "_stop_at_layer_idx", None) is None or self.name is None: 113 ↛ 115line 113 didn't jump to line 115 because the condition on line 113 was always true
114 return
115 match = re.search(r"\.layers\.(\d+)", self.name) or re.search(r"blocks\.(\d+)", self.name)
116 if not match or int(match.group(1)) != self._stop_at_layer_idx:
117 return
118 if len(args) > 0 and isinstance(args[0], torch.Tensor):
119 tensor = args[0]
120 elif "hidden_states" in kwargs and isinstance(kwargs["hidden_states"], torch.Tensor):
121 tensor = kwargs["hidden_states"]
122 else:
123 raise ValueError(f"Cannot find input tensor to stop at layer {self.name}")
124 raise StopAtLayerException(self.hook_in(tensor))