Skip to content
AI.info

Training and optimization

NaNs, Divergence, Saturation, and Silent Instability

Diagnose invalid values, loss explosions, dead activations, saturated gates, overflow, underflow, and optimizer-state corruption with staged instrumentation.

By the end you can

The first invalid value matters more than the final NaN

Once an infinity enters a squared-gradient accumulator, many later values may become invalid. The error that finally surfaces can be far from the line of code that caused it.

Capture the earliest non-finite activation, loss component, gradient, or parameter. Preserve the triggering batch and full state before recovery changes the evidence.

This is not a counsel of perfection. Both major frameworks ship a first-failure mode. It stops at the operation that produced the invalid value, not at the crash.

The docstring of torch.autograd.detect_anomaly records PyTorch's default: “If check_nan is True, any backward computation that generates "nan" value will raise an error. Default True.” JAX puts the same idea behind a flag. Turning on jax_debug_nans “will cause computations to error-out immediately on production of a NaN”, because “Switching this option on adds a NaN check to every floating point type value produced by XLA.”

Both are slow, and both say so. The PyTorch documentation warns that the mode is for debugging only, since the extra tests slow execution. That is why it is off in an ordinary run. It is not a reason to leave it off in a run that has already failed.

Debug numerical failure at the first corrupt value, not the last visible symptom.

Key idea

Skipping an invalid step may not repair already corrupted state

If overflow is detected before optimizer moments update, skipping can protect parameters. That skip is a specified contract rather than improvised practice.

Dynamic loss scaling is written as a numbered procedure in NVIDIA's mixed-precision guide. If there is an Inf or NaN in the weight gradients, reduce the scale S, then “Skip the weight update and move to the next iteration.” S is increased again only if there has been no Inf or NaN in the last N iterations. PyTorch's GradScaler implements exactly that: “If no inf/NaN gradients are found, invokes optimizer.step() using the unscaled gradients. Otherwise, optimizer.step() is skipped to avoid corrupting the params.” Its documented defaults are init_scale=2.0**16 (65,536), backoff_factor=0.5, growth_factor=2.0 and growth_interval=2000 iterations. Two independent implementations, one rule: detect, halve, skip, retry, and creep the scale back up.

If invalid values already entered buffers or running statistics, the checkpoint may remain damaged. The guarded skip protects the parameters of the step that overflowed; it says nothing about state written before the check ran.

Recovery should load the last known-good state, fix the cause, and replay the batch under instrumentation. Continuing blindly converts a diagnosable event into hidden drift.

Use finite assertions, anomaly detection, norm alerts, overflow counters, and checkpoint quarantine. Clamps and epsilons have to be justified from the domain, because they change the function.

A pipeline that clips every extreme value may stay finite while learning an unintended objective. Preventing them is a matter of data validation and robust losses, not only numerical patches.

Two published runs show what recovering from divergence actually costs. PaLM 540B was one: “we observed spikes in the loss roughly 20 times during training, despite the fact that gradient clipping was enabled”. The remedy was to rewind, not to patch in place. “We re-started training from a checkpoint roughly 100 steps before the spike started, and skipped roughly 200–500 data batches”, the 2022 paper reports.

The OPT-175B run diverged as well, and the fix had the same shape: “When the loss diverged, we found that lowering the learning rate and restarting from an earlier checkpoint allowed for the job to recover and continue training”. That team also released its development chronicles with the code. The note of 7 January 2022 announcing the completed 175B run records “~90 restarts over the course of training the lineage of this current model”. Ninety restarts is what one finished model cost.

A safe recovery boundary must include every state changed by the failed computation.

Visual

How one unstable value spreads

The exact path depends on operation order and optimizer state. The OPT-175B team logged one such chain instead of inferring it, reporting “a correlation between loss divergence, our dynamic loss scalar crashing to 0, and the l2-norm of the activations of the final layer spiking”. Three instruments, one failure. The activation norm, the scaler, and the loss each recorded a different stage of the same event.

FigureProcess · 5 steps
  1. 1

    Extreme input or parameter

    A large magnitude enters a sensitive operation.

  2. 2

    Overflow or invalid transform

    Exponentiation, division, log, norm, or reduction produces infinity or NaN.

  3. 3

    Backward contamination

    Local derivatives carry invalid values into connected gradients.

  4. 4

    Optimizer-state corruption

    Momentum or second moments retain the failure after the batch ends.

  5. 5

    Future forward failure

    Updated parameters create invalid activations on ordinary examples.

Analogy

Finding the first contaminated tank in a water network

Contamination leaves one tank, travels the pipes, and reaches many neighborhoods at once. Testing only the last faucet reveals the problem too late.

Water at least announces itself. Numbers need not. A value can underflow to zero in silence, and optimizer state carries the damage along mathematical dependencies rather than pipes, so a squared-gradient buffer may already be ruined while the loss still looks ordinary.

That silence has a threshold and a measured price. In FP16 any value whose magnitude is below 2^-24 becomes zero. In a Mandarin speech-recognition run, roughly 5% of weight-gradient values had exponents smaller than -24. The 2018 mixed-precision paper states the consequence: “These small valued gradients would become zero in the optimizer when multiplied with the learning rate and adversely affect the model accuracy.” No exception is raised at the moment those gradients disappear. Trace to the first source regardless.

Preserve the earliest corrupt tensor, operation, and batch.

Comparison

Visible and silent instability

A run can degrade long before it crashes, and the two published extremes below are both finite for most of their duration.

Divergence that never leaves the representable range still ends a run. The BLOOM team spent months on one before changing the number format rather than the code at the failing line. A 104-billion-parameter run was, in Stas Bekman's 2022 account of the training, “a complete failure”, with an ever-diverging lm-loss. His conclusion is one sentence long: “Training huge LLM models in FP16 is a no-no.”

