Corruption detection — is this the data, or the machine?¶
exa drift trigger used to fire an automated retrain on two gates: a z-score above a threshold,
and a cooldown that had elapsed. Silent data corruption perturbs exactly the statistic that
z-score is computed from, so a hardware fault produced a high z-score, an autonomous retrain, and
a model trained on corrupt data. Nothing in the path asked whether the anomaly was the data or the
machine.
ExaMLOps now classifies the anomaly before it remediates it. The remediation is a function of the
class, never of the z-score, and only one class — data_drift — permits an autonomous retrain.
This changes when auto-retrain fires. See Migration.
Why a NaN/Inf guard is not corruption detection¶
Gate-level fault injection on a production datacenter GPU (arXiv:2605.04213, The Anatomy of Silent Data Corruption; 63 CUDA benchmarks, >3M simulator hours) measured special values (NaN/±INF) at 1.01% of silent data corruption, and states the consequence directly: "Software detection targeting NaN/±INF captures minimal SDCs." The same abstract verifies that single-bit flips are under 40% of bit-flip events — so the majority are multi-bit — and that corruption addresses exhibit periodicity.
This is an operating condition, not an exception. Meta observes SDC on roughly 1 machine in 1,000; SDC caused 1.4% of Llama-3 training interruptions; Alibaba measures 3.61‱ across >1M processors.
A guard that catches ~1% of a phenomenon must never be logged, described or reported as
"corruption checked". detect_corruption() therefore also computes an unexpected-zero rate
against a per-model baseline, and reports a spread-shift statistic alongside it.
The second axis was already being collected¶
input_snapshots / input_baselines record embedding statistics per inference, independently of
the prediction distribution. That gives a second axis, and the two together separate cases that
prediction drift alone cannot:
| Scenario | Input drift | Prediction drift | Correct action |
|---|---|---|---|
| Real data drift | ↑ | ↑ | retrain |
| Hardware corruption / SDC | ≈ 0 | ↑ | quarantine the node, do not retrain |
| Model or serving regression | ≈ 0 | ↑ | roll back the deployment |
Prediction drift without input drift is evidence against data drift, not for it.
The four classes¶
classify_anomaly() returns exactly one of these, with the remediation it implies:
| Class | Means | Remediation | Autonomous? |
|---|---|---|---|
data_drift |
both axes moved, corruption negative | retrain |
yes |
suspected_hardware |
corruption signal positive | quarantine_node |
no |
suspected_regression |
outputs moved, inputs held, corruption negative | rollback_deployment |
no |
undetermined |
the signals do not separate | none |
no |
undetermined is a first-class outcome, not an error. When there is no corruption baseline, no
input evidence, or the axes conflict, the platform says so, raises an operator event, and takes no
autonomous action. Guessing here costs GPU-hours and ships a model trained on corrupt data.
Commands¶
# The corruption signal per model — NaN/Inf *and* unexpected zeros, never one alone
exa drift corruption status
exa drift corruption status JPCP
# Record what "normal" looks like for this model. Zero rates differ legitimately between
# models, so like `exa drift baseline` this is an explicit, audited act.
exa drift corruption baseline JPCP --reason "post-deploy reference"
# Name the anomaly behind a drift breach
exa drift corruption classify
exa drift corruption classify JPCP
# Measure the detector against injected corruption and publish the rate
exa drift corruption selftest JPCP
A suppressed retrain shows up in three places: the command's own Suppressed table, a
drift_events row of kind corruption, and an audit event
(drift_retrain_suppressed / autopilot_retrain_suppressed) carrying the class and the reason. A
retrain that does not happen leaves no trace of its own, so these are it — and they are also the
denominator that makes "failures per human intervention" mean something.
What the detector covers — and what it does not¶
A detector may not be credited with corruption classes it was never tested against. exa drift
corruption selftest injects each class into the model's own recent predictions and reports what
was caught:
| Class | Detected by | Sets suspected_sdc |
|---|---|---|
nullification (silent zeros) |
zero rate vs baseline | yes |
special_values (NaN/±Inf) |
NaN/Inf rate | yes |
mantissa_flip (non-special multi-bit) |
distribution_shift (reported only) |
no |
The third row is deliberate. A spread change is not specific enough to corruption to justify
blocking a legitimate retrain, so it is reported and never gating. selftest will show a
detection rate near zero for it — that is the honest number, and printing it is the point. Coverage
is bounded by what was injected, not by the assumed distribution of the phenomenon.
Migration¶
The behaviour change operators notice first: prediction drift alone no longer fires an
autonomous retrain. A model with drift snapshots but no input snapshots classifies as
undetermined, and exa drift trigger reports it under Suppressed instead of retraining.
To restore autonomous retraining for a model:
- Ensure the bridge is running so
input_snapshotsare collected for it. - Set the input baseline:
exa drift input baseline <MODEL>. - Set the corruption baseline:
exa drift corruption baseline <MODEL>. - Confirm with
exa drift corruption classify <MODEL>— a genuinely drifting model should readdata_drift, andAuto? yes.
Expect undetermined to be common in week one, until baselines settle. That is the intended
trade: a missed retrain costs latency, while a wrong retrain costs GPU-hours and ships a model
trained on corrupt data.
Design¶
- ADR 0114 — Detect corruption before diagnosing drift (requirements G4.2 · G4.3)
examlops.corruption— the statistic, the classifier and the injection harness- Related: Judge calibration, which applies the same principle (every governance instrument must itself be measured) to LLM judges