open-interpretability 0.0.20__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (256) hide show
  1. open_interpretability-0.0.20.dist-info/METADATA +157 -0
  2. open_interpretability-0.0.20.dist-info/RECORD +256 -0
  3. open_interpretability-0.0.20.dist-info/WHEEL +4 -0
  4. open_interpretability-0.0.20.dist-info/entry_points.txt +2 -0
  5. open_interpretability-0.0.20.dist-info/licenses/LICENSE +21 -0
  6. openinterp/__init__.py +6 -0
  7. openinterp/agents/__init__.py +25 -0
  8. openinterp/agents/base.py +229 -0
  9. openinterp/agents/judge.py +149 -0
  10. openinterp/agents/proposer.py +471 -0
  11. openinterp/agents/proposer_profiles.py +79 -0
  12. openinterp/agents/review_measurements.py +173 -0
  13. openinterp/agents/reviewer.py +498 -0
  14. openinterp/agreement.py +63 -0
  15. openinterp/audit_options.py +79 -0
  16. openinterp/baseline.py +254 -0
  17. openinterp/benchmark.py +450 -0
  18. openinterp/blends.py +239 -0
  19. openinterp/builder/__init__.py +6 -0
  20. openinterp/builder/agents.py +260 -0
  21. openinterp/builder/aligned.py +41 -0
  22. openinterp/builder/charts.py +411 -0
  23. openinterp/builder/circuit_pages.py +37 -0
  24. openinterp/builder/circuits.py +264 -0
  25. openinterp/builder/compiler.py +495 -0
  26. openinterp/builder/corpora.py +98 -0
  27. openinterp/builder/coverage.py +46 -0
  28. openinterp/builder/dashboard.py +255 -0
  29. openinterp/builder/disk_memo.py +65 -0
  30. openinterp/builder/experiment.py +105 -0
  31. openinterp/builder/heat.py +121 -0
  32. openinterp/builder/hrefs.py +71 -0
  33. openinterp/builder/learned_baseline.py +204 -0
  34. openinterp/builder/model_facts.py +138 -0
  35. openinterp/builder/neuronpedia_export.py +107 -0
  36. openinterp/builder/parallel.py +100 -0
  37. openinterp/builder/parquet_export.py +458 -0
  38. openinterp/builder/pieces.py +127 -0
  39. openinterp/builder/pii.py +211 -0
  40. openinterp/builder/registry_stats.py +144 -0
  41. openinterp/builder/research.py +109 -0
  42. openinterp/builder/sections.py +28 -0
  43. openinterp/builder/significance.py +103 -0
  44. openinterp/builder/sister_graph.py +180 -0
  45. openinterp/builder/standing.py +185 -0
  46. openinterp/builder/statuses.py +34 -0
  47. openinterp/builder/stopped.py +91 -0
  48. openinterp/builder/stops.py +30 -0
  49. openinterp/builder/superpositions.py +102 -0
  50. openinterp/builder/templates/_agent_role_cells.html +9 -0
  51. openinterp/builder/templates/_causal_chart.html +67 -0
  52. openinterp/builder/templates/_circuit.html +8 -0
  53. openinterp/builder/templates/_dashboard.html +133 -0
  54. openinterp/builder/templates/_experiment.html +500 -0
  55. openinterp/builder/templates/_macros.html +82 -0
  56. openinterp/builder/templates/_patchscope.html +33 -0
  57. openinterp/builder/templates/_prior_art.html +24 -0
  58. openinterp/builder/templates/_specificity.html +21 -0
  59. openinterp/builder/templates/_steering.html +34 -0
  60. openinterp/builder/templates/_superposition.html +62 -0
  61. openinterp/builder/templates/_vocab_chart.html +88 -0
  62. openinterp/builder/templates/_wiring.html +44 -0
  63. openinterp/builder/templates/about.html +95 -0
  64. openinterp/builder/templates/agent.html +69 -0
  65. openinterp/builder/templates/agents.html +80 -0
  66. openinterp/builder/templates/base.html +103 -0
  67. openinterp/builder/templates/circuit.html +64 -0
  68. openinterp/builder/templates/circuits.html +46 -0
  69. openinterp/builder/templates/experiment.html +21 -0
  70. openinterp/builder/templates/hub.html +16 -0
  71. openinterp/builder/templates/index.html +387 -0
  72. openinterp/builder/templates/layer.html +69 -0
  73. openinterp/builder/templates/learned-baseline.html +44 -0
  74. openinterp/builder/templates/model.html +151 -0
  75. openinterp/builder/templates/models.html +47 -0
  76. openinterp/builder/templates/neuron.html +234 -0
  77. openinterp/builder/templates/paper.html +39 -0
  78. openinterp/builder/templates/redirect.html +1 -0
  79. openinterp/builder/templates/research.html +31 -0
  80. openinterp/builder/templates/user.html +54 -0
  81. openinterp/builder/templates/users.html +39 -0
  82. openinterp/builder/templates/wiki/experiment.md +271 -0
  83. openinterp/builder/templates/wiki/index.md +32 -0
  84. openinterp/builder/templates/wiki/neuron.md +30 -0
  85. openinterp/builder/templates/wiki/stop.md +36 -0
  86. openinterp/builder/tree.py +470 -0
  87. openinterp/builder/users.py +142 -0
  88. openinterp/builder/wiki.py +427 -0
  89. openinterp/checks.py +429 -0
  90. openinterp/cli.py +494 -0
  91. openinterp/cli_baseline.py +332 -0
  92. openinterp/cli_benchmark.py +300 -0
  93. openinterp/cli_build.py +128 -0
  94. openinterp/cli_check.py +51 -0
  95. openinterp/cli_concepts.py +76 -0
  96. openinterp/cli_corpus.py +252 -0
  97. openinterp/cli_costs.py +257 -0
  98. openinterp/cli_coverage.py +76 -0
  99. openinterp/cli_crossref.py +86 -0
  100. openinterp/cli_errata.py +142 -0
  101. openinterp/cli_evidence.py +495 -0
  102. openinterp/cli_evidence_store.py +93 -0
  103. openinterp/cli_explain.py +101 -0
  104. openinterp/cli_loop.py +125 -0
  105. openinterp/cli_memory.py +28 -0
  106. openinterp/cli_neuronpedia.py +175 -0
  107. openinterp/cli_pause.py +45 -0
  108. openinterp/cli_pole_effects.py +133 -0
  109. openinterp/cli_probing.py +119 -0
  110. openinterp/cli_registry.py +348 -0
  111. openinterp/cli_reproduce.py +446 -0
  112. openinterp/cli_review.py +302 -0
  113. openinterp/cli_scan.py +223 -0
  114. openinterp/cli_score.py +446 -0
  115. openinterp/cli_sisters.py +86 -0
  116. openinterp/cli_submission.py +217 -0
  117. openinterp/cli_superpositions.py +118 -0
  118. openinterp/cli_wiring.py +54 -0
  119. openinterp/codeid.py +73 -0
  120. openinterp/concepts.py +400 -0
  121. openinterp/core/__init__.py +59 -0
  122. openinterp/core/agent_names.py +76 -0
  123. openinterp/core/attribution.py +101 -0
  124. openinterp/core/cache_dirs.py +15 -0
  125. openinterp/core/causal.py +82 -0
  126. openinterp/core/confounds.py +103 -0
  127. openinterp/core/contributors.py +26 -0
  128. openinterp/core/corpora.py +126 -0
  129. openinterp/core/costs.py +430 -0
  130. openinterp/core/draws.py +109 -0
  131. openinterp/core/errata.py +244 -0
  132. openinterp/core/evidence.py +160 -0
  133. openinterp/core/examples.py +93 -0
  134. openinterp/core/exceptions.py +29 -0
  135. openinterp/core/explanation.py +130 -0
  136. openinterp/core/fence.py +42 -0
  137. openinterp/core/heavy_lock.py +114 -0
  138. openinterp/core/identifiers.py +198 -0
  139. openinterp/core/isolation.py +149 -0
  140. openinterp/core/ledger.py +130 -0
  141. openinterp/core/licenses.py +28 -0
  142. openinterp/core/manifold.py +117 -0
  143. openinterp/core/partners.py +32 -0
  144. openinterp/core/patchscope.py +59 -0
  145. openinterp/core/paths.py +40 -0
  146. openinterp/core/pause.py +96 -0
  147. openinterp/core/pricing.py +178 -0
  148. openinterp/core/prior_art.py +44 -0
  149. openinterp/core/probing.py +146 -0
  150. openinterp/core/profiles.py +221 -0
  151. openinterp/core/project.py +28 -0
  152. openinterp/core/run_environment.py +38 -0
  153. openinterp/core/schemas.py +484 -0
  154. openinterp/core/specificity.py +49 -0
  155. openinterp/core/stats.py +172 -0
  156. openinterp/core/steering.py +49 -0
  157. openinterp/core/strict.py +19 -0
  158. openinterp/core/takedowns.py +136 -0
  159. openinterp/core/transcripts.py +130 -0
  160. openinterp/core/wiring.py +80 -0
  161. openinterp/corpus.py +476 -0
  162. openinterp/corpus_cache.py +145 -0
  163. openinterp/corpus_layouts.py +108 -0
  164. openinterp/corpus_presets.py +168 -0
  165. openinterp/corpus_rebuild.py +93 -0
  166. openinterp/coverage.py +155 -0
  167. openinterp/crossref.py +442 -0
  168. openinterp/data/__init__.py +9 -0
  169. openinterp/data/offensive_words.txt +58 -0
  170. openinterp/data/public_suffix_list.dat +15771 -0
  171. openinterp/data/sample_corpus.txt +40 -0
  172. openinterp/draw_steps.py +359 -0
  173. openinterp/errata_checks.py +129 -0
  174. openinterp/evidence.py +462 -0
  175. openinterp/evidence_branch.py +119 -0
  176. openinterp/evidence_lenses.py +75 -0
  177. openinterp/experiment_loop.py +357 -0
  178. openinterp/explain.py +252 -0
  179. openinterp/half_precision.py +79 -0
  180. openinterp/harness/__init__.py +37 -0
  181. openinterp/harness/ane/__init__.py +9 -0
  182. openinterp/harness/ane/backend.py +324 -0
  183. openinterp/harness/ane/cache.py +276 -0
  184. openinterp/harness/ane/chunks.py +163 -0
  185. openinterp/harness/ane/convert.py +116 -0
  186. openinterp/harness/ane/forward.py +258 -0
  187. openinterp/harness/ane/qualified.py +40 -0
  188. openinterp/harness/annotations.py +329 -0
  189. openinterp/harness/attribution.py +96 -0
  190. openinterp/harness/causal.py +264 -0
  191. openinterp/harness/confounds.py +184 -0
  192. openinterp/harness/covariance.py +301 -0
  193. openinterp/harness/crossref.py +195 -0
  194. openinterp/harness/cuda_devices.py +88 -0
  195. openinterp/harness/dead.py +124 -0
  196. openinterp/harness/devices.py +401 -0
  197. openinterp/harness/dictionaries.py +496 -0
  198. openinterp/harness/dreaming.py +116 -0
  199. openinterp/harness/environment.py +64 -0
  200. openinterp/harness/explanation_scores.py +371 -0
  201. openinterp/harness/features.py +276 -0
  202. openinterp/harness/first_position.py +137 -0
  203. openinterp/harness/heads.py +226 -0
  204. openinterp/harness/hooks.py +499 -0
  205. openinterp/harness/input_attribution.py +367 -0
  206. openinterp/harness/jlens.py +183 -0
  207. openinterp/harness/layer_cache.py +301 -0
  208. openinterp/harness/layer_major.py +274 -0
  209. openinterp/harness/lookahead.py +93 -0
  210. openinterp/harness/manifold.py +200 -0
  211. openinterp/harness/memory.py +471 -0
  212. openinterp/harness/moe.py +134 -0
  213. openinterp/harness/patcher.py +322 -0
  214. openinterp/harness/patchscope.py +177 -0
  215. openinterp/harness/probing.py +383 -0
  216. openinterp/harness/scan.py +462 -0
  217. openinterp/harness/scan_backend.py +201 -0
  218. openinterp/harness/sister_passes.py +463 -0
  219. openinterp/harness/sisters.py +363 -0
  220. openinterp/harness/sites.py +101 -0
  221. openinterp/harness/specificity.py +107 -0
  222. openinterp/harness/steering.py +159 -0
  223. openinterp/harness/superpositions.py +116 -0
  224. openinterp/harness/units.py +203 -0
  225. openinterp/harness/wiring.py +432 -0
  226. openinterp/harness/wiring_activations.py +285 -0
  227. openinterp/langid.py +73 -0
  228. openinterp/names.py +129 -0
  229. openinterp/neuronpedia_baseline.py +88 -0
  230. openinterp/neuronpedia_review.py +148 -0
  231. openinterp/planner.py +466 -0
  232. openinterp/planner_effects.py +89 -0
  233. openinterp/planner_heads.py +92 -0
  234. openinterp/planner_models.py +174 -0
  235. openinterp/planner_sisters.py +122 -0
  236. openinterp/planner_states.py +57 -0
  237. openinterp/planner_superpositions.py +114 -0
  238. openinterp/pole_effects.py +102 -0
  239. openinterp/prior_art.py +143 -0
  240. openinterp/reproduce.py +450 -0
  241. openinterp/revision.py +45 -0
  242. openinterp/runner_cache.py +35 -0
  243. openinterp/score_controls.py +169 -0
  244. openinterp/sensitive.py +243 -0
  245. openinterp/show.py +236 -0
  246. openinterp/signing.py +209 -0
  247. openinterp/sisters.py +476 -0
  248. openinterp/superposition_finders.py +225 -0
  249. openinterp/sync/__init__.py +5 -0
  250. openinterp/sync/r2.py +280 -0
  251. openinterp/syntax.py +399 -0
  252. openinterp/takedown_checks.py +76 -0
  253. openinterp/toxicity.py +158 -0
  254. openinterp/verdict_regression.py +79 -0
  255. openinterp/wiring_backfill.py +170 -0
  256. openinterp/wordnet.py +362 -0
