Skip to main content

One-line circuit breaker for PyTorch training — catches NaN gradients, logs spikes, prevents model corruption.

Project description

⚡ StabilityGuard

One-line circuit breaker for PyTorch training.
Add one line. See exactly which layer is about to explode.

License: MIT Python 3.9+ PyTorch 2.0+


The Problem

Training large neural networks fails catastrophically when gradient spikes or NaN explosions occur. A single corrupted gradient can poison your optimizer's state (momentum buffers, Adam's second moments), forcing you to restart from the last checkpoint—often thousands of steps back.

The failure cascade:

  1. A gradient spike hits one layer (10-1000× larger than normal)
  2. Optimizer state gets corrupted with NaN/Inf values
  3. Subsequent steps propagate NaN through the entire model
  4. By the time PyTorch throws RuntimeError: Function returned nan, it's too late—your checkpoint is poisoned

What PyTorch tells you:

RuntimeError: Function 'AddmmBackward0' returned nan values in its 0th output

What you actually need to know:

  • Which layer caused the spike?
  • When did it start (exact step number)?
  • What was the gradient magnitude vs. normal baseline?
  • Can we catch it before it corrupts the optimizer?

The cost: For a 7B LLM on 8×A100 GPUs, a single NaN explosion at 40% training wastes ~72 GPU-hours. You lose days of compute because one gradient went haywire.

StabilityGuard solves this by monitoring every layer's gradients in real-time and catching spikes before they corrupt your training run.

The Fix

pip install stabilityguard

Before StabilityGuard (standard PyTorch):

from torch.optim import AdamW

optimizer = AdamW(model.parameters(), lr=2e-4)

for batch in dataloader:
    loss = model(batch)
    loss.backward()
    optimizer.step()
    optimizer.zero_grad()

After StabilityGuard (one-line change):

from torch.optim import AdamW
from stabilityguard import GuardedOptimizer  # ← the only change

base_opt = AdamW(model.parameters(), lr=2e-4)
optimizer = GuardedOptimizer(base_opt, model,
    spike_threshold=10.0,      # gradient norm ratio to trigger alert
    nan_action="skip",         # "skip" | "rollback" | "raise"
    log_every=50               # steps between diagnostic summaries
)

for batch in dataloader:
    loss = model(batch)
    loss.backward()
    optimizer.step()           # GuardedOptimizer intercepts here
    optimizer.zero_grad()

What You See

When a spike hits at step 847:

╔══════════════════════════════════════════════════════════════╗
║  ⚠ STABILITYGUARD — SPIKE DETECTED @ step 847                ║
╠══════════════════════════════════════════════════════════════╣
║  Trigger layer  : transformer.h.11.mlp.c_proj                ║
║  Grad norm      : 847.3  (baseline: 1.2, ratio: 706.1x)      ║
║  Action taken   : optimizer.step() SKIPPED                   ║
║  Loss (pre-skip): 14.71                                      ║
╚══════════════════════════════════════════════════════════════╝
stabilityguard.log written → ./sg_logs/spike_step847.json

Your model weights are untouched. Training continues.

How It Works

  1. Backward hooks on every nn.Module capture per-layer gradient L2 norms
  2. EMA baselines track normal gradient behavior (α=0.01, ~100 step lookback)
  3. Spike detection fires when current_norm / ema_baseline > threshold
  4. NaN/Inf short-circuit catches corrupted gradients via torch.isfinite
  5. Actions execute automatically: skip the step, rollback to checkpoint, or raise

Configuration

Parameter Default Description
spike_threshold 10.0 Ratio of current norm to EMA baseline that triggers a spike
nan_action "skip" Action on spike: "skip", "rollback", or "raise"
log_every 50 Steps between periodic diagnostic summaries
log_dir "./sg_logs" Directory for JSON spike reports
ema_alpha 0.01 EMA smoothing factor (smaller = slower adaptation)
warmup_steps 10 Steps before spike detection activates
verbose True Print diagnostic summaries to stdout

Actions

Action Behavior
skip Skip optimizer.step() — corrupted gradients discarded, weights unchanged
rollback Restore model + optimizer to the last clean checkpoint
raise Throw GradientSpikeError for interactive debugging

Integrations

Weights & Biases

from stabilityguard.integrations.wandb import WandBBridge
bridge = WandBBridge()
# Metrics logged under sg/ namespace automatically

MLflow

from stabilityguard.integrations.mlflow import MLflowBridge
bridge = MLflowBridge()

HuggingFace Transformers

from stabilityguard.integrations.huggingface import StabilityGuardCallback

trainer = Trainer(
    model=model,
    args=training_args,
    callbacks=[StabilityGuardCallback(spike_threshold=10.0)],
)

Performance

Metric Value
Per-step overhead (GPU) Low (estimated 1-5% on modern GPUs)
Per-step overhead (CPU) High (~60%, not recommended for production)
Per-step overhead (spike detected) ~3ms (includes JSON write)
Recommended for GPU-based training (production workloads)
External dependencies 0 (only PyTorch)
License MIT

Install from source

git clone https://github.com/stabilityguard/stabilityguard.git
cd stabilityguard
pip install -e ".[test]"
pytest tests/ -v

License

MIT — use it anywhere, no restrictions.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

stabilityguard-0.1.3.tar.gz (22.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

stabilityguard-0.1.3-py3-none-any.whl (22.1 kB view details)

Uploaded Python 3

File details

Details for the file stabilityguard-0.1.3.tar.gz.

File metadata

  • Download URL: stabilityguard-0.1.3.tar.gz
  • Upload date:
  • Size: 22.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for stabilityguard-0.1.3.tar.gz
Algorithm Hash digest
SHA256 dfce5271aef4482943bbdd01a1a2a381980bcfacaafb1ceaea028502f97d8299
MD5 416379fb648cc27a746d6fc9176f22c2
BLAKE2b-256 43999f80b8048766aca78c4fb7cf2a03a8164b6c6f3c216560b3bbe40ba91787

See more details on using hashes here.

File details

Details for the file stabilityguard-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: stabilityguard-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 22.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for stabilityguard-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 ab5fed114c48e220947b4082a18d3385a01503d6d1f53a502085e64148829ef9
MD5 3e66f9501eeb50aec6b95696603b40ce
BLAKE2b-256 38c5892bd6bc4400642a190936ca87791b9e2e4270bef2cb8c07f1ad2bf3a6d9

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page