Inference & Deployment

Inference in ZeroProofML v0.4 uses strict SCM semantics: no stochastic thresholds and explicit ⊥ outputs for singular inputs.

Runtime Rules

  • Use a fixed τ_infer to decide when denominators are treated as singular (|Q| < τ_infer ⇒ ⊥).
  • When you know the training-time margin τ_train (typically τ_train > τ_infer), you can detect the training–inference gap region τ_infer ≤ |Q| < τ_train and treat it as numerically risky at deployment time (e.g., log it, trigger fallbacks, or tighten τ_infer).
  • No gradient policies are applied; forward behaviour matches the strict SCM decode rule used by zeroproof.inference.mode.strict_inference.
  • Encode ⊥ as nan when bridging to IEEE-754 via utils.ieee_bridge.to_ieee.

Exporting Models

  • TorchScript (deprecated in recent PyTorch): use zeroproof.inference.script_module(model) (wraps torch.jit.script) if you still rely on TorchScript; otherwise prefer ONNX.
  • ONNX: use zeroproof.inference.export_onnx_model(...) or zeroproof.inference.export_bundle(...).
  • Checkpoints: saved via SCMTrainer.save_checkpoint, compatible with both SCM-only and projective graphs.

Bundle validation

export_bundle(...) writes a minimal bundle:

  • model.onnx
  • metadata.json (includes tau_infer, optional tau_train, and the strict output contract)

The strict inference output contract is versioned via strict_inference_schema_version in metadata.json.

Validate a bundle before shipping:

from zeroproof.inference import validate_bundle

validate_bundle("path/to/bundle_dir")

Reference deployment (robotics RR IK)

An end-to-end reference path (train → bundle → strict inference → fallback → report) is provided as:

python scripts/reference_robotics_deployment.py --device cpu --epochs 2 --n-samples 6000

It writes a self-contained run directory under results/reference_deploy_robotics/ including bundle/ and VALIDATION_REPORT.md.

Pattern: strict gate + direction head (censoring/regimes)

For 3-way censoring problems (below / in-range / above), you can combine:

1) strict bottom gating via |Q| < τ_infer; and 2) a 2-class direction head to disambiguate which censored regime applies when the sample is bottom.

import torch

from zeroproof.inference import decode_strict_censored_3way

# Given projective head outputs (P, Q) and optional direction logits.
decoded, bottom_mask, class_id = decode_strict_censored_3way(
    P.squeeze(-1),
    Q.squeeze(-1),
    tau_infer=1e-6,
    direction_logits=dir_logits,  # shape (B, 2); omit to fall back to sign(P)
)

Monitoring + fallbacks

At deployment time, treat masks as authoritative and log them explicitly:

from zeroproof.inference import StrictInferenceMonitor, reject_on_gap

decoded, bottom_mask, gap_mask = wrapped(x)  # strict outputs
decoded_safe, accept_mask = reject_on_gap(decoded, bottom_mask, gap_mask)

mon = StrictInferenceMonitor(bundle_id="my_bundle")
mon.update(bottom_mask, gap_mask)
rates = mon.rates()

Choosing τ_infer (post-hoc sweep)

If your strict gate is of the form |Q| < τ_infer, you can evaluate safety trade-offs post-hoc by sweeping τ_infer over cached |Q| values:

from zeroproof.metrics import tau_infer_sweep_from_q_abs

# q_abs: cached |Q|, is_in_range: boolean ground-truth mask
curves = tau_infer_sweep_from_q_abs(q_abs, is_in_range=is_in_range, taus=[1e-6, 1e-5, 1e-4])

Use write_tau_infer_sweep(...) to save both a JSON payload and a compact Markdown report.

Typical trade-off: increasing τ_infer reduces false in-range predictions on truly censored points, but can increase false censoring on truly in-range points.

Named operating points

These are named deployment presets for selecting thresholds + fallback behavior. They are not magic values — use a post-hoc sweep (or a held-out calibration set) to set the actual τ_infer/τ_train for your domain.

safety_first

Goal: minimize unsafe accepts by rejecting both strict-bottom and gap-region samples.

from zeroproof.inference import InferenceConfig, reject_on_gap, safe_sentinel, strict_inference

cfg = InferenceConfig(tau_infer=1e-4, tau_train=1e-3)
decoded, bottom_mask, gap_mask = strict_inference(P, Q, config=cfg)

decoded, accept_mask = reject_on_gap(decoded, bottom_mask, gap_mask)
decoded = safe_sentinel(decoded, ~accept_mask, sentinel=float("nan"))

Trade-off: lower false-finite-on-censored (or “unsafe accept”) at the cost of higher rejection / false-censoring rates.

direction_aware

Goal: when bottoms happen, keep the censored direction meaningful by using an auxiliary direction head.

from zeroproof.inference import decode_strict_censored_3way

decoded, bottom_mask, class_id = decode_strict_censored_3way(
    P.squeeze(-1),
    Q.squeeze(-1),
    tau_infer=1e-6,
    direction_logits=dir_logits,  # (B, 2)
)

Trade-off: adds an extra head (and training/eval complexity), but produces more actionable outputs on censored / singular inputs.

accuracy_first

Goal: maximize coverage by keeping τ_infer tight and treating the gap region as “monitor-only”.

from zeroproof.inference import StrictInferenceMonitor, reject_on_bottom

decoded, bottom_mask, gap_mask = wrapped(x)
decoded, accept_mask = reject_on_bottom(decoded, bottom_mask)

mon = StrictInferenceMonitor(bundle_id="my_bundle")
mon.update(bottom_mask, gap_mask)  # log rates; do not reject gap by default

Trade-off: higher coverage and in-range accuracy when things are well-behaved, but more risk exposure near the training–inference gap unless you monitor and gate carefully.

Safety Checklist

  • Validate coverage on a held-out set using losses.coverage.coverage before shipping.
  • Monitor the rate of ⊥ outputs in production; aggressive rejection loss during training usually lowers this.
  • For robotics or control, ensure sign consistency loss was active so orientation of infinities is preserved.