AQIT 0.1.0
Loading...
Searching...
No Matches
weight_diff.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2"""Weight delta analysis: base vs fine-tuned checkpoint (LLM)."""
3
4from __future__ import annotations
5
6import re
7from collections import defaultdict
8from pathlib import Path
9from typing import Any
10
11import torch
12
13from aquin.compute.sae_diff import load_checkpoint_state
14from aquin.compute.weight_rank import _matrix_ranks, _to_2d
15
16
17_LAYER_RE = re.compile(r"(?:^|\.)(?:layers?|layer|block)\.?(\d+)(?:\.|$)", re.I)
18_DEFAULT_LORA_R = 8
19_DEFAULT_LORA_ALPHA = 16.0
20_SMOKE_LORA_PATH = Path.home() / ".aquin" / "smoke" / "weight-diff-lora.pt"
23def _layer_from_key(key: str) -> int:
24 m = _LAYER_RE.search(key.replace("_", "."))
25 return int(m.group(1)) if m else -1
26
28def _matrix_label_from_key(key: str) -> str:
29 kl = key.lower()
30 for tag in ("q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj", "query", "key", "value"):
31 if tag in kl:
32 return tag
33 if "mlp" in kl and "gate" in kl:
34 return "mlp_gate"
35 if "mlp" in kl and "down" in kl:
36 return "mlp_down"
37 if "mlp" in kl:
38 return "mlp"
39 if "attn" in kl:
40 return "attn"
41 parts = key.split(".")
42 return parts[-2] if len(parts) >= 2 else parts[-1]
43
44
46 """Tiny LoRA state_dict so desktop smoke can run without a real fine-tune."""
47 dest = _SMOKE_LORA_PATH
48 if dest.is_file():
49 return dest
50 dest.parent.mkdir(parents=True, exist_ok=True)
51 r, d = _DEFAULT_LORA_R, 32
52 state = {
53 "model.layers.0.self_attn.q_proj.lora_A": torch.randn(r, d) * 0.05,
54 "model.layers.0.self_attn.q_proj.lora_B": torch.randn(d, r) * 0.05,
55 "model.layers.1.self_attn.v_proj.lora_A": torch.randn(r, d) * 0.05,
56 "model.layers.1.self_attn.v_proj.lora_B": torch.randn(d, r) * 0.05,
57 }
58 torch.save(state, dest)
59 return dest
60
61
62def _has_lora_keys(state: dict[str, Any]) -> bool:
63 return any("lora_a" in k.lower() or "lora_b" in k.lower() for k in state)
64
65
67 *,
68 layer: int,
69 matrix: str,
70 module: str,
71 delta: torch.Tensor,
72 base_norm: float | None = None,
73) -> dict[str, Any]:
74 l2 = float(delta.norm().item())
75 rel = (l2 / max(base_norm, 1e-9)) if base_norm is not None and base_norm > 1e-9 else 0.0
76 stable, nuclear = _matrix_ranks(delta)
77 return {
78 "layer": layer,
79 "matrix": matrix,
80 "module": module,
81 "delta_l2": round(l2, 6),
82 "delta_l2_relative": round(rel, 6),
83 "delta_stable_rank": stable,
84 "delta_nuclear_ratio": nuclear,
85 }
86
87
88def _layer_profile(matrices: list[dict[str, Any]]) -> list[dict[str, Any]]:
89 totals: dict[int, float] = defaultdict(float)
90 counts: dict[int, int] = defaultdict(int)
91 for row in matrices:
92 layer = int(row["layer"])
93 if layer < 0:
94 continue
95 totals[layer] += float(row["delta_l2"])
96 counts[layer] += 1
97 return [
98 {
99 "layer": layer,
100 "delta_l2": round(totals[layer], 6),
101 "n_matrices": counts[layer],
102 }
103 for layer in sorted(totals)
104 ]
105
106
108 *,
109 model_id: str,
110 mode: str,
111 checkpoint_name: str,
112 checkpoint_path: str,
113 delta_mode: str,
114 matrices: list[dict[str, Any]],
115 step: int | None = None,
116) -> dict[str, Any]:
117 sorted_m = sorted(matrices, key=lambda x: x["delta_l2"], reverse=True)
118 total_l2 = round(sum(m["delta_l2"] for m in matrices), 6)
119 max_l2 = round(max((m["delta_l2"] for m in matrices), default=0.0), 6)
120 mean_stable = (
121 round(sum(m["delta_stable_rank"] for m in matrices) / len(matrices), 4)
122 if matrices else 0.0
123 )
124 return {
125 "schema_version": 1,
126 "type": "weightDiff",
127 "baseModelId": model_id,
128 "ftCheckpointName": checkpoint_name,
129 "checkpointPath": checkpoint_path,
130 "modelMode": mode,
131 "deltaMode": delta_mode,
132 "trainingStep": step,
133 "nMatrices": len(matrices),
134 "totalDeltaL2": total_l2,
135 "maxDeltaL2": max_l2,
136 "meanDeltaStableRank": mean_stable,
137 "layerProfile": _layer_profile(matrices),
138 "topChanged": sorted_m[:25],
139 "matrices": sorted_m,
140 }
141
142
143def _iter_tl_matrix_pairs(tl_base: Any, tl_ft: Any) -> list[tuple[int, str, str, torch.Tensor, torch.Tensor]]:
144 n_layers = int(getattr(tl_base.cfg, "n_layers", 0) or len(tl_base.blocks))
145 pairs: list[tuple[int, str, str, torch.Tensor, torch.Tensor]] = []
146 for layer in range(n_layers):
147 block_b = tl_base.blocks[layer]
148 block_f = tl_ft.blocks[layer]
149 attn = block_b.attn
150 attn_f = block_f.attn
151 mlp_b = block_b.mlp
152 mlp_f = block_f.mlp
153 specs: list[tuple[str, torch.Tensor, torch.Tensor]] = [
154 ("Q", attn.W_Q, attn_f.W_Q),
155 ("K", attn.W_K, attn_f.W_K),
156 ("V", attn.W_V, attn_f.W_V),
157 ("O", attn.W_O, attn_f.W_O),
158 ("MLP_up", mlp_b.W_in, mlp_f.W_in),
159 ("MLP_down", mlp_b.W_out, mlp_f.W_out),
160 ]
161 if hasattr(mlp_b, "W_gate"):
162 specs.append(("MLP_gate", mlp_b.W_gate, mlp_f.W_gate))
163 for name, wb, wf in specs:
164 pairs.append((layer, name, f"blocks.{layer}.{name}", wb.detach(), wf.detach()))
165 return pairs
166
167
169 model_id: str,
170 checkpoint_path: str | Path,
171 *,
172 checkpoint_name: str = "checkpoint",
173) -> dict[str, Any]:
174 from transformers import AutoModelForCausalLM
175
176 from aquin.compute.model_loader import get_config, resolve_model_id
177 from aquin.compute.sae_diff import load_checkpoint_state
178
179 short = resolve_model_id(model_id)
180 cfg = get_config(short)
181 state, step = load_checkpoint_state(checkpoint_path)
182 if _has_lora_keys(state):
184 short,
185 state,
186 checkpoint_name=checkpoint_name,
187 checkpoint_path=str(checkpoint_path),
188 step=step,
189 )
190
191 hf_name = cfg["hf_name"]
192 trust = bool(cfg.get("trust_remote_code", False))
193 base_model = AutoModelForCausalLM.from_pretrained(
194 hf_name,
195 torch_dtype=torch.float32,
196 device_map="cpu",
197 low_cpu_mem_usage=True,
198 trust_remote_code=trust,
199 )
200 base_sd = {k: v.detach().float().cpu() for k, v in base_model.state_dict().items()}
201 del base_model
202
203 matrices: list[dict[str, Any]] = []
204 for key, ft_w in state.items():
205 base_w = base_sd.get(key)
206 if base_w is None or not isinstance(ft_w, torch.Tensor):
207 continue
208 wf = ft_w.detach().float().cpu()
209 if base_w.shape != wf.shape or base_w.ndim < 2:
210 continue
211 delta = wf - base_w
212 layer = _layer_from_key(key)
213 matrix = _matrix_label_from_key(key)
214 base_norm = float(_to_2d(base_w).norm().item())
215 matrices.append(
217 layer=layer,
218 matrix=matrix,
219 module=key,
220 delta=delta,
221 base_norm=base_norm,
222 )
223 )
224 return _finalize_payload(
225 model_id=short,
226 mode="llm",
227 checkpoint_name=checkpoint_name,
228 checkpoint_path=str(checkpoint_path),
229 delta_mode="hf_state_dict",
230 matrices=matrices,
231 step=step,
232 )
233
234
236 model_id: str,
237 state_dict: dict[str, Any],
238 *,
239 checkpoint_name: str = "checkpoint",
240 checkpoint_path: str = "",
241 step: int | None = None,
242 lora_r: int = _DEFAULT_LORA_R,
243 lora_alpha: float = _DEFAULT_LORA_ALPHA,
244) -> dict[str, Any]:
245 pairs: dict[str, dict[str, torch.Tensor]] = {}
246 for key, tensor in state_dict.items():
247 kl = key.lower()
248 if ".lora_a" in kl:
249 prefix = key.split(".lora_A")[0].split(".lora_a")[0]
250 pairs.setdefault(prefix, {})["A"] = tensor.float()
251 elif ".lora_b" in kl:
252 prefix = key.split(".lora_B")[0].split(".lora_b")[0]
253 pairs.setdefault(prefix, {})["B"] = tensor.float()
254
255 scale = lora_alpha / max(lora_r, 1)
256 matrices: list[dict[str, Any]] = []
257 for prefix, ab in pairs.items():
258 a = ab.get("A")
259 b = ab.get("B")
260 if a is None or b is None:
261 continue
262 if b.ndim == 2 and a.ndim == 2:
263 delta = (b @ a) * scale
264 else:
265 delta = (b.reshape(-1, 1) @ a.reshape(1, -1)) * scale
266 layer = _layer_from_key(prefix)
267 matrix = _matrix_label_from_key(prefix)
268 matrices.append(
270 layer=layer,
271 matrix=matrix,
272 module=prefix,
273 delta=delta.cpu(),
274 base_norm=None,
275 )
276 )
277
278 return _finalize_payload(
279 model_id=model_id,
280 mode="llm",
281 checkpoint_name=checkpoint_name,
282 checkpoint_path=checkpoint_path,
283 delta_mode="lora_effective",
284 matrices=matrices,
285 step=step,
286 )
287
288
290 model_id: str,
291 checkpoint_path: str | Path,
292 *,
293 checkpoint_name: str | None = None,
294) -> dict[str, Any]:
295 from aquin.compute.model_loader import resolve_model_id
296
297 ckpt = Path(checkpoint_path)
298 if not ckpt.exists():
299 raise FileNotFoundError(f"Checkpoint not found: {ckpt}")
300
301 name = checkpoint_name or ckpt.stem
302 short = resolve_model_id(model_id)
303 return run_llm_hf_weight_diff(short, ckpt, checkpoint_name=name)
304
305
306def run_weight_diff_from_args(args: dict[str, Any]) -> dict[str, Any]:
307 from aquin.compute.model_loader import get_active_model_id, resolve_model_id
308
309 model_id = args.get("model_id") or get_active_model_id() or "llama-3.2-1b"
310 checkpoint = args.get("checkpoint")
311 raw = str(checkpoint or "").strip()
312 if not raw or raw.lower() in ("none", "null", "default", "-"):
313 checkpoint = str(ensure_smoke_lora_checkpoint())
314
315 try:
316 model_id = resolve_model_id(str(model_id))
317 except ValueError as exc:
318 return {"error": str(exc)}
319
320 try:
321 return run_weight_diff(
322 model_id,
323 checkpoint,
324 checkpoint_name=args.get("name"),
325 )
326 except Exception as exc:
327 return {"error": str(exc)}
dict[str, Any] _matrix_entry(*, int layer, str matrix, str module, torch.Tensor delta, float|None base_norm=None)
int _layer_from_key(str key)
bool _has_lora_keys(dict[str, Any] state)
list[dict[str, Any]] _layer_profile(list[dict[str, Any]] matrices)
str _matrix_label_from_key(str key)
dict[str, Any] _finalize_payload(*, str model_id, str mode, str checkpoint_name, str checkpoint_path, str delta_mode, list[dict[str, Any]] matrices, int|None step=None)
list[tuple[int, str, str, torch.Tensor, torch.Tensor]] _iter_tl_matrix_pairs(Any tl_base, Any tl_ft)
dict[str, Any] run_weight_diff(str model_id, str|Path checkpoint_path, *, str|None checkpoint_name=None)
dict[str, Any] run_llm_lora_weight_diff(str model_id, dict[str, Any] state_dict, *, str checkpoint_name="checkpoint", str checkpoint_path="", int|None step=None, int lora_r=_DEFAULT_LORA_R, float lora_alpha=_DEFAULT_LORA_ALPHA)
dict[str, Any] run_weight_diff_from_args(dict[str, Any] args)
dict[str, Any] run_llm_hf_weight_diff(str model_id, str|Path checkpoint_path, *, str checkpoint_name="checkpoint")