circuitcheck 0.1.0__tar.gz
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.
- circuitcheck-0.1.0/LICENSE +21 -0
- circuitcheck-0.1.0/PKG-INFO +171 -0
- circuitcheck-0.1.0/README.md +130 -0
- circuitcheck-0.1.0/pyproject.toml +134 -0
- circuitcheck-0.1.0/pyproject.toml.orig +95 -0
- circuitcheck-0.1.0/src/circuitcheck/__init__.py +16 -0
- circuitcheck-0.1.0/src/circuitcheck/cli.py +236 -0
- circuitcheck-0.1.0/src/circuitcheck/diff/__init__.py +27 -0
- circuitcheck-0.1.0/src/circuitcheck/diff/core.py +502 -0
- circuitcheck-0.1.0/src/circuitcheck/diff/graphs.py +213 -0
- circuitcheck-0.1.0/src/circuitcheck/labels/__init__.py +42 -0
- circuitcheck-0.1.0/src/circuitcheck/labels/autointerp.py +383 -0
- circuitcheck-0.1.0/src/circuitcheck/labels/neuronpedia.py +148 -0
- circuitcheck-0.1.0/src/circuitcheck/labels/providers.py +200 -0
- circuitcheck-0.1.0/src/circuitcheck/py.typed +0 -0
- circuitcheck-0.1.0/src/circuitcheck/report.py +290 -0
- circuitcheck-0.1.0/src/circuitcheck/tracer.py +165 -0
- circuitcheck-0.1.0/src/circuitcheck/validate/__init__.py +33 -0
- circuitcheck-0.1.0/src/circuitcheck/validate/backend.py +41 -0
- circuitcheck-0.1.0/src/circuitcheck/validate/controls.py +81 -0
- circuitcheck-0.1.0/src/circuitcheck/validate/core.py +330 -0
- circuitcheck-0.1.0/src/circuitcheck/validate/metrics.py +49 -0
- circuitcheck-0.1.0/src/circuitcheck/validate/sandbox.py +129 -0
- circuitcheck-0.1.0/src/circuitcheck/validate/toy.py +139 -0
- circuitcheck-0.1.0/src/circuitcheck/validate/types.py +142 -0
- circuitcheck-0.1.0/tests/__init__.py +0 -0
- circuitcheck-0.1.0/tests/conftest.py +12 -0
- circuitcheck-0.1.0/tests/test_diff.py +175 -0
- circuitcheck-0.1.0/tests/test_docs_snippets.py +110 -0
- circuitcheck-0.1.0/tests/test_labels.py +156 -0
- circuitcheck-0.1.0/tests/test_live_results.py +64 -0
- circuitcheck-0.1.0/tests/test_notebooks.py +31 -0
- circuitcheck-0.1.0/tests/test_report_cli.py +105 -0
- circuitcheck-0.1.0/tests/test_validate.py +131 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 albertlieyingadrian
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: circuitcheck
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Tools on top of circuit-tracer: diff attribution graphs across training, validate circuits with controls, and label features cheaply.
|
|
5
|
+
Keywords: interpretability,circuits,attribution-graphs,transcoders,circuit-tracer,autointerp
|
|
6
|
+
Author: albertlieyingadrian
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Dist: numpy>=1.26
|
|
18
|
+
Requires-Dist: pydantic>=2.7,<3
|
|
19
|
+
Requires-Dist: typer>=0.12
|
|
20
|
+
Requires-Dist: anthropic>=0.69 ; extra == 'anthropic'
|
|
21
|
+
Requires-Dist: pytest>=9.0 ; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest-cov>=7.0 ; extra == 'dev'
|
|
23
|
+
Requires-Dist: ruff>=0.17 ; extra == 'dev'
|
|
24
|
+
Requires-Dist: mypy>=2.0 ; extra == 'dev'
|
|
25
|
+
Requires-Dist: nbclient>=0.10 ; extra == 'dev'
|
|
26
|
+
Requires-Dist: nbformat>=5.10 ; extra == 'dev'
|
|
27
|
+
Requires-Dist: ipykernel>=6.29 ; extra == 'dev'
|
|
28
|
+
Requires-Dist: openai>=1.100 ; extra == 'openai'
|
|
29
|
+
Requires-Dist: torch>=2.4 ; extra == 'tracer'
|
|
30
|
+
Requires-Dist: transformers>=4.51 ; extra == 'tracer'
|
|
31
|
+
Requires-Python: >=3.11
|
|
32
|
+
Project-URL: Homepage, https://github.com/albertlieyingadrian/circuitcheck
|
|
33
|
+
Project-URL: Source, https://github.com/albertlieyingadrian/circuitcheck
|
|
34
|
+
Project-URL: Documentation, https://github.com/albertlieyingadrian/circuitcheck/tree/main/docs
|
|
35
|
+
Project-URL: Changelog, https://github.com/albertlieyingadrian/circuitcheck/blob/main/CHANGELOG.md
|
|
36
|
+
Provides-Extra: anthropic
|
|
37
|
+
Provides-Extra: dev
|
|
38
|
+
Provides-Extra: openai
|
|
39
|
+
Provides-Extra: tracer
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# circuitcheck
|
|
43
|
+
|
|
44
|
+
<p align="center"><img src="docs/images/demo.gif" alt="circuitcheck validate --demo: the toy model's planted switch comes back as a switch, its two readouts as readouts and its redundant pair as backups" width="760"></p>
|
|
45
|
+
|
|
46
|
+
**Test what an attribution graph claims.** circuitcheck is three tools on top of
|
|
47
|
+
[circuit-tracer](https://github.com/decoderesearch/circuit-tracer) attribution graphs:
|
|
48
|
+
|
|
49
|
+
| Tool | Question it answers | Runs without a GPU on |
|
|
50
|
+
|---|---|---|
|
|
51
|
+
| `circuitcheck validate` | Is a group of graph features a **switch** (it causes the behaviour) or a **readout** (it only reflects it)? | the built-in toy model |
|
|
52
|
+
| `circuitcheck diff` | Which features and edges did fine-tuning or RL add or remove, and is the change real? | saved graph summaries |
|
|
53
|
+
| `circuitcheck label` | What does each feature do, in a few words, and how far can that label be trusted? | cached model replies |
|
|
54
|
+
|
|
55
|
+
▶ [Watch the 2.5-minute walkthrough](docs/videos/walkthrough.mp4) · [Use cases](docs/use-cases.mdx) ·
|
|
56
|
+
[circuitcheck vs circuit-tracer](docs/vs-circuit-tracer.mdx) · [Docs](docs/index.mdx)
|
|
57
|
+
|
|
58
|
+
It grew out of [steerlab's circuits episodes](https://albertlieyingadrian.github.io/steerlab/experiments/#circuits).
|
|
59
|
+
In [a password lock](https://albertlieyingadrian.github.io/steerlab/experiments/lock-circuit/report/) and
|
|
60
|
+
[an RL reward hack](https://albertlieyingadrian.github.io/steerlab/experiments/rl-hack-circuit/report/), the graph's
|
|
61
|
+
most influential features all turned out to be readouts. circuitcheck turns the checks behind that finding into
|
|
62
|
+
reusable code.
|
|
63
|
+
|
|
64
|
+
## Install
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install circuitcheck # after the first PyPI release; until then:
|
|
68
|
+
pip install "git+https://github.com/albertlieyingadrian/circuitcheck"
|
|
69
|
+
# real models: circuit-tracer from GitHub, plus torch
|
|
70
|
+
pip install "git+https://github.com/decoderesearch/circuit-tracer"
|
|
71
|
+
# labels: pip install "circuitcheck[anthropic]" (Claude Haiku 5.5, the default) or "[openai]"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Five-minute tour (no GPU, no key)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
circuitcheck validate --demo --html validate.html
|
|
78
|
+
circuitcheck diff examples/data/rl_hack_graphs.json.gz --before installed/before --after installed/after \
|
|
79
|
+
--control-before control/before --control-after control/after --html diff.html
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from circuitcheck.validate import logit_diff, validate
|
|
84
|
+
from circuitcheck.validate.toy import demo, demo_nodes
|
|
85
|
+
|
|
86
|
+
backend = demo()
|
|
87
|
+
metric = logit_diff(backend.token_id(" Austin"), backend.token_id(" Sacramento"))
|
|
88
|
+
report = validate(backend, "dallas", demo_nodes(backend), metric=metric, counterfactual="oakland")
|
|
89
|
+
print([(g.name, g.verdict.value) for g in report.groups])
|
|
90
|
+
# [('top-5', 'switch'), ('L5/100@5', 'switch'), ('L7/200@5', 'readout'), ('L8/201@5', 'readout'), ...]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
On a real model, `circuitcheck.tracer.TracerBackend` wraps a circuit-tracer `ReplacementModel`:
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
from circuit_tracer import ReplacementModel
|
|
97
|
+
from circuitcheck.tracer import TracerBackend
|
|
98
|
+
|
|
99
|
+
model = ReplacementModel.from_pretrained("google/gemma-2-2b", "gemma")
|
|
100
|
+
report = validate(TracerBackend(model), prompt, nodes, metric=metric, counterfactual=other_prompt)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## What each tool checks
|
|
104
|
+
|
|
105
|
+
**diff.** Graphs of the same prompts before and after training are reduced to per-feature influence and
|
|
106
|
+
feature-to-feature edges, summed over positions. Each feature gets a status (gained, lost, up, down, stable). The
|
|
107
|
+
statuses are backed by:
|
|
108
|
+
|
|
109
|
+
- a paired swap null: before and after labels are swapped at random within each prompt pair, with a
|
|
110
|
+
Benjamini-Hochberg correction across features;
|
|
111
|
+
- a bootstrap interval and a split-half check;
|
|
112
|
+
- optionally, a control run (did the change happen only in the run that learned the behaviour?) and a replication
|
|
113
|
+
seed.
|
|
114
|
+
|
|
115
|
+
The validity check compares replacement and completeness scores, error-node influence and per-layer reconstruction
|
|
116
|
+
error (`circuitcheck.tracer.reconstruction_error`) before and after. The transcoders were trained on the base model,
|
|
117
|
+
and the report says so every time.
|
|
118
|
+
|
|
119
|
+
**validate.** For each group of features:
|
|
120
|
+
|
|
121
|
+
- ablation (set them to 0);
|
|
122
|
+
- steering (multiply them by 3);
|
|
123
|
+
- a counterfactual patch (copy their values from a prompt with a different answer);
|
|
124
|
+
- each of the above on 20 random feature sets matched by layer and position, and on 20 sets matched by write size
|
|
125
|
+
(|activation| x decoder norm).
|
|
126
|
+
|
|
127
|
+
The verdict follows printed rules: switch, partial, backup, nonspecific or readout. An optional explanation swap (Gao's
|
|
128
|
+
test) runs label rules written as code in a checked sandbox. Output is JSON plus an HTML page.
|
|
129
|
+
|
|
130
|
+
**labels.** The pipeline:
|
|
131
|
+
|
|
132
|
+
1. Explain each feature from its top activating records.
|
|
133
|
+
2. Score the label on held-out records: detection AUROC (activating and silent excerpts) and, optionally, simulation
|
|
134
|
+
correlation.
|
|
135
|
+
3. Report calibration: stated confidence against the earned score, and the detector's expected calibration error.
|
|
136
|
+
|
|
137
|
+
Every reply is cached. Labels export in Neuronpedia's explanation format, and Neuronpedia's own labels can be scored by
|
|
138
|
+
the same detector for a fair comparison.
|
|
139
|
+
|
|
140
|
+
## Results that reproduce the episodes
|
|
141
|
+
|
|
142
|
+
- **Graph diff.** On the RL-hack study's 48 graphs, `diff_graphs` picks the same ten features the study did, in the
|
|
143
|
+
same order. 9 of the 10 are specific to the run that hacked and 6 come back in another seed (test:
|
|
144
|
+
`tests/test_diff.py::test_reproduces_rl_hack_selection`). Split halves of the prompts agree on 70% of the top 10, and
|
|
145
|
+
reconstruction error is unchanged by RL (0.500 vs 0.499).
|
|
146
|
+
- **Labels.** Claude Haiku 5.5 labelled 50 Gemma-2-2B transcoder features for $0.26 in total. Its labels match
|
|
147
|
+
Neuronpedia's on detection (AUROC 0.80 vs 0.78 and 0.80) and beat them on simulation (r = 0.45 vs 0.37 and 0.38).
|
|
148
|
+
The explainer's stated confidence barely predicts its score (r = 0.11), so use the score, not the confidence.
|
|
149
|
+
- **Live validator runs** on the RL-hack model and the password-locked model: see `examples/data/live/`.
|
|
150
|
+
|
|
151
|
+
## Layout
|
|
152
|
+
|
|
153
|
+
```
|
|
154
|
+
src/circuitcheck/
|
|
155
|
+
diff/ graphs.py (summaries and loaders: circuit-tracer .pt, Neuronpedia JSON), core.py (the diff)
|
|
156
|
+
validate/ core.py (checks and verdicts), controls.py, metrics.py, sandbox.py, toy.py
|
|
157
|
+
labels/ autointerp.py (explain, detect, simulate, calibrate), providers.py (Claude, OpenAI, cache), neuronpedia.py
|
|
158
|
+
tracer.py circuit-tracer adapter and reconstruction error
|
|
159
|
+
report.py HTML pages
|
|
160
|
+
cli.py the circuitcheck command
|
|
161
|
+
examples/ notebooks 01-03, data/, scripts/ (how the bundled data was made), modal/ (live GPU checks)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Development
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
uv venv && uv pip install -e ".[dev]"
|
|
168
|
+
pytest && ruff check && mypy src
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
MIT licence.
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# circuitcheck
|
|
2
|
+
|
|
3
|
+
<p align="center"><img src="docs/images/demo.gif" alt="circuitcheck validate --demo: the toy model's planted switch comes back as a switch, its two readouts as readouts and its redundant pair as backups" width="760"></p>
|
|
4
|
+
|
|
5
|
+
**Test what an attribution graph claims.** circuitcheck is three tools on top of
|
|
6
|
+
[circuit-tracer](https://github.com/decoderesearch/circuit-tracer) attribution graphs:
|
|
7
|
+
|
|
8
|
+
| Tool | Question it answers | Runs without a GPU on |
|
|
9
|
+
|---|---|---|
|
|
10
|
+
| `circuitcheck validate` | Is a group of graph features a **switch** (it causes the behaviour) or a **readout** (it only reflects it)? | the built-in toy model |
|
|
11
|
+
| `circuitcheck diff` | Which features and edges did fine-tuning or RL add or remove, and is the change real? | saved graph summaries |
|
|
12
|
+
| `circuitcheck label` | What does each feature do, in a few words, and how far can that label be trusted? | cached model replies |
|
|
13
|
+
|
|
14
|
+
▶ [Watch the 2.5-minute walkthrough](docs/videos/walkthrough.mp4) · [Use cases](docs/use-cases.mdx) ·
|
|
15
|
+
[circuitcheck vs circuit-tracer](docs/vs-circuit-tracer.mdx) · [Docs](docs/index.mdx)
|
|
16
|
+
|
|
17
|
+
It grew out of [steerlab's circuits episodes](https://albertlieyingadrian.github.io/steerlab/experiments/#circuits).
|
|
18
|
+
In [a password lock](https://albertlieyingadrian.github.io/steerlab/experiments/lock-circuit/report/) and
|
|
19
|
+
[an RL reward hack](https://albertlieyingadrian.github.io/steerlab/experiments/rl-hack-circuit/report/), the graph's
|
|
20
|
+
most influential features all turned out to be readouts. circuitcheck turns the checks behind that finding into
|
|
21
|
+
reusable code.
|
|
22
|
+
|
|
23
|
+
## Install
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pip install circuitcheck # after the first PyPI release; until then:
|
|
27
|
+
pip install "git+https://github.com/albertlieyingadrian/circuitcheck"
|
|
28
|
+
# real models: circuit-tracer from GitHub, plus torch
|
|
29
|
+
pip install "git+https://github.com/decoderesearch/circuit-tracer"
|
|
30
|
+
# labels: pip install "circuitcheck[anthropic]" (Claude Haiku 5.5, the default) or "[openai]"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Five-minute tour (no GPU, no key)
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
circuitcheck validate --demo --html validate.html
|
|
37
|
+
circuitcheck diff examples/data/rl_hack_graphs.json.gz --before installed/before --after installed/after \
|
|
38
|
+
--control-before control/before --control-after control/after --html diff.html
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from circuitcheck.validate import logit_diff, validate
|
|
43
|
+
from circuitcheck.validate.toy import demo, demo_nodes
|
|
44
|
+
|
|
45
|
+
backend = demo()
|
|
46
|
+
metric = logit_diff(backend.token_id(" Austin"), backend.token_id(" Sacramento"))
|
|
47
|
+
report = validate(backend, "dallas", demo_nodes(backend), metric=metric, counterfactual="oakland")
|
|
48
|
+
print([(g.name, g.verdict.value) for g in report.groups])
|
|
49
|
+
# [('top-5', 'switch'), ('L5/100@5', 'switch'), ('L7/200@5', 'readout'), ('L8/201@5', 'readout'), ...]
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
On a real model, `circuitcheck.tracer.TracerBackend` wraps a circuit-tracer `ReplacementModel`:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from circuit_tracer import ReplacementModel
|
|
56
|
+
from circuitcheck.tracer import TracerBackend
|
|
57
|
+
|
|
58
|
+
model = ReplacementModel.from_pretrained("google/gemma-2-2b", "gemma")
|
|
59
|
+
report = validate(TracerBackend(model), prompt, nodes, metric=metric, counterfactual=other_prompt)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## What each tool checks
|
|
63
|
+
|
|
64
|
+
**diff.** Graphs of the same prompts before and after training are reduced to per-feature influence and
|
|
65
|
+
feature-to-feature edges, summed over positions. Each feature gets a status (gained, lost, up, down, stable). The
|
|
66
|
+
statuses are backed by:
|
|
67
|
+
|
|
68
|
+
- a paired swap null: before and after labels are swapped at random within each prompt pair, with a
|
|
69
|
+
Benjamini-Hochberg correction across features;
|
|
70
|
+
- a bootstrap interval and a split-half check;
|
|
71
|
+
- optionally, a control run (did the change happen only in the run that learned the behaviour?) and a replication
|
|
72
|
+
seed.
|
|
73
|
+
|
|
74
|
+
The validity check compares replacement and completeness scores, error-node influence and per-layer reconstruction
|
|
75
|
+
error (`circuitcheck.tracer.reconstruction_error`) before and after. The transcoders were trained on the base model,
|
|
76
|
+
and the report says so every time.
|
|
77
|
+
|
|
78
|
+
**validate.** For each group of features:
|
|
79
|
+
|
|
80
|
+
- ablation (set them to 0);
|
|
81
|
+
- steering (multiply them by 3);
|
|
82
|
+
- a counterfactual patch (copy their values from a prompt with a different answer);
|
|
83
|
+
- each of the above on 20 random feature sets matched by layer and position, and on 20 sets matched by write size
|
|
84
|
+
(|activation| x decoder norm).
|
|
85
|
+
|
|
86
|
+
The verdict follows printed rules: switch, partial, backup, nonspecific or readout. An optional explanation swap (Gao's
|
|
87
|
+
test) runs label rules written as code in a checked sandbox. Output is JSON plus an HTML page.
|
|
88
|
+
|
|
89
|
+
**labels.** The pipeline:
|
|
90
|
+
|
|
91
|
+
1. Explain each feature from its top activating records.
|
|
92
|
+
2. Score the label on held-out records: detection AUROC (activating and silent excerpts) and, optionally, simulation
|
|
93
|
+
correlation.
|
|
94
|
+
3. Report calibration: stated confidence against the earned score, and the detector's expected calibration error.
|
|
95
|
+
|
|
96
|
+
Every reply is cached. Labels export in Neuronpedia's explanation format, and Neuronpedia's own labels can be scored by
|
|
97
|
+
the same detector for a fair comparison.
|
|
98
|
+
|
|
99
|
+
## Results that reproduce the episodes
|
|
100
|
+
|
|
101
|
+
- **Graph diff.** On the RL-hack study's 48 graphs, `diff_graphs` picks the same ten features the study did, in the
|
|
102
|
+
same order. 9 of the 10 are specific to the run that hacked and 6 come back in another seed (test:
|
|
103
|
+
`tests/test_diff.py::test_reproduces_rl_hack_selection`). Split halves of the prompts agree on 70% of the top 10, and
|
|
104
|
+
reconstruction error is unchanged by RL (0.500 vs 0.499).
|
|
105
|
+
- **Labels.** Claude Haiku 5.5 labelled 50 Gemma-2-2B transcoder features for $0.26 in total. Its labels match
|
|
106
|
+
Neuronpedia's on detection (AUROC 0.80 vs 0.78 and 0.80) and beat them on simulation (r = 0.45 vs 0.37 and 0.38).
|
|
107
|
+
The explainer's stated confidence barely predicts its score (r = 0.11), so use the score, not the confidence.
|
|
108
|
+
- **Live validator runs** on the RL-hack model and the password-locked model: see `examples/data/live/`.
|
|
109
|
+
|
|
110
|
+
## Layout
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
src/circuitcheck/
|
|
114
|
+
diff/ graphs.py (summaries and loaders: circuit-tracer .pt, Neuronpedia JSON), core.py (the diff)
|
|
115
|
+
validate/ core.py (checks and verdicts), controls.py, metrics.py, sandbox.py, toy.py
|
|
116
|
+
labels/ autointerp.py (explain, detect, simulate, calibrate), providers.py (Claude, OpenAI, cache), neuronpedia.py
|
|
117
|
+
tracer.py circuit-tracer adapter and reconstruction error
|
|
118
|
+
report.py HTML pages
|
|
119
|
+
cli.py the circuitcheck command
|
|
120
|
+
examples/ notebooks 01-03, data/, scripts/ (how the bundled data was made), modal/ (live GPU checks)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
uv venv && uv pip install -e ".[dev]"
|
|
127
|
+
pytest && ruff check && mypy src
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
MIT licence.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["uv_build>=0.11.7,<0.14"]
|
|
3
|
+
build-backend = "uv_build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "circuitcheck"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Tools on top of circuit-tracer: diff attribution graphs across training, validate circuits with controls, and label features cheaply."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
keywords = [
|
|
14
|
+
"interpretability",
|
|
15
|
+
"circuits",
|
|
16
|
+
"attribution-graphs",
|
|
17
|
+
"transcoders",
|
|
18
|
+
"circuit-tracer",
|
|
19
|
+
"autointerp",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
23
|
+
"Intended Audience :: Science/Research",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.11",
|
|
26
|
+
"Programming Language :: Python :: 3.12",
|
|
27
|
+
"Programming Language :: Python :: 3.13",
|
|
28
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
29
|
+
"Typing :: Typed",
|
|
30
|
+
]
|
|
31
|
+
dependencies = [
|
|
32
|
+
"numpy>=1.26",
|
|
33
|
+
"pydantic>=2.7,<3",
|
|
34
|
+
"typer>=0.12",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
[[project.authors]]
|
|
38
|
+
name = "albertlieyingadrian"
|
|
39
|
+
|
|
40
|
+
[project.optional-dependencies]
|
|
41
|
+
tracer = [
|
|
42
|
+
"torch>=2.4",
|
|
43
|
+
"transformers>=4.51",
|
|
44
|
+
]
|
|
45
|
+
anthropic = ["anthropic>=0.69"]
|
|
46
|
+
openai = ["openai>=1.100"]
|
|
47
|
+
dev = [
|
|
48
|
+
"pytest>=9.0",
|
|
49
|
+
"pytest-cov>=7.0",
|
|
50
|
+
"ruff>=0.17",
|
|
51
|
+
"mypy>=2.0",
|
|
52
|
+
"nbclient>=0.10",
|
|
53
|
+
"nbformat>=5.10",
|
|
54
|
+
"ipykernel>=6.29",
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
[project.scripts]
|
|
58
|
+
circuitcheck = "circuitcheck.cli:app"
|
|
59
|
+
|
|
60
|
+
[project.urls]
|
|
61
|
+
Homepage = "https://github.com/albertlieyingadrian/circuitcheck"
|
|
62
|
+
Source = "https://github.com/albertlieyingadrian/circuitcheck"
|
|
63
|
+
Documentation = "https://github.com/albertlieyingadrian/circuitcheck/tree/main/docs"
|
|
64
|
+
Changelog = "https://github.com/albertlieyingadrian/circuitcheck/blob/main/CHANGELOG.md"
|
|
65
|
+
|
|
66
|
+
[dependency-groups]
|
|
67
|
+
dev = [
|
|
68
|
+
"pytest>=9.0",
|
|
69
|
+
"pytest-cov>=7.0",
|
|
70
|
+
"ruff>=0.17",
|
|
71
|
+
"mypy>=2.0",
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
[tool.uv.build-backend]
|
|
75
|
+
module-name = "circuitcheck"
|
|
76
|
+
source-include = ["tests/**"]
|
|
77
|
+
|
|
78
|
+
[tool.pytest.ini_options]
|
|
79
|
+
testpaths = ["tests"]
|
|
80
|
+
addopts = "-ra --strict-markers --strict-config -m 'not slow'"
|
|
81
|
+
markers = ["slow: real-model or network runs, excluded by default (run with -m slow)"]
|
|
82
|
+
|
|
83
|
+
[tool.ruff]
|
|
84
|
+
line-length = 120
|
|
85
|
+
target-version = "py311"
|
|
86
|
+
extend-exclude = [
|
|
87
|
+
".agents",
|
|
88
|
+
".claude",
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
[tool.ruff.lint]
|
|
92
|
+
select = [
|
|
93
|
+
"E",
|
|
94
|
+
"F",
|
|
95
|
+
"W",
|
|
96
|
+
"I",
|
|
97
|
+
"B",
|
|
98
|
+
"UP",
|
|
99
|
+
"SIM",
|
|
100
|
+
"RUF",
|
|
101
|
+
]
|
|
102
|
+
ignore = [
|
|
103
|
+
"RUF001",
|
|
104
|
+
"RUF002",
|
|
105
|
+
"RUF003",
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
[tool.mypy]
|
|
109
|
+
python_version = "3.11"
|
|
110
|
+
strict = true
|
|
111
|
+
plugins = ["pydantic.mypy"]
|
|
112
|
+
|
|
113
|
+
[[tool.mypy.overrides]]
|
|
114
|
+
module = [
|
|
115
|
+
"torch",
|
|
116
|
+
"torch.*",
|
|
117
|
+
"transformers",
|
|
118
|
+
"transformers.*",
|
|
119
|
+
"circuit_tracer",
|
|
120
|
+
"circuit_tracer.*",
|
|
121
|
+
"anthropic",
|
|
122
|
+
"anthropic.*",
|
|
123
|
+
"openai",
|
|
124
|
+
"openai.*",
|
|
125
|
+
]
|
|
126
|
+
ignore_missing_imports = true
|
|
127
|
+
|
|
128
|
+
[tool.coverage.run]
|
|
129
|
+
source = ["circuitcheck"]
|
|
130
|
+
branch = true
|
|
131
|
+
|
|
132
|
+
[tool.coverage.report]
|
|
133
|
+
show_missing = true
|
|
134
|
+
skip_covered = true
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
# Keep the upper bound: uv_build follows uv's versioning and may change between minors.
|
|
3
|
+
requires = ["uv_build>=0.11.7,<0.14"]
|
|
4
|
+
build-backend = "uv_build"
|
|
5
|
+
|
|
6
|
+
[project]
|
|
7
|
+
name = "circuitcheck"
|
|
8
|
+
# The ONLY place the version lives. Code reads it with importlib.metadata.
|
|
9
|
+
version = "0.1.0"
|
|
10
|
+
description = "Tools on top of circuit-tracer: diff attribution graphs across training, validate circuits with controls, and label features cheaply."
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
authors = [{ name = "albertlieyingadrian" }]
|
|
16
|
+
keywords = ["interpretability", "circuits", "attribution-graphs", "transcoders", "circuit-tracer", "autointerp"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 2 - Pre-Alpha",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
25
|
+
"Typing :: Typed",
|
|
26
|
+
]
|
|
27
|
+
# The base install stays light: no torch. Everything runs on saved graphs, the toy model or cached labels.
|
|
28
|
+
dependencies = [
|
|
29
|
+
"numpy>=1.26",
|
|
30
|
+
"pydantic>=2.7,<3",
|
|
31
|
+
"typer>=0.12",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
# circuit-tracer is installed from GitHub (it is not on PyPI): see the README.
|
|
36
|
+
tracer = ["torch>=2.4", "transformers>=4.51"]
|
|
37
|
+
anthropic = ["anthropic>=0.69"]
|
|
38
|
+
openai = ["openai>=1.100"]
|
|
39
|
+
dev = ["pytest>=9.0", "pytest-cov>=7.0", "ruff>=0.17", "mypy>=2.0", "nbclient>=0.10", "nbformat>=5.10", "ipykernel>=6.29"]
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
circuitcheck = "circuitcheck.cli:app"
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/albertlieyingadrian/circuitcheck"
|
|
46
|
+
Source = "https://github.com/albertlieyingadrian/circuitcheck"
|
|
47
|
+
Documentation = "https://github.com/albertlieyingadrian/circuitcheck/tree/main/docs"
|
|
48
|
+
Changelog = "https://github.com/albertlieyingadrian/circuitcheck/blob/main/CHANGELOG.md"
|
|
49
|
+
|
|
50
|
+
[dependency-groups]
|
|
51
|
+
dev = [
|
|
52
|
+
"pytest>=9.0",
|
|
53
|
+
"pytest-cov>=7.0",
|
|
54
|
+
"ruff>=0.17",
|
|
55
|
+
"mypy>=2.0",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
[tool.uv.build-backend]
|
|
59
|
+
module-name = "circuitcheck"
|
|
60
|
+
# The wheel ships only src/circuitcheck; the sdist also carries the tests.
|
|
61
|
+
source-include = ["tests/**"]
|
|
62
|
+
|
|
63
|
+
[tool.pytest.ini_options]
|
|
64
|
+
testpaths = ["tests"]
|
|
65
|
+
addopts = "-ra --strict-markers --strict-config -m 'not slow'"
|
|
66
|
+
markers = [
|
|
67
|
+
"slow: real-model or network runs, excluded by default (run with -m slow)",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
[tool.ruff]
|
|
71
|
+
line-length = 120
|
|
72
|
+
target-version = "py311"
|
|
73
|
+
# Agent skills are third-party tooling, not this project's code.
|
|
74
|
+
extend-exclude = [".agents", ".claude"]
|
|
75
|
+
|
|
76
|
+
[tool.ruff.lint]
|
|
77
|
+
select = ["E", "F", "W", "I", "B", "UP", "SIM", "RUF"]
|
|
78
|
+
ignore = ["RUF001", "RUF002", "RUF003"]
|
|
79
|
+
|
|
80
|
+
[tool.mypy]
|
|
81
|
+
python_version = "3.11"
|
|
82
|
+
strict = true
|
|
83
|
+
plugins = ["pydantic.mypy"]
|
|
84
|
+
|
|
85
|
+
[[tool.mypy.overrides]]
|
|
86
|
+
module = ["torch", "torch.*", "transformers", "transformers.*", "circuit_tracer", "circuit_tracer.*", "anthropic", "anthropic.*", "openai", "openai.*"]
|
|
87
|
+
ignore_missing_imports = true
|
|
88
|
+
|
|
89
|
+
[tool.coverage.run]
|
|
90
|
+
source = ["circuitcheck"]
|
|
91
|
+
branch = true
|
|
92
|
+
|
|
93
|
+
[tool.coverage.report]
|
|
94
|
+
show_missing = true
|
|
95
|
+
skip_covered = true
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""circuitcheck: tools on top of circuit-tracer.
|
|
2
|
+
|
|
3
|
+
- `circuitcheck.diff`: compare attribution graphs before and after fine-tuning or RL;
|
|
4
|
+
- `circuitcheck.validate`: test whether graph features are a switch or a readout, against random and matched-norm
|
|
5
|
+
controls;
|
|
6
|
+
- `circuitcheck.labels`: cheap automatic feature labels with detection and simulation scores;
|
|
7
|
+
- `circuitcheck.tracer`: the circuit-tracer adapter (needs torch and circuit-tracer);
|
|
8
|
+
- `circuitcheck.report`: HTML pages for all three.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
12
|
+
|
|
13
|
+
try:
|
|
14
|
+
__version__ = version("circuitcheck")
|
|
15
|
+
except PackageNotFoundError: # pragma: no cover
|
|
16
|
+
__version__ = "0.0.0+local"
|