mechbench-compute 0.10.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.
Files changed (53) hide show
  1. mechbench_compute-0.10.0/PKG-INFO +179 -0
  2. mechbench_compute-0.10.0/README.md +147 -0
  3. mechbench_compute-0.10.0/mechbench_compute/__init__.py +312 -0
  4. mechbench_compute-0.10.0/mechbench_compute/_arch.py +267 -0
  5. mechbench_compute-0.10.0/mechbench_compute/_forward.py +358 -0
  6. mechbench_compute-0.10.0/mechbench_compute/_forward_gemma3.py +218 -0
  7. mechbench_compute-0.10.0/mechbench_compute/_forward_llama.py +205 -0
  8. mechbench_compute-0.10.0/mechbench_compute/_forward_qwen.py +192 -0
  9. mechbench_compute-0.10.0/mechbench_compute/_smoke.py +74 -0
  10. mechbench_compute-0.10.0/mechbench_compute/_smoke_12b.py +129 -0
  11. mechbench_compute-0.10.0/mechbench_compute/_smoke_attribution.py +99 -0
  12. mechbench_compute-0.10.0/mechbench_compute/_smoke_bench.py +77 -0
  13. mechbench_compute-0.10.0/mechbench_compute/_smoke_bitexact.py +171 -0
  14. mechbench_compute-0.10.0/mechbench_compute/_smoke_distill.py +332 -0
  15. mechbench_compute-0.10.0/mechbench_compute/_smoke_e2b.py +152 -0
  16. mechbench_compute-0.10.0/mechbench_compute/_smoke_interventions.py +67 -0
  17. mechbench_compute-0.10.0/mechbench_compute/_smoke_plots.py +174 -0
  18. mechbench_compute-0.10.0/mechbench_compute/attribution.py +254 -0
  19. mechbench_compute-0.10.0/mechbench_compute/backends.py +91 -0
  20. mechbench_compute-0.10.0/mechbench_compute/bench.py +229 -0
  21. mechbench_compute-0.10.0/mechbench_compute/blocks.py +486 -0
  22. mechbench_compute-0.10.0/mechbench_compute/cache.py +81 -0
  23. mechbench_compute-0.10.0/mechbench_compute/distill.py +753 -0
  24. mechbench_compute-0.10.0/mechbench_compute/errors.py +81 -0
  25. mechbench_compute-0.10.0/mechbench_compute/finetune.py +113 -0
  26. mechbench_compute-0.10.0/mechbench_compute/generate.py +295 -0
  27. mechbench_compute-0.10.0/mechbench_compute/geometry.py +614 -0
  28. mechbench_compute-0.10.0/mechbench_compute/head_weights.py +478 -0
  29. mechbench_compute-0.10.0/mechbench_compute/hooks.py +106 -0
  30. mechbench_compute-0.10.0/mechbench_compute/hub.py +186 -0
  31. mechbench_compute-0.10.0/mechbench_compute/interventions.py +578 -0
  32. mechbench_compute-0.10.0/mechbench_compute/inventory.py +185 -0
  33. mechbench_compute-0.10.0/mechbench_compute/lens.py +124 -0
  34. mechbench_compute-0.10.0/mechbench_compute/lm_bridge.py +77 -0
  35. mechbench_compute-0.10.0/mechbench_compute/lora.py +137 -0
  36. mechbench_compute-0.10.0/mechbench_compute/model.py +439 -0
  37. mechbench_compute-0.10.0/mechbench_compute/peft.py +123 -0
  38. mechbench_compute-0.10.0/mechbench_compute/platform_kinds.py +138 -0
  39. mechbench_compute-0.10.0/mechbench_compute/plot.py +817 -0
  40. mechbench_compute-0.10.0/mechbench_compute/probes.py +274 -0
  41. mechbench_compute-0.10.0/mechbench_compute/prompts.py +212 -0
  42. mechbench_compute-0.10.0/mechbench_compute/protocol.py +1198 -0
  43. mechbench_compute-0.10.0/mechbench_compute.egg-info/PKG-INFO +179 -0
  44. mechbench_compute-0.10.0/mechbench_compute.egg-info/SOURCES.txt +51 -0
  45. mechbench_compute-0.10.0/mechbench_compute.egg-info/dependency_links.txt +1 -0
  46. mechbench_compute-0.10.0/mechbench_compute.egg-info/requires.txt +18 -0
  47. mechbench_compute-0.10.0/mechbench_compute.egg-info/top_level.txt +1 -0
  48. mechbench_compute-0.10.0/pyproject.toml +51 -0
  49. mechbench_compute-0.10.0/setup.cfg +4 -0
  50. mechbench_compute-0.10.0/tests/test_backends.py +112 -0
  51. mechbench_compute-0.10.0/tests/test_blocks.py +173 -0
  52. mechbench_compute-0.10.0/tests/test_hub.py +16 -0
  53. mechbench_compute-0.10.0/tests/test_inventory.py +130 -0
