6from __future__
import annotations
8from pathlib
import Path
15 def __init__(self, d_model: int = 2048, n_features: int = 16384):
17 self.d_model = d_model
19 self.
b_pre = nn.Parameter(torch.zeros(d_model))
20 self.
W_enc = nn.Parameter(torch.nn.init.kaiming_uniform_(torch.empty(d_model, n_features)))
21 self.
b_enc = nn.Parameter(torch.zeros(n_features))
22 self.
W_dec = nn.Parameter(torch.nn.init.kaiming_uniform_(torch.empty(n_features, d_model)))
23 self.
b_dec = nn.Parameter(torch.zeros(d_model))
28 norms = self.
W_dec.norm(dim=-1, keepdim=
True).clamp(min=1e-8)
31 def encode(self, x: torch.Tensor) -> torch.Tensor:
34 def decode(self, f: torch.Tensor) -> torch.Tensor:
37 def forward(self, x: torch.Tensor):
42 def get_top_features(self, x: torch.Tensor, k: int = 10) -> list[tuple[int, float]]:
43 f = self.
encode(x.unsqueeze(0)).squeeze(0)
45 return [(int(i), float(v))
for i, v
in zip(topk.indices, topk.values)]
47 def save(self, path: str | Path) ->
None:
49 path.parent.mkdir(parents=
True, exist_ok=
True)
50 torch.save({
"d_model": self.
d_model,
"n_features": self.
n_features,
"state_dict": self.state_dict()}, path)
53 def _find_tensor(cls, tensors: dict, *names: str) -> torch.Tensor |
None:
57 leaf_to_full: dict[str, str] = {}
58 for key, val
in tensors.items():
59 if isinstance(val, torch.Tensor):
60 leaf_to_full[key.split(
".")[-1]] = key
62 leaf = name.split(
".")[-1]
63 if leaf
in leaf_to_full:
64 return tensors[leaf_to_full[leaf]]
68 def _infer_dims(cls, tensors: dict) -> tuple[int, int]:
69 enc = cls.
_find_tensor(tensors,
"W_enc",
"encoder.weight")
70 dec = cls.
_find_tensor(tensors,
"W_dec",
"decoder.weight")
71 if enc
is not None and enc.ndim == 2:
72 if enc.shape[0] > enc.shape[1]:
73 return int(enc.shape[1]), int(enc.shape[0])
74 return int(enc.shape[0]), int(enc.shape[1])
75 if dec
is not None and dec.ndim == 2:
76 if dec.shape[0] > dec.shape[1]:
77 return int(dec.shape[1]), int(dec.shape[0])
78 return int(dec.shape[1]), int(dec.shape[0])
80 "Cannot infer SAE dimensions — missing W_enc/encoder.weight or W_dec/decoder.weight. "
81 f
"Keys: {sorted(tensors.keys())}"
85 def _coerce_checkpoint(cls, ckpt: Any) -> tuple[dict[str, Any], dict[str, torch.Tensor]]:
86 if isinstance(ckpt, cls):
87 return {
"d_model": ckpt.d_model,
"n_features": ckpt.n_features}, ckpt.state_dict()
89 if not isinstance(ckpt, dict):
90 raise ValueError(f
"Unrecognised SAE checkpoint type: {type(ckpt)}")
93 if "state_dict" in ckpt
and isinstance(ckpt[
"state_dict"], dict):
94 tensors = {k: v
for k, v
in ckpt[
"state_dict"].items()
if isinstance(v, torch.Tensor)}
95 elif "model_state" in ckpt
and isinstance(ckpt[
"model_state"], dict):
96 tensors = {k: v
for k, v
in ckpt[
"model_state"].items()
if isinstance(v, torch.Tensor)}
97 elif "model" in ckpt
and isinstance(ckpt[
"model"], dict):
98 tensors = {k: v
for k, v
in ckpt[
"model"].items()
if isinstance(v, torch.Tensor)}
100 tensors = {k: v
for k, v
in ckpt.items()
if isinstance(v, torch.Tensor)}
102 for nested_key
in (
"cfg",
"config",
"args"):
103 nested = ckpt.get(nested_key)
104 if isinstance(nested, dict):
105 for key
in (
"d_model",
"d_in",
"n_features",
"dict_size",
"d_sae",
"num_features"):
106 if key
in nested
and key
not in meta:
107 meta[key] = nested[key]
112 def _meta_int(cls, meta: dict[str, Any], *keys: str) -> int |
None:
114 if key
in meta
and meta[key]
is not None:
115 return int(meta[key])
119 def _remap_tensors(cls, tensors: dict[str, torch.Tensor], sae:
"SparseAutoencoder") -> dict[str, torch.Tensor]:
121 "encoder.weight":
"W_enc",
122 "encoder.bias":
"b_enc",
123 "decoder.weight":
"W_dec",
124 "decoder.bias":
"b_dec",
125 "encoder_pre_bias":
"b_pre",
133 out = dict(sae.state_dict())
135 for src_key, dst_key
in key_map.items():
139 if dst_key ==
"W_enc":
143 if t.shape[0] > t.shape[1]:
146 elif dst_key ==
"W_dec":
148 if t.shape == (sae.d_model, sae.n_features):
156 def load(cls, path: str | Path, device: str =
"cuda") ->
"SparseAutoencoder":
158 if path.suffix ==
".safetensors":
163 ckpt = load_checkpoint(path, map_location=device)
164 except Exception
as exc:
165 if is_corrupt_pytorch_zip(exc)
or (
166 exc.__cause__
is not None and is_corrupt_pytorch_zip(exc.__cause__)
169 f
"SAE checkpoint at {path} is corrupted or incomplete. "
170 "Delete the file and run: aquin load sae <model>-l<layer>"
175 if looks_like_catalog_metadata_dict(ckpt):
177 f
"SAE checkpoint at {path} is catalog metadata, not weights. "
178 "Re-download with: aquin load sae <model>-l<n>"
180 if isinstance(ckpt, cls):
185 if not isinstance(ckpt, dict):
187 f
"Not an SAE dictionary checkpoint ({type(ckpt)}): {path}. "
188 "Use ~/.aquin/sae/user/<model>/<name>/sae_layerN.pt — "
189 "not capture files like sae/sae_layer_N.pt or layers/layer_N.pt."
194 cls.
_find_tensor(tensors,
"W_enc",
"encoder.weight")
is None
195 and cls.
_find_tensor(tensors,
"W_dec",
"decoder.weight")
is None
198 f
"SAE checkpoint at {path} is not a valid PyTorch weights file. "
199 "Re-download with: aquin load sae <model>-l<n> "
200 "or place weights with: aquin load sae --path <file.pt> --layer <n>"
202 d_model = cls.
_meta_int(meta,
"d_model",
"d_in")
203 n_features = cls.
_meta_int(meta,
"n_features",
"dict_size",
"d_sae",
"num_features")
204 if d_model
is None or n_features
is None:
206 d_model = d_model
if d_model
is not None else inferred_d
207 n_features = n_features
if n_features
is not None else inferred_n
209 sae = cls(d_model=d_model, n_features=n_features)
211 if cls.
_find_tensor(tensors,
"W_enc",
"encoder.weight")
is not None:
212 sae.load_state_dict(remapped, strict=
False)
214 sae.load_state_dict({k: v
for k, v
in remapped.items()
if k
in sae.state_dict()}, strict=
False)
220 def _load_safetensors(cls, path: Path, device: str =
"cuda") ->
"SparseAutoencoder":
221 from safetensors.torch
import load_file
222 tensors = load_file(str(path), device=device)
228 "W_enc": [
"W_enc",
"encoder.weight"],
229 "b_enc": [
"b_enc",
"encoder.bias"],
230 "W_dec": [
"W_dec",
"decoder.weight"],
231 "b_dec": [
"b_dec",
"decoder.bias"],
232 "b_pre": [
"b_pre",
"pre_bias",
"encoder_pre_bias"],
235 def _get(candidates: list[str]) -> torch.Tensor |
None:
241 W_enc = _get(key_map[
"W_enc"])
242 b_enc = _get(key_map[
"b_enc"])
243 W_dec = _get(key_map[
"W_dec"])
244 b_dec = _get(key_map[
"b_dec"])
245 b_pre = _get(key_map[
"b_pre"])
247 if W_enc
is None or W_dec
is None:
248 raise ValueError(f
"Unrecognised safetensors layout. Keys: {list(tensors.keys())}")
252 raise ValueError(f
"Unexpected W_enc shape: {W_enc.shape}")
253 if W_enc.shape[0] > W_enc.shape[1]:
254 n_features, d_model = W_enc.shape
257 d_model, n_features = W_enc.shape
259 sae = cls(d_model=d_model, n_features=n_features)
260 sae.W_enc.data = W_enc.float()
261 if W_dec.shape == (n_features, d_model):
262 sae.W_dec.data = W_dec.float()
263 elif W_dec.shape == (d_model, n_features):
264 sae.W_dec.data = W_dec.T.float()
266 sae.W_dec.data = W_dec.T.float()
267 if b_enc
is not None:
268 sae.b_enc.data = b_enc.float()
269 if b_dec
is not None:
270 sae.b_dec.data = b_dec.float()
271 if b_pre
is not None:
272 sae.b_pre.data = b_pre.float()
int|None _meta_int(cls, dict[str, Any] meta, *str keys)
list[tuple[int, float]] get_top_features(self, torch.Tensor x, int k=10)
tuple[int, int] _infer_dims(cls, dict tensors)
None _normalise_decoder(self)
__init__(self, int d_model=2048, int n_features=16384)
tuple[dict[str, Any], dict[str, torch.Tensor]] _coerce_checkpoint(cls, Any ckpt)
torch.Tensor|None _find_tensor(cls, dict tensors, *str names)
torch.Tensor encode(self, torch.Tensor x)
"SparseAutoencoder" _load_safetensors(cls, Path path, str device="cuda")
torch.Tensor decode(self, torch.Tensor f)
dict[str, torch.Tensor] _remap_tensors(cls, dict[str, torch.Tensor] tensors, "SparseAutoencoder" sae)
forward(self, torch.Tensor x)
"SparseAutoencoder" load(cls, str|Path path, str device="cuda")
None save(self, str|Path path)