AQIT 0.1.0
Loading...
Searching...
No Matches
model_families.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2"""Family-based model resolution for LLMs beyond the catalog."""
3
4from __future__ import annotations
5
6import json
7import re
8from pathlib import Path
9from typing import Any
10
11_RUNTIME_LLM: dict[str, dict[str, Any]] = {}
12FAMILY_MODEL_DIR = Path.home() / ".aquin" / "family_models"
13
14# HF AutoConfig.model_type → family id (fine-tunes under any org)
15_MODEL_TYPE_TO_FAMILY: dict[str, str] = {
16 "gpt2": "gpt2",
17 "gpt_neox": "pythia",
18 "llama": "llama",
19 "mistral": "llama", # same LoRA targets / hook shape as Llama
20 "qwen": "qwen",
21 "qwen2": "qwen",
22 "qwen3": "qwen",
23 "qwen2_moe": "qwen",
24}
25
26# Architecture class name substrings → family id (backup when model_type is missing)
27_ARCH_HINTS: tuple[tuple[str, str], ...] = (
28 ("Qwen3", "qwen"),
29 ("Qwen2", "qwen"),
30 ("Qwen", "qwen"),
31 ("Llama", "llama"),
32 ("Mistral", "llama"),
33 ("GPTNeoX", "pythia"),
34 ("GPT2", "gpt2"),
35 ("LFM", "lfm"),
36)
37
38LLM_FAMILIES: list[dict[str, Any]] = [
39 {
40 "id": "gpt2",
41 "label": "GPT-2",
42 "hf_patterns": (re.compile(r"^(?:openai/)?gpt2(?:-|$)", re.I),),
43 "lora_target_modules": ["c_attn", "c_fc"],
44 "hook": "blocks.{layer}.hook_resid_post",
45 "examples": ["gpt2", "gpt2-medium", "gpt2-large", "gpt2-xl"],
46 "trust_remote_code": False,
47 },
48 {
49 "id": "pythia",
50 "label": "Pythia",
51 "hf_patterns": (re.compile(r"^EleutherAI/pythia", re.I),),
52 "slug_prefix": "EleutherAI/",
53 "lora_target_modules": ["query_key_value", "dense"],
54 "hook": "blocks.{layer}.hook_resid_post",
55 "examples": [
56 "EleutherAI/pythia-160m-deduped",
57 "EleutherAI/pythia-410m-deduped",
58 "EleutherAI/pythia-1.4b-deduped",
59 "EleutherAI/pythia-2.8b",
60 ],
61 "trust_remote_code": False,
62 },
63 {
64 "id": "llama",
65 "label": "Llama",
66 "hf_patterns": (
67 re.compile(r"^meta-llama/Llama", re.I),
68 re.compile(r"^meta-llama/Meta-Llama", re.I),
69 ),
70 "lora_target_modules": ["q_proj", "v_proj"],
71 "hook": "blocks.{layer}.hook_resid_post",
72 "examples": [
73 "meta-llama/Llama-3.2-1B-Instruct",
74 "meta-llama/Llama-3.2-3B-Instruct",
75 "meta-llama/Llama-3.1-8B-Instruct",
76 ],
77 "trust_remote_code": False,
78 },
79 {
80 "id": "lfm",
81 "label": "LFM (Liquid AI)",
82 "hf_patterns": (re.compile(r"^LiquidAI/LFM", re.I),),
83 "lora_target_modules": ["q_proj", "v_proj"],
84 "hook": "blocks.{layer}.hook_resid_post",
85 "examples": [
86 "LiquidAI/LFM2.5-1.2B-Instruct",
87 "LiquidAI/LFM2.5-1.2B-Thinking",
88 "LiquidAI/LFM2.5-230M",
89 ],
90 "trust_remote_code": False,
91 "hf_only": True,
92 },
93 {
94 "id": "qwen",
95 "label": "Qwen",
96 # Official Qwen/* plus community fine-tunes with "qwen" in the repo name
97 "hf_patterns": (
98 re.compile(r"^Qwen/Qwen", re.I),
99 re.compile(r"(?i)(?:^|/)[^/\s]*qwen"),
100 ),
101 "lora_target_modules": ["q_proj", "v_proj"],
102 "hook": "blocks.{layer}.hook_resid_post",
103 "examples": [
104 "Qwen/Qwen3-8B",
105 "Qwen/Qwen2.5-7B-Instruct",
106 "Qwen/Qwen2.5-0.5B-Instruct",
107 ],
108 "trust_remote_code": False,
109 "hf_only": True,
110 },
111]
112
113
114
115def _slug_from_hf(hf_name: str) -> str:
116 """Stable Aquin slug from a HuggingFace repo id."""
117 part = hf_name.split("/")[-1].lower()
118 part = re.sub(r"[^a-z0-9._-]+", "-", part).strip("-")
119 return part or "model"
120
121
122def _match_family(hf_name: str, families: list[dict[str, Any]]) -> dict[str, Any] | None:
123 for fam in families:
124 for pat in fam.get("hf_patterns", ()):
125 if pat.search(hf_name):
126 return fam
127 return None
128
129
130def _family_by_id(family_id: str) -> dict[str, Any] | None:
131 for fam in LLM_FAMILIES:
132 if fam["id"] == family_id:
133 return fam
134 return None
135
136
137def _match_family_from_hf_config(hf_name: str) -> dict[str, Any] | None:
138 """Infer LLM family from HuggingFace config when the repo id is not an official org prefix.
139
140 Covers fine-tunes (e.g. Hahmdong/*-qwen3-*) that keep the base architecture in config.json.
141 """
142 try:
143 from transformers import AutoConfig
144
145 cfg = AutoConfig.from_pretrained(hf_name, trust_remote_code=True)
146 except Exception:
147 return None
148
149 model_type = str(getattr(cfg, "model_type", "") or "").lower()
150 fam_id = _MODEL_TYPE_TO_FAMILY.get(model_type)
151 if fam_id:
152 return _family_by_id(fam_id)
153
154 arches = getattr(cfg, "architectures", None) or []
155 for arch in arches:
156 name = str(arch)
157 for hint, fid in _ARCH_HINTS:
158 if hint.lower() in name.lower():
159 return _family_by_id(fid)
160 return None
161
162
163def normalize_llm_hf_id(model_id: str) -> str | None:
164 raw = (model_id or "").strip()
165 if not raw:
166 return None
167 if "/" in raw:
168 return raw
169 if re.match(r"^gpt2(?:-|$)", raw, re.I):
170 return raw
171 if raw.lower().startswith("pythia"):
172 return f"EleutherAI/{raw}"
173 return None
174
175
176
177def _infer_llm_dims(hf_name: str, *, trust_remote_code: bool) -> tuple[int, int]:
178 from transformers import AutoConfig
179
180 cfg = AutoConfig.from_pretrained(hf_name, trust_remote_code=trust_remote_code)
181 n_layers = int(
182 getattr(cfg, "num_hidden_layers", None)
183 or getattr(cfg, "n_layer", None)
184 or getattr(cfg, "n_layers", 0)
185 )
186 d_model = int(
187 getattr(cfg, "hidden_size", None)
188 or getattr(cfg, "n_embd", None)
189 or getattr(cfg, "d_model", 0)
190 )
191 if n_layers < 1 or d_model < 1:
192 raise ValueError(f"Could not infer layer count / width for {hf_name}")
193 return d_model, n_layers
194
195
196
197def build_llm_family_config(hf_name: str, family: dict[str, Any]) -> dict[str, Any]:
198 from transformers import AutoConfig
199
200 trust = bool(family.get("trust_remote_code", False))
201 d_model, n_layers = _infer_llm_dims(hf_name, trust_remote_code=trust)
202 hcfg = AutoConfig.from_pretrained(hf_name, trust_remote_code=trust)
203 n_heads = int(
204 getattr(hcfg, "num_attention_heads", None)
205 or getattr(hcfg, "n_head", None)
206 or 32
207 )
208 sae_layer = max(0, n_layers // 2)
209 slug = _slug_from_hf(hf_name)
210 cfg: dict[str, Any] = {
211 "hf_name": hf_name,
212 "d_model": d_model,
213 "n_layers": n_layers,
214 "n_heads": n_heads,
215 "sae_layer": sae_layer,
216 "sae_source": "user",
217 "sae_layers": {},
218 "family": family["id"],
219 "hook_template": family.get("hook", "blocks.{layer}.hook_resid_post"),
220 "lora_target_modules": list(family.get("lora_target_modules", ["q_proj", "v_proj"])),
221 "trust_remote_code": trust,
222 "catalog": False,
223 "_slug": slug,
224 }
225 if family.get("hf_only"):
226 cfg["hf_only"] = True
227 if family.get("hf_first"):
228 cfg["hf_first"] = True
229 return cfg
230
231
232
233def _persist_family_config(slug: str, cfg: dict[str, Any]) -> None:
234 FAMILY_MODEL_DIR.mkdir(parents=True, exist_ok=True)
235 payload = {k: v for k, v in cfg.items() if not str(k).startswith("_")}
236 path = FAMILY_MODEL_DIR / f"{slug}.json"
237 path.write_text(json.dumps(payload, indent=2), encoding="utf-8")
238
239
240def _load_family_config_from_disk(slug: str) -> dict[str, Any] | None:
241 path = FAMILY_MODEL_DIR / f"{slug}.json"
242 if not path.is_file():
243 return None
244 try:
245 data = json.loads(path.read_text(encoding="utf-8"))
246 except Exception:
247 return None
248 return data if isinstance(data, dict) else None
249
250
251def get_runtime_llm_config(slug: str) -> dict[str, Any] | None:
252 if slug in _RUNTIME_LLM:
253 return _RUNTIME_LLM[slug]
255 if cfg:
256 _RUNTIME_LLM[slug] = cfg
257 return cfg
258 return None
259
260
261def resolve_llm_family(model_id: str) -> tuple[str, dict[str, Any]]:
262 """Resolve catalog-external LLM slug or HF id via family rules. Raises ValueError if unsupported."""
263 raw = (model_id or "").strip()
264 if not raw:
265 raise ValueError("model_id required")
266
267 cached = get_runtime_llm_config(raw)
268 if cached:
269 return raw, cached
270
271 hf_name = normalize_llm_hf_id(raw) or raw
272
273 family = _match_family(hf_name, LLM_FAMILIES)
274 if not family and "/" in hf_name:
275 family = _match_family_from_hf_config(hf_name)
276 if not family and "/" not in hf_name:
277 raise ValueError(
278 f"Unknown LLM '{model_id}'. Pass a HuggingFace repo id "
279 f"(e.g. gpt2-medium, EleutherAI/pythia-410m-deduped, meta-llama/Llama-3.2-3B-Instruct)."
280 )
281 if not family:
282 raise ValueError(
283 f"LLM '{hf_name}' is not in a supported family yet. "
284 f"Supported: GPT-2, Pythia (EleutherAI/pythia-*), Llama (meta-llama/Llama*), "
285 f"LFM (LiquidAI/LFM*), Qwen (Qwen/Qwen* and fine-tunes with qwen in the name / "
286 f"qwen2|qwen3 architecture)."
287 )
288
289 slug = _slug_from_hf(hf_name)
290 cached = get_runtime_llm_config(slug)
291 if cached:
292 return slug, cached
293
294 cfg = build_llm_family_config(hf_name, family)
295 _RUNTIME_LLM[slug] = cfg
296 _persist_family_config(slug, cfg)
297 return slug, cfg
298
299
300
301
302def is_llm_family_candidate(model_id: str) -> bool:
303 raw = (model_id or "").strip()
305 return True
306 hf_name = normalize_llm_hf_id(raw) or raw
307 if "/" not in hf_name and not re.match(r"^gpt2", raw, re.I):
308 return False
309 return _match_family(hf_name, LLM_FAMILIES) is not None
310
311
312
313def family_help_lines() -> list[str]:
314 lines = ["Model families (pass HuggingFace repo id to aquin load --model):", ""]
315 lines.append("LLMs:")
316 for fam in LLM_FAMILIES:
317 ex = ", ".join(fam.get("examples", [])[:3])
318 lines.append(f" {fam['label']}: {ex}")
319 lines.append("")
320 lines.append("Load SAEs with: aquin load sae <model-l{n}> or --path <file.pt>")
321 lines.append("Family models use inspection tools; self-train SAE: aquin sae train (see /docs/sae-training).")
322 return lines
tuple[int, int] _infer_llm_dims(str hf_name, *, bool trust_remote_code)
dict[str, Any] build_llm_family_config(str hf_name, dict[str, Any] family)
dict[str, Any]|None _match_family_from_hf_config(str hf_name)
None _persist_family_config(str slug, dict[str, Any] cfg)
dict[str, Any]|None _load_family_config_from_disk(str slug)
bool is_llm_family_candidate(str model_id)
tuple[str, dict[str, Any]] resolve_llm_family(str model_id)
str|None normalize_llm_hf_id(str model_id)
dict[str, Any]|None _family_by_id(str family_id)
dict[str, Any]|None get_runtime_llm_config(str slug)
dict[str, Any]|None _match_family(str hf_name, list[dict[str, Any]] families)