@@ -0,0 +1,179 @@
1
+ Metadata-Version: 2.4
2
+ Name: mechbench-compute
3
+ Version: 0.10.0
4
+ Summary: Composable mechanistic-interpretability primitives: hook-aware forward, interventions, activation cache, logit lens, probes, and geometry.
5
+ Author-email: Benji Smith <benji@shaxpir.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/mechbench/mechbench-compute
8
+ Project-URL: Website, https://mechbench.ai
9
+ Keywords: interpretability,mechanistic-interpretability,mlx,transformers,gemma
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
+ Requires-Python: >=3.11
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: huggingface-hub>=0.24
19
+ Requires-Dist: mechbench-schema>=0.13
20
+ Requires-Dist: mlx>=0.20; sys_platform == "darwin" and platform_machine == "arm64"
21
+ Requires-Dist: mlx-lm>=0.20; sys_platform == "darwin" and platform_machine == "arm64"
22
+ Requires-Dist: mlx-vlm>=0.6.1; sys_platform == "darwin" and platform_machine == "arm64"
23
+ Requires-Dist: numpy>=1.26
24
+ Requires-Dist: transformers>=4.40
25
+ Requires-Dist: matplotlib>=3.8
26
+ Requires-Dist: scikit-learn>=1.4
27
+ Provides-Extra: eval
28
+ Requires-Dist: lm-eval>=0.4; extra == "eval"
29
+ Provides-Extra: dev
30
+ Requires-Dist: pytest>=8; extra == "dev"
31
+ Requires-Dist: ruff>=0.5; extra == "dev"
32
+
33
+ # mechbench-compute
34
+
35
+ The compute engine for [mechbench](https://mechbench.ai) — composable mechanistic-interpretability primitives built on MLX.
36
+
37
+ This repo provides:
38
+
39
+ - **Hook-aware forward pass.** One canonical path through the model; instrumentation via named hook points and TransformerLens-style callbacks.
40
+ - **Declarative interventions.** `Ablate`, `Capture`, `Patch` primitives composable into a single `model.run(..., interventions=[...])` call.
41
+ - **Activation cache.** `ActivationCache` container for collected activations; bf16 throughout, float32 only at the analysis boundary.
42
+ - **Architecture adapter.** `Arch` dataclass that handles per-variant differences (layer count, global-attention pattern, RoPE parameters, etc.). Currently supports Gemma 4 E4B and E2B; the adapter pattern follows TransformerLens 3's `TransformerBridge`.
43
+ - **Analysis helpers.** Logit lens, direct logit attribution (`accumulated_resid`, `decompose_resid`, `head_results`, `logit_attrs`), fact vectors, centroid decoding, probe primitives, head-weight static analysis, geometry metrics.
44
+ - **Plot helpers.** Matplotlib conventions baked in for quick diagnostic figures — not the full visualization surface (that lives in `mechbench-ui`).
45
+
46
+ See [`PACKAGE_README.md`](PACKAGE_README.md) for the full API tour and worked examples.
47
+
48
+ ## Install
49
+
50
+ ```bash
51
+ pip install mechbench-compute
52
+ ```
53
+
54
+ From source:
55
+
56
+ ```bash
57
+ git clone https://github.com/mechbench/mechbench-compute.git
58
+ cd mechbench-compute
59
+ pip install -e '.[dev]'
60
+ ```
61
+
62
+ Apple Silicon required (MLX is the only supported backend today). A PyTorch backend would live as `mechbench_compute.backends.torch` alongside the MLX one if/when the need arises; splitting repos by backend is explicitly not planned.
63
+
64
+ ## Quick start
65
+
66
+ ```python
67
+ from mechbench_compute import Model, Ablate, Capture
68
+
69
+ model = Model.load()
70
+ ids = model.tokenize("Complete this sentence with one word: The Eiffel Tower is in")
71
+
72
+ result = model.run(ids)
73
+ for tok, p in result.top_k(model.tokenizer, k=5):
74
+ print(f"{tok!r:20s} p={p:.4f}")
75
+ ```
76
+
77
+ ## Distributional-target training (`distill` + `lora`)
78
+
79
+ Primitives for training a model toward a specified *distribution* over
80
+ responses rather than toward example responses (task
81
+ `000114`):
82
+ soft-target cross-entropy at decision tokens has gradient P − T, so the
83
+ adapter learns to *emit the distribution*.
84
+
85
+ ```python
86
+ import mlx.nn as nn
87
+ import mlx.optimizers as optim
88
+ import numpy as np
89
+ from mechbench_compute import Model, distill, lora
90
+ from mechbench_compute.distill import TargetMap
91
+
92
+ model = Model.load()
93
+ tok = model.tokenizer
94
+
95
+ # A target is a Map<String, Double> — hardcoded, from JSON, or uniform —
96
+ # with whole-map transforms that each return a new map:
97
+ target = TargetMap.from_json("weights.json").sqrt().normalize()
98
+ target = TargetMap.uniform([str(i) for i in range(1, 7)]) # fair d6
99
+
100
+ # Compile it against the rendered prompt: items become token paths
101
+ # (multi-token items share trie nodes; a closer appends continuation
102
+ # anchors so the flattening can't leak past the envelope):
103
+ prompt = distill.render_chat(tok, system, "Please roll the die.",
104
+ prefill='{ "roll": ')
105
+ trie = target.tokenize(tok, prompt, closer=" }")
106
+
107
+ n = lora.apply_lora(model.lm) # freeze + wrap q/v projections
108
+ step = nn.value_and_grad(model.lm, distill.soft_ce)
109
+ opt = optim.Adam(learning_rate=1e-4)
110
+ rng = np.random.default_rng(7)
111
+ for _ in range(steps):
112
+ batch = [trie.hard_example(trie.sample(rng)) for _ in range(3)]
113
+ batch.append(trie.marginal_example()) # exact first-token marginal
114
+ batch.append(sharp_anchor) # keeps confident tasks sharp
115
+ loss, grads = step(model.lm, batch)
116
+ opt.update(model.lm, grads)
117
+
118
+ lora.save_adapter(model.lm, "adapter.safetensors")
119
+ # Later, on a fresh model: merge + exact undo
120
+ handle = lora.fuse(model.lm, lora.load_adapter("adapter.safetensors"),
121
+ scale=16 / 8) # alpha / rank from training
122
+ lora.restore(model.lm, handle)
123
+ ```
124
+
125
+ Calibration is measured at item level (`trie.score`, `distill.item_metrics`
126
+ — captured mass, entropy, KL from target) and at the decision token
127
+ (`distill.first_token_metrics`). `python -m mechbench_compute._smoke_distill`
128
+ runs the full lifecycle on E2B.
129
+
130
+ **Two forward paths.** Training and scoring call `Model.lm` (the text
131
+ decoder, uniform across families) directly — plain module calls,
132
+ differentiable, no instrumentation. `Model.run` remains the hook-aware
133
+ forward for capture/patch/lens work. Adapters bridge the two: `fuse` an
134
+ adapter into the weights and every instrumented run sees the adapted
135
+ model; `restore` flips it back, so base-vs-adapted comparisons run in one
136
+ script.
137
+
138
+ **Scoring tiers** (task 000227): `score_items` is the sequential
139
+ reference oracle; `score_items_batched` adds length-bucketed batching
140
+ (~1.5×); `score_items_fast` additionally splits the forward via
141
+ `Model.trunk_hidden` / `Model.head_logits` and unembeds only the
142
+ supervised rows (~1.5–2.1× vs oracle, family-dependent — best when
143
+ items share no prefix, e.g. cross-document scoring);
144
+ `score_items_cached` encodes a shared prompt **once** into a KV cache
145
+ (`Model.prompt_cache`) and scores each item's 1–4 suffix tokens against
146
+ a per-item copy — the tier for shared-prompt batteries. Measured
147
+ (flat name batteries, cached vs oracle): E2B 2.8×, Gemma-3-4B 3.4×,
148
+ Qwen-3B 2.6×, Llama-8B 3.0×; positions/attention exact by construction,
149
+ bf16 envelope from decomposed attention: mass-region |ΔlogP| ≤ 0.45,
150
+ renormalized KL ≤ 2.4e-2 bits, idempotent (0.0 across repeat calls).
151
+ Fast-tier envelope: max |ΔlogP| ≤ 0.99 (deep tail) / ≤ 0.24
152
+ (mass region), renorm KL ≤ 6.5e-3 bits. All of it is bf16 matmul
153
+ tiling — same rows, same math, verified bit-exact where shapes match.
154
+ Flat-target KL diagnostics weight the tail, so switch tiers only
155
+ between comparisons, never mid-experiment. Known upstream limit
156
+ (mlx 0.31.2 / mlx-lm 0.31.3 / mlx-vlm 0.6.1): **batched cached decoding
157
+ corrupts every batch row after the first** on both stacks (reproduced
158
+ with natively built B=4 caches and identical rows), which is why the
159
+ cached tier is per-item; batched suffix scoring behind an upstream fix
160
+ is the remaining ~5–10× path.
161
+
162
+ ## Status
163
+
164
+ The `Arch` adapter supports Gemma 4 E4B and E2B; generalization to other architecture families is ongoing.
165
+
166
+ The substrate epic that will define how intermediate results are cached and shared across experiments is `000162` (DAG solver + content-addressed memoization). It consumes the canonical-serialization guarantee from `000161` (binary formats) and the path grammar from `000163` (identity scheme).
167
+
168
+ ## Relationship to other mechbench repos
169
+
170
+ - **`mechbench-schema`** — the typed emission contract. `mechbench-compute` emits records shaped by schema types.
171
+ - **`mechbench-experiments`** — research scripts and findings that consume this package. Uses `mechbench-compute` as its primary dependency.
172
+ - **`mechbench-runner`** — exposes these primitives as agent-callable tools. Imports `mechbench-compute`.
173
+ - **`mechbench-ui`** — TypeScript frontend. Does not import `mechbench-compute` directly; reads bundles produced by it through the `mechbench-schema` contract.
174
+
175
+ See [mechbench.ai](https://mechbench.ai) for the family overview and the design principles.
176
+
177
+ ## License
178
+
179
+ MIT.
@@ -0,0 +1,147 @@
1
+ # mechbench-compute
2
+
3
+ The compute engine for [mechbench](https://mechbench.ai) — composable mechanistic-interpretability primitives built on MLX.
4
+
5
+ This repo provides:
6
+
7
+ - **Hook-aware forward pass.** One canonical path through the model; instrumentation via named hook points and TransformerLens-style callbacks.
8
+ - **Declarative interventions.** `Ablate`, `Capture`, `Patch` primitives composable into a single `model.run(..., interventions=[...])` call.
9
+ - **Activation cache.** `ActivationCache` container for collected activations; bf16 throughout, float32 only at the analysis boundary.
10
+ - **Architecture adapter.** `Arch` dataclass that handles per-variant differences (layer count, global-attention pattern, RoPE parameters, etc.). Currently supports Gemma 4 E4B and E2B; the adapter pattern follows TransformerLens 3's `TransformerBridge`.
11
+ - **Analysis helpers.** Logit lens, direct logit attribution (`accumulated_resid`, `decompose_resid`, `head_results`, `logit_attrs`), fact vectors, centroid decoding, probe primitives, head-weight static analysis, geometry metrics.
12
+ - **Plot helpers.** Matplotlib conventions baked in for quick diagnostic figures — not the full visualization surface (that lives in `mechbench-ui`).
13
+
14
+ See [`PACKAGE_README.md`](PACKAGE_README.md) for the full API tour and worked examples.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ pip install mechbench-compute
20
+ ```
21
+
22
+ From source:
23
+
24
+ ```bash
25
+ git clone https://github.com/mechbench/mechbench-compute.git
26
+ cd mechbench-compute
27
+ pip install -e '.[dev]'
28
+ ```
29
+
30
+ Apple Silicon required (MLX is the only supported backend today). A PyTorch backend would live as `mechbench_compute.backends.torch` alongside the MLX one if/when the need arises; splitting repos by backend is explicitly not planned.
31
+
32
+ ## Quick start
33
+
34
+ ```python
35
+ from mechbench_compute import Model, Ablate, Capture
36
+
37
+ model = Model.load()
38
+ ids = model.tokenize("Complete this sentence with one word: The Eiffel Tower is in")
39
+
40
+ result = model.run(ids)
41
+ for tok, p in result.top_k(model.tokenizer, k=5):
42
+ print(f"{tok!r:20s} p={p:.4f}")
43
+ ```
44
+
45
+ ## Distributional-target training (`distill` + `lora`)
46
+
47
+ Primitives for training a model toward a specified *distribution* over
48
+ responses rather than toward example responses (task
49
+ `000114`):
50
+ soft-target cross-entropy at decision tokens has gradient P − T, so the
51
+ adapter learns to *emit the distribution*.
52
+
53
+ ```python
54
+ import mlx.nn as nn
55
+ import mlx.optimizers as optim
56
+ import numpy as np
57
+ from mechbench_compute import Model, distill, lora
58
+ from mechbench_compute.distill import TargetMap
59
+
60
+ model = Model.load()
61
+ tok = model.tokenizer
62
+
63
+ # A target is a Map<String, Double> — hardcoded, from JSON, or uniform —
64
+ # with whole-map transforms that each return a new map:
65
+ target = TargetMap.from_json("weights.json").sqrt().normalize()
66
+ target = TargetMap.uniform([str(i) for i in range(1, 7)]) # fair d6
67
+
68
+ # Compile it against the rendered prompt: items become token paths
69
+ # (multi-token items share trie nodes; a closer appends continuation
70
+ # anchors so the flattening can't leak past the envelope):
71
+ prompt = distill.render_chat(tok, system, "Please roll the die.",
72
+ prefill='{ "roll": ')
73
+ trie = target.tokenize(tok, prompt, closer=" }")
74
+
75
+ n = lora.apply_lora(model.lm) # freeze + wrap q/v projections
76
+ step = nn.value_and_grad(model.lm, distill.soft_ce)
77
+ opt = optim.Adam(learning_rate=1e-4)
78
+ rng = np.random.default_rng(7)
79
+ for _ in range(steps):
80
+ batch = [trie.hard_example(trie.sample(rng)) for _ in range(3)]
81
+ batch.append(trie.marginal_example()) # exact first-token marginal
82
+ batch.append(sharp_anchor) # keeps confident tasks sharp
83
+ loss, grads = step(model.lm, batch)
84
+ opt.update(model.lm, grads)
85
+
86
+ lora.save_adapter(model.lm, "adapter.safetensors")
87
+ # Later, on a fresh model: merge + exact undo
88
+ handle = lora.fuse(model.lm, lora.load_adapter("adapter.safetensors"),
89
+ scale=16 / 8) # alpha / rank from training
90
+ lora.restore(model.lm, handle)
91
+ ```
92
+
93
+ Calibration is measured at item level (`trie.score`, `distill.item_metrics`
94
+ — captured mass, entropy, KL from target) and at the decision token
95
+ (`distill.first_token_metrics`). `python -m mechbench_compute._smoke_distill`
96
+ runs the full lifecycle on E2B.
97
+
98
+ **Two forward paths.** Training and scoring call `Model.lm` (the text
99
+ decoder, uniform across families) directly — plain module calls,
100
+ differentiable, no instrumentation. `Model.run` remains the hook-aware
101
+ forward for capture/patch/lens work. Adapters bridge the two: `fuse` an
102
+ adapter into the weights and every instrumented run sees the adapted
103
+ model; `restore` flips it back, so base-vs-adapted comparisons run in one
104
+ script.
105
+
106
+ **Scoring tiers** (task 000227): `score_items` is the sequential
107
+ reference oracle; `score_items_batched` adds length-bucketed batching
108
+ (~1.5×); `score_items_fast` additionally splits the forward via
109
+ `Model.trunk_hidden` / `Model.head_logits` and unembeds only the
110
+ supervised rows (~1.5–2.1× vs oracle, family-dependent — best when
111
+ items share no prefix, e.g. cross-document scoring);
112
+ `score_items_cached` encodes a shared prompt **once** into a KV cache
113
+ (`Model.prompt_cache`) and scores each item's 1–4 suffix tokens against
114
+ a per-item copy — the tier for shared-prompt batteries. Measured
115
+ (flat name batteries, cached vs oracle): E2B 2.8×, Gemma-3-4B 3.4×,
116
+ Qwen-3B 2.6×, Llama-8B 3.0×; positions/attention exact by construction,
117
+ bf16 envelope from decomposed attention: mass-region |ΔlogP| ≤ 0.45,
118
+ renormalized KL ≤ 2.4e-2 bits, idempotent (0.0 across repeat calls).
119
+ Fast-tier envelope: max |ΔlogP| ≤ 0.99 (deep tail) / ≤ 0.24
120
+ (mass region), renorm KL ≤ 6.5e-3 bits. All of it is bf16 matmul
121
+ tiling — same rows, same math, verified bit-exact where shapes match.
122
+ Flat-target KL diagnostics weight the tail, so switch tiers only
123
+ between comparisons, never mid-experiment. Known upstream limit
124
+ (mlx 0.31.2 / mlx-lm 0.31.3 / mlx-vlm 0.6.1): **batched cached decoding
125
+ corrupts every batch row after the first** on both stacks (reproduced
126
+ with natively built B=4 caches and identical rows), which is why the
127
+ cached tier is per-item; batched suffix scoring behind an upstream fix
128
+ is the remaining ~5–10× path.
129
+
130
+ ## Status
131
+
132
+ The `Arch` adapter supports Gemma 4 E4B and E2B; generalization to other architecture families is ongoing.
133
+
134
+ The substrate epic that will define how intermediate results are cached and shared across experiments is `000162` (DAG solver + content-addressed memoization). It consumes the canonical-serialization guarantee from `000161` (binary formats) and the path grammar from `000163` (identity scheme).
135
+
136
+ ## Relationship to other mechbench repos
137
+
138
+ - **`mechbench-schema`** — the typed emission contract. `mechbench-compute` emits records shaped by schema types.
139
+ - **`mechbench-experiments`** — research scripts and findings that consume this package. Uses `mechbench-compute` as its primary dependency.
140
+ - **`mechbench-runner`** — exposes these primitives as agent-callable tools. Imports `mechbench-compute`.
141
+ - **`mechbench-ui`** — TypeScript frontend. Does not import `mechbench-compute` directly; reads bundles produced by it through the `mechbench-schema` contract.
142
+
143
+ See [mechbench.ai](https://mechbench.ai) for the family overview and the design principles.
144
+
145
+ ## License
146
+
147
+ MIT.
@@ -0,0 +1,312 @@
1
+ """mechbench_compute — a mechanistic-interpretability framework for
2
+ Google's Gemma 4 E4B running locally on Apple Silicon via MLX.
3
+
4
+ Quick start:
5
+
6
+ from mechbench_compute import Model
7
+
8
+ model = Model.load("mlx-community/gemma-4-E4B-it-bf16")
9
+ ids = model.tokenize("Complete this sentence with one word: The Eiffel Tower is in")
10
+
11
+ # Forward pass, no instrumentation:
12
+ result = model.run(ids)
13
+ for tok, p in result.top_k(model.tokenizer, k=5):
14
+ print(f'{tok!r:20s} p={p:.4f}')
15
+
16
+ Capture activations:
17
+
18
+ result = model.run(ids, capture=['blocks.23.attn.weights',
19
+ 'blocks.14.mlp_out'])
20
+ weights = result.cache['blocks.23.attn.weights'] # [1, 8, S, S], bf16
21
+ mlp = result.cache['blocks.14.mlp_out'] # [1, S, 2560], bf16
22
+
23
+ Modify activations with a hook:
24
+
25
+ import mlx.core as mx
26
+
27
+ def zero_layer_14_mlp(act, info):
28
+ return mx.zeros_like(act)
29
+
30
+ result = model.run(ids, hooks={'blocks.14.mlp_out': zero_layer_14_mlp})
31
+
32
+ Declarative interventions (Ablate / Capture / Patch), prompt tooling,
33
+ logit-lens + geometry helpers, and matplotlib plot helpers are all
34
+ re-exported from this module — see README.md for the full API tour.
35
+
36
+ The full list of hook points is at mechbench_compute.all_hook_names().
37
+ """
38
+
39
+ # Everything below the substrate check assumes a substrate. Rather than
40
+ # refusing to import at all, the package now *loads* anywhere and refuses
41
+ # on use — because the modules that report on a machine with no backend
42
+ # (`backends`, `inventory`) live inside this package, and a gate at
43
+ # import time made them reachable only from a machine that did not need
44
+ # them. `doctor` is exactly that machine's tool.
45
+ from .backends import (
46
+ BACKENDS,
47
+ Backend,
48
+ active as active_backend,
49
+ available as available_backends,
50
+ describe_platform,
51
+ require as _require_backend,
52
+ )
53
+
54
+ if active_backend() is not None:
55
+ from ._arch import (
56
+ Arch,
57
+ D_MODEL,
58
+ GLOBAL_LAYERS,
59
+ LAYER_HOOK_POINTS,
60
+ N_HEADS,
61
+ N_LAYERS,
62
+ VOCAB_SIZE,
63
+ all_hook_names,
64
+ layer_type,
65
+ )
66
+ from .cache import ActivationCache
67
+ from .errors import (
68
+ CacheKeyError,
69
+ InterpError,
70
+ InvalidHookName,
71
+ LayerIndexOutOfRange,
72
+ )
73
+ from .geometry import (
74
+ VocabConcentration,
75
+ centroid_decode,
76
+ cluster_purity,
77
+ cohesion,
78
+ cosine_matrix,
79
+ effective_vocab_size,
80
+ entropy_bits,
81
+ fact_vectors,
82
+ fact_vectors_at,
83
+ fact_vectors_at_hook,
84
+ fact_vectors_pooled,
85
+ intra_inter_separation,
86
+ iterate_clusters,
87
+ nearest_neighbor_purity,
88
+ orthogonalize_against,
89
+ silhouette_cosine,
90
+ top_k_mass,
91
+ vocab_concentration,
92
+ )
93
+ from .generate import generate_labeled_corpus, generate_text
94
+ from .head_weights import (
95
+ CircuitAnalysis,
96
+ CircuitComponent,
97
+ HeadSpec,
98
+ PositionWrite,
99
+ get_head_spec,
100
+ head_key_tokens,
101
+ head_ov_actual_writes,
102
+ head_ov_position_writes,
103
+ head_read_tokens,
104
+ ov_circuit,
105
+ qk_circuit,
106
+ )
107
+ from .attribution import (
108
+ accumulated_resid,
109
+ decompose_resid,
110
+ head_results,
111
+ logit_attrs,
112
+ )
113
+ from .probes import Probe
114
+ from .hooks import HookFn, HookInfo, parse_hook_name
115
+ from .interventions import Ablate, Capture, Intervention, Patch, compose
116
+ from .lens import logit_lens_final, logit_lens_per_position
117
+ from .model import Model, RunResult
118
+ from .plot import (
119
+ bar_by_layer,
120
+ grouped_row_heatmap,
121
+ head_heatmap,
122
+ intensity_curve,
123
+ leaderboard_bar,
124
+ lens_trajectory,
125
+ logprob_trajectory,
126
+ pca_scatter,
127
+ position_heatmap,
128
+ probe_diagonal_heatmap,
129
+ similarity_heatmap,
130
+ )
131
+ from .prompts import (
132
+ Prompt,
133
+ PromptSet,
134
+ ValidatedPrompt,
135
+ ValidatedPromptSet,
136
+ )
137
+ from .distill import (
138
+ Example,
139
+ TargetMap,
140
+ TargetTrie,
141
+ first_token_metrics,
142
+ item_metrics,
143
+ render_chat,
144
+ score_items,
145
+ score_items_batched,
146
+ score_items_cached,
147
+ score_items_fast,
148
+ soft_ce,
149
+ )
150
+ from .lora import (
151
+ LoRALinear,
152
+ apply_lora,
153
+ fuse,
154
+ load_adapter,
155
+ restore,
156
+ save_adapter,
157
+ )
158
+
159
+ else:
160
+
161
+ def __getattr__(name: str) -> object:
162
+ """Explain, rather than fail with a missing-module traceback.
163
+
164
+ Reached only on a machine with no compute backend, where every
165
+ name below is genuinely unavailable — so the same explanation is
166
+ the right answer for all of them.
167
+
168
+ Submodules that touch MLX directly (`model`, `protocol`, …) are
169
+ not covered: importing one still raises out of MLX itself. That
170
+ is the situation `mechbench-runner doctor` exists to catch first.
171
+ """
172
+ if name.startswith("__"):
173
+ raise AttributeError(name)
174
+
175
+ # Submodules that do not touch the substrate stay reachable —
176
+ # `backends` and `inventory` are precisely what reports on a
177
+ # machine like this one, and `from mechbench_compute import
178
+ # inventory` has to keep working. Without this the package
179
+ # attribute hook shadows the submodule and answers "no backend"
180
+ # for a module that never needed one.
181
+ import importlib
182
+
183
+ try:
184
+ return importlib.import_module(f".{name}", __name__)
185
+ except ImportError:
186
+ pass
187
+
188
+ # Either a name that lives behind the substrate, or a submodule
189
+ # that failed on MLX itself. Both get the explanation rather than
190
+ # a traceback out of a package nobody asked for.
191
+ _require_backend()
192
+ raise AttributeError(name) # unreachable; require() always raises
193
+
194
+ __version__ = "0.10.0"
195
+
196
+ __all__ = [
197
+ # Substrate — the part that answers on a machine with no backend
198
+ "Backend",
199
+ "BACKENDS",
200
+ "active_backend",
201
+ "available_backends",
202
+ "describe_platform",
203
+ # Main API
204
+ "Model",
205
+ "RunResult",
206
+ "ActivationCache",
207
+ # Declarative interventions
208
+ "Ablate",
209
+ "Capture",
210
+ "Patch",
211
+ "Intervention",
212
+ "compose",
213
+ # Prompts (specific prompt collections live in experiments.prompts)
214
+ "Prompt",
215
+ "PromptSet",
216
+ "ValidatedPrompt",
217
+ "ValidatedPromptSet",
218
+ # Distributional-target training (distill + lora, task 000114)
219
+ "TargetMap",
220
+ "TargetTrie",
221
+ "Example",
222
+ "soft_ce",
223
+ "render_chat",
224
+ "score_items",
225
+ "score_items_batched",
226
+ "score_items_cached",
227
+ "score_items_fast",
228
+ "item_metrics",
229
+ "first_token_metrics",
230
+ "LoRALinear",
231
+ "apply_lora",
232
+ "save_adapter",
233
+ "load_adapter",
234
+ "fuse",
235
+ "restore",
236
+ # Logit lens
237
+ "logit_lens_final",
238
+ "logit_lens_per_position",
239
+ # Direct logit attribution + residual decomposition
240
+ "accumulated_resid",
241
+ "decompose_resid",
242
+ "head_results",
243
+ "logit_attrs",
244
+ # Fact vectors + geometry
245
+ "fact_vectors",
246
+ "fact_vectors_at",
247
+ "fact_vectors_at_hook",
248
+ "fact_vectors_pooled",
249
+ "centroid_decode",
250
+ "cohesion",
251
+ "cosine_matrix",
252
+ "intra_inter_separation",
253
+ "iterate_clusters",
254
+ "cluster_purity",
255
+ "silhouette_cosine",
256
+ "nearest_neighbor_purity",
257
+ "orthogonalize_against",
258
+ # Probes (persistent concept vectors)
259
+ "Probe",
260
+ # Generation (for corpus-building workflows)
261
+ "generate_text",
262
+ "generate_labeled_corpus",
263
+ # Head-weight analysis (static interp on W_Q/W_K/W_V/W_O)
264
+ "HeadSpec",
265
+ "CircuitComponent",
266
+ "CircuitAnalysis",
267
+ "PositionWrite",
268
+ "get_head_spec",
269
+ "head_read_tokens",
270
+ "head_key_tokens",
271
+ "qk_circuit",
272
+ "ov_circuit",
273
+ "head_ov_position_writes",
274
+ "head_ov_actual_writes",
275
+ # Vocabulary-space concentration
276
+ "VocabConcentration",
277
+ "vocab_concentration",
278
+ "top_k_mass",
279
+ "entropy_bits",
280
+ "effective_vocab_size",
281
+ # Plot helpers
282
+ "bar_by_layer",
283
+ "lens_trajectory",
284
+ "logprob_trajectory",
285
+ "position_heatmap",
286
+ "pca_scatter",
287
+ "similarity_heatmap",
288
+ "head_heatmap",
289
+ "probe_diagonal_heatmap",
290
+ "grouped_row_heatmap",
291
+ "intensity_curve",
292
+ "leaderboard_bar",
293
+ # Hook types (for users writing raw callbacks)
294
+ "HookInfo",
295
+ "HookFn",
296
+ "parse_hook_name",
297
+ # Architecture facts
298
+ "Arch",
299
+ "N_LAYERS",
300
+ "D_MODEL",
301
+ "N_HEADS",
302
+ "VOCAB_SIZE",
303
+ "GLOBAL_LAYERS",
304
+ "LAYER_HOOK_POINTS",
305
+ "layer_type",
306
+ "all_hook_names",
307
+ # Errors
308
+ "InterpError",
309
+ "InvalidHookName",
310
+ "LayerIndexOutOfRange",
311
+ "CacheKeyError",
312
+ ]