The replacement works because of range, not precision — “The key to BF16 format is that it has the same exponent as FP32 and thus doesn't suffer from overflow”. Google Cloud's TPU documentation gives the same fact from outside the project: “The dynamic range of bfloat16 and float32 are equivalent. However, bfloat16 uses half of the memory space.” The GLM-130B team recorded the choice independently: “In BLOOM-176B, the BF16 format is used instead of FP16, due to its wide range of values on NVIDIA Ampere GPUs (i.e., A100).”

Silent degradation is the other end, and it can be counted. The same mixed-precision paper sampled the activation gradients of the Multibox SSD network. Its histogram caption reads: “2% of the values are in the [2-34, 2-32) range, 2% of values are in the [2-24, 2-23) range, and 67% of values are zero”. Two thirds of the sampled signal was already gone, with no NaN anywhere.

Accumulation loses more of it. IBM researchers training networks in 8-bit floating point described what happens when a small addend meets a large one: “it is possible that this smaller number may be truncated entirely after addition due to limited mantissa bits”. They called it swamping, and it limited reduced-precision training until chunk-based accumulation and stochastic rounding were introduced. A flat gradient histogram, not an exception, is the evidence here.

FigureComparison · 3 columns

Visible failure

Loss or parameters become NaN or infinity.

  • Signal: explicit non-finite values
  • Response: stop update
  • Need: first-failure trace
  • Risk: corrupted checkpoint

Divergent finite run

Loss and norms grow rapidly while values remain representable.

  • Signal: exponential growth
  • Response: inspect rate and scale
  • Need: trend alerts
  • Risk: sudden overflow later

Silent degradation

Underflow, saturation, dead units, or clipping erases useful learning.

  • Signal: flat or sparse gradients
  • Response: layerwise diagnostics
  • Need: reference comparison
  • Risk: wasted compute

Example

Operations that frequently create trouble

Stable implementations use transformed formulas and guarded domains.

Half precision has a hard ceiling. It is 65,504. Micikevicius and his co-authors set the loss-scaling factor against that ceiling, advising “choosing a factor so that its product with the maximum absolute gradient value is below 65,504 (the maximum value representable in FP16)”. The Multibox SSD detector “failed to train in FP16 without loss-scaling”; a “loss-scaling factor of 8 recovers the relevant gradient values and mixed-precision training matches FP32 mAP”. Overflow instead fills the weight gradients with infinities and NaNs. Those, the paper warns, “irreversibly damage the weights after an update”.

The distance to that ceiling shrinks with scale, and two teams measured the approach. In the ViT-22B work an 8-billion-parameter model's loss began rising within 2,000 steps, and the paper says why: “Without normalization, attention logits quickly grow to over 50000 in magnitude, resulting in one-hot attention weights after the softmax, and subsequently unstable training losses and gradients.” Over 50,000, against a representable maximum of 65,504. The GLM-130B team met the same pressure in a different architecture: “Second, the attention scores grow so large that they exceed FP16's range, as the model scales up.”

  • Log of zero: Probabilities or variances reach an invalid boundary without epsilon or stable log-domain computation.
  • Exponentials: Large logits overflow unless softmax and log-sum-exp subtract a reference value. The ViT-22B repair was structural rather than a clamp: LayerNorm applied to the queries and the keys, which Dehghani and his co-authors call QK Normalization.
  • Division by tiny counts: Empty masks, near-zero norms, or variance estimates amplify noise, and the defect is catalogued rather than hypothetical. A divide-by-zero in TensorFlow was published as CVE-2021-37636 on 12 August 2021, a CWE-369 with a CVSS 3.1 base score of 5.5: “TensorFlow is an end-to-end open source platform for machine learning. In affected versions the implementation of `tf.raw_ops.SparseDenseCwiseDiv` is vulnerable to a division by 0 error.” The security advisory, reported by the Aivul Team from Qihoo 360, names the cause — the implementation “uses a common class for all binary operations but fails to treat the division by 0 case separately” — and the fix shipped in TensorFlow 2.6.0, with backports to 2.5.1, 2.4.3 and 2.3.4.
  • Squared residuals: Extreme targets dominate reductions and overflow low-precision accumulators.
  • Normalization variance: Tiny batches or constant features produce unstable denominators.
  • Optimizer moments: One extreme gradient contaminates long-lived state even after later gradients appear normal, which is why the PaLM team rewound roughly 100 steps and skipped roughly 200–500 batches instead of dropping the single offending update.

Visual

Trace numerical failure systematically

Minimize the run while preserving the trigger. Steps 1 and 4 are shipped features with defined semantics, not manual instrumentation you have to build.

The docstring of torch.autograd.detect_anomaly states what enabling it buys: “Running the forward pass with detection enabled will allow the backward pass to print the traceback of the forward operation that created the failing backward function.” That is the jump from the last symptom to the first cause, performed by the framework. JAX does the isolation differently but to the same end. Under @jax.jit, jax_debug_nans re-runs the function “in de-optimized op-by-op mode”, so the check lands on the individual operation that produced the NaN rather than on the fused computation that reported it.

FigureProcess · 5 steps
  1. 1. Enable finite checks

    Assert on inputs, activations, loss components, gradients, updates, and state buffers.

  2. 2. Capture the first batch

    Save identifiers, transformed tensors, masks, weights, and random state.

  3. 3. Reproduce at higher precision

    Compare FP32 or FP64 diagnostics where feasible.

  4. 4. Isolate the operation

    Replace stable formulas, clamp valid domains, or simplify the offending branch.

  5. 5. Retest the full state path

    Verify scheduler, scaler, normalization, and optimizer buffers after recovery.

Key takeaways