tenseleyflow/sway / fadb0ae

Browse files

sway(core): NullCalibratedBackend protocol for z-score calibration

Authored by espadonne
SHA
fadb0ae0a14970b20dfdcc78ffb72a71c3bc1031
Parents
a4b94dc
Tree
d99d373

2 changed files

StatusFile+-
M src/dlm_sway/__init__.py 2 0
M src/dlm_sway/core/scoring.py 24 0
src/dlm_sway/__init__.pymodified
@@ -12,6 +12,7 @@ from dlm_sway.core.model import LoadedModel, Model, ModelSpec
1212
 from dlm_sway.core.result import ProbeResult, SuiteResult, SwayScore, Verdict
1313
 from dlm_sway.core.scoring import (
1414
     DifferentialBackend,
15
+    NullCalibratedBackend,
1516
     RollingLogprob,
1617
     ScalableDifferentialBackend,
1718
     ScoringBackend,
@@ -24,6 +25,7 @@ __all__ = [
2425
     "LoadedModel",
2526
     "Model",
2627
     "ModelSpec",
28
+    "NullCalibratedBackend",
2729
     "ProbeError",
2830
     "ProbeResult",
2931
     "RollingLogprob",
src/dlm_sway/core/scoring.pymodified
@@ -161,6 +161,30 @@ class ScalableDifferentialBackend(DifferentialBackend, Protocol):
161161
     def as_scaled_adapter(self, lam: float) -> AbstractContextManager[_ScoringModel]: ...
162162
 
163163
 
164
+@runtime_checkable
165
+class NullCalibratedBackend(DifferentialBackend, Protocol):
166
+    """A differential backend that can produce a "null adapter" view.
167
+
168
+    A null adapter has the *same structure* (rank, alpha, target modules)
169
+    as the real adapter but with weights drawn from a zero-mean Gaussian.
170
+    Running probes against this view yields the baseline "how much
171
+    signal does random noise produce" distribution — the denominator in
172
+    every numeric probe's z-score.
173
+
174
+    The context manager takes a ``seed`` so calibration runs can be
175
+    reproduced and multiple independent null samples can be drawn to
176
+    estimate ``std``.
177
+
178
+    Implementations MUST restore the real adapter on exit, including
179
+    on exceptions, so a caller can freely interleave null and real
180
+    calibrations within the same backend lifetime.
181
+    """
182
+
183
+    def as_null_adapter(
184
+        self, seed: int, *, init_scale: float = 0.02
185
+    ) -> AbstractContextManager[_ScoringModel]: ...
186
+
187
+
164188
 # Helper Protocol for type-checking the yielded context object: it
165189
 # must satisfy both Model and ScoringBackend. mypy doesn't support
166190
 # intersection types, so we spell it out explicitly.