AQIT 0.1.0
Loading...
Searching...
No Matches
sae_stats_check.py
Go to the documentation of this file.
1# Copyright (c) 2025-present Aquin Labs Private Limited. All Rights Reserved.
2"""`aquin sae-stats --check` — save JSON + diagram to the current working directory."""
3
4from __future__ import annotations
5
6import json
7from datetime import datetime, timezone
8from pathlib import Path
9from typing import Any
10
11_JSON_NAME = "sae-stats-check.json"
12_PNG_NAME = "sae-stats-check.png"
13
14
15def _normalize_sae_stats_result(result: dict[str, Any]) -> dict[str, Any]:
16 if not isinstance(result, dict):
17 return {"error": "Invalid sae-stats result"}
18
19 data = result
20 card = result.get("card")
21 if isinstance(card, dict) and isinstance(card.get("data"), dict):
22 card_data = card["data"]
23 data = {
24 "schema_version": result.get("schema_version", 1),
25 "model_id": card_data.get("modelId") or result.get("model_id"),
26 "mode": card_data.get("mode") or result.get("mode"),
27 "n_probes": card_data.get("nProbes") or result.get("n_probes"),
28 "layers_requested": card_data.get("layersRequested") or result.get("layers_requested"),
29 "top_k": card_data.get("topK") or result.get("top_k"),
30 "probes": card_data.get("probes") or result.get("probes", []),
31 "layer_stats": card_data.get("layerStats") or result.get("layer_stats", []),
32 "layer_profile": card_data.get("layerProfile") or result.get("layer_profile", []),
33 "heatmap": card_data.get("heatmap") or result.get("heatmap", {}),
34 "saved_to": card_data.get("savedTo") or result.get("saved_to"),
35 }
36 elif isinstance(result.get("content"), dict):
37 data = result["content"]
38
39 if result.get("error"):
40 data = {**data, "error": result["error"]}
41 return data
42
43
45 result: dict[str, Any],
46 *,
47 tool_name: str | None,
48 cwd: str | Path,
49) -> tuple[Path, Path]:
50 cwd = Path(cwd)
51 json_path = cwd / _JSON_NAME
52 png_path = cwd / _PNG_NAME
53
54 flat = _normalize_sae_stats_result(result)
55 payload = {
56 "saved_at": datetime.now(timezone.utc).isoformat(),
57 "tool": tool_name,
58 **flat,
59 }
60 json_path.write_text(json.dumps(payload, indent=2, default=str), encoding="utf-8")
61 _plot_sae_stats_check(flat, png_path)
62 return json_path, png_path
63
64
65def _plot_sae_stats_check(result: dict[str, Any], png_path: Path) -> None:
66 import matplotlib
67
68 matplotlib.use("Agg")
69 import matplotlib.pyplot as plt
70
71 if result.get("error"):
72 fig, ax = plt.subplots(figsize=(6, 2), facecolor="#0f1117")
73 ax.axis("off")
74 ax.text(0.5, 0.5, f"Error: {result['error']}", ha="center", va="center", wrap=True, color="#e5e7eb")
75 fig.savefig(png_path, dpi=140, bbox_inches="tight", facecolor="#0f1117")
76 plt.close(fig)
77 return
78
79 model = str(result.get("model_id") or "")
80 mode = str(result.get("mode") or "")
81 n_probes = int(result.get("n_probes") or 0)
82 profile = [p for p in (result.get("layer_profile") or []) if p.get("sae_available", True)]
83 heatmap = result.get("heatmap") or {}
84
85 title = f"SAE stats — {model} · {mode} · {n_probes} probes"
86 fig = plt.figure(figsize=(12, 7.5), facecolor="#0f1117")
87 gs = fig.add_gridspec(2, 2, height_ratios=[1, 1.35], hspace=0.38, wspace=0.28)
88 ax_profile = fig.add_subplot(gs[0, 0])
89 ax_sparse = fig.add_subplot(gs[0, 1])
90 ax_heat = fig.add_subplot(gs[1, :])
91
92 fig.suptitle(title, color="#e5e7eb", fontsize=11, y=0.98)
93
94 _plot_layer_profile(ax_profile, profile, metric="mean_l0", title="Mean L0 per layer", color="#6366f1")
95 _plot_layer_profile(ax_sparse, profile, metric="sparsity", title="Sparsity per layer", color="#34d399", pct=True)
96 _plot_heatmap(ax_heat, heatmap, result.get("probes") or [])
97
98 for ax in (ax_profile, ax_sparse, ax_heat):
99 ax.set_facecolor("#0f1117")
100 for spine in ax.spines.values():
101 spine.set_color("#374151")
102
103 fig.savefig(png_path, dpi=140, bbox_inches="tight", facecolor="#0f1117")
104 plt.close(fig)
105
106
108 ax: Any,
109 profile: list[dict[str, Any]],
110 *,
111 metric: str,
112 title: str,
113 color: str,
114 pct: bool = False,
115) -> None:
116 ax.set_title(title, color="#e5e7eb", fontsize=10)
117
118 if not profile:
119 ax.axis("off")
120 ax.text(0.5, 0.5, "No layer profile", ha="center", va="center", color="#9ca3af")
121 return
122
123 layers = [int(p.get("layer", i)) for i, p in enumerate(profile)]
124 vals = [float(p.get(metric) or 0) for p in profile]
125 ax.bar(layers, vals, color=color, width=0.75, alpha=0.9)
126 ax.set_xlabel("Layer", color="#9ca3af", fontsize=8)
127 ylab = "Sparsity" if pct else "Mean L0"
128 ax.set_ylabel(ylab, color="#9ca3af", fontsize=8)
129 ax.tick_params(colors="#6b7280", labelsize=8)
130 ax.grid(axis="y", alpha=0.2, color="#4b5563")
131 if pct:
132 ax.set_ylim(0, min(1.05, max(vals) * 1.15 + 0.05))
133
134
135def _plot_heatmap(ax: Any, heatmap: dict[str, Any], probes: list[dict[str, Any]]) -> None:
136 rows = heatmap.get("rows") or []
137 cols = heatmap.get("cols") or []
138 values = heatmap.get("values") or []
139 metric = str(heatmap.get("metric") or "mean_l0")
140
141 ax.set_title(f"Probe × layer heatmap ({metric})", color="#e5e7eb", fontsize=10, pad=10)
142
143 if not rows or not cols or not values:
144 ax.axis("off")
145 ax.text(0.5, 0.5, "No heatmap data", ha="center", va="center", color="#9ca3af")
146 return
147
148 import numpy as np
149
150 data = np.array(values, dtype=float)
151 if data.ndim != 2:
152 ax.axis("off")
153 ax.text(0.5, 0.5, "Invalid heatmap shape", ha="center", va="center", color="#9ca3af")
154 return
155
156 im = ax.imshow(data, aspect="auto", cmap="viridis", origin="upper")
157 ax.set_xticks(range(len(cols)))
158 ax.set_xticklabels(cols, fontsize=7, color="#d1d5db", rotation=45, ha="right")
159 ax.set_yticks(range(len(rows)))
160 ylabels = [_probe_label(row_id, probes) for row_id in rows]
161 ax.set_yticklabels(ylabels, fontsize=7, color="#d1d5db")
162 ax.tick_params(axis="x", colors="#6b7280")
163 ax.tick_params(axis="y", colors="#6b7280")
164 cbar = ax.figure.colorbar(im, ax=ax, fraction=0.025, pad=0.02)
165 cbar.ax.tick_params(colors="#9ca3af", labelsize=7)
166 cbar.set_label(metric, color="#9ca3af", fontsize=8)
167
168
169def _probe_label(row_id: str, probes: list[dict[str, Any]]) -> str:
170 for p in probes:
171 if str(p.get("id")) == str(row_id):
172 stressor = p.get("stressor")
173 if stressor:
174 return f"{row_id} ({stressor})"[:24]
175 text = str(p.get("text") or p.get("prompt") or "")
176 if text:
177 return _trunc(text, 22)
178 break
179 return str(row_id)[:22]
180
181
182def _trunc(text: str, limit: int) -> str:
183 t = text.strip()
184 if len(t) <= limit:
185 return t or "·"
186 return t[: limit - 1] + "…"
tuple[Path, Path] write_sae_stats_check(dict[str, Any] result, *, str|None tool_name, str|Path cwd)
None _plot_sae_stats_check(dict[str, Any] result, Path png_path)
str _trunc(str text, int limit)
str _probe_label(str row_id, list[dict[str, Any]] probes)
dict[str, Any] _normalize_sae_stats_result(dict[str, Any] result)
None _plot_layer_profile(Any ax, list[dict[str, Any]] profile, *, str metric, str title, str color, bool pct=False)
None _plot_heatmap(Any ax, dict[str, Any] heatmap, list[dict[str, Any]] probes)