brainpatch 1.2.0__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.
- brainpatch/__init__.py +92 -0
- brainpatch/backends/__init__.py +19 -0
- brainpatch/backends/llamacpp.py +383 -0
- brainpatch/backends/mlx_backend.py +213 -0
- brainpatch/backends/transformers_backend.py +473 -0
- brainpatch/backends/vllm_backend.py +299 -0
- brainpatch/backends/vllm_worker.py +129 -0
- brainpatch/cli.py +825 -0
- brainpatch/config.py +245 -0
- brainpatch/datasets/__init__.py +20 -0
- brainpatch/datasets/contrast_sets.py +64 -0
- brainpatch/evaluation/__init__.py +28 -0
- brainpatch/evaluation/metrics.py +223 -0
- brainpatch/patch/__init__.py +64 -0
- brainpatch/patch/compiler.py +324 -0
- brainpatch/patch/format.py +489 -0
- brainpatch/patch/loader.py +312 -0
- brainpatch/patch/registry.py +300 -0
- brainpatch/patch/tensors.py +236 -0
- brainpatch/patch/validation.py +157 -0
- brainpatch/paths.py +184 -0
- brainpatch/py.typed +0 -0
- brainpatch/research/__init__.py +16 -0
- brainpatch/research/antisycophancy.py +348 -0
- brainpatch/research/behaviour_eval.py +711 -0
- brainpatch/research/generation_eval.py +346 -0
- brainpatch/research/ml/__init__.py +35 -0
- brainpatch/research/ml/activation_store.py +232 -0
- brainpatch/research/ml/causal.py +386 -0
- brainpatch/research/ml/corpus.py +165 -0
- brainpatch/research/ml/evaluation.py +188 -0
- brainpatch/research/ml/extraction.py +464 -0
- brainpatch/research/ml/feature_analysis.py +317 -0
- brainpatch/research/ml/generation.py +109 -0
- brainpatch/research/ml/hooks.py +183 -0
- brainpatch/research/ml/intervention.py +274 -0
- brainpatch/research/ml/model.py +219 -0
- brainpatch/research/ml/patch_search.py +337 -0
- brainpatch/research/ml/runtime.py +343 -0
- brainpatch/research/ml/sae.py +383 -0
- brainpatch/research/ml/training.py +376 -0
- brainpatch/research/stance_rubric.py +170 -0
- brainpatch/research/sycophancy_data.py +982 -0
- brainpatch/research/sycophancy_data_r1.py +1701 -0
- brainpatch/research/sycophancy_data_v2.py +1649 -0
- brainpatch/research/sycophancy_data_v3.py +2288 -0
- brainpatch/research/sycophancy_v2_build.py +362 -0
- brainpatch/research/sycophancy_v3_build.py +188 -0
- brainpatch/research/utility_probe.py +139 -0
- brainpatch/runtime/__init__.py +50 -0
- brainpatch/runtime/auto.py +157 -0
- brainpatch/runtime/base.py +311 -0
- brainpatch/runtime/capabilities.py +96 -0
- brainpatch/runtime/model.py +260 -0
- brainpatch/runtime/scheduling.py +13 -0
- brainpatch/schemas/__init__.py +35 -0
- brainpatch/schemas/contrast.py +161 -0
- brainpatch/schemas/feature.py +193 -0
- brainpatch/schemas/manifest.py +167 -0
- brainpatch/schemas/patch.py +379 -0
- brainpatch/schemas/patch_io.py +88 -0
- brainpatch/schemas/sae.py +146 -0
- brainpatch/server/__init__.py +11 -0
- brainpatch/server/app.py +269 -0
- brainpatch/steering/__init__.py +13 -0
- brainpatch/steering/plan.py +177 -0
- brainpatch/steering/schedule.py +138 -0
- brainpatch/ui/__init__.py +11 -0
- brainpatch/ui/app.py +201 -0
- brainpatch/verify/__init__.py +66 -0
- brainpatch/verify/behavioural.py +156 -0
- brainpatch/verify/checks.py +204 -0
- brainpatch/verify/corruptions.py +335 -0
- brainpatch/verify/report.py +133 -0
- brainpatch/verify/vectors.py +95 -0
- brainpatch/verify/workflow.py +331 -0
- brainpatch-1.2.0.dist-info/METADATA +556 -0
- brainpatch-1.2.0.dist-info/RECORD +82 -0
- brainpatch-1.2.0.dist-info/WHEEL +5 -0
- brainpatch-1.2.0.dist-info/entry_points.txt +2 -0
- brainpatch-1.2.0.dist-info/licenses/LICENSE +190 -0
- brainpatch-1.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""The detection layers, cheapest first.
|
|
2
|
+
|
|
3
|
+
Each layer answers a different question, and each is blind to something. The
|
|
4
|
+
point of running all of them against every corruption is to find out *what each
|
|
5
|
+
is blind to*, empirically, rather than assuming.
|
|
6
|
+
|
|
7
|
+
`unsigned_cosine` is included deliberately and is **not** a recommended check.
|
|
8
|
+
It is here because it is the check this project actually ran, which reported
|
|
9
|
+
cosine = 1.0 on an artifact that was behaviourally the sign control. Its column
|
|
10
|
+
in the detection matrix is the argument.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from pathlib import Path
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
from brainpatch.verify import vectors as vec
|
|
20
|
+
|
|
21
|
+
#: Ordered cheapest to most expensive. Behavioural reproduction is run
|
|
22
|
+
#: separately -- it needs a GPU and a frozen evaluation set.
|
|
23
|
+
DETECTION_LAYERS: tuple[str, ...] = (
|
|
24
|
+
"schema",
|
|
25
|
+
"checksum",
|
|
26
|
+
"shape_dtype",
|
|
27
|
+
"model_compatibility",
|
|
28
|
+
"unsigned_cosine",
|
|
29
|
+
"signed_cosine",
|
|
30
|
+
"delta_norm",
|
|
31
|
+
"execution_trace",
|
|
32
|
+
"schedule_trace",
|
|
33
|
+
"runtime_smoke",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
#: A layer flags a defect when the quantity it inspects differs from the
|
|
37
|
+
#: reference by more than this. Deliberately loose: the question is whether a
|
|
38
|
+
#: layer can see a defect *at all*, not how finely it can measure.
|
|
39
|
+
COSINE_TOLERANCE = 1e-3
|
|
40
|
+
NORM_TOLERANCE = 1e-3
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class Reference:
|
|
45
|
+
"""Everything the static layers compare a candidate against."""
|
|
46
|
+
|
|
47
|
+
delta: list[float]
|
|
48
|
+
layer: int
|
|
49
|
+
site: str
|
|
50
|
+
model_id: str
|
|
51
|
+
revision: str | None
|
|
52
|
+
shape: list[int]
|
|
53
|
+
schedule: dict[str, float] | None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def reference_from(path: Path) -> Reference:
|
|
57
|
+
from brainpatch.patch.loader import load_patch
|
|
58
|
+
|
|
59
|
+
loaded = load_patch(path)
|
|
60
|
+
intervention = loaded.manifest.interventions[0]
|
|
61
|
+
values = [float(v) for v in loaded.vectors[intervention.vector].data]
|
|
62
|
+
return Reference(
|
|
63
|
+
delta=vec.scaled(values, intervention.coefficient),
|
|
64
|
+
layer=int(intervention.layer),
|
|
65
|
+
site=str(intervention.site),
|
|
66
|
+
model_id=loaded.manifest.base_model.model_id,
|
|
67
|
+
revision=loaded.manifest.base_model.revision,
|
|
68
|
+
shape=list(loaded.vectors[intervention.vector].shape),
|
|
69
|
+
schedule=loaded.manifest.schedule,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def run_static_layers(path: Path, reference: Reference) -> dict[str, Any]:
|
|
74
|
+
"""Run every layer that does not need a GPU.
|
|
75
|
+
|
|
76
|
+
Returns ``{layer: caught}`` plus the measured quantities, so the matrix can
|
|
77
|
+
be audited rather than trusted.
|
|
78
|
+
"""
|
|
79
|
+
from brainpatch.patch.format import PatchFormatError
|
|
80
|
+
from brainpatch.patch.loader import PatchLoadError, load_patch
|
|
81
|
+
|
|
82
|
+
caught: dict[str, bool] = {}
|
|
83
|
+
measured: dict[str, Any] = {}
|
|
84
|
+
|
|
85
|
+
# -- checksum: load_patch verifies internally and raises on mismatch -------
|
|
86
|
+
try:
|
|
87
|
+
loaded = load_patch(path, verify_checksums=True)
|
|
88
|
+
caught["checksum"] = False
|
|
89
|
+
except PatchLoadError as exc:
|
|
90
|
+
measured["checksum_error"] = str(exc)
|
|
91
|
+
return {"caught": {layer: layer == "checksum" for layer in DETECTION_LAYERS},
|
|
92
|
+
"measured": measured, "loadable": False}
|
|
93
|
+
|
|
94
|
+
# -- schema ---------------------------------------------------------------
|
|
95
|
+
try:
|
|
96
|
+
loaded.manifest.validate()
|
|
97
|
+
caught["schema"] = False
|
|
98
|
+
except PatchFormatError as exc:
|
|
99
|
+
caught["schema"] = True
|
|
100
|
+
measured["schema_error"] = str(exc)
|
|
101
|
+
|
|
102
|
+
intervention = loaded.manifest.interventions[0]
|
|
103
|
+
tensor = loaded.vectors[intervention.vector]
|
|
104
|
+
delta = vec.scaled([float(v) for v in tensor.data], intervention.coefficient)
|
|
105
|
+
|
|
106
|
+
# -- shape / dtype --------------------------------------------------------
|
|
107
|
+
caught["shape_dtype"] = list(tensor.shape) != reference.shape
|
|
108
|
+
measured["shape"] = list(tensor.shape)
|
|
109
|
+
|
|
110
|
+
# -- model compatibility --------------------------------------------------
|
|
111
|
+
caught["model_compatibility"] = (
|
|
112
|
+
loaded.manifest.base_model.model_id != reference.model_id
|
|
113
|
+
or loaded.manifest.base_model.revision != reference.revision
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# -- direction, signed and unsigned --------------------------------------
|
|
117
|
+
ref_norm = vec.norm(reference.delta)
|
|
118
|
+
cand_norm = vec.norm(delta)
|
|
119
|
+
cosine = vec.cosine(delta, reference.delta)
|
|
120
|
+
measured["signed_cosine"] = cosine
|
|
121
|
+
measured["unsigned_cosine"] = abs(cosine)
|
|
122
|
+
caught["signed_cosine"] = abs(cosine - 1.0) > COSINE_TOLERANCE
|
|
123
|
+
# The blind spot, made explicit: |cos| cannot distinguish v from -v.
|
|
124
|
+
caught["unsigned_cosine"] = abs(abs(cosine) - 1.0) > COSINE_TOLERANCE
|
|
125
|
+
|
|
126
|
+
# -- magnitude ------------------------------------------------------------
|
|
127
|
+
ratio = cand_norm / ref_norm
|
|
128
|
+
measured["delta_norm"] = cand_norm
|
|
129
|
+
measured["norm_ratio"] = ratio
|
|
130
|
+
caught["delta_norm"] = abs(ratio - 1.0) > NORM_TOLERANCE
|
|
131
|
+
|
|
132
|
+
# -- declared structure ---------------------------------------------------
|
|
133
|
+
measured["layer"] = int(intervention.layer)
|
|
134
|
+
measured["site"] = str(intervention.site)
|
|
135
|
+
measured["schedule"] = loaded.manifest.schedule
|
|
136
|
+
caught["schedule_trace"] = loaded.manifest.schedule != reference.schedule
|
|
137
|
+
|
|
138
|
+
return {"caught": caught, "measured": measured, "loadable": True}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def run_execution_trace(
|
|
142
|
+
backend: Any, path: Path, reference: Reference, num_layers: int
|
|
143
|
+
) -> dict[str, Any]:
|
|
144
|
+
"""Ask the *runtime* where and when it would intervene.
|
|
145
|
+
|
|
146
|
+
Reads ``resolve_edits``, not the manifest. A manifest declaring
|
|
147
|
+
``site: prompt`` and a runtime steering every generated token is exactly the
|
|
148
|
+
disagreement this layer exists to find, and no amount of manifest inspection
|
|
149
|
+
would surface it.
|
|
150
|
+
"""
|
|
151
|
+
from brainpatch.patch.loader import load_patch
|
|
152
|
+
|
|
153
|
+
loaded = load_patch(path)
|
|
154
|
+
active = backend.install_patch(loaded)
|
|
155
|
+
try:
|
|
156
|
+
# Which layers carry an edit, on which kind of pass?
|
|
157
|
+
prompt_layers = [
|
|
158
|
+
layer
|
|
159
|
+
for layer in range(num_layers)
|
|
160
|
+
if backend.resolve_edits(0, layer=layer, is_prompt_pass=True)
|
|
161
|
+
]
|
|
162
|
+
continuation_layers = [
|
|
163
|
+
layer
|
|
164
|
+
for layer in range(num_layers)
|
|
165
|
+
if backend.resolve_edits(0, layer=layer, is_prompt_pass=False)
|
|
166
|
+
]
|
|
167
|
+
finally:
|
|
168
|
+
backend.remove_patch(active.patch.manifest.name)
|
|
169
|
+
|
|
170
|
+
expected_prompt = reference.site in ("prompt", "all")
|
|
171
|
+
expected_continuation = reference.site in ("continuation", "all")
|
|
172
|
+
|
|
173
|
+
caught = (
|
|
174
|
+
prompt_layers != ([reference.layer] if expected_prompt else [])
|
|
175
|
+
or continuation_layers != ([reference.layer] if expected_continuation else [])
|
|
176
|
+
)
|
|
177
|
+
return {
|
|
178
|
+
"caught": bool(caught),
|
|
179
|
+
"prompt_layers": prompt_layers,
|
|
180
|
+
"continuation_layers": continuation_layers,
|
|
181
|
+
"expected_layer": reference.layer,
|
|
182
|
+
"expected_site": reference.site,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def run_runtime_smoke(backend: Any, path: Path, prompt: str) -> dict[str, Any]:
|
|
187
|
+
"""Does it load, install and produce non-degenerate text at all?
|
|
188
|
+
|
|
189
|
+
The weakest possible behavioural check, and included precisely to show how
|
|
190
|
+
little it catches. Almost every corruption here will pass it.
|
|
191
|
+
"""
|
|
192
|
+
from brainpatch.patch.loader import load_patch
|
|
193
|
+
from brainpatch.runtime.base import GenerationConfig
|
|
194
|
+
|
|
195
|
+
loaded = load_patch(path)
|
|
196
|
+
active = backend.install_patch(loaded)
|
|
197
|
+
try:
|
|
198
|
+
text = backend.generate(prompt, GenerationConfig(max_new_tokens=24, temperature=0.0))
|
|
199
|
+
finally:
|
|
200
|
+
backend.remove_patch(active.patch.manifest.name)
|
|
201
|
+
stripped = text.strip()
|
|
202
|
+
words = stripped.split()
|
|
203
|
+
degenerate = not stripped or (len(words) > 6 and len(set(words)) <= 2)
|
|
204
|
+
return {"caught": bool(degenerate), "sample": text[:160]}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
"""Controlled, deterministic corruptions of a compiled `.brainpatch` artifact.
|
|
2
|
+
|
|
3
|
+
Every corruption starts from a known-correct reference and changes exactly one
|
|
4
|
+
thing. Every product is a **well-formed archive with valid checksums** -- that is
|
|
5
|
+
deliberate and it is the whole design. A variant that fails to load teaches
|
|
6
|
+
nothing about which validation layer catches it, and the class this suite exists
|
|
7
|
+
to expose is precisely the one that survives loading, schema validation and
|
|
8
|
+
checksum verification and is still behaviourally wrong.
|
|
9
|
+
|
|
10
|
+
No corruption introduces executable content. The `.brainpatch` format is inert
|
|
11
|
+
data by construction; a corruption suite that weakened that would be testing a
|
|
12
|
+
different, worse format.
|
|
13
|
+
|
|
14
|
+
**No torch.** Everything here transforms a manifest and a list of floats, so it
|
|
15
|
+
runs in the fast local test suite where `tests/conftest.py` blocks the ML stack.
|
|
16
|
+
Vector arithmetic lives in `brainpatch.verify.vectors` and is done in Python doubles, which is
|
|
17
|
+
more precise than the float32 path it replaces. Generation and execution tracing
|
|
18
|
+
genuinely need a model; those stay in `brainpatch.verify.behavioural`, Modal-only.
|
|
19
|
+
|
|
20
|
+
Corruptions are declarative and recorded: each returns a description of exactly
|
|
21
|
+
what it did, so `corruptions.json` is a reproduction recipe rather than a label.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
from dataclasses import dataclass, field
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
from typing import Any, Callable
|
|
29
|
+
|
|
30
|
+
from brainpatch.patch.format import Intervention, Manifest
|
|
31
|
+
from brainpatch.patch.loader import LoadedPatch, load_patch, save_patch
|
|
32
|
+
from brainpatch.verify import vectors as vec
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class Corruption:
|
|
37
|
+
"""One defect, its rationale, and where we predict it will be caught."""
|
|
38
|
+
|
|
39
|
+
key: str
|
|
40
|
+
#: What real-world mistake this stands in for.
|
|
41
|
+
defect: str
|
|
42
|
+
#: Pre-registered prediction, so the detection matrix can contradict it.
|
|
43
|
+
expected_detection: str
|
|
44
|
+
apply: Callable[[Manifest, dict[str, Any]], tuple[Manifest, dict[str, Any]]]
|
|
45
|
+
#: Filled in when the variant is built.
|
|
46
|
+
detail: dict[str, Any] = field(default_factory=dict)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _clone(manifest: Manifest, **changes: Any) -> Manifest:
|
|
50
|
+
"""A manifest identical to the original except for named fields."""
|
|
51
|
+
fields = {
|
|
52
|
+
"name": manifest.name,
|
|
53
|
+
"base_model": manifest.base_model,
|
|
54
|
+
"interventions": list(manifest.interventions),
|
|
55
|
+
"description": manifest.description,
|
|
56
|
+
"format_version": manifest.format_version,
|
|
57
|
+
"evidence_level": manifest.evidence_level,
|
|
58
|
+
"evaluation": dict(manifest.evaluation),
|
|
59
|
+
"compatibility": dict(manifest.compatibility),
|
|
60
|
+
"provenance": dict(manifest.provenance),
|
|
61
|
+
"max_abs_strength": manifest.max_abs_strength,
|
|
62
|
+
"default_strength": manifest.default_strength,
|
|
63
|
+
"license": manifest.license,
|
|
64
|
+
"authors": list(manifest.authors),
|
|
65
|
+
"schedule": dict(manifest.schedule) if manifest.schedule else None,
|
|
66
|
+
}
|
|
67
|
+
fields.update(changes)
|
|
68
|
+
return Manifest(**fields)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _with_intervention(manifest: Manifest, **changes: Any) -> Manifest:
|
|
72
|
+
original = manifest.interventions[0]
|
|
73
|
+
fields = {
|
|
74
|
+
"layer": original.layer,
|
|
75
|
+
"vector": original.vector,
|
|
76
|
+
"coefficient": original.coefficient,
|
|
77
|
+
"site": original.site,
|
|
78
|
+
}
|
|
79
|
+
fields.update(changes)
|
|
80
|
+
return _clone(manifest, interventions=[Intervention(**fields)])
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _values(vectors: dict[str, Any], key: str) -> list[float]:
|
|
84
|
+
"""The stored vector as plain Python floats."""
|
|
85
|
+
return [float(v) for v in vectors[key].data]
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _store(vectors: dict[str, Any], key: str, values: list[float]) -> dict[str, Any]:
|
|
89
|
+
"""Rebuild the vector map with one vector replaced, preserving dtype and shape."""
|
|
90
|
+
from brainpatch.patch import tensors as ts
|
|
91
|
+
|
|
92
|
+
out = dict(vectors)
|
|
93
|
+
out[key] = ts.Tensor(
|
|
94
|
+
dtype=vectors[key].dtype,
|
|
95
|
+
shape=list(vectors[key].shape),
|
|
96
|
+
data=[float(v) for v in values],
|
|
97
|
+
)
|
|
98
|
+
return out
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# -- the corruptions -----------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def flip_sign(manifest: Manifest, vectors: dict[str, Any]):
|
|
105
|
+
"""The defect this project actually shipped.
|
|
106
|
+
|
|
107
|
+
Discovery selected the negated decoder column; the compiler emits the
|
|
108
|
+
unsigned column and carries sign in the coefficient; the coefficient was
|
|
109
|
+
written positive. Behaviourally the sign control.
|
|
110
|
+
"""
|
|
111
|
+
return _with_intervention(
|
|
112
|
+
manifest, coefficient=-manifest.interventions[0].coefficient
|
|
113
|
+
), vectors
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def shift_layer(delta: int):
|
|
117
|
+
"""Off-by-one in a layer index. Plausible in any hand-written manifest."""
|
|
118
|
+
|
|
119
|
+
def apply(manifest: Manifest, vectors: dict[str, Any]):
|
|
120
|
+
layer = manifest.interventions[0].layer + delta
|
|
121
|
+
return _with_intervention(manifest, layer=layer), vectors
|
|
122
|
+
|
|
123
|
+
return apply
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def scale_coefficient(factor: float):
|
|
127
|
+
"""Calibration drift: right direction, wrong dose."""
|
|
128
|
+
|
|
129
|
+
def apply(manifest: Manifest, vectors: dict[str, Any]):
|
|
130
|
+
coefficient = manifest.interventions[0].coefficient * factor
|
|
131
|
+
return _with_intervention(manifest, coefficient=coefficient), vectors
|
|
132
|
+
|
|
133
|
+
return apply
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def replace_site(site: str):
|
|
137
|
+
"""Injection site changed -- e.g. prompt-only becomes always-on.
|
|
138
|
+
|
|
139
|
+
The `site` field exists because v3's validated configuration was prompt-token
|
|
140
|
+
only and the format could not express it. Losing it silently re-runs the
|
|
141
|
+
intervention on every generated token, a configuration with no test evidence.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
def apply(manifest: Manifest, vectors: dict[str, Any]):
|
|
145
|
+
return _with_intervention(manifest, site=site), vectors
|
|
146
|
+
|
|
147
|
+
return apply
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def precision_roundtrip(dtype: str):
|
|
151
|
+
"""Store the vector through a lower-precision type and back.
|
|
152
|
+
|
|
153
|
+
Models the real cost of serialization precision. Round-to-nearest-even in
|
|
154
|
+
both cases, matching what a real store does.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
def apply(manifest: Manifest, vectors: dict[str, Any]):
|
|
158
|
+
key = manifest.interventions[0].vector
|
|
159
|
+
return manifest, _store(vectors, key, vec.round_to(_values(vectors, key), dtype))
|
|
160
|
+
|
|
161
|
+
return apply
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def renormalize_vector(scale: float):
|
|
165
|
+
"""Vector rescaled while the coefficient is left alone.
|
|
166
|
+
|
|
167
|
+
The applied delta is `coefficient x vector`, so rescaling the tensor changes
|
|
168
|
+
the dose without changing any declared number. A reader of the manifest sees
|
|
169
|
+
the validated coefficient and is wrong about what gets applied.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
def apply(manifest: Manifest, vectors: dict[str, Any]):
|
|
173
|
+
key = manifest.interventions[0].vector
|
|
174
|
+
return manifest, _store(vectors, key, vec.scaled(_values(vectors, key), scale))
|
|
175
|
+
|
|
176
|
+
return apply
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def stored_vector_wrong(seed: int = 0):
|
|
180
|
+
"""Metadata correct, tensor wrong.
|
|
181
|
+
|
|
182
|
+
Everything a reader can inspect in the manifest is right. The archive is
|
|
183
|
+
consistent and its checksums verify -- they are computed over whatever is
|
|
184
|
+
stored. Only a comparison against an external reference, or behaviour, sees it.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
def apply(manifest: Manifest, vectors: dict[str, Any]):
|
|
188
|
+
key = manifest.interventions[0].vector
|
|
189
|
+
original = _values(vectors, key)
|
|
190
|
+
replacement = vec.unit(vec.gaussian(len(original), seed=seed))
|
|
191
|
+
return manifest, _store(
|
|
192
|
+
vectors, key, vec.scaled(replacement, vec.norm(original))
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return apply
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def manifest_coefficient_wrong(factor: float = 0.5):
|
|
199
|
+
"""Tensor correct, declared coefficient wrong.
|
|
200
|
+
|
|
201
|
+
The mirror image of `renormalize_vector`, and the reason numerical fidelity
|
|
202
|
+
must be computed on the *applied delta* rather than on either half alone.
|
|
203
|
+
"""
|
|
204
|
+
|
|
205
|
+
def apply(manifest: Manifest, vectors: dict[str, Any]):
|
|
206
|
+
coefficient = manifest.interventions[0].coefficient * factor
|
|
207
|
+
return _with_intervention(manifest, coefficient=coefficient), vectors
|
|
208
|
+
|
|
209
|
+
return apply
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def orthogonal_direction(seed: int = 7):
|
|
213
|
+
"""A direction orthogonal to the validated one, same norm.
|
|
214
|
+
|
|
215
|
+
The strongest test of "numerically plausible, semantically absent": shape,
|
|
216
|
+
dtype, norm and coefficient are all exactly right, and the intervention is
|
|
217
|
+
unrelated to the one that was validated.
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
def apply(manifest: Manifest, vectors: dict[str, Any]):
|
|
221
|
+
key = manifest.interventions[0].vector
|
|
222
|
+
original = _values(vectors, key)
|
|
223
|
+
candidate = vec.orthogonalise(vec.gaussian(len(original), seed=seed), original)
|
|
224
|
+
return manifest, _store(
|
|
225
|
+
vectors, key, vec.scaled(vec.unit(candidate), vec.norm(original))
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return apply
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
#: The registry. Ordered so the detection matrix reads from crudest to subtlest.
|
|
232
|
+
CORRUPTIONS: tuple[Corruption, ...] = (
|
|
233
|
+
Corruption(
|
|
234
|
+
"sign_flip", "coefficient sign flipped (the defect this project shipped)",
|
|
235
|
+
"signed cosine and behaviour; NOT unsigned cosine", flip_sign,
|
|
236
|
+
),
|
|
237
|
+
Corruption(
|
|
238
|
+
"layer_plus_1", "layer index off by one, upward",
|
|
239
|
+
"execution trace and behaviour", shift_layer(+1),
|
|
240
|
+
),
|
|
241
|
+
Corruption(
|
|
242
|
+
"layer_minus_1", "layer index off by one, downward",
|
|
243
|
+
"execution trace and behaviour", shift_layer(-1),
|
|
244
|
+
),
|
|
245
|
+
Corruption(
|
|
246
|
+
"coefficient_half", "coefficient scaled x0.5 (under-dosed)",
|
|
247
|
+
"delta norm and behaviour", scale_coefficient(0.5),
|
|
248
|
+
),
|
|
249
|
+
Corruption(
|
|
250
|
+
"coefficient_double", "coefficient scaled x2 (over-dosed)",
|
|
251
|
+
"delta norm and behaviour", scale_coefficient(2.0),
|
|
252
|
+
),
|
|
253
|
+
Corruption(
|
|
254
|
+
"site_all", "prompt-only schedule replaced with always-on",
|
|
255
|
+
"execution trace and behaviour", replace_site("all"),
|
|
256
|
+
),
|
|
257
|
+
Corruption(
|
|
258
|
+
"site_continuation", "injection moved to generated tokens only",
|
|
259
|
+
"execution trace and behaviour", replace_site("continuation"),
|
|
260
|
+
),
|
|
261
|
+
# NEGATIVE CONTROL, not a defect. The v1 format already stores vectors as
|
|
262
|
+
# F16, so an F16 round-trip is bit-exact identity. It is retained on purpose:
|
|
263
|
+
# a suite of thirteen defects with no null case cannot distinguish "this
|
|
264
|
+
# layer detects defects" from "this layer fires at everything". Any layer
|
|
265
|
+
# that flags this one is producing a false positive.
|
|
266
|
+
Corruption(
|
|
267
|
+
"f16_roundtrip", "NEGATIVE CONTROL: vector stored through float16 and back "
|
|
268
|
+
"(identity -- the format already stores F16)",
|
|
269
|
+
"nothing: this is a no-op and must be caught by no layer",
|
|
270
|
+
precision_roundtrip("float16"),
|
|
271
|
+
),
|
|
272
|
+
Corruption(
|
|
273
|
+
"bf16_roundtrip", "vector stored through bfloat16 and back "
|
|
274
|
+
"(real perturbation: bf16 trades mantissa for exponent)",
|
|
275
|
+
"nothing cheap; too small for any cosine or norm threshold",
|
|
276
|
+
precision_roundtrip("bfloat16"),
|
|
277
|
+
),
|
|
278
|
+
Corruption(
|
|
279
|
+
"vector_rescaled", "vector rescaled x2, coefficient untouched",
|
|
280
|
+
"delta norm and behaviour; NOT cosine", renormalize_vector(2.0),
|
|
281
|
+
),
|
|
282
|
+
Corruption(
|
|
283
|
+
"vector_wrong", "manifest correct, stored vector replaced by a random direction",
|
|
284
|
+
"signed cosine and behaviour", stored_vector_wrong(),
|
|
285
|
+
),
|
|
286
|
+
Corruption(
|
|
287
|
+
"coefficient_declared_wrong", "vector correct, declared coefficient halved",
|
|
288
|
+
"delta norm and behaviour", manifest_coefficient_wrong(0.5),
|
|
289
|
+
),
|
|
290
|
+
Corruption(
|
|
291
|
+
"orthogonal_direction", "same norm and coefficient, direction orthogonal to validated",
|
|
292
|
+
"signed cosine and behaviour; NOT norm, shape or schema", orthogonal_direction(),
|
|
293
|
+
),
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def build_variants(reference: Path, out_dir: Path) -> list[dict[str, Any]]:
|
|
298
|
+
"""Write every corrupted variant. Returns the `corruptions.json` payload."""
|
|
299
|
+
import hashlib
|
|
300
|
+
|
|
301
|
+
loaded: LoadedPatch = load_patch(reference)
|
|
302
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
303
|
+
|
|
304
|
+
key = loaded.manifest.interventions[0].vector
|
|
305
|
+
reference_delta = vec.scaled(
|
|
306
|
+
_values(loaded.vectors, key), loaded.manifest.interventions[0].coefficient
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
records: list[dict[str, Any]] = []
|
|
310
|
+
for corruption in CORRUPTIONS:
|
|
311
|
+
manifest, vectors = corruption.apply(loaded.manifest, loaded.vectors)
|
|
312
|
+
manifest = _clone(manifest, name=f"corrupt-{corruption.key.replace('_', '-')}")
|
|
313
|
+
path = out_dir / f"{corruption.key}.brainpatch"
|
|
314
|
+
save_patch(manifest, vectors, path, overwrite=True)
|
|
315
|
+
|
|
316
|
+
intervention = manifest.interventions[0]
|
|
317
|
+
delta = vec.scaled(_values(vectors, intervention.vector), intervention.coefficient)
|
|
318
|
+
cos = vec.cosine(delta, reference_delta)
|
|
319
|
+
records.append(
|
|
320
|
+
{
|
|
321
|
+
"key": corruption.key,
|
|
322
|
+
"defect": corruption.defect,
|
|
323
|
+
"expected_detection": corruption.expected_detection,
|
|
324
|
+
"artifact": path.name,
|
|
325
|
+
"sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
|
|
326
|
+
"bytes": path.stat().st_size,
|
|
327
|
+
"layer": intervention.layer,
|
|
328
|
+
"site": intervention.site,
|
|
329
|
+
"coefficient": intervention.coefficient,
|
|
330
|
+
"delta_norm": vec.norm(delta),
|
|
331
|
+
"signed_cosine_to_reference": cos,
|
|
332
|
+
"unsigned_cosine_to_reference": abs(cos),
|
|
333
|
+
}
|
|
334
|
+
)
|
|
335
|
+
return records
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""Human-readable artifact-fidelity reports and the detection matrix.
|
|
2
|
+
|
|
3
|
+
The report always prints all four components, including the ones that passed. The
|
|
4
|
+
interesting artifact is the one that passes three and fails the fourth, and that
|
|
5
|
+
is only legible if all four are shown.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
COMPONENTS = ("structural", "numerical", "execution", "behavioural")
|
|
13
|
+
|
|
14
|
+
#: Column labels for the matrix, short enough to fit side by side.
|
|
15
|
+
LAYER_ABBREV = {
|
|
16
|
+
"schema": "schm",
|
|
17
|
+
"checksum": "csum",
|
|
18
|
+
"shape_dtype": "shape",
|
|
19
|
+
"model_compatibility": "model",
|
|
20
|
+
"unsigned_cosine": "ucos",
|
|
21
|
+
"signed_cosine": "scos",
|
|
22
|
+
"delta_norm": "norm",
|
|
23
|
+
"execution_trace": "exec",
|
|
24
|
+
"schedule_trace": "sched",
|
|
25
|
+
"runtime_smoke": "smoke",
|
|
26
|
+
"behavioural_reproduction": "BEHAV",
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
_CHECK_LABEL = {
|
|
30
|
+
"schema": "Schema",
|
|
31
|
+
"checksum": "Checksums",
|
|
32
|
+
"model_compatibility": "Model compatibility",
|
|
33
|
+
"signed_cosine": "Signed direction",
|
|
34
|
+
"delta_norm": "Delta norm",
|
|
35
|
+
"execution_trace": "Layer/site execution",
|
|
36
|
+
"behavioural_reproduction": "Behavior reproduction",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def render_artifact_report(variant: dict[str, Any], *, tolerance: float) -> str:
|
|
41
|
+
"""The per-artifact report from the spec.
|
|
42
|
+
|
|
43
|
+
``caught = True`` means the layer detected a defect, so it renders as FAIL.
|
|
44
|
+
"""
|
|
45
|
+
caught = dict(variant["caught"])
|
|
46
|
+
caught["behavioural_reproduction"] = variant.get("caught_behavioural", False)
|
|
47
|
+
|
|
48
|
+
lines = [f"Artifact: {variant['key']}", ""]
|
|
49
|
+
lines.append("Artifact Fidelity")
|
|
50
|
+
lines.append("")
|
|
51
|
+
width = max(len(v) for v in _CHECK_LABEL.values())
|
|
52
|
+
for layer, label in _CHECK_LABEL.items():
|
|
53
|
+
if layer not in caught:
|
|
54
|
+
continue
|
|
55
|
+
state = "FAIL" if caught[layer] else "PASS"
|
|
56
|
+
lines.append(f"{label.ljust(width)} {state}")
|
|
57
|
+
|
|
58
|
+
delta = variant.get("behavioural_delta")
|
|
59
|
+
if delta is not None and delta == delta: # not NaN
|
|
60
|
+
lines.append("")
|
|
61
|
+
lines.append(f"behavioural_delta = {delta:.4f} (tolerance {tolerance})")
|
|
62
|
+
|
|
63
|
+
lines += ["", "Components:"]
|
|
64
|
+
fidelity = variant.get("fidelity", {})
|
|
65
|
+
for component in COMPONENTS:
|
|
66
|
+
lines.append(f" {component.ljust(12)} {fidelity.get(component, 'not measured')}")
|
|
67
|
+
|
|
68
|
+
lines += ["", "Overall:", variant.get("verdict", "UNKNOWN")]
|
|
69
|
+
return "\n".join(lines)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def render_detection_matrix(
|
|
73
|
+
variants: list[dict[str, Any]], layers: list[str]
|
|
74
|
+
) -> str:
|
|
75
|
+
"""Rows = corruptions, columns = layers. ``X`` means the layer caught it."""
|
|
76
|
+
header = f"{'corruption':<28}{'delta':>9} " + " ".join(
|
|
77
|
+
f"{LAYER_ABBREV.get(layer, layer[:5]):>5}" for layer in layers
|
|
78
|
+
)
|
|
79
|
+
lines = [header, "-" * len(header)]
|
|
80
|
+
for variant in variants:
|
|
81
|
+
caught = dict(variant["caught"])
|
|
82
|
+
caught["behavioural_reproduction"] = variant.get("caught_behavioural", False)
|
|
83
|
+
delta = variant.get("behavioural_delta", float("nan"))
|
|
84
|
+
cells = " ".join(
|
|
85
|
+
f"{('X' if caught.get(layer) else '.'):>5}" for layer in layers
|
|
86
|
+
)
|
|
87
|
+
lines.append(f"{variant['key']:<28}{delta:>9.4f} {cells}")
|
|
88
|
+
return "\n".join(lines)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def to_csv(variants: list[dict[str, Any]], layers: list[str]) -> str:
|
|
92
|
+
rows = [",".join(["corruption", "verdict", "behavioural_delta"] + layers)]
|
|
93
|
+
for variant in variants:
|
|
94
|
+
caught = dict(variant["caught"])
|
|
95
|
+
caught["behavioural_reproduction"] = variant.get("caught_behavioural", False)
|
|
96
|
+
cells = [
|
|
97
|
+
variant["key"],
|
|
98
|
+
variant.get("verdict", ""),
|
|
99
|
+
f"{variant.get('behavioural_delta', float('nan')):.4f}",
|
|
100
|
+
]
|
|
101
|
+
cells += ["caught" if caught.get(layer) else "-" for layer in layers]
|
|
102
|
+
rows.append(",".join(cells))
|
|
103
|
+
return "\n".join(rows) + "\n"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def summarise_claim(
|
|
107
|
+
variants: list[dict[str, Any]], layers: list[str]
|
|
108
|
+
) -> dict[str, Any]:
|
|
109
|
+
"""Does the behavioural layer catch anything the cheap layers miss?
|
|
110
|
+
|
|
111
|
+
``unsigned_cosine`` is excluded from "cheap layers" on purpose. It is in the
|
|
112
|
+
matrix as the check that failed historically, not as one a validator should
|
|
113
|
+
rely on; counting it as a legitimate catcher would let the very blind spot
|
|
114
|
+
under study argue that nothing is blind.
|
|
115
|
+
"""
|
|
116
|
+
cheap = [
|
|
117
|
+
layer for layer in layers
|
|
118
|
+
if layer not in ("behavioural_reproduction", "unsigned_cosine")
|
|
119
|
+
]
|
|
120
|
+
behaviour_only, false_positives = [], []
|
|
121
|
+
for variant in variants:
|
|
122
|
+
caught = dict(variant["caught"])
|
|
123
|
+
failed_behaviour = variant.get("caught_behavioural", False)
|
|
124
|
+
hits = [layer for layer in cheap if caught.get(layer)]
|
|
125
|
+
if failed_behaviour and not hits:
|
|
126
|
+
behaviour_only.append(variant["key"])
|
|
127
|
+
if "NEGATIVE CONTROL" in variant.get("defect", "") and (hits or failed_behaviour):
|
|
128
|
+
false_positives.append(variant["key"])
|
|
129
|
+
return {
|
|
130
|
+
"claim_supported": bool(behaviour_only),
|
|
131
|
+
"caught_only_by_behaviour": behaviour_only,
|
|
132
|
+
"negative_control_false_positives": false_positives,
|
|
133
|
+
}
|