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.
- open_interpretability-0.0.20.dist-info/METADATA +157 -0
- open_interpretability-0.0.20.dist-info/RECORD +256 -0
- open_interpretability-0.0.20.dist-info/WHEEL +4 -0
- open_interpretability-0.0.20.dist-info/entry_points.txt +2 -0
- open_interpretability-0.0.20.dist-info/licenses/LICENSE +21 -0
- openinterp/__init__.py +6 -0
- openinterp/agents/__init__.py +25 -0
- openinterp/agents/base.py +229 -0
- openinterp/agents/judge.py +149 -0
- openinterp/agents/proposer.py +471 -0
- openinterp/agents/proposer_profiles.py +79 -0
- openinterp/agents/review_measurements.py +173 -0
- openinterp/agents/reviewer.py +498 -0
- openinterp/agreement.py +63 -0
- openinterp/audit_options.py +79 -0
- openinterp/baseline.py +254 -0
- openinterp/benchmark.py +450 -0
- openinterp/blends.py +239 -0
- openinterp/builder/__init__.py +6 -0
- openinterp/builder/agents.py +260 -0
- openinterp/builder/aligned.py +41 -0
- openinterp/builder/charts.py +411 -0
- openinterp/builder/circuit_pages.py +37 -0
- openinterp/builder/circuits.py +264 -0
- openinterp/builder/compiler.py +495 -0
- openinterp/builder/corpora.py +98 -0
- openinterp/builder/coverage.py +46 -0
- openinterp/builder/dashboard.py +255 -0
- openinterp/builder/disk_memo.py +65 -0
- openinterp/builder/experiment.py +105 -0
- openinterp/builder/heat.py +121 -0
- openinterp/builder/hrefs.py +71 -0
- openinterp/builder/learned_baseline.py +204 -0
- openinterp/builder/model_facts.py +138 -0
- openinterp/builder/neuronpedia_export.py +107 -0
- openinterp/builder/parallel.py +100 -0
- openinterp/builder/parquet_export.py +458 -0
- openinterp/builder/pieces.py +127 -0
- openinterp/builder/pii.py +211 -0
- openinterp/builder/registry_stats.py +144 -0
- openinterp/builder/research.py +109 -0
- openinterp/builder/sections.py +28 -0
- openinterp/builder/significance.py +103 -0
- openinterp/builder/sister_graph.py +180 -0
- openinterp/builder/standing.py +185 -0
- openinterp/builder/statuses.py +34 -0
- openinterp/builder/stopped.py +91 -0
- openinterp/builder/stops.py +30 -0
- openinterp/builder/superpositions.py +102 -0
- openinterp/builder/templates/_agent_role_cells.html +9 -0
- openinterp/builder/templates/_causal_chart.html +67 -0
- openinterp/builder/templates/_circuit.html +8 -0
- openinterp/builder/templates/_dashboard.html +133 -0
- openinterp/builder/templates/_experiment.html +500 -0
- openinterp/builder/templates/_macros.html +82 -0
- openinterp/builder/templates/_patchscope.html +33 -0
- openinterp/builder/templates/_prior_art.html +24 -0
- openinterp/builder/templates/_specificity.html +21 -0
- openinterp/builder/templates/_steering.html +34 -0
- openinterp/builder/templates/_superposition.html +62 -0
- openinterp/builder/templates/_vocab_chart.html +88 -0
- openinterp/builder/templates/_wiring.html +44 -0
- openinterp/builder/templates/about.html +95 -0
- openinterp/builder/templates/agent.html +69 -0
- openinterp/builder/templates/agents.html +80 -0
- openinterp/builder/templates/base.html +103 -0
- openinterp/builder/templates/circuit.html +64 -0
- openinterp/builder/templates/circuits.html +46 -0
- openinterp/builder/templates/experiment.html +21 -0
- openinterp/builder/templates/hub.html +16 -0
- openinterp/builder/templates/index.html +387 -0
- openinterp/builder/templates/layer.html +69 -0
- openinterp/builder/templates/learned-baseline.html +44 -0
- openinterp/builder/templates/model.html +151 -0
- openinterp/builder/templates/models.html +47 -0
- openinterp/builder/templates/neuron.html +234 -0
- openinterp/builder/templates/paper.html +39 -0
- openinterp/builder/templates/redirect.html +1 -0
- openinterp/builder/templates/research.html +31 -0
- openinterp/builder/templates/user.html +54 -0
- openinterp/builder/templates/users.html +39 -0
- openinterp/builder/templates/wiki/experiment.md +271 -0
- openinterp/builder/templates/wiki/index.md +32 -0
- openinterp/builder/templates/wiki/neuron.md +30 -0
- openinterp/builder/templates/wiki/stop.md +36 -0
- openinterp/builder/tree.py +470 -0
- openinterp/builder/users.py +142 -0
- openinterp/builder/wiki.py +427 -0
- openinterp/checks.py +429 -0
- openinterp/cli.py +494 -0
- openinterp/cli_baseline.py +332 -0
- openinterp/cli_benchmark.py +300 -0
- openinterp/cli_build.py +128 -0
- openinterp/cli_check.py +51 -0
- openinterp/cli_concepts.py +76 -0
- openinterp/cli_corpus.py +252 -0
- openinterp/cli_costs.py +257 -0
- openinterp/cli_coverage.py +76 -0
- openinterp/cli_crossref.py +86 -0
- openinterp/cli_errata.py +142 -0
- openinterp/cli_evidence.py +495 -0
- openinterp/cli_evidence_store.py +93 -0
- openinterp/cli_explain.py +101 -0
- openinterp/cli_loop.py +125 -0
- openinterp/cli_memory.py +28 -0
- openinterp/cli_neuronpedia.py +175 -0
- openinterp/cli_pause.py +45 -0
- openinterp/cli_pole_effects.py +133 -0
- openinterp/cli_probing.py +119 -0
- openinterp/cli_registry.py +348 -0
- openinterp/cli_reproduce.py +446 -0
- openinterp/cli_review.py +302 -0
- openinterp/cli_scan.py +223 -0
- openinterp/cli_score.py +446 -0
- openinterp/cli_sisters.py +86 -0
- openinterp/cli_submission.py +217 -0
- openinterp/cli_superpositions.py +118 -0
- openinterp/cli_wiring.py +54 -0
- openinterp/codeid.py +73 -0
- openinterp/concepts.py +400 -0
- openinterp/core/__init__.py +59 -0
- openinterp/core/agent_names.py +76 -0
- openinterp/core/attribution.py +101 -0
- openinterp/core/cache_dirs.py +15 -0
- openinterp/core/causal.py +82 -0
- openinterp/core/confounds.py +103 -0
- openinterp/core/contributors.py +26 -0
- openinterp/core/corpora.py +126 -0
- openinterp/core/costs.py +430 -0
- openinterp/core/draws.py +109 -0
- openinterp/core/errata.py +244 -0
- openinterp/core/evidence.py +160 -0
- openinterp/core/examples.py +93 -0
- openinterp/core/exceptions.py +29 -0
- openinterp/core/explanation.py +130 -0
- openinterp/core/fence.py +42 -0
- openinterp/core/heavy_lock.py +114 -0
- openinterp/core/identifiers.py +198 -0
- openinterp/core/isolation.py +149 -0
- openinterp/core/ledger.py +130 -0
- openinterp/core/licenses.py +28 -0
- openinterp/core/manifold.py +117 -0
- openinterp/core/partners.py +32 -0
- openinterp/core/patchscope.py +59 -0
- openinterp/core/paths.py +40 -0
- openinterp/core/pause.py +96 -0
- openinterp/core/pricing.py +178 -0
- openinterp/core/prior_art.py +44 -0
- openinterp/core/probing.py +146 -0
- openinterp/core/profiles.py +221 -0
- openinterp/core/project.py +28 -0
- openinterp/core/run_environment.py +38 -0
- openinterp/core/schemas.py +484 -0
- openinterp/core/specificity.py +49 -0
- openinterp/core/stats.py +172 -0
- openinterp/core/steering.py +49 -0
- openinterp/core/strict.py +19 -0
- openinterp/core/takedowns.py +136 -0
- openinterp/core/transcripts.py +130 -0
- openinterp/core/wiring.py +80 -0
- openinterp/corpus.py +476 -0
- openinterp/corpus_cache.py +145 -0
- openinterp/corpus_layouts.py +108 -0
- openinterp/corpus_presets.py +168 -0
- openinterp/corpus_rebuild.py +93 -0
- openinterp/coverage.py +155 -0
- openinterp/crossref.py +442 -0
- openinterp/data/__init__.py +9 -0
- openinterp/data/offensive_words.txt +58 -0
- openinterp/data/public_suffix_list.dat +15771 -0
- openinterp/data/sample_corpus.txt +40 -0
- openinterp/draw_steps.py +359 -0
- openinterp/errata_checks.py +129 -0
- openinterp/evidence.py +462 -0
- openinterp/evidence_branch.py +119 -0
- openinterp/evidence_lenses.py +75 -0
- openinterp/experiment_loop.py +357 -0
- openinterp/explain.py +252 -0
- openinterp/half_precision.py +79 -0
- openinterp/harness/__init__.py +37 -0
- openinterp/harness/ane/__init__.py +9 -0
- openinterp/harness/ane/backend.py +324 -0
- openinterp/harness/ane/cache.py +276 -0
- openinterp/harness/ane/chunks.py +163 -0
- openinterp/harness/ane/convert.py +116 -0
- openinterp/harness/ane/forward.py +258 -0
- openinterp/harness/ane/qualified.py +40 -0
- openinterp/harness/annotations.py +329 -0
- openinterp/harness/attribution.py +96 -0
- openinterp/harness/causal.py +264 -0
- openinterp/harness/confounds.py +184 -0
- openinterp/harness/covariance.py +301 -0
- openinterp/harness/crossref.py +195 -0
- openinterp/harness/cuda_devices.py +88 -0
- openinterp/harness/dead.py +124 -0
- openinterp/harness/devices.py +401 -0
- openinterp/harness/dictionaries.py +496 -0
- openinterp/harness/dreaming.py +116 -0
- openinterp/harness/environment.py +64 -0
- openinterp/harness/explanation_scores.py +371 -0
- openinterp/harness/features.py +276 -0
- openinterp/harness/first_position.py +137 -0
- openinterp/harness/heads.py +226 -0
- openinterp/harness/hooks.py +499 -0
- openinterp/harness/input_attribution.py +367 -0
- openinterp/harness/jlens.py +183 -0
- openinterp/harness/layer_cache.py +301 -0
- openinterp/harness/layer_major.py +274 -0
- openinterp/harness/lookahead.py +93 -0
- openinterp/harness/manifold.py +200 -0
- openinterp/harness/memory.py +471 -0
- openinterp/harness/moe.py +134 -0
- openinterp/harness/patcher.py +322 -0
- openinterp/harness/patchscope.py +177 -0
- openinterp/harness/probing.py +383 -0
- openinterp/harness/scan.py +462 -0
- openinterp/harness/scan_backend.py +201 -0
- openinterp/harness/sister_passes.py +463 -0
- openinterp/harness/sisters.py +363 -0
- openinterp/harness/sites.py +101 -0
- openinterp/harness/specificity.py +107 -0
- openinterp/harness/steering.py +159 -0
- openinterp/harness/superpositions.py +116 -0
- openinterp/harness/units.py +203 -0
- openinterp/harness/wiring.py +432 -0
- openinterp/harness/wiring_activations.py +285 -0
- openinterp/langid.py +73 -0
- openinterp/names.py +129 -0
- openinterp/neuronpedia_baseline.py +88 -0
- openinterp/neuronpedia_review.py +148 -0
- openinterp/planner.py +466 -0
- openinterp/planner_effects.py +89 -0
- openinterp/planner_heads.py +92 -0
- openinterp/planner_models.py +174 -0
- openinterp/planner_sisters.py +122 -0
- openinterp/planner_states.py +57 -0
- openinterp/planner_superpositions.py +114 -0
- openinterp/pole_effects.py +102 -0
- openinterp/prior_art.py +143 -0
- openinterp/reproduce.py +450 -0
- openinterp/revision.py +45 -0
- openinterp/runner_cache.py +35 -0
- openinterp/score_controls.py +169 -0
- openinterp/sensitive.py +243 -0
- openinterp/show.py +236 -0
- openinterp/signing.py +209 -0
- openinterp/sisters.py +476 -0
- openinterp/superposition_finders.py +225 -0
- openinterp/sync/__init__.py +5 -0
- openinterp/sync/r2.py +280 -0
- openinterp/syntax.py +399 -0
- openinterp/takedown_checks.py +76 -0
- openinterp/toxicity.py +158 -0
- openinterp/verdict_regression.py +79 -0
- openinterp/wiring_backfill.py +170 -0
- 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
|