AQIT
0.1.0
Toggle main menu visibility
Loading...
Searching...
No Matches
sae_catalog_metrics.py
Go to the documentation of this file.
1
# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2
"""Pre-publish metrics for public SAE catalog rows (UMAP + layer InterpScore)."""
3
4
from
__future__
import
annotations
5
6
import
json
7
from
pathlib
import
Path
8
from
typing
import
Any
9
10
import
torch
11
12
from
aquin.compute.sae
import
SparseAutoencoder
13
14
CATALOG_PROBE_PROMPTS = [
15
"The capital of France is"
,
16
"In quantum mechanics, particles can"
,
17
"The recipe calls for two cups of"
,
18
"She opened the letter and read"
,
19
"function merge_sort(arr):"
,
20
]
21
22
23
def
compute_umap_points_from_sae
(sae_path: str | Path) -> list[dict[str, Any]]:
24
"""3D UMAP of decoder rows (same recipe as bridge._compute_umap_points)."""
25
sae = SparseAutoencoder.load(str(sae_path), device=
"cpu"
)
26
w = sae.W_dec.float().cpu().detach()
27
try
:
28
import
umap
29
30
reducer = umap.UMAP(n_components=3, random_state=42, n_neighbors=15)
31
embedding = reducer.fit_transform(w.numpy())
32
return
[
33
{
"feature_idx"
: i,
"x"
: float(r[0]),
"y"
: float(r[1]),
"z"
: float(r[2])}
34
for
i, r
in
enumerate(embedding)
35
]
36
except
ImportError:
37
w_centered = w - w.mean(0)
38
_, _, vt = torch.linalg.svd(w_centered, full_matrices=
False
)
39
proj = (w_centered @ vt[:3].T).numpy()
40
return
[
41
{
"feature_idx"
: i,
"x"
: float(r[0]),
"y"
: float(r[1]),
"z"
: float(r[2])}
42
for
i, r
in
enumerate(proj)
43
]
44
45
46
def
_collect_top_features
(
47
*,
48
model_id: str,
49
layer: int,
50
prompts: list[str],
51
n_per_prompt: int,
52
max_features: int,
53
) -> list[tuple[int, float, str]]:
54
from
aquin.compute.causal_trace
import
run_chat
55
from
aquin.compute.feature_analysis
import
run_feature_analysis_unlabeled
56
from
aquin.compute.model_loader
import
get_loaded_model, load_model
57
58
model = get_loaded_model()
59
if
model
is
None
:
60
model = load_model(model_id)
61
62
best: dict[int, tuple[float, str]] = {}
63
for
prompt
in
prompts:
64
try
:
65
response = run_chat(prompt, model_id=model_id, max_new_tokens=120, temperature=0.7)
66
except
Exception
as
exc:
67
print(f
"[catalog-metrics] generation failed ({prompt[:40]!r}): {exc}"
, flush=
True
)
68
continue
69
try
:
70
feat = run_feature_analysis_unlabeled(
71
prompt, response, model, model_id=model_id, layer=layer,
72
)
73
except
Exception
as
exc:
74
print(f
"[catalog-metrics] feature pass failed ({prompt[:40]!r}): {exc}"
, flush=
True
)
75
continue
76
for
row
in
(feat.get(
"top_response_features"
)
or
[])[:n_per_prompt]:
77
idx = int(row[
"feature_idx"
])
78
act = float(row.get(
"activation"
)
or
0.0)
79
prev = best.get(idx)
80
if
prev
is
None
or
act > prev[0]:
81
best[idx] = (act, prompt)
82
83
ranked = sorted(
84
((idx, act, prompt)
for
idx, (act, prompt)
in
best.items()),
85
key=
lambda
t: t[1],
86
reverse=
True
,
87
)
88
return
ranked[:max_features]
89
90
91
def
compute_layer_interp_score
(
92
*,
93
model_id: str,
94
layer: int,
95
sae_path: str | Path,
96
prompts: list[str] |
None
=
None
,
97
n_features: int = 8,
98
n_per_prompt: int = 5,
99
n_samples: int = 8,
100
client: Any |
None
=
None
,
101
) -> dict[str, Any]:
102
"""Mean InterpScore over top response features from catalog probe prompts."""
103
from
aquin.compute.interp_score
import
run_interp_score
104
from
aquin.compute.model_loader
import
get_loaded_model, load_model
105
from
aquin.compute.openai_client
import
get_openai_client
106
from
aquin.compute.user_sae
import
activate_user_sae
107
108
sae_path = Path(sae_path).resolve()
109
if
not
sae_path.is_file():
110
raise
FileNotFoundError(f
"SAE not found: {sae_path}"
)
111
112
activate_user_sae(model_id=model_id, layer=layer, path=sae_path, name=sae_path.parent.name)
113
114
if
client
is
None
:
115
client = get_openai_client({})
116
if
client
is
None
:
117
return
{
118
"interp_score"
:
None
,
119
"feature_scores"
: [],
120
"n_benchmarked"
: 0,
121
"error"
:
"OpenAI not configured. Set OPENAI_API_KEY."
,
122
}
123
124
probe_prompts = list(prompts
or
CATALOG_PROBE_PROMPTS)
125
candidates =
_collect_top_features
(
126
model_id=model_id,
127
layer=layer,
128
prompts=probe_prompts,
129
n_per_prompt=n_per_prompt,
130
max_features=n_features,
131
)
132
if
not
candidates:
133
return
{
134
"interp_score"
:
None
,
135
"feature_scores"
: [],
136
"n_benchmarked"
: 0,
137
"error"
:
"No active features found on probe prompts."
,
138
}
139
140
model = get_loaded_model()
141
if
model
is
None
:
142
model = load_model(model_id)
143
144
from
aquin.compute.feature_analysis
import
load_sae
145
146
sae = load_sae(model_id, layer)
147
feature_scores: list[dict[str, Any]] = []
148
scores: list[float] = []
149
150
for
feature_idx, activation, prompt
in
candidates:
151
print(f
"[catalog-metrics] InterpScore f{feature_idx} (act={activation:.3f})"
, flush=
True
)
152
interp = run_interp_score(
153
feature_idx,
154
prompt,
155
model,
156
sae,
157
client,
158
n_samples=n_samples,
159
model_id=model_id,
160
layer=layer,
161
)
162
score = interp.get(
"score"
)
163
row = {
164
"feature_idx"
: feature_idx,
165
"activation"
: round(activation, 4),
166
"prompt"
: prompt,
167
"score"
: score,
168
"purity_score"
: interp.get(
"purity_score"
),
169
"label"
: interp.get(
"label"
),
170
"error"
: interp.get(
"error"
),
171
}
172
feature_scores.append(row)
173
if
score
is
not
None
:
174
scores.append(float(score))
175
176
mean_score = round(sum(scores) / len(scores), 4)
if
scores
else
None
177
return
{
178
"interp_score"
: mean_score,
179
"feature_scores"
: feature_scores,
180
"n_benchmarked"
: len(scores),
181
"n_candidates"
: len(candidates),
182
"prompts"
: probe_prompts,
183
}
184
185
186
def
write_catalog_sidecars
(
187
*,
188
sae_path: str | Path,
189
umap_points: list[dict[str, Any]],
190
metrics: dict[str, Any],
191
) -> tuple[Path, Path]:
192
sae_path = Path(sae_path).resolve()
193
umap_path = sae_path.with_suffix(
".umap.json"
)
194
metrics_path = sae_path.with_suffix(
".metrics.json"
)
195
umap_path.write_text(json.dumps({
"points"
: umap_points}, indent=2), encoding=
"utf-8"
)
196
metrics_path.write_text(json.dumps(metrics, indent=2), encoding=
"utf-8"
)
197
return
umap_path, metrics_path
198
199
200
def
compute_and_write_catalog_sidecars
(
201
*,
202
model_id: str,
203
layer: int,
204
sae_path: str | Path,
205
skip_interp: bool =
False
,
206
**interp_kwargs: Any,
207
) -> dict[str, Any]:
208
sae_path = Path(sae_path).resolve()
209
print(f
"[catalog-metrics] UMAP on {sae_path.name}…"
, flush=
True
)
210
umap_points =
compute_umap_points_from_sae
(sae_path)
211
print(f
"[catalog-metrics] UMAP done ({len(umap_points)} points)"
, flush=
True
)
212
213
metrics: dict[str, Any] = {
"layer"
: layer,
"model_id"
: model_id,
"n_umap_points"
: len(umap_points)}
214
if
not
skip_interp:
215
from
aquin.compute.loader_shim
import
apply
as
shim_apply
216
217
shim_apply()
218
print(f
"[catalog-metrics] InterpScore layer {layer}…"
, flush=
True
)
219
interp =
compute_layer_interp_score
(
220
model_id=model_id,
221
layer=layer,
222
sae_path=sae_path,
223
**interp_kwargs,
224
)
225
metrics.update(interp)
226
if
interp.get(
"interp_score"
)
is
not
None
:
227
print(f
"[catalog-metrics] layer {layer} interp_score={interp['interp_score']}"
, flush=
True
)
228
elif
interp.get(
"error"
):
229
print(f
"[catalog-metrics] interp skipped: {interp['error']}"
, flush=
True
)
230
231
umap_path, metrics_path =
write_catalog_sidecars
(
232
sae_path=sae_path, umap_points=umap_points, metrics=metrics,
233
)
234
print(f
"[catalog-metrics] wrote {umap_path.name}, {metrics_path.name}"
, flush=
True
)
235
return
{
236
"umap_path"
: str(umap_path),
237
"metrics_path"
: str(metrics_path),
238
"interp_score"
: metrics.get(
"interp_score"
),
239
"n_umap_points"
: len(umap_points),
240
}
aquin.compute.causal_trace
Definition
causal_trace.py:1
aquin.compute.feature_analysis
Definition
feature_analysis.py:1
aquin.compute.interp_score
Definition
interp_score.py:1
aquin.compute.loader_shim
Definition
loader_shim.py:1
aquin.compute.model_loader
Definition
model_loader.py:1
aquin.compute.openai_client
Definition
openai_client.py:1
aquin.compute.sae_catalog_metrics.write_catalog_sidecars
tuple[Path, Path] write_catalog_sidecars(*, str|Path sae_path, list[dict[str, Any]] umap_points, dict[str, Any] metrics)
Definition
sae_catalog_metrics.py:195
aquin.compute.sae_catalog_metrics.compute_umap_points_from_sae
list[dict[str, Any]] compute_umap_points_from_sae(str|Path sae_path)
Definition
sae_catalog_metrics.py:27
aquin.compute.sae_catalog_metrics.compute_and_write_catalog_sidecars
dict[str, Any] compute_and_write_catalog_sidecars(*, str model_id, int layer, str|Path sae_path, bool skip_interp=False, **Any interp_kwargs)
Definition
sae_catalog_metrics.py:211
aquin.compute.sae_catalog_metrics._collect_top_features
list[tuple[int, float, str]] _collect_top_features(*, str model_id, int layer, list[str] prompts, int n_per_prompt, int max_features)
Definition
sae_catalog_metrics.py:57
aquin.compute.sae_catalog_metrics.compute_layer_interp_score
dict[str, Any] compute_layer_interp_score(*, str model_id, int layer, str|Path sae_path, list[str]|None prompts=None, int n_features=8, int n_per_prompt=5, int n_samples=8, Any|None client=None)
Definition
sae_catalog_metrics.py:105
aquin.compute.sae
Definition
sae.py:1
aquin.compute.user_sae
Definition
user_sae.py:1
aquin
compute
sae_catalog_metrics.py
AQIT · Aquin Labs Private Limited · Apache 2.0 · Generated by
1.18.0