#000049 §7 #22: speedup (batch + cuda auto-detect + ONNX-int8 export) + the gate-item-4 verdict at proper n

Speedup (§3 plan): ShadowNLI._nli_batch batches forwards
(ARBORIST_NLI_BATCH=64); device auto-detect (ARBORIST_NLI_DEVICE, else
cuda-if-available); auto-prefer an ONNX export — bench/scripts/export_nli_onnx.py
/ make export-nli-onnx exports + int8-dynamic-quantizes the pinned
checkpoint into ~/.arborist/models/nli/<ver>/onnx/ (operator state, NOT
committed), _ensure_loaded loads model_quantized.onnx via
optimum.onnxruntime (backend onnx-int8), falls back to torch silently.
torch-cpu-batch1 ~120ms/pair → onnx-int8-cpu-batched ~32ms/pair (~4x);
seconds on a 4090. optimum[onnxruntime] added to the [nli] extra; 24
tests.

Gate-item-4 verdict at proper n: ARBORIST_NLI_SHADOW=1 make bench-qa
BENCH_QA_N=1 → 223 cells (89 STRICT / 90 HYBRID / 44 UNGROUNDED; also
surfaced + fixed a lone-surrogate bug). Shadow sweep over those: NLI-as-
runtime-veto on STRICT has ~26% FP at θc 0.5, ~8% at θc 0.90, ~0% only
at θc 0.99 — and θc 0.99 gives up most recombination recall (hard
synthetic recombinations bottom out ~0.76). FAILS the §7 #12 gate on
this design. Only untried path that might pass: a Phase-3 runtime hook
running NLI on the verifier's actual matched clauses (1-3), not
top-6-by-overlap. Until then: runtime NLI demotion stays off; the 2
fixtures stay permanent boundary markers; θc stays 0.5. Production
verifier unchanged; falsification-hard stays 10/12.
This commit is contained in:
russell@unturf.com 2026-05-12 17:21:32 -04:00
parent 96ff586da1
commit 87d9db15c7
No known key found for this signature in database
8 changed files with 4587 additions and 30 deletions

View file

@ -136,6 +136,36 @@ def test_shadownli_construction_never_raises_and_starts_unavailable():
assert nli.available is False # not loaded until first check
assert nli.model_version == load_manifest()["nli_model_version"]
assert nli.theta_contra == 0.5 and nli.theta_entail == 0.9
assert nli.device_pref in ("auto", "cpu", "cuda") # default 'auto'
assert nli.batch_size >= 1
assert nli.backend is None # set at load → "torch" / "onnx" / "onnx-int8"
assert nli._onnx_dir().name == "onnx" # conventional export location
def test_shadownli_device_and_onnx_dir_env_overrides(monkeypatch, tmp_path):
monkeypatch.setenv("ARBORIST_NLI_DEVICE", "cpu")
monkeypatch.setenv("ARBORIST_NLI_ONNX_DIR", str(tmp_path / "x"))
monkeypatch.setenv("ARBORIST_NLI_BATCH", "8")
nli = ShadowNLI()
assert nli.device_pref == "cpu" and nli.batch_size == 8
assert nli._onnx_dir() == tmp_path / "x"
def test_nli_batch_matches_single_when_available():
nli = ShadowNLI()
nli._ensure_loaded()
if not nli.available:
pytest.skip("[nli] extra not installed")
pairs = [("Jupiter is the largest planet.", "Mercury is the largest planet."),
("Paris is the capital of France.", "Paris is the capital of France.")]
batched = nli._nli_batch(pairs)
singles = [nli._nli(p, h) for p, h in pairs]
for b, s in zip(batched, singles):
# batching pads the shorter sequence; with attention masking that's
# ~a no-op for fp32, and within quantization noise for int8 ONNX —
# the operative thing (argmax label, gate decision) must not move.
assert max(range(3), key=lambda i: b[i]) == max(range(3), key=lambda i: s[i])
assert all(abs(x - y) < 0.05 for x, y in zip(b, s))
def test_check_returns_shadowresult_and_degrades_gracefully():