2"""Standalone confidence pattern analysis over probe datasets (LLM)."""
4from __future__
import annotations
9import torch.nn.functional
as F
14_META_KEYS = (
"id",
"stressor",
"lang",
"quant_run_id",
"condition",
"label",
"group")
15DEFAULT_THRESHOLD = 0.40
16_BASELINE_TAG =
"baseline"
20 tokens = model.to_tokens(text[:512])
22 logits = model(tokens)
23 probs = F.softmax(logits[0].float(), dim=-1)
24 log_probs = F.log_softmax(logits[0].float(), dim=-1)
25 greedy = logits[0].argmax(dim=-1)
26 idx = torch.arange(len(greedy), device=greedy.device)
27 token_confs = probs[idx, greedy]
28 entropies = -(probs * log_probs).sum(dim=-1)
29 mean_conf = float(token_confs.mean().item())
32 "mean_confidence": round(mean_conf, 4),
33 "max_prob": round(float(max(token_confs.max().item(), last_dist.max().item())), 4),
34 "entropy": round(float(entropies.mean().item()), 4),
35 "ece_proxy": round(abs(mean_conf - 0.5), 4),
36 "last_token_entropy": round(float(-(last_dist * last_dist.clamp(min=1e-10).log()).sum().item()), 4),
40def _llm_sae_join_row(model: Any, model_id: str, layer: int, text: str) -> dict[str, Any]:
44 sae = load_sae(model_id, layer)
45 except Exception
as exc:
46 return {
"sae_available":
False,
"sae_error": str(exc)}
47 acts = _llm_probe_acts(model, sae, model_id, layer, text)
48 l0 = float((acts > 1e-6).sum().item())
49 top_idx = int(acts.argmax().item())
51 "sae_available":
True,
53 "mean_l0": round(l0, 4),
54 "top_feature_idx": top_idx,
55 "top_feature_act": round(float(acts[top_idx].item()), 6),
59def _stressor_summary(rows: list[dict[str, Any]], *, baseline: str = _BASELINE_TAG) -> list[dict[str, Any]]:
60 groups: dict[str, list[dict[str, Any]]] = {}
62 key = str(row.get(
"stressor")
or row.get(
"label")
or "unknown")
63 groups.setdefault(key, []).append(row)
65 base_rows = groups.get(baseline, [])
67 sum(r[
"mean_confidence"]
for r
in base_rows) / len(base_rows)
68 if base_rows
else None
71 sum(r[
"entropy"]
for r
in base_rows) / len(base_rows)
72 if base_rows
else None
75 out: list[dict[str, Any]] = []
76 for stressor, items
in sorted(groups.items()):
77 confs = [r[
"mean_confidence"]
for r
in items]
78 ents = [r[
"entropy"]
for r
in items]
81 "n_probes": len(items),
82 "mean_confidence": round(sum(confs) / len(confs), 4),
83 "mean_entropy": round(sum(ents) / len(ents), 4),
84 "ece_proxy": ece_proxy(confs),
85 "low_confidence_count": sum(1
for r
in items
if r.get(
"low_confidence")),
87 if base_conf
is not None and stressor != baseline:
88 entry[
"confidence_delta"] = round(entry[
"mean_confidence"] - base_conf, 4)
89 if base_ent
is not None and stressor != baseline:
90 entry[
"entropy_delta"] = round(entry[
"mean_entropy"] - base_ent, 4)
99 probes: list[dict[str, Any]],
100 per_probe: list[dict[str, Any]],
103 sae_layer: int |
None,
105 confs = [r[
"mean_confidence"]
for r
in per_probe]
107 heatmap_rows = [s[
"stressor"]
for s
in stressor_summary]
109 [s[
"mean_confidence"], s[
"mean_entropy"], s[
"ece_proxy"]]
110 for s
in stressor_summary
115 "model_id": model_id,
117 "n_probes": len(per_probe),
118 "threshold": threshold,
119 "aggregate_ece_proxy": ece_proxy(confs),
120 "mean_confidence": round(sum(confs) / len(confs), 4)
if confs
else 0.0,
121 "low_confidence_count": sum(1
for r
in per_probe
if r[
"low_confidence"]),
122 "join_sae": join_sae,
123 "sae_layer": sae_layer
if join_sae
else None,
125 "stressor_summary": stressor_summary,
127 "metric":
"confidence_patterns",
128 "rows": heatmap_rows,
129 "cols": [
"mean_confidence",
"mean_entropy",
"ece_proxy"],
130 "values": heatmap_values,
138 probes: list[dict[str, Any]],
140 threshold: float = DEFAULT_THRESHOLD,
141 join_sae: bool =
False,
142 sae_layer: int |
None =
None,
146 resolved_layer = int(sae_layer
if sae_layer
is not None else get_sae_layer(model_id))
148 per_probe: list[dict[str, Any]] = []
152 row: dict[str, Any] = {
153 "id": probe.get(
"id"),
155 **{k: probe[k]
for k
in _META_KEYS
if k
in probe
and k !=
"id"},
157 "low_confidence": metrics[
"mean_confidence"] < threshold,
161 per_probe.append(row)
170 sae_layer=resolved_layer,
181 model_id = args.get(
"model_id")
or "llama-3.2-1b"
182 prompts_raw = args.get(
"prompts")
183 if prompts_raw
is None or prompts_raw ==
"":
186 "Missing prompts. Pass a file path to JSON/JSONL, a JSON array of "
187 'strings, e.g. \'["The capital of France is", "2+2 equals"]\', or an '
188 "array of {text: ...} objects."
194 except (OSError, ValueError, TypeError)
as exc:
195 return {
"error": str(exc)}
197 join_sae = bool(args.get(
"join_sae")
or args.get(
"join-sae"))
198 layer_raw = args.get(
"layer")
or args.get(
"sae_layer")
199 sae_layer = int(layer_raw)
if layer_raw
is not None else None
200 threshold = float(args.get(
"threshold")
or DEFAULT_THRESHOLD)
203 model_id = resolve_model_id(str(model_id))
204 except ValueError
as exc:
205 return {
"error": str(exc)}
207 model = get_loaded_model()
210 model = load_model(model_id)
211 except Exception
as exc:
212 return {
"error": str(exc)}
225 """Accept file path, JSON array string, list of strings, or list of probe objects."""
226 if isinstance(prompts_raw, list):
229 if isinstance(prompts_raw, dict):
230 if isinstance(prompts_raw.get(
"probes"), list):
232 text = prompts_raw.get(
"text")
or prompts_raw.get(
"prompt")
234 return [{
"id":
"probe_0",
"text": str(text)}]
235 raise ValueError(
"prompts object needs probes[] or text/prompt")
237 s = str(prompts_raw).strip()
239 raise ValueError(
"prompts is empty")
246 parsed = json.loads(s)
247 except json.JSONDecodeError
as exc:
248 raise ValueError(f
"prompts looks like JSON but failed to parse: {exc}")
from exc
253 from pathlib
import Path
256 return load_probes(s)
257 except (OSError, ValueError, FileNotFoundError):
258 p = Path(s).expanduser()
262 lines = [ln.strip()
for ln
in s.splitlines()
if ln.strip()]
265 return [{
"id":
"probe_0",
"text": s}]
269 probes: list[dict[str, Any]] = []
270 for i, row
in enumerate(rows):
271 if isinstance(row, str):
274 probes.append({
"id": f
"probe_{i}",
"text": text})
276 if not isinstance(row, dict):
277 raise ValueError(f
"Probe row {i} must be string or object")
282 or row.get(
"sentence")
285 raise ValueError(f
"Probe row {i} missing text/prompt")
286 out: dict[str, Any] = {
"id": row.get(
"id")
or f
"probe_{i}",
"text": str(text)}
287 for key
in _META_KEYS:
288 if row.get(key)
is not None:
292 raise ValueError(
"No probes after parsing prompts")
dict[str, Any] run_confidence_analysis(Any model, str model_id, list[dict[str, Any]] probes, *, float threshold=DEFAULT_THRESHOLD, bool join_sae=False, int|None sae_layer=None)
dict[str, Any] run_confidence_analysis_from_args(dict[str, Any] args)
list[dict[str, Any]] _coerce_probes(Any prompts_raw)
dict[str, Any] _prompt_confidence_metrics(Any model, str text)
list[dict[str, Any]] _stressor_summary(list[dict[str, Any]] rows, *, str baseline=_BASELINE_TAG)
dict[str, Any] _finalize_payload(*, str mode, str model_id, list[dict[str, Any]] probes, list[dict[str, Any]] per_probe, float threshold, bool join_sae, int|None sae_layer)
list[dict[str, Any]] _rows_to_probes(list[Any] rows)
dict[str, Any] _llm_sae_join_row(Any model, str model_id, int layer, str text)