AQIT 0.1.0
Loading...
Searching...
No Matches
merge_analysis.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2"""Pre-merge LoRA analysis: weight-diff + rank/collapse signals + optional behavioral scores."""
3
4from __future__ import annotations
5
6from pathlib import Path
7from typing import Any
8
9from aquin.compute.weight_diff import run_weight_diff
10
11
12def _collapse_signals(matrices: list[dict[str, Any]], mean_stable: float) -> list[dict[str, Any]]:
13 threshold = max(3.0, mean_stable * 1.75)
14 out: list[dict[str, Any]] = []
15 for row in sorted(matrices, key=lambda m: m.get("delta_stable_rank", 0), reverse=True):
16 rank = float(row.get("delta_stable_rank") or 0)
17 if rank < threshold:
18 continue
19 out.append(
20 {
21 "layer": row.get("layer"),
22 "matrix": row.get("matrix"),
23 "module": row.get("module"),
24 "deltaL2": row.get("delta_l2"),
25 "deltaStableRank": rank,
26 "deltaNuclearRatio": row.get("delta_nuclear_ratio"),
27 }
28 )
29 if len(out) >= 12:
30 break
31 return out
32
33
35 *,
36 collapse_signals: list[dict[str, Any]],
37 behavioral: dict[str, Any] | None,
38 total_delta_l2: float,
39) -> tuple[str, list[str]]:
40 warnings: list[str] = []
41 severity = 0
42
43 if collapse_signals:
44 n = len(collapse_signals)
45 warnings.append(
46 f"{n} matrix(ices) show elevated stable rank in ΔW "
47 f"(possible rank expansion before merge)."
48 )
49 severity += 1 if n <= 3 else 2
50
51 if total_delta_l2 > 50.0:
52 warnings.append(f"Large aggregate ‖ΔW‖ ({total_delta_l2:.4g}) — verify adapter scale before merge.")
53 severity += 1
54
55 if behavioral:
56 consistency = float(behavioral.get("consistencyScore") or 1.0)
57 robustness = float(behavioral.get("robustnessScore") or 1.0)
58 if consistency < 0.55:
59 warnings.append(f"Low behavioral consistency ({consistency:.2f}) on probe generations.")
60 severity += 2
61 elif consistency < 0.7:
62 warnings.append(f"Moderate behavioral drift ({consistency:.2f}) on probe generations.")
63 severity += 1
64 if robustness < 0.45:
65 warnings.append(f"Low robustness score ({robustness:.2f}) across probe categories.")
66 severity += 2
67 elif robustness < 0.6:
68 warnings.append(f"Moderate robustness score ({robustness:.2f}).")
69 severity += 1
70
71 if severity >= 3:
72 return "fail", warnings
73 if severity >= 1:
74 return "warn", warnings
75 return "pass", warnings
76
77
79 model_id: str,
80 checkpoint_path: str | Path,
81 *,
82 checkpoint_name: str | None = None,
83 prompts: list[str] | None = None,
84 with_behavioral: bool = True,
85) -> dict[str, Any]:
86 ckpt = Path(checkpoint_path)
87 name = checkpoint_name or ckpt.stem
88
89 weight = run_weight_diff(model_id, ckpt, checkpoint_name=name)
90 if weight.get("error"):
91 return weight
92
93 matrices = weight.get("matrices") or weight.get("topChanged") or []
94 mean_stable = float(weight.get("meanDeltaStableRank") or 0.0)
95 collapse = _collapse_signals(matrices, mean_stable)
96
97 behavioral: dict[str, Any] | None = None
98 behavioral_error: str | None = None
99 if with_behavioral:
100 try:
101 from aquin.compute.model_diff import _run_model_diff
102
103 behavioral = _run_model_diff(model_id, str(ckpt), prompts or [], n_prompts=5)
104 except Exception as e:
105 behavioral_error = str(e)
106
107 verdict, warnings = _merge_verdict(
108 collapse_signals=collapse,
109 behavioral=behavioral,
110 total_delta_l2=float(weight.get("totalDeltaL2") or 0.0),
111 )
112
113 return {
114 "schema_version": 1,
115 "type": "mergeAnalysis",
116 "baseModelId": weight.get("baseModelId"),
117 "ftCheckpointName": weight.get("ftCheckpointName"),
118 "checkpointPath": weight.get("checkpointPath"),
119 "modelMode": weight.get("modelMode"),
120 "deltaMode": weight.get("deltaMode"),
121 "trainingStep": weight.get("trainingStep"),
122 "mergeVerdict": verdict,
123 "warnings": warnings,
124 "nMatrices": weight.get("nMatrices"),
125 "totalDeltaL2": weight.get("totalDeltaL2"),
126 "maxDeltaL2": weight.get("maxDeltaL2"),
127 "meanDeltaStableRank": mean_stable,
128 "collapseSignals": collapse,
129 "topChanged": weight.get("topChanged") or [],
130 "layerProfile": weight.get("layerProfile") or [],
131 "behavioralDiff": behavioral,
132 "behavioralError": behavioral_error,
133 "withBehavioral": with_behavioral,
134 }
135
136
137def run_merge_analysis_from_args(args: dict[str, Any]) -> dict[str, Any]:
138 from aquin.compute.model_loader import get_active_model_id, resolve_model_id
139
140 model_id = args.get("model_id") or get_active_model_id() or "llama-3.2-1b"
141 checkpoint = args.get("checkpoint")
142 if not checkpoint:
143 return {"error": "Missing --checkpoint <path>. Run: aquin diff weight --help"}
144
145 try:
146 model_id = resolve_model_id(str(model_id))
147 except ValueError as e:
148 return {"error": str(e)}
149
150 prompts_path = args.get("prompts")
151 prompts = None
152 if prompts_path:
153 from aquin.compute.sae_diff import load_prompts
154
155 prompts = load_prompts(str(prompts_path))
156
157 return run_merge_analysis(
158 model_id,
159 checkpoint,
160 checkpoint_name=args.get("name"),
161 prompts=prompts,
162 with_behavioral=not args.get("no_behavioral"),
163 )
dict[str, Any] run_merge_analysis(str model_id, str|Path checkpoint_path, *, str|None checkpoint_name=None, list[str]|None prompts=None, bool with_behavioral=True)
tuple[str, list[str]] _merge_verdict(*, list[dict[str, Any]] collapse_signals, dict[str, Any]|None behavioral, float total_delta_l2)
list[dict[str, Any]] _collapse_signals(list[dict[str, Any]] matrices, float mean_stable)
dict[str, Any] run_merge_analysis_from_args(dict[str, Any] args)