@@ -0,0 +1,102 @@
1
+ """Each MLP pole's effect on the loss, estimated by attribution patching (issue #3486).
2
+
3
+ Mean-ablating one pole replaces its part of the activation with that part's mean over the
4
+ fit windows. The part is the positive part or the negative part. This is the ablation that
5
+ ``openinterp/coverage.py`` applies to every pole at once. Measuring it pole by pole costs
6
+ one forward pass per pole: 73,728 for GPT-2 small. To first order, the change in mean
7
+ loss is the sum over tokens of ``dL/dh × (mean − part)``. One backward pass per batch
8
+ gives it for every pole of every layer. :func:`exact_effect` measures single poles, to
9
+ check the estimate.
10
+
11
+ The planner draws some explore units in proportion to this effect (``planner_effects``).
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from collections.abc import Sequence
17
+
18
+ import torch
19
+
20
+ from openinterp.coverage import mean_loss, pole_means
21
+ from openinterp.harness.attribution import _corrupted_grads
22
+ from openinterp.harness.hooks import NeuronHookRunner
23
+
24
+ POLES = ("positive", "negative")
25
+
26
+
27
+ def _loss_sum(ids: torch.Tensor):
28
+ """A metric for ``_corrupted_grads``: the summed next-token cross-entropy on ``ids``."""
29
+
30
+ def metric(logits: torch.Tensor) -> torch.Tensor:
31
+ target = ids[:, 1:].to(logits.device).flatten()
32
+ return torch.nn.functional.cross_entropy(
33
+ logits[:, :-1].flatten(0, 1).float(), target, reduction="sum"
34
+ )
35
+
36
+ return metric
37
+
38
+
39
+ def estimate(
40
+ runner: NeuronHookRunner,
41
+ fit: torch.Tensor,
42
+ test: torch.Tensor,
43
+ layers: Sequence[int],
44
+ batch: int,
45
+ ) -> dict[tuple[int, str], torch.Tensor]:
46
+ """``{(layer, pole): tensor[width]}``: each pole's estimated rise in mean loss on ``test``."""
47
+ means = {layer: pole_means(runner, fit, layer, batch) for layer in layers}
48
+ totals: dict[tuple[int, str], torch.Tensor] = {}
49
+ for chunk in test.split(batch):
50
+ acts, grads = _corrupted_grads(runner, chunk, list(layers), _loss_sum(chunk))
51
+ for layer, act, grad in zip(layers, acts, grads, strict=True):
52
+ act, grad = act.detach().float(), grad.float()
53
+ m_pos, m_neg = (m.to(act.device) for m in means[layer])
54
+ parts = {"positive": (m_pos - act.clamp(min=0)), "negative": (m_neg - act.clamp(max=0))}
55
+ for pole, diff in parts.items():
56
+ value = (diff * grad).sum(dim=(0, 1)).cpu()
57
+ key = (layer, pole)
58
+ totals[key] = value if key not in totals else totals[key] + value
59
+ tokens = test[:, 1:].numel()
60
+ return {key: value / tokens for key, value in totals.items()}
61
+
62
+
63
+ def single_pole(means, idx: int, pole: str):
64
+ """An intervention that mean-ablates only pole ``pole`` of neuron ``idx``."""
65
+ m_pos, m_neg = means
66
+
67
+ def intervene(act: torch.Tensor) -> torch.Tensor:
68
+ out = act.clone()
69
+ column = act[..., idx]
70
+ if pole == "positive":
71
+ out[..., idx] = m_pos[idx].to(act.device, act.dtype) + column.clamp(max=0)
72
+ else:
73
+ out[..., idx] = column.clamp(min=0) + m_neg[idx].to(act.device, act.dtype)
74
+ return out
75
+
76
+ return intervene
77
+
78
+
79
+ def exact_effect(
80
+ runner: NeuronHookRunner,
81
+ fit: torch.Tensor,
82
+ test: torch.Tensor,
83
+ layer: int,
84
+ idx: int,
85
+ pole: str,
86
+ clean: float,
87
+ batch: int,
88
+ ) -> float:
89
+ """The measured rise in mean loss on ``test`` when one pole is mean-ablated."""
90
+ means = pole_means(runner, fit, layer, batch)
91
+ return mean_loss(runner, test, batch, {layer: single_pole(means, idx, pole)}) - clean
92
+
93
+
94
+ def top_poles(effects: dict[tuple[int, str], torch.Tensor], k: int) -> list[dict]:
95
+ """The ``k`` poles with the largest estimated effect, largest first."""
96
+ rows = [
97
+ (float(v), layer, idx, pole)
98
+ for (layer, pole), values in effects.items()
99
+ for idx, v in enumerate(values.tolist())
100
+ ]
101
+ rows.sort(reverse=True)
102
+ return [{"layer": la, "neuron": i, "pole": p, "effect": v} for v, la, i, p in rows[:k]]
@@ -0,0 +1,143 @@
1
+ """Find prior art for a unit after the proposer answers (`open-interpretability audit`).
2
+
3
+ A dictionary latent that Neuronpedia hosts is looked up directly. A neuron pole is
4
+ aligned with each dictionary preset that Neuronpedia hosts at its layer
5
+ (:func:`openinterp.harness.crossref.align_layer`). Rows from ``open-interpretability crossref`` are
6
+ used where they exist. The latents above chance on each side are then looked up, the best
7
+ ``TOP`` first.
8
+
9
+ Lookups go through the cached, polite fetch of :mod:`openinterp.crossref`. A failed
10
+ lookup or alignment is left out, so prior art never stops an audit.
11
+ ``$OPENINTERP_PRIOR_ART=0`` turns the readout off.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import os
17
+ from collections.abc import Callable
18
+
19
+ from openinterp.core.fence import fence
20
+ from openinterp.core.prior_art import PriorArt
21
+
22
+ TOP = 3 # latents per side of a pole
23
+ ENV = "OPENINTERP_PRIOR_ART"
24
+
25
+
26
+ def enabled() -> bool:
27
+ """False when ``$OPENINTERP_PRIOR_ART`` turns the readout off."""
28
+ return os.environ.get(ENV, "1").lower() not in ("", "0", "off", "false")
29
+
30
+
31
+ def find_prior_art(submission, runner, *, rows: dict | None = None, fetch: Callable | None = None):
32
+ """Return the unit's prior art; ``None`` for a unit kind with none, or when turned off.
33
+
34
+ ``rows`` are crossref rows by slug (:func:`openinterp.crossref.load_rows`). ``fetch``
35
+ returns a latent's explanations (default :func:`openinterp.crossref.latent_explanations`).
36
+ """
37
+ from openinterp.crossref import latent_explanations
38
+
39
+ if not enabled():
40
+ return None
41
+ fetch = fetch or latent_explanations
42
+ ident = submission.identifier
43
+ if ident.kind == "dictionary_feature":
44
+ entry = _entry(fetch, ident.dictionary, ident.layer, ident.feature_idx, "self", None)
45
+ return [entry] if entry else []
46
+ if ident.kind != "mlp_neuron_pole":
47
+ return None
48
+ found = []
49
+ for name, side, matches in _aligned(ident, runner, rows):
50
+ for feature_idx, cosine in matches:
51
+ entry = _entry(fetch, name, ident.layer, feature_idx, side, cosine)
52
+ if entry:
53
+ found.append(entry)
54
+ return found
55
+
56
+
57
+ def _entry(fetch, dictionary: str, layer: int, feature_idx: int, relation: str, cosine):
58
+ """The latent's first machine-written explanation, else its first; ``None`` if none."""
59
+ from openinterp.crossref import latent_url, machine_explanation
60
+
61
+ url = latent_url(dictionary, layer, feature_idx)
62
+ if url is None:
63
+ return None
64
+ explanations = fetch(dictionary, layer, feature_idx) or []
65
+ best = machine_explanation(explanations) or next(iter(explanations), None)
66
+ if best is None:
67
+ return None
68
+ return PriorArt(
69
+ dictionary=dictionary,
70
+ feature_idx=feature_idx,
71
+ relation=relation,
72
+ cosine=cosine,
73
+ description=best.description,
74
+ explainer=best.explainer,
75
+ url=url,
76
+ )
77
+
78
+
79
+ def _aligned(ident, runner, rows: dict | None):
80
+ """Yield ``(dictionary, side, [(feature_idx, cosine)])`` for each hosted dictionary."""
81
+ from openinterp.crossref import dictionaries_for, load_rows
82
+ from openinterp.harness.dictionaries import DICTIONARIES
83
+
84
+ rows = load_rows() if rows is None else rows
85
+ known = {r.dictionary: r for r in rows.get(ident.slug, [])}
86
+ for name in dictionaries_for(ident.model_name, ident.layer):
87
+ if not DICTIONARIES[name].neuronpedia_source(ident.layer):
88
+ continue
89
+ sides = _sides(known[name]) if name in known else _align(runner, name, ident)
90
+ for side, (matches, chance) in sides.items():
91
+ above = [m for m in matches if abs(m[1]) > chance]
92
+ yield name, side, sorted(above, key=lambda m: -abs(m[1]))[:TOP]
93
+
94
+
95
+ def _sides(row) -> dict:
96
+ """``{side: ([(feature_idx, cosine)], chance)}`` of a stored crossref row."""
97
+ out = {}
98
+ for side in ("writes", "reads"):
99
+ s = getattr(row, side)
100
+ if s is not None:
101
+ out[side] = ([(m.feature_idx, m.cosine) for m in s.matches], s.chance)
102
+ return out
103
+
104
+
105
+ def _align(runner, name: str, ident) -> dict:
106
+ """Align the pole with dictionary ``name`` now; empty if that fails."""
107
+ from openinterp.core.exceptions import ArchitectureNotSupportedError
108
+ from openinterp.harness.crossref import align_layer
109
+ from openinterp.harness.dictionaries import dictionary_spec
110
+
111
+ try:
112
+ found = align_layer(
113
+ runner, dictionary_spec(name), ident.layer, [(ident.neuron_idx, ident.pole)]
114
+ )
115
+ except (ArchitectureNotSupportedError, OSError, ValueError):
116
+ return {}
117
+ write, read = found[(ident.neuron_idx, ident.pole)]
118
+ out = {"writes": ([(m.feature_idx, m.cosine) for m in write.matches], write.chance)}
119
+ if read is not None:
120
+ out["reads"] = ([(m.feature_idx, m.cosine) for m in read.matches], read.chance)
121
+ return out
122
+
123
+
124
+ def format_for_reviewer(prior_art: list[PriorArt] | None) -> str:
125
+ """The prior art as a block of the reviewer's prompt; empty if there is none."""
126
+ if not prior_art:
127
+ return ""
128
+ lines = []
129
+ for p in prior_art:
130
+ where = (
131
+ "this latent itself"
132
+ if p.relation == "self"
133
+ else f"{p.dictionary} latent {p.feature_idx}, which {p.relation} along this pole "
134
+ f"(cosine {p.cosine:+.2f})"
135
+ )
136
+ lines.append(f"- {where}: {p.description} [{p.source}, {p.explainer}]")
137
+ return (
138
+ "Prior art: published explanations of this unit or of latents aligned with it. They "
139
+ "are other people's claims, not measurements, and the proposer did not see them. "
140
+ "Where one disagrees with the hypothesis, write probes that tell them apart.\n"
141
+ + fence("\n".join(lines))
142
+ + "\n"
143
+ )