Coverage for transformer_lens/model_bridge/generalized_components/alibi_joint_qkv_attention.py: 84%

71 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2026-10-10 05:10 +0000

1"""ALiBi joint QKV attention bridge component. 

2 

3Handles models that use ALiBi (Attention with Linear Biases) with fused QKV projections. 

4Splits fused QKV, reimplements attention with ALiBi bias and hooks at each stage. 

5""" 

6 

7from __future__ import annotations 

8 

9from typing import Any, Dict, Optional 

10 

11import torch 

12 

13from transformer_lens.model_bridge.generalized_components.alibi_utils import ( 

14 build_alibi_tensor, 

15) 

16from transformer_lens.model_bridge.generalized_components.base import ( 

17 GeneralizedComponent, 

18) 

19from transformer_lens.model_bridge.generalized_components.joint_qkv_attention import ( 

20 JointQKVAttentionBridge, 

21) 

22 

23 

24class ALiBiJointQKVAttentionBridge(JointQKVAttentionBridge): 

25 """Attention bridge for models using ALiBi position encoding with fused QKV. 

26 

27 Splits fused QKV, reimplements attention with ALiBi bias fused into scores, 

28 and fires hooks at each stage (hook_q, hook_k, hook_v, hook_attn_scores, 

29 hook_pattern). ALiBi bias is added to raw attention scores before scaling. 

30 """ 

31 

32 def __init__( 

33 self, 

34 name: str, 

35 config: Any, 

36 split_qkv_matrix: Any = None, 

37 submodules: Optional[Dict[str, GeneralizedComponent]] = None, 

38 **kwargs: Any, 

39 ): 

40 super().__init__( 

41 name=name, 

42 config=config, 

43 split_qkv_matrix=split_qkv_matrix, 

44 submodules=submodules, 

45 requires_position_embeddings=False, 

46 requires_attention_mask=False, 

47 **kwargs, 

48 ) 

49 

50 def forward(self, *args: Any, **kwargs: Any) -> Any: 

51 """Forward pass: split QKV, apply ALiBi, fire hooks.""" 

52 if self.original_component is None: 52 ↛ 53line 52 didn't jump to line 53 because the condition on line 52 was never true

53 raise RuntimeError( 

54 f"Original component not set for {self.name}. " 

55 "Call set_original_component() first." 

56 ) 

57 

58 if len(args) > 0 and isinstance(args[0], torch.Tensor): 58 ↛ 60line 58 didn't jump to line 60 because the condition on line 58 was always true

59 hidden_states = args[0] 

60 elif "hidden_states" in kwargs: 

61 hidden_states = kwargs.pop("hidden_states") 

62 else: 

63 raise ValueError("Could not find hidden_states in args or kwargs") 

64 

65 hooked_input = self.hook_in(hidden_states) 

66 

67 # Split fused QKV via parent's split mechanism 

68 q_output = self.q(hooked_input) 

69 k_output = self.k(hooked_input) 

70 v_output = self.v(hooked_input) 

71 

72 attn_output, attn_weights = self._reconstruct_attention( 

73 q_output, k_output, v_output, **kwargs 

74 ) 

75 

76 output = self.hook_out(attn_output) 

77 return output, attn_weights 

78 

79 def _reconstruct_attention( 

80 self, q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, **kwargs: Any 

81 ) -> tuple[torch.Tensor, torch.Tensor]: 

82 """Reconstruct attention with ALiBi bias fused into scores.""" 

83 num_heads = self.config.n_heads if self.config else 32 

84 num_kv_heads = getattr(self.config, "n_key_value_heads", None) or num_heads 

85 q, k, v, batch_size, seq_len, head_dim = self._reshape_qkv_to_heads( 

86 q, k, v, num_heads, num_kv_heads 

87 ) 

88 

89 # Without this, cached decoding attends to the current token alone. 

90 # HF caches pre-expansion K/V, so update before the GQA expand; the 

91 # ALiBi bias and mask below arrive sized to the full key length. 

92 k, v = self._update_kv_cache(k, v, **kwargs) 

93 

94 # GQA/MQA: expand K/V heads to match Q heads 

95 if num_kv_heads != num_heads: 

96 n_rep = num_heads // num_kv_heads 

97 k = k.repeat_interleave(n_rep, dim=1) 

98 v = v.repeat_interleave(n_rep, dim=1) 

99 

100 inv_norm_factor = head_dim**-0.5 

101 

102 # Raw attention scores 

103 attn_scores = torch.matmul(q, k.transpose(-2, -1)) 

104 attn_scores = attn_scores.view(batch_size, num_heads, seq_len, -1) 

105 

106 # Upcast for numerical stability 

107 input_dtype = attn_scores.dtype 

108 if input_dtype in (torch.float16, torch.bfloat16): 108 ↛ 109line 108 didn't jump to line 109 because the condition on line 108 was never true

109 attn_scores = attn_scores.to(torch.float32) 

110 

111 # Add ALiBi bias 

112 alibi = kwargs.get("alibi", None) 

113 if alibi is not None: 

114 kv_len = attn_scores.shape[-1] 

115 alibi_view = alibi.view(batch_size, num_heads, 1, -1) 

116 if alibi_view.shape[-1] > kv_len: 116 ↛ 117line 116 didn't jump to line 117 because the condition on line 116 was never true

117 alibi_view = alibi_view[..., :kv_len] 

118 attn_scores = attn_scores + alibi_view 

119 

120 # Scale after ALiBi (matches HF Falcon) 

121 attn_scores = attn_scores * inv_norm_factor 

122 

123 # Add attention mask 

124 attention_mask = kwargs.get("attention_mask", None) 

125 if attention_mask is not None: 125 ↛ 129line 125 didn't jump to line 129 because the condition on line 125 was always true

126 attention_mask = self._normalize_compatibility_mask_sentinel(attention_mask) 

127 attn_scores = attn_scores + attention_mask[:, :, :, : attn_scores.shape[-1]] 

128 

129 attn_scores = self.hook_attn_scores(attn_scores) 

130 

131 attn_weights = self._softmax_dropout_pattern( 

132 attn_scores, upcast_to_fp32=True, target_dtype=q.dtype 

133 ) 

134 

135 # Weighted sum 

136 attn_output = torch.matmul( 

137 attn_weights.view(batch_size * num_heads, seq_len, -1), 

138 v.reshape(batch_size * num_heads, -1, head_dim), 

139 ) 

140 attn_output = attn_output.view(batch_size, num_heads, seq_len, head_dim) 

141 attn_output = self._reshape_attn_output( 

142 attn_output, batch_size, seq_len, num_heads, head_dim 

143 ) 

144 attn_output = self._apply_output_projection(attn_output) 

145 

146 return attn_output, attn_weights 

147 

148 def get_random_inputs( 

149 self, 

150 batch_size: int = 2, 

151 seq_len: int = 8, 

152 device: Optional[torch.device] = None, 

153 dtype: Optional[torch.dtype] = None, 

154 ) -> Dict[str, Any]: 

155 """Generate test inputs including ALiBi tensor and attention mask.""" 

156 if device is None: 156 ↛ 158line 156 didn't jump to line 158 because the condition on line 156 was always true

157 device = torch.device("cpu") 

158 if dtype is None: 158 ↛ 161line 158 didn't jump to line 161 because the condition on line 158 was always true

159 dtype = torch.float32 

160 

161 d_model = self.config.d_model if self.config and hasattr(self.config, "d_model") else 2048 

162 num_heads = self.config.n_heads if self.config and hasattr(self.config, "n_heads") else 32 

163 

164 attention_mask = torch.ones(batch_size, seq_len, device=device, dtype=torch.long) 

165 # HF Falcon passes alibi as [batch*heads, 1, seq] — reshape to match 

166 alibi_4d = build_alibi_tensor(attention_mask, num_heads, dtype) 

167 alibi = alibi_4d.reshape(batch_size * num_heads, 1, seq_len) 

168 

169 # Causal mask: [batch, 1, seq, seq] 

170 causal_mask = torch.triu( 

171 torch.full((seq_len, seq_len), torch.finfo(dtype).min, device=device, dtype=dtype), 

172 diagonal=1, 

173 ) 

174 causal_mask = causal_mask.unsqueeze(0).unsqueeze(0).expand(batch_size, 1, -1, -1) 

175 

176 return { 

177 "hidden_states": torch.randn(batch_size, seq_len, d_model, device=device, dtype=dtype), 

178 "alibi": alibi, 

179 "attention_mask": causal_mask, 

180 }