AQIT
0.1.0
Toggle main menu visibility
Loading...
Searching...
No Matches
attention_routing.py
Go to the documentation of this file.
1
# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2
# This file is part of the Aquin Engine. Unauthorized copying, modification,
3
# distribution, or use of this file, via any medium, is strictly prohibited.
4
# Proprietary and confidential. See LICENSE for terms.
5
6
"""Per-head attention routing: sink + induction scores across all layers."""
7
8
from
__future__
import
annotations
9
10
from
typing
import
Any
11
12
import
torch
13
14
15
def
_count_induction_pairs
(token_ids: list[int]) -> int:
16
"""Positions (q, j) where token[j] repeats at q and j+1 is valid."""
17
n = 0
18
for
q
in
range(len(token_ids)):
19
for
j
in
range(q):
20
if
token_ids[j] == token_ids[q]
and
j + 1 < len(token_ids):
21
n += 1
22
return
n
23
24
25
def
_score_head
(
26
pattern: torch.Tensor,
27
token_ids: list[int],
28
) -> tuple[float, float]:
29
"""pattern: (seq, seq) causal attn weights for one head."""
30
seq_len = pattern.shape[0]
31
32
if
seq_len > 1:
33
sink = float(pattern[1:, 0].mean().item())
34
else
:
35
sink = float(pattern[0, 0].item())
36
37
ind_sum = 0.0
38
ind_count = 0
39
for
q
in
range(seq_len):
40
tq = token_ids[q]
41
for
j
in
range(q):
42
if
token_ids[j] == tq
and
j + 1 < seq_len:
43
ind_sum += float(pattern[q, j + 1].item())
44
ind_count += 1
45
induction = ind_sum / max(ind_count, 1)
46
return
sink, induction
47
48
49
def
_heads_from_pattern_stack
(
50
patterns: list[tuple[int, torch.Tensor]],
51
token_ids: list[int],
52
) -> list[dict]:
53
"""patterns: list of (layer_idx, tensor [n_heads, seq, seq])."""
54
heads: list[dict] = []
55
for
layer, attn
in
patterns:
56
if
attn
is
None
:
57
continue
58
t = attn.detach().float()
59
if
t.ndim == 4:
60
t = t[0]
61
if
t.ndim != 3:
62
continue
63
n_heads = t.shape[0]
64
for
h
in
range(n_heads):
65
sink, induction =
_score_head
(t[h], token_ids)
66
heads.append(
67
{
68
"layer"
: int(layer),
69
"head"
: int(h),
70
"sink_score"
: round(sink, 4),
71
"induction_score"
: round(induction, 4),
72
}
73
)
74
return
heads
75
76
77
def
_patterns_from_tl_cache
(model: Any, tokens: torch.Tensor, n_layers: int) -> list[tuple[int, torch.Tensor]]:
78
with
torch.no_grad():
79
_, cache = model.run_with_cache(
80
tokens,
81
names_filter=
lambda
name: name.endswith(
"attn.hook_pattern"
),
82
)
83
out: list[tuple[int, torch.Tensor]] = []
84
for
layer
in
range(n_layers):
85
key = f
"blocks.{layer}.attn.hook_pattern"
86
if
key
not
in
cache:
87
continue
88
attn = cache[key]
89
if
isinstance(attn, torch.Tensor):
90
out.append((layer, attn[0]
if
attn.ndim == 4
else
attn))
91
return
out
92
93
94
def
_patterns_from_hf_output_attentions
(
95
model: Any, tokens: torch.Tensor
96
) -> tuple[list[tuple[int, torch.Tensor]], str |
None
]:
97
"""
98
HF-native / HfLlmShim path.
99
TransformerLens patterns are unavailable on hf_only models (LFM, …).
100
"""
101
hf = getattr(model,
"hf_model"
,
None
)
102
if
hf
is
None
:
103
return
[],
None
104
try
:
105
with
torch.no_grad():
106
out = hf(
107
input_ids=tokens,
108
output_attentions=
True
,
109
use_cache=
False
,
110
return_dict=
True
,
111
)
112
except
TypeError:
113
try
:
114
with
torch.no_grad():
115
out = hf(input_ids=tokens, output_attentions=
True
, return_dict=
True
)
116
except
Exception
as
exc:
# noqa: BLE001
117
return
[], f
"output_attentions failed: {exc}"
118
except
Exception
as
exc:
# noqa: BLE001
119
return
[], f
"output_attentions failed: {exc}"
120
121
attns = getattr(out,
"attentions"
,
None
)
122
if
not
attns:
123
return
[], (
124
"Model returned no attentions (output_attentions unused or architecture "
125
"does not expose multi-head attention weights)."
126
)
127
128
patterns: list[tuple[int, torch.Tensor]] = []
129
for
layer, layer_attn
in
enumerate(attns):
130
if
layer_attn
is
None
:
131
continue
# e.g. non-attention / conv hybrid layers
132
patterns.append((layer, layer_attn[0]
if
layer_attn.ndim == 4
else
layer_attn))
133
if
not
patterns:
134
return
[],
"All layer attentions were empty (hybrid/conv-only blocks?)."
135
return
patterns,
None
136
137
138
def
run_attention_routing
(
139
model: Any,
140
prompt: str,
141
*,
142
top_k: int = 5,
143
model_id: str =
""
,
144
) -> dict:
145
tokens = model.to_tokens(prompt)
146
if
tokens.ndim == 1:
147
tokens = tokens.unsqueeze(0)
148
token_ids = tokens[0].tolist()
149
n_layers = int(getattr(getattr(model,
"cfg"
,
None
),
"n_layers"
, 0)
or
0)
150
n_heads = int(getattr(getattr(model,
"cfg"
,
None
),
"n_heads"
, 0)
or
0)
151
152
source =
"tl_cache"
153
warning: str |
None
=
None
154
patterns =
_patterns_from_tl_cache
(model, tokens, n_layers)
if
n_layers
else
[]
155
156
if
not
patterns:
157
patterns, warning =
_patterns_from_hf_output_attentions
(model, tokens)
158
source =
"hf_output_attentions"
159
160
heads =
_heads_from_pattern_stack
(patterns, token_ids)
161
162
if
heads
and
(n_layers <= 0
or
n_heads <= 0):
163
n_layers = max(h[
"layer"
]
for
h
in
heads) + 1
164
n_heads = max(h[
"head"
]
for
h
in
heads) + 1
165
166
by_sink = sorted(heads, key=
lambda
x: x[
"sink_score"
], reverse=
True
)
167
by_ind = sorted(heads, key=
lambda
x: x[
"induction_score"
], reverse=
True
)
168
n_ind_pairs =
_count_induction_pairs
(token_ids)
169
170
result: dict[str, Any] = {
171
"prompt"
: prompt,
172
"model_id"
: model_id,
173
"n_layers"
: n_layers,
174
"n_heads"
: n_heads,
175
"n_induction_pairs"
: n_ind_pairs,
176
"heads"
: heads,
177
"top_sink_heads"
: by_sink[:top_k],
178
"top_induction_heads"
: by_ind[:top_k]
if
n_ind_pairs
else
[],
179
"source"
: source,
180
}
181
if
not
heads:
182
result[
"warning"
] = warning
or
(
183
"No per-head attention patterns available for this model. "
184
"TransformerLens hook_pattern cache was empty and HF output_attentions "
185
"did not return usable weights."
186
)
187
elif
warning:
188
result[
"warning"
] = warning
189
return
result
aquin.compute.attention_routing._patterns_from_tl_cache
list[tuple[int, torch.Tensor]] _patterns_from_tl_cache(Any model, torch.Tensor tokens, int n_layers)
Definition
attention_routing.py:81
aquin.compute.attention_routing.run_attention_routing
dict run_attention_routing(Any model, str prompt, *, int top_k=5, str model_id="")
Definition
attention_routing.py:148
aquin.compute.attention_routing._heads_from_pattern_stack
list[dict] _heads_from_pattern_stack(list[tuple[int, torch.Tensor]] patterns, list[int] token_ids)
Definition
attention_routing.py:56
aquin.compute.attention_routing._count_induction_pairs
int _count_induction_pairs(list[int] token_ids)
Definition
attention_routing.py:19
aquin.compute.attention_routing._score_head
tuple[float, float] _score_head(torch.Tensor pattern, list[int] token_ids)
Definition
attention_routing.py:32
aquin.compute.attention_routing._patterns_from_hf_output_attentions
tuple[list[tuple[int, torch.Tensor]], str|None] _patterns_from_hf_output_attentions(Any model, torch.Tensor tokens)
Definition
attention_routing.py:100
aquin
compute
attention_routing.py
AQIT · Aquin Labs Private Limited · Apache 2.0 · Generated by
1.18.0