AQIT
0.1.0
Toggle main menu visibility
Loading...
Searching...
No Matches
activation_store.py
Go to the documentation of this file.
1
# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2
"""Resolve saved activation directories for SAE training (chunks, captures, prior runs)."""
3
4
from
__future__
import
annotations
5
6
import
json
7
from
datetime
import
datetime, timezone
8
from
pathlib
import
Path
9
from
typing
import
Any
10
11
import
torch
12
13
CHUNK_SIZE = 50_000
14
MANIFEST_NAME =
"manifest.json"
15
16
17
def
find_chunk_paths
(acts_dir: Path) -> list[Path]:
18
return
sorted(acts_dir.glob(
"chunk_*.pt"
))
19
20
21
def
read_manifest
(acts_dir: Path) -> dict[str, Any] |
None
:
22
path = acts_dir / MANIFEST_NAME
23
if
not
path.is_file():
24
return
None
25
try
:
26
data = json.loads(path.read_text(encoding=
"utf-8"
))
27
return
data
if
isinstance(data, dict)
else
None
28
except
Exception:
29
return
None
30
31
32
def
write_collect_manifest
(
33
acts_dir: Path,
34
*,
35
model_id: str,
36
layer: int,
37
model_mode: str,
38
d_model: int,
39
source: str,
40
n_vectors: int,
41
checkpoint_path: str |
None
=
None
,
42
corpus_path: str |
None
=
None
,
43
) -> Path:
44
acts_dir.mkdir(parents=
True
, exist_ok=
True
)
45
chunks = [p.name
for
p
in
find_chunk_paths
(acts_dir)]
46
manifest = {
47
"schema_version"
: 1,
48
"source"
: source,
49
"created_at"
: datetime.now(timezone.utc).isoformat(),
50
"model_id"
: model_id,
51
"layer"
: layer,
52
"model_mode"
: model_mode,
53
"d_model"
: d_model,
54
"n_vectors"
: n_vectors,
55
"checkpoint"
: checkpoint_path,
56
"corpus"
: corpus_path,
57
"files"
: {
58
"chunks"
: chunks,
59
"norm"
:
"norm.pt"
if
(acts_dir /
"norm.pt"
).is_file()
else
None
,
60
},
61
}
62
out = acts_dir / MANIFEST_NAME
63
out.write_text(json.dumps(manifest, indent=2), encoding=
"utf-8"
)
64
return
out
65
66
67
def
_split_save_chunks
(stacked: torch.Tensor, out_dir: Path) -> int:
68
out_dir.mkdir(parents=
True
, exist_ok=
True
)
69
n = int(stacked.shape[0])
70
chunk_i = 0
71
for
idx
in
range(0, n, CHUNK_SIZE):
72
torch.save(stacked[idx : idx + CHUNK_SIZE], out_dir / f
"chunk_{chunk_i}.pt"
)
73
chunk_i += 1
74
return
n
75
76
77
def
import_capture_to_chunks
(capture_dir: Path, layer: int, out_dir: Path) -> int:
78
manifest =
read_manifest
(capture_dir)
79
if
not
manifest:
80
raise
ValueError(f
"No manifest.json in {capture_dir}"
)
81
82
rel = (manifest.get(
"files"
)
or
{}).get(
"activations"
, {}).get(str(layer))
83
if
not
rel:
84
layers = list((manifest.get(
"files"
)
or
{}).get(
"activations"
, {}).keys())
85
hint = f
" Available layers: {', '.join(layers)}"
if
layers
else
""
86
raise
ValueError(f
"Capture has no activations for layer {layer}.{hint}"
)
87
88
tensor_path = capture_dir / rel
89
if
not
tensor_path.is_file():
90
raise
FileNotFoundError(f
"Capture activation file missing: {tensor_path}"
)
91
92
stacked = torch.load(tensor_path, map_location=
"cpu"
, weights_only=
False
)
93
if
stacked.dim() != 2:
94
raise
ValueError(f
"Expected 2D activations [N, d_model], got shape {tuple(stacked.shape)}"
)
95
96
n =
_split_save_chunks
(stacked, out_dir)
97
write_collect_manifest
(
98
out_dir,
99
model_id=str(manifest.get(
"model_id"
)
or
""
),
100
layer=layer,
101
model_mode=str(manifest.get(
"model_mode"
)
or
"llm"
),
102
d_model=int(stacked.shape[1]),
103
source=
"capture"
,
104
n_vectors=n,
105
checkpoint_path=manifest.get(
"checkpoint"
),
106
)
107
return
n
108
109
110
def
import_capture_to_chunks_balanced
(
111
capture_dir: Path,
112
layer: int,
113
out_dir: Path,
114
*,
115
group: str |
None
=
None
,
116
) -> int:
117
manifest =
read_manifest
(capture_dir)
118
if
not
manifest:
119
raise
ValueError(f
"No manifest.json in {capture_dir}"
)
120
121
rel = (manifest.get(
"files"
)
or
{}).get(
"activations"
, {}).get(str(layer))
122
if
not
rel:
123
layers = list((manifest.get(
"files"
)
or
{}).get(
"activations"
, {}).keys())
124
hint = f
" Available layers: {', '.join(layers)}"
if
layers
else
""
125
raise
ValueError(f
"Capture has no activations for layer {layer}.{hint}"
)
126
127
tensor_path = capture_dir / rel
128
if
not
tensor_path.is_file():
129
raise
FileNotFoundError(f
"Capture activation file missing: {tensor_path}"
)
130
131
stacked = torch.load(tensor_path, map_location=
"cpu"
, weights_only=
False
)
132
if
stacked.dim() != 2:
133
raise
ValueError(f
"Expected 2D activations [N, d_model], got shape {tuple(stacked.shape)}"
)
134
135
probes = manifest.get(
"probes"
)
if
isinstance(manifest.get(
"probes"
), list)
else
[]
136
if
not
probes:
137
return
import_capture_to_chunks
(capture_dir, layer, out_dir)
138
139
from
aquin.compute.activation_capture
import
balance_probes
140
141
balanced, meta = balance_probes([p
for
p
in
probes
if
isinstance(p, dict)], group=group)
142
if
not
meta.get(
"balanced"
):
143
return
import_capture_to_chunks
(capture_dir, layer, out_dir)
144
145
keep_ids = {str(p.get(
"id"
))
for
p
in
balanced
if
p.get(
"id"
)
is
not
None
}
146
keep_indices = [
147
i
for
i, p
in
enumerate(probes)
148
if
isinstance(p, dict)
and
str(p.get(
"id"
))
in
keep_ids
149
]
150
if
not
keep_indices:
151
return
import_capture_to_chunks
(capture_dir, layer, out_dir)
152
153
subset = stacked[torch.tensor(keep_indices, dtype=torch.long)]
154
n =
_split_save_chunks
(subset, out_dir)
155
write_collect_manifest
(
156
out_dir,
157
model_id=str(manifest.get(
"model_id"
)
or
""
),
158
layer=layer,
159
model_mode=str(manifest.get(
"model_mode"
)
or
"llm"
),
160
d_model=int(subset.shape[1]),
161
source=
"capture-balanced"
,
162
n_vectors=n,
163
checkpoint_path=manifest.get(
"checkpoint"
),
164
)
165
return
n
166
167
168
def
resolve_training_acts_dir
(
169
path: str | Path,
170
layer: int,
171
*,
172
balance: bool =
False
,
173
balance_group: str |
None
=
None
,
174
) -> Path:
175
"""
176
Return a directory containing chunk_*.pt for training.
177
178
Accepts:
179
- a collect dir (chunk_*.pt + optional manifest.json)
180
- a prior train run dir (…/my-run/_acts_layer{N})
181
- a capture-activations output dir (manifest.json + layers/layer_{N}.pt)
182
"""
183
root = Path(path).expanduser().resolve()
184
if
not
root.exists():
185
raise
FileNotFoundError(f
"Activations path not found: {root}"
)
186
187
if
find_chunk_paths
(root):
188
return
root
189
190
sub = root / f
"_acts_layer{layer}"
191
if
find_chunk_paths
(sub):
192
return
sub
193
194
if
(root / MANIFEST_NAME).is_file():
195
manifest =
read_manifest
(root)
196
if
manifest
and
manifest.get(
"files"
, {}).get(
"activations"
):
197
suffix = f
"_balanced_{balance_group}"
if
balance
and
balance_group
else
(
"_balanced"
if
balance
else
""
)
198
cache = root / f
"_train_cache_layer{layer}{suffix}"
199
if
find_chunk_paths
(cache):
200
return
cache
201
if
balance:
202
n =
import_capture_to_chunks_balanced
(root, layer, cache, group=balance_group)
203
print(f
"[sae-train] imported {n:,} balanced vectors from capture → {cache}"
, flush=
True
)
204
else
:
205
n =
import_capture_to_chunks
(root, layer, cache)
206
print(f
"[sae-train] imported {n:,} vectors from capture → {cache}"
, flush=
True
)
207
return
cache
208
209
raise
FileNotFoundError(
210
f
"No training activations for layer {layer} under {root}.\n"
211
f
"Expected chunk_*.pt, _acts_layer{layer}/, or capture manifest with layers/layer_{layer}.pt"
212
)
213
214
215
def
count_vectors
(acts_dir: Path) -> int:
216
total = 0
217
for
p
in
find_chunk_paths
(acts_dir):
218
t = torch.load(p, map_location=
"cpu"
, weights_only=
False
)
219
total += int(t.shape[0])
220
return
total
221
222
223
def
compute_norm
(acts_dir: Path, d_model: int) -> tuple[torch.Tensor, torch.Tensor]:
224
norm_path = acts_dir /
"norm.pt"
225
if
norm_path.exists():
226
d = torch.load(norm_path, map_location=
"cpu"
, weights_only=
False
)
227
return
d[
"mean"
], d[
"std"
]
228
229
chunks =
find_chunk_paths
(acts_dir)
230
if
not
chunks:
231
raise
FileNotFoundError(f
"No chunk_*.pt in {acts_dir}"
)
232
233
running_mean = torch.zeros(d_model)
234
n_total = 0
235
for
p
in
chunks:
236
c = torch.load(p, map_location=
"cpu"
, weights_only=
False
)
237
running_mean += c.sum(0)
238
n_total += c.shape[0]
239
mean = running_mean / max(n_total, 1)
240
241
running_var = torch.zeros(d_model)
242
for
p
in
chunks:
243
c = torch.load(p, map_location=
"cpu"
, weights_only=
False
)
244
running_var += ((c - mean) ** 2).sum(0)
245
std = (running_var / max(n_total, 1)).sqrt().clamp(min=1e-6)
246
torch.save({
"mean"
: mean,
"std"
: std}, norm_path)
247
return
mean, std
248
249
250
def
validate_acts_manifest
(acts_dir: Path, *, model_id: str, layer: int) ->
None
:
251
manifest =
read_manifest
(acts_dir)
252
if
not
manifest:
253
return
254
man_layer = manifest.get(
"layer"
)
255
if
man_layer
is
not
None
and
int(man_layer) != int(layer):
256
raise
ValueError(
257
f
"Activations manifest is for layer {man_layer}, but --layer {layer} was passed."
258
)
259
man_model = manifest.get(
"model_id"
)
260
if
not
man_model:
261
return
262
try
:
263
from
aquin.compute.model_loader
import
resolve_model_id
264
265
want = resolve_model_id(model_id)
266
got = resolve_model_id(str(man_model))
267
if
want != got:
268
print(
269
f
"[sae-train] warning: activations model_id={got!r} differs from session model {want!r}"
,
270
flush=
True
,
271
)
272
except
ValueError:
273
pass
aquin.compute.activation_capture
Definition
activation_capture.py:1
aquin.compute.activation_store.find_chunk_paths
list[Path] find_chunk_paths(Path acts_dir)
Definition
activation_store.py:21
aquin.compute.activation_store._split_save_chunks
int _split_save_chunks(torch.Tensor stacked, Path out_dir)
Definition
activation_store.py:71
aquin.compute.activation_store.resolve_training_acts_dir
Path resolve_training_acts_dir(str|Path path, int layer, *, bool balance=False, str|None balance_group=None)
Definition
activation_store.py:178
aquin.compute.activation_store.count_vectors
int count_vectors(Path acts_dir)
Definition
activation_store.py:219
aquin.compute.activation_store.validate_acts_manifest
None validate_acts_manifest(Path acts_dir, *, str model_id, int layer)
Definition
activation_store.py:254
aquin.compute.activation_store.import_capture_to_chunks
int import_capture_to_chunks(Path capture_dir, int layer, Path out_dir)
Definition
activation_store.py:81
aquin.compute.activation_store.compute_norm
tuple[torch.Tensor, torch.Tensor] compute_norm(Path acts_dir, int d_model)
Definition
activation_store.py:227
aquin.compute.activation_store.import_capture_to_chunks_balanced
int import_capture_to_chunks_balanced(Path capture_dir, int layer, Path out_dir, *, str|None group=None)
Definition
activation_store.py:120
aquin.compute.activation_store.write_collect_manifest
Path write_collect_manifest(Path acts_dir, *, str model_id, int layer, str model_mode, int d_model, str source, int n_vectors, str|None checkpoint_path=None, str|None corpus_path=None)
Definition
activation_store.py:47
aquin.compute.activation_store.read_manifest
dict[str, Any]|None read_manifest(Path acts_dir)
Definition
activation_store.py:25
aquin.compute.model_loader
Definition
model_loader.py:1
aquin
compute
activation_store.py
AQIT · Aquin Labs Private Limited · Apache 2.0 · Generated by
1.18.0