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,108 @@
1
+ """How a dataset's rows become documents, for the layouts of ``open-interpretability corpus``."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from collections.abc import Sequence
7
+ from itertools import pairwise
8
+
9
+ # WikiText's top-level heading row (" = Title = \n") starts an article; sections are
10
+ # " = = Section = = \n". Its text is tokenised: "1 @,@ 000", "well @-@ known", "lobster ,".
11
+ _WIKITEXT_TITLE = re.compile(r" = [^=].* = \n?")
12
+ _WIKITEXT_HEADING = re.compile(r"(?:= )+(.*?)(?: =)+")
13
+ _WIKITEXT_DETOKENIZE = [
14
+ (re.compile(r" @(.)@ "), r"\1"), # number separators and hyphens
15
+ (re.compile(r" ([.,;:!?%)\]}])"), r"\1"),
16
+ (re.compile(r"([(\[{$]) "), r"\1"),
17
+ (re.compile(r" (n't|'s|'re|'ve|'m|'ll|'d)\b"), r"\1"),
18
+ (re.compile(r" ' s\b"), "'s"), # possessive after punctuation: "Inc. ' s"
19
+ (re.compile(r"s ' "), "s' "), # plural possessive
20
+ (re.compile(r'" ([^"]*?) "'), r'"\1"'),
21
+ ]
22
+
23
+
24
+ def wikitext_articles(rows: Sequence[str | None]) -> list[str]:
25
+ """Return the WikiText articles that start and end within ``rows``, detokenised.
26
+
27
+ Headings become plain lines, and blank lines separate paragraphs. An article cut by
28
+ the start or end of ``rows`` (a row group's edge) is dropped, not truncated.
29
+ """
30
+ starts = [i for i, row in enumerate(rows) if _WIKITEXT_TITLE.fullmatch(row or "")]
31
+ return [_wikitext_article(rows[a:b]) for a, b in pairwise(starts)]
32
+
33
+
34
+ def _wikitext_article(rows: Sequence[str | None]) -> str:
35
+ paragraphs = []
36
+ for row in rows:
37
+ text = (row or "").strip()
38
+ if heading := _WIKITEXT_HEADING.fullmatch(text):
39
+ text = heading.group(1)
40
+ for pattern, repl in _WIKITEXT_DETOKENIZE:
41
+ text = pattern.sub(repl, text)
42
+ if text:
43
+ paragraphs.append(text)
44
+ return "\n\n".join(paragraphs)
45
+
46
+
47
+ # The parts of a book that are left out: its first 10% and its last 5%. They hold its front
48
+ # matter (producer credits, title page, contents, preface) and back matter (notes, index).
49
+ BOOK_FRONT = 0.10
50
+ BOOK_BACK = 0.05
51
+ PASSAGE_CHARS = (
52
+ 3000 # a passage's length, at most: the default of `open-interpretability corpus --max-chars`
53
+ )
54
+ _PARAGRAPH_BREAK = re.compile(r"\n[ \t]*\n")
55
+ _LINE_BREAK = re.compile(r"[ \t]*\n[ \t]*")
56
+
57
+
58
+ def book_passages(rows: Sequence[str | None]) -> list[str]:
59
+ """Return passages from the body of each book in ``rows``, in order.
60
+
61
+ A book is one row, often hundreds of thousands of characters. Cut to its start, it
62
+ would give only front matter. So each book loses its first :data:`BOOK_FRONT` and last
63
+ :data:`BOOK_BACK`. The paragraphs at those cuts can be partial, so they are dropped
64
+ too. The paragraphs left join into passages of at most :data:`PASSAGE_CHARS`, which
65
+ always start and end at a paragraph break. A longer paragraph is a passage alone. The
66
+ books' lines are wrapped at about 70 characters, so a line break within a paragraph
67
+ becomes a space. Verse loses its line breaks too.
68
+ """
69
+ passages = []
70
+ for book in rows:
71
+ text = book or ""
72
+ body = text[int(len(text) * BOOK_FRONT) : len(text) - int(len(text) * BOOK_BACK)]
73
+ paragraphs = [_LINE_BREAK.sub(" ", p).strip() for p in _PARAGRAPH_BREAK.split(body)]
74
+ passage: list[str] = []
75
+ for paragraph in filter(None, paragraphs[1:-1]):
76
+ if passage and len("\n\n".join([*passage, paragraph])) > PASSAGE_CHARS:
77
+ passages.append("\n\n".join(passage))
78
+ passage = []
79
+ passage.append(paragraph)
80
+ if passage:
81
+ passages.append("\n\n".join(passage))
82
+ return passages
83
+
84
+
85
+ # Row layouts other than one document per row: rows of one row group -> its documents.
86
+ LAYOUTS = {"wikitext": wikitext_articles, "book_passages": book_passages}
87
+
88
+
89
+ def chat_formatter(model: str, revision: str | None = None):
90
+ """Return a function that writes a conversation with ``model``'s chat template.
91
+
92
+ Also returns the model commit that the template comes from. A conversation is a list
93
+ of ``{"role": ..., "content": ...}`` messages. The text keeps the template's special
94
+ tokens as text (e.g. ``<|im_start|>``), which the tokenizer reads back as the same
95
+ tokens. Raises ``ValueError`` if the model's tokenizer has no chat template.
96
+ """
97
+ from huggingface_hub import HfApi
98
+ from transformers import AutoTokenizer
99
+
100
+ revision = HfApi().model_info(model, revision=revision).sha
101
+ tokenizer = AutoTokenizer.from_pretrained(model, revision=revision)
102
+ if not tokenizer.chat_template:
103
+ raise ValueError(f"{model}'s tokenizer has no chat template")
104
+
105
+ def render(messages) -> str:
106
+ return tokenizer.apply_chat_template(list(messages or []), tokenize=False)
107
+
108
+ return render, revision
@@ -0,0 +1,168 @@
1
+ """The datasets that the corpus command can sample by name, and how to read them.
2
+
3
+ The command is ``open-interpretability corpus --dataset``.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from dataclasses import dataclass
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class Preset:
13
+ """A dataset that ``open-interpretability corpus --dataset`` can sample, and how to read it."""
14
+
15
+ dataset: str
16
+ files: str # glob within the dataset repo; comma-separated globs are sampled equally
17
+ text_column: str = "text" # comma-separated columns join into one document
18
+ revision: str | None = None # default branch or tag; pinned to a commit on use
19
+ layout: str | None = None # how rows become documents (see LAYOUTS); None: one per row
20
+ docs_per_row_group: int = 20
21
+ # A regular expression whose group, matched in each file glob, is its documents' `lang`.
22
+ lang_pattern: str | None = None
23
+ # The dataset column that gives each document's programming language (`code_language`).
24
+ code_language_column: str | None = None
25
+ # A model ID: `text_column` holds conversations, and this model's chat template writes
26
+ # each as text (unless `open-interpretability corpus --chat-template` names another model).
27
+ chat_template: str | None = None
28
+
29
+
30
+ WIKIPEDIA_LANGUAGES = ("en", "de", "fr", "ru", "el", "ar", "hi", "zh", "ja", "ko")
31
+
32
+ # The languages of the `fineweb-2` preset. A key is FineWeb-2's folder name: an ISO 639-3
33
+ # code and a script. Its value is the code that a document's `lang` records. Wikipedia's
34
+ # folders and lid.176 (openinterp.langid) use the same codes. So one language has one
35
+ # label in a blended corpus. Add entries, never change one: a corpus rebuilt from its
36
+ # sidecar reads this table. FineWeb-2 has no English (that is FineWeb). The set spans 12
37
+ # scripts. Some pairs share a script (Spanish and Portuguese, Russian and Ukrainian,
38
+ # Arabic and Persian). The reviewer uses such pairs for hard negatives.
39
+ FINEWEB2_LANGUAGES = {
40
+ "spa_Latn": "es", "por_Latn": "pt", "ita_Latn": "it", "nld_Latn": "nl",
41
+ "pol_Latn": "pl", "tur_Latn": "tr", "vie_Latn": "vi", "ind_Latn": "id",
42
+ "swh_Latn": "sw", "rus_Cyrl": "ru", "ukr_Cyrl": "uk", "arb_Arab": "ar",
43
+ "fas_Arab": "fa", "cmn_Hani": "zh", "jpn_Jpan": "ja", "kor_Hang": "ko",
44
+ "hin_Deva": "hi", "ben_Beng": "bn", "tha_Thai": "th", "heb_Hebr": "he",
45
+ "ell_Grek": "el", "tam_Taml": "ta",
46
+ } # fmt: skip
47
+
48
+ # The values of the github-code preset's `language` column (the dataset card's list).
49
+ # `open-interpretability corpus --detect-code` (openinterp.codeid) gives only these names too,
50
+ # so one programming language has one label in exact and detected corpora.
51
+ GITHUB_CODE_LANGUAGES = (
52
+ "Assembly", "Batchfile", "C", "C#", "C++", "CMake", "CSS", "Dockerfile", "FORTRAN",
53
+ "GO", "HTML", "Haskell", "Java", "JavaScript", "Julia", "Lua", "Makefile", "Markdown",
54
+ "PHP", "Perl", "PowerShell", "Python", "Ruby", "Rust", "SQL", "Scala", "Shell", "TeX",
55
+ "TypeScript", "Visual Basic",
56
+ ) # fmt: skip
57
+
58
+ # Licences of datasets whose cards state none: copies of a dataset, with its licence.
59
+ UNSTATED_LICENSES = {"emozilla/pg19": "apache-2.0"} # deepmind/pg19's card
60
+
61
+ PRESETS = {
62
+ # Clean, educational web text (FineWeb filtered by an educational-quality classifier),
63
+ # closer to recent models' pretraining mix than the Pile. ODC-By: attribute excerpts.
64
+ "fineweb-edu": Preset("HuggingFaceFW/fineweb-edu", "sample/10BT/*.parquet"),
65
+ # The first 10k Pile documents; the registry's original corpus.
66
+ "pile-10k": Preset("NeelNanda/pile-10k", "data/*.parquet"),
67
+ # The Pile without Books3, BookCorpus2, OpenSubtitles, YTSubtitles and OWT2: ~90x
68
+ # pile-10k's documents, for rare concepts. The main branch is zstd JSONL, so read the
69
+ # Hub's Parquet conversion (its first 891,748 training documents). Licence "other": each
70
+ # Pile component keeps its own terms.
71
+ "pile-uncopyrighted": Preset(
72
+ "monology/pile-uncopyrighted",
73
+ "default/partial-train/*.parquet",
74
+ revision="refs/convert/parquet",
75
+ ),
76
+ # C4 English validation split: the standard web baseline of the SAE literature. The
77
+ # main branch is gzipped JSON, so read the Hub's Parquet conversion. ODC-By.
78
+ "c4": Preset("allenai/c4", "en/partial-validation/*.parquet", revision="refs/convert/parquet"),
79
+ # Synthetic short stories written with a 3-4 year-old's vocabulary: a low-confound control
80
+ # corpus for small models. Parquet is on the main branch. CDLA-Sharing-1.0: share excerpts
81
+ # under the same licence.
82
+ "tinystories": Preset("roneneldan/TinyStories", "data/train-*.parquet"),
83
+ # WikiText-103: 28,475 Good and Featured Wikipedia articles, long and clean, for windows
84
+ # of 512+ tokens and entity/fact neurons. One row per paragraph or heading, so rows are
85
+ # joined back into articles. CC BY-SA 3.0 and GFDL: attribute and share excerpts alike.
86
+ "wikitext": Preset(
87
+ "Salesforce/wikitext", "wikitext-103-raw-v1/train-*.parquet", layout="wikitext"
88
+ ),
89
+ # GitHub source files in 32 languages, deduplicated and filtered, for syntax, bracket
90
+ # and indentation neurons. This is codeparrot/github-code without autogenerated, very
91
+ # long-lined or mostly non-alphanumeric files. Row groups are ~26 MB of 10,000 files from ~9,000
92
+ # repos in shuffled order, so take 100 from each. The card says Apache-2.0, but each
93
+ # file keeps its repo's licence (its `license` column: mostly MIT and GPL).
94
+ "github-code": Preset(
95
+ "codeparrot/github-code-clean",
96
+ "data/train-*.parquet",
97
+ "code",
98
+ docs_per_row_group=100,
99
+ code_language_column="language",
100
+ ),
101
+ # Wikipedia (November 2023 dump) in ten languages and eight scripts, with an equal share
102
+ # of documents each, for language-identity and script neurons. Latin script: English,
103
+ # German and French. Other scripts: Russian (Cyrillic), Greek, Arabic, Hindi
104
+ # (Devanagari), Chinese (Han), Japanese (kana and kanji) and Korean (Hangul). Row groups
105
+ # are 1,000 articles of 10-22 MB, so take 50 from each: 500 documents read one row group
106
+ # per language. CC BY-SA 3.0 and GFDL: attribute and share excerpts alike.
107
+ "wikipedia-multilingual": Preset(
108
+ "wikimedia/wikipedia",
109
+ ",".join(f"20231101.{lang}/train-*.parquet" for lang in WIKIPEDIA_LANGUAGES),
110
+ docs_per_row_group=50,
111
+ lang_pattern=r"^\d+\.([\w-]+)/",
112
+ ),
113
+ # Web text in 22 languages besides English (FINEWEB2_LANGUAGES), with an equal share of
114
+ # documents each. It is FineWeb's pipeline run on Common Crawl for other languages. It
115
+ # gives the blend non-English text in more registers than encyclopedic Wikipedia. Only
116
+ # each language's first file is read. It holds 1.2M-4M documents from many crawls, in
117
+ # row groups of 1,000 documents (~3.6 MB) from one crawl each. Reading the footers of
118
+ # every file (~2,000) took 10 minutes. ODC-By 1.0: attribute excerpts. The card also
119
+ # requires users to obey the Common Crawl terms of use.
120
+ "fineweb-2": Preset(
121
+ "HuggingFaceFW/fineweb-2",
122
+ ",".join(f"data/{folder}/train/000_00000.parquet" for folder in FINEWEB2_LANGUAGES),
123
+ lang_pattern=r"^data/([a-z]{3}_[A-Z][a-z]{3})/",
124
+ ),
125
+ # Scientific papers from Semantic Scholar (peS2o v2), for academic prose, citations and
126
+ # section structure. Half the documents are abstracts with their titles (`s2ag`). Half
127
+ # are full texts (`s2orc`): title, abstract, then the body with its section headings
128
+ # and in-text citations. The main branch is gzipped JSON, so read the Hub's Parquet
129
+ # conversion. Its train split holds only abstracts, so full texts come from its
130
+ # validation split: 3 files of 41,032 papers, all full texts. Row groups are
131
+ # 1,000 documents (~1 MB of abstracts, ~32 MB of full texts). ODC-By 1.0: attribute
132
+ # excerpts.
133
+ "pes2o": Preset(
134
+ "allenai/peS2o",
135
+ "v2/partial-train/*.parquet,v2/partial-validation/000[1-3].parquet",
136
+ revision="refs/convert/parquet",
137
+ ),
138
+ # Mathematical web text from Common Crawl, with LaTeX, for number, operator and LaTeX
139
+ # neurons. Row groups are 1,000 documents of ~3.6 MB. ODC-By 1.0: attribute excerpts.
140
+ # The card also requires users to obey the Common Crawl terms of use.
141
+ "open-web-math": Preset("open-web-math/open-web-math", "data/train-*.parquet"),
142
+ # Grade-school word problems and their worked solutions (train split), for arithmetic
143
+ # neurons. A document is a problem, then its solution. A solution keeps the dataset's
144
+ # calculator annotations ("<<48/2=24>>24") and final answer ("#### 72"). MIT.
145
+ "gsm8k": Preset("openai/gsm8k", "main/train-*.parquet", "question,answer"),
146
+ # Multi-turn conversations between a user and ChatGPT (the SFT train split of UltraChat,
147
+ # filtered and truecased), for instruct models. Qwen2.5's chat template writes each, so
148
+ # every model scans the same text (`--chat-template MODEL` writes it with another
149
+ # model's). Row groups are 1,000 conversations of ~6.5 MB. MIT.
150
+ # Project Gutenberg books published before 1919 (PG-19), for narrative, dialogue and
151
+ # older English. deepmind/pg19 has only a loading script, so read emozilla/pg19, its
152
+ # Parquet copy. Only its validation and test splits: 150 books in ~35 MB. A training
153
+ # row group is 1,000 books (~380 MB). Each book is one row, of ~270,000 characters
154
+ # (median), so the book_passages layout gives passages from its body
155
+ # (openinterp.corpus_layouts). Public-domain texts; PG-19 is Apache-2.0
156
+ # (UNSTATED_LICENSES).
157
+ "pg19": Preset(
158
+ "emozilla/pg19",
159
+ "data/validation-*.parquet,data/test-*.parquet",
160
+ layout="book_passages",
161
+ ),
162
+ "ultrachat": Preset(
163
+ "HuggingFaceH4/ultrachat_200k",
164
+ "data/train_sft-*.parquet",
165
+ "messages",
166
+ chat_template="Qwen/Qwen2.5-0.5B",
167
+ ),
168
+ }
@@ -0,0 +1,93 @@
1
+ """Rebuild the corpus that a record scanned (`open-interpretability corpus --same-as`).
2
+
3
+ A follow-up review draws held-out windows from the scan's corpus (`open-interpretability score`).
4
+ Corpora are git-ignored, and a scan can read a corpus that only one experiment used. So
5
+ the record's source is what brings the same bytes back.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from pathlib import Path
11
+
12
+ from openinterp.blends import build_blend
13
+ from openinterp.core.schemas import COMMITTED_SOURCES, CorpusSource
14
+ from openinterp.corpus import build_from_hub, load_sources, source_path
15
+
16
+ COMMITTED_SIDECARS = tuple(Path(name) for name in COMMITTED_SOURCES)
17
+
18
+
19
+ def scanned_source(submission) -> CorpusSource:
20
+ """Return the source of the corpus that ``submission`` scanned.
21
+
22
+ A record without its own source can match a committed sidecar by SHA-256. Raises
23
+ ``ValueError`` if neither says how to rebuild the corpus.
24
+ """
25
+ provenance = submission.provenance
26
+ if provenance is None:
27
+ raise ValueError("the record has no provenance, so it names no corpus")
28
+ source = provenance.corpus_source or load_sources(COMMITTED_SIDECARS).get(
29
+ provenance.corpus_sha256
30
+ )
31
+ if source is None:
32
+ raise ValueError(
33
+ f"the record doesn't say how to rebuild its corpus ({provenance.corpus}, "
34
+ f"SHA-256 {provenance.corpus_sha256})"
35
+ )
36
+ return source
37
+
38
+
39
+ def rebuild(source: CorpusSource, out: Path) -> CorpusSource:
40
+ """Write the corpus that ``source`` describes to ``out``, at its pinned dataset commits.
41
+
42
+ Raises ``ValueError``, and leaves no corpus, if the bytes differ. A scan's held-out
43
+ windows must come from its own text, so a near match is not acceptable.
44
+ """
45
+ built = _build(source, out)
46
+ if built.sha256 != source.sha256:
47
+ out.unlink()
48
+ source_path(out).unlink()
49
+ raise ValueError(
50
+ f"the rebuilt corpus has SHA-256 {built.sha256}, but the record scanned "
51
+ f"{source.sha256}. Its dataset or preset may have changed."
52
+ )
53
+ return built
54
+
55
+
56
+ def _build(source: CorpusSource, out: Path) -> CorpusSource:
57
+ if source.blend:
58
+ return build_blend(
59
+ source.blend,
60
+ out,
61
+ documents=source.documents,
62
+ seed=source.seed,
63
+ max_chars=source.max_chars,
64
+ ascii=source.ascii,
65
+ revisions=_revisions(source.components or []),
66
+ )
67
+ return build_from_hub(
68
+ source.dataset,
69
+ out,
70
+ files=source.files,
71
+ text_column=source.text_column,
72
+ revision=source.revision,
73
+ documents=source.documents,
74
+ seed=source.seed,
75
+ max_chars=source.max_chars,
76
+ docs_per_row_group=source.docs_per_row_group,
77
+ layout=source.layout,
78
+ first=source.first,
79
+ ascii=source.ascii,
80
+ lang_pattern=source.lang_pattern,
81
+ detect_lang=source.lang_detector is not None,
82
+ code_language_column=source.code_language_column,
83
+ detect_code=source.code_detector is not None,
84
+ chat_template=source.chat_template,
85
+ chat_template_revision=source.chat_template_revision,
86
+ )
87
+
88
+
89
+ def _revisions(components: list[CorpusSource]) -> dict[str, str | None]:
90
+ """A blend's pinned commits: each dataset's, and each chat template model's."""
91
+ revisions = {part.dataset: part.revision for part in components}
92
+ templates = {p.chat_template: p.chat_template_revision for p in components if p.chat_template}
93
+ return revisions | templates
openinterp/coverage.py ADDED
@@ -0,0 +1,155 @@
1
+ """Registry coverage: how much of each MLP layer's effect on the loss the VERIFIED poles keep.
2
+
3
+ For one layer, mean-ablating every neuron raises the model's next-token loss. The rise
4
+ is what that layer contributes. The coverage of the layer is the share of that rise that
5
+ comes back when only the VERIFIED poles keep their real activations:
6
+
7
+ recovered = (loss_ablated - loss_kept) / (loss_ablated - loss_clean)
8
+
9
+ A neuron's activation has two poles: its positive part and its negative part. Each part is
10
+ replaced by its own mean over a separate sample of the corpus, unless that pole is
11
+ VERIFIED. So a neuron whose positive pole is VERIFIED keeps its positive part only.
12
+
13
+ The same number of poles, drawn at random from the layer's other poles, gives a baseline.
14
+ It is the mean over several draws.
15
+ VERIFIED poles that recover no more than random ones explain no more of the loss than
16
+ any poles would.
17
+
18
+ This is a running measure of how much of the model the registry explains. It is
19
+ reported, not gated. ``open-interpretability coverage`` writes one row per layer.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import random
25
+ from collections.abc import Sequence
26
+ from pathlib import Path
27
+
28
+ import torch
29
+
30
+ from openinterp.core.paths import DEFAULT_WIKI_DIR
31
+ from openinterp.harness.hooks import NeuronHookRunner
32
+
33
+ CE_CHUNK = 512 # token positions per cross-entropy chunk: Qwen2.5's vocabulary is 151,936
34
+
35
+
36
+ def verified_poles(model_name: str, wiki_dir: Path = DEFAULT_WIKI_DIR) -> dict[int, set]:
37
+ """The neuron poles that stand VERIFIED, as ``{layer: {(neuron_idx, pole)}}``.
38
+
39
+ Standing uses the site's rules, errata included (``builder/standing.py``).
40
+ """
41
+ from openinterp.builder.compiler import load_records
42
+ from openinterp.builder.tree import build_tree
43
+ from openinterp.core.errata import load_errata
44
+ from openinterp.core.paths import experiments_dir
45
+
46
+ records = load_records(experiments_dir(Path(wiki_dir)))
47
+ out: dict[int, set] = {}
48
+ for model in build_tree(records, load_errata(wiki_dir)):
49
+ if model.name != model_name:
50
+ continue
51
+ for layer in model.layers:
52
+ for neuron in layer.neurons:
53
+ for side, standing in neuron.standings.items():
54
+ if standing.verified:
55
+ out.setdefault(layer.layer, set()).add((neuron.idx, side))
56
+ return out
57
+
58
+
59
+ def windows(runner: NeuronHookRunner, texts: Sequence[str], count: int, tokens: int, seed: int):
60
+ """Two disjoint sets of ``count`` windows of ``tokens`` tokens: one to fit, one to test.
61
+
62
+ Each window is the start of one document long enough to fill it.
63
+ """
64
+ ids = [runner.tokenizer(t)["input_ids"] for t in texts]
65
+ starts = [row[:tokens] for row in ids if len(row) >= tokens]
66
+ random.Random(seed).shuffle(starts)
67
+ if len(starts) < 2 * count:
68
+ raise ValueError(f"only {len(starts)} documents have {tokens} tokens; need {2 * count}")
69
+ fit, test = starts[:count], starts[count : 2 * count]
70
+ return torch.tensor(fit), torch.tensor(test)
71
+
72
+
73
+ def pole_means(runner: NeuronHookRunner, ids: torch.Tensor, layer: int, batch: int):
74
+ """The mean positive part and mean negative part of each neuron's activation."""
75
+ pos = neg = None
76
+ for chunk in ids.split(batch):
77
+ act = runner.layer_activations(chunk, [layer])[layer].float().flatten(0, -2)
78
+ p, n = act.clamp(min=0).sum(0), act.clamp(max=0).sum(0)
79
+ pos, neg = (p, n) if pos is None else (pos + p, neg + n)
80
+ total = ids.numel()
81
+ return pos / total, neg / total
82
+
83
+
84
+ def keep_poles(means, keep: set, width: int):
85
+ """An intervention that mean-ablates every pole outside ``keep``."""
86
+ m_pos, m_neg = means
87
+ keep_pos = torch.zeros(width, dtype=torch.bool)
88
+ keep_neg = torch.zeros(width, dtype=torch.bool)
89
+ for idx, pole in keep:
90
+ (keep_pos if pole == "positive" else keep_neg)[idx] = True
91
+
92
+ def intervene(act: torch.Tensor) -> torch.Tensor:
93
+ dev = act.device
94
+ pos = torch.where(keep_pos.to(dev), act.clamp(min=0), m_pos.to(dev, act.dtype))
95
+ neg = torch.where(keep_neg.to(dev), act.clamp(max=0), m_neg.to(dev, act.dtype))
96
+ return pos + neg
97
+
98
+ return intervene
99
+
100
+
101
+ @torch.no_grad()
102
+ def mean_loss(runner: NeuronHookRunner, ids: torch.Tensor, batch: int, interventions=None):
103
+ """Mean next-token cross-entropy over ``ids``, with optional MLP interventions."""
104
+ total, count = 0.0, 0
105
+ for chunk in ids.split(batch):
106
+ chunk = chunk.to(runner.device)
107
+ logits = runner.run_with_interventions({"input_ids": chunk}, interventions)
108
+ flat = logits[:, :-1].flatten(0, 1)
109
+ target = chunk[:, 1:].flatten()
110
+ for lo in range(0, target.numel(), CE_CHUNK):
111
+ part = flat[lo : lo + CE_CHUNK].float()
112
+ total += torch.nn.functional.cross_entropy(
113
+ part, target[lo : lo + CE_CHUNK], reduction="sum"
114
+ ).item()
115
+ count += target.numel()
116
+ return total / count
117
+
118
+
119
+ def random_poles(verified: set, width: int, seed: int) -> set:
120
+ """As many poles as ``verified``, drawn from the layer's other poles (or all of them)."""
121
+ others = [
122
+ (i, p) for i in range(width) for p in ("positive", "negative") if (i, p) not in verified
123
+ ]
124
+ return set(random.Random(seed).sample(others, min(len(verified), len(others))))
125
+
126
+
127
+ def recovered(clean: float, ablated: float, kept: float) -> float | None:
128
+ """The share of the ablation's loss rise that keeping the poles brings back."""
129
+ rise = ablated - clean
130
+ return None if rise <= 0 else (ablated - kept) / rise
131
+
132
+
133
+ def layer_row(runner, fit, test, layer: int, verified: set, clean: float, batch: int, seed: int,
134
+ draws: int = 5): # fmt: skip
135
+ """Measure one layer: ablated, kept-VERIFIED and kept-random losses, and shares.
136
+
137
+ The random baseline is the mean over ``draws`` random sets of poles.
138
+ """
139
+ width = runner.mlp_width(layer)
140
+ means = pole_means(runner, fit, layer, batch)
141
+ ablated = mean_loss(runner, test, batch, {layer: keep_poles(means, set(), width)})
142
+ row = {"layer": layer, "width": width, "verified_poles": len(verified),
143
+ "loss_clean": clean, "loss_ablated": ablated} # fmt: skip
144
+ if verified:
145
+ kept = mean_loss(runner, test, batch, {layer: keep_poles(means, verified, width)})
146
+ shares = []
147
+ for d in range(draws):
148
+ rand = random_poles(verified, width, seed + 1000 * layer + d)
149
+ loss = mean_loss(runner, test, batch, {layer: keep_poles(means, rand, width)})
150
+ shares.append(recovered(clean, ablated, loss))
151
+ valid = [x for x in shares if x is not None]
152
+ row |= {"loss_kept_verified": kept, "recovered": recovered(clean, ablated, kept),
153
+ "recovered_random": sum(valid) / len(valid) if valid else None,
154
+ "recovered_random_draws": shares} # fmt: skip
155
+ return row