2"""Local user-trained SAE registry and active binding for inspect / steer."""
4from __future__
import annotations
7from pathlib
import Path
10USER_SAE_ROOT = Path.home() /
".aquin" /
"sae" /
"user"
11ACTIVE_SAE_PATH = Path.home() /
".aquin" /
"active_user_sae.json"
15 meta_path = sae_path.with_suffix(
".meta.json")
16 if not meta_path.exists():
19 return json.loads(meta_path.read_text(encoding=
"utf-8"))
26 if stem.startswith(
"sae_layer"):
28 return int(stem.replace(
"sae_layer",
""))
38 slug = resolve_model_id(model_id)
39 roots = [USER_SAE_ROOT / slug]
40 return [r
for r
in roots
if r.is_dir()]
46 layer: int |
None =
None,
48 embedding: bool |
None =
None,
50 """Resolve ~/.aquin/sae/user/<model>/<name>/sae_layer{L}.pt"""
54 safe = name.replace(
"/",
"--").replace(
" ",
"_")
57 candidates: list[Path] = []
60 if not run_dir.is_dir():
63 candidates.append(run_dir / f
"sae_layer{layer}.pt")
65 found = sorted(run_dir.glob(
"sae_layer*.pt"))
69 f
"User SAE '{name}' has multiple layers ({layers}). Pass --layer <n>."
71 candidates.extend(found)
73 for path
in candidates:
77 slug = resolve_model_id(model_id)
78 expected = USER_SAE_ROOT / slug / safe
80 expected_file = expected / f
"sae_layer{layer}.pt"
82 expected_file = expected /
"sae_layer<N>.pt"
85 f
"Session model resolves to: {slug}",
86 f
"Expected path: {expected_file}",
90 hints.append(
"On disk:")
91 for row
in all_runs[:8]:
92 hints.append(f
" --user {row['name']} --layer {row.get('layer', '?')} ({row['model_id']})")
94 hints.append(f
"No user SAEs under {USER_SAE_ROOT}/")
95 hints.append(
"Train: aquin sae train --layer <n> --name my-run --quick")
97 layer_hint = f
" --layer {layer}" if layer
is not None else ""
98 raise FileNotFoundError(
99 f
"No user SAE '{name}' for this model{layer_hint}.\n" +
"\n".join(hints)
103def resolve_path_sae(path: str | Path, *, model_id: str |
None =
None, layer: int |
None =
None) -> tuple[Path, str, int]:
104 """Resolve explicit .pt path; infer model + layer from meta or args."""
105 sae_path = Path(path).expanduser().resolve()
106 if not sae_path.is_file():
107 raise FileNotFoundError(f
"SAE file not found: {sae_path}")
111 if not is_valid_sae_checkpoint_path(sae_path):
112 detail: str |
None =
None
114 load_checkpoint(sae_path, map_location=
"cpu")
115 detail =
"the file loaded, but it does not contain SAE weight tensors"
116 except Exception
as exc:
117 detail = str(exc).strip()
or exc.__class__.__name__
119 "Invalid SAE checkpoint for --path.\n"
120 f
" file: {sae_path}\n"
121 " expected: a non-empty PyTorch/safetensors SAE checkpoint containing encoder/decoder weights\n"
122 f
" detail: {detail}\n"
123 "Use a real SAE weights file, or re-download/re-export the checkpoint and try again."
127 resolved_layer = layer
128 if resolved_layer
is None:
129 resolved_layer = meta.get(
"layer")
130 if resolved_layer
is None:
132 if resolved_layer
is None:
133 raise ValueError(f
"Could not infer layer for {sae_path}. Pass --layer <n>.")
135 resolved_model = model_id
or meta.get(
"model_id")
136 if not resolved_model:
137 raise ValueError(f
"Could not infer model for {sae_path}. Pass --model <id> or use a .meta.json.")
141 resolved_model = resolve_model_id(resolved_model)
142 return sae_path, resolved_model, int(resolved_layer)
145def list_user_saes(model_id: str |
None =
None) -> list[dict[str, Any]]:
146 """Scan ~/.aquin/sae/user for trained SAE checkpoints."""
147 rows: list[dict[str, Any]] = []
148 if not USER_SAE_ROOT.is_dir():
151 for model_dir
in sorted(USER_SAE_ROOT.iterdir()):
152 if not model_dir.is_dir():
154 dir_name = model_dir.name
155 if dir_name.startswith(
"embed-"):
162 want = resolve_model_id(model_id)
163 if slug != want
and dir_name != model_id:
166 if slug != model_id
and dir_name != model_id:
169 for run_dir
in sorted(model_dir.iterdir()):
170 if not run_dir.is_dir():
172 for sae_file
in sorted(run_dir.glob(
"sae_layer*.pt")):
177 "name": run_dir.name,
178 "model_id": meta.get(
"model_id")
or slug,
179 "layer": layer
if layer
is not None else meta.get(
"layer"),
180 "path": str(sae_file),
182 "steps": meta.get(
"steps"),
189 if not ACTIVE_SAE_PATH.exists():
192 data = json.loads(ACTIVE_SAE_PATH.read_text(encoding=
"utf-8"))
193 return data
if isinstance(data, dict)
else None
203 name: str |
None =
None,
204 embedding: bool =
False,
207 "model_id": model_id,
209 "path": str(Path(path).resolve()),
214 ACTIVE_SAE_PATH.parent.mkdir(parents=
True, exist_ok=
True)
215 ACTIVE_SAE_PATH.write_text(json.dumps(binding, indent=2), encoding=
"utf-8")
220 if ACTIVE_SAE_PATH.exists():
221 ACTIVE_SAE_PATH.unlink(missing_ok=
True)
225 """Return active user SAE path when it matches model + layer."""
230 bound_layer = int(binding[
"layer"])
231 except (KeyError, TypeError, ValueError):
233 if bound_layer != int(layer):
236 bound_model = binding.get(
"model_id")
243 if resolve_model_id(model_id) != resolve_model_id(str(bound_model)):
246 if str(bound_model).lower() != str(model_id).lower():
249 path = Path(str(binding.get(
"path",
"")))
250 return path
if path.is_file()
else None
258 name: str |
None =
None,
260 """Register active user SAE and warm the in-process cache."""
264 short = resolve_model_id(model_id)
274 feature_analysis._sae_cache.pop((short, layer),
None)
275 feature_analysis.load_sae(short, layer)
list[Path] user_sae_dirs_for_model(str model_id, *, bool embedding=False)
Path|None get_active_user_sae_path(str model_id, int layer)
Path resolve_user_sae_path(str model_id, str name, int|None layer=None, *, bool|None embedding=None)
dict[str, Any]|None load_active_binding()
dict[str, Any] activate_user_sae(*, str model_id, int layer, Path path, str|None name=None)
list[dict[str, Any]] list_user_saes(str|None model_id=None)
int|None _layer_from_filename(Path path)
tuple[Path, str, int] resolve_path_sae(str|Path path, *, str|None model_id=None, int|None layer=None)
dict[str, Any] _read_meta(Path sae_path)
None clear_active_binding()
dict[str, Any] save_active_binding(*, str model_id, int layer, str|Path path, str|None name=None, bool embedding=False)