Skip to content
AI360Xpert
Beta

Edge of Stability

Gradient descent with a large step rides the highest safe curvature, hovering just above the classical limit while the loss still falls.

Sharpness climbs until it touches the stability ceiling at 2 over step size, then hovers there while the loss oscillates downward
Sharpness climbs until it touches the stability ceiling at 2 over step size, then hovers there while the loss oscillates downward

Why Does This Exist?

Classical optimization says gradient descent with step size η\eta diverges when the sharpness (largest Hessian eigenvalue λmax⁡\lambda_{\max}) exceeds 2/η2/\eta. Yet practitioners train with large steps, watch the loss wobble, and still converge. Cohen and coauthors (2021) named what happens: progressive sharpening lifts curvature until it hits the ceiling, then training runs at the edge of stability, hovering just above 2/η2/\eta with non-monotone but decreasing loss.

This matters because the edge regime is where good solutions live. Small steps that satisfy the classical limit converge monotonically to the nearest minimum, often a sharp one. Large steps that ride the edge oscillate, escape sharp basins, and settle flatter. This page quantifies the ceiling, walks through the two phases, and states the practical readout. Step-size schedules are covered under learning rate schedules; the basin shapes are on the loss geometry page.

The one idea you need first

On a quadratic with curvature λ\lambda, one gradient step multiplies the distance to the minimum by ∣1−ηλ∣|1 - \eta \lambda|. Below 2/η2/\eta the factor is under one (converge); above it, over one (diverge, oscillating in sign). Neural nets are not quadratic, but their local sharpness plays the same role step by step.

Think of It Like This

Driving a washboard road fast

Drive slowly over corrugations and the ride is smooth but you crawl. Drive fast and the car starts bouncing: each bump launches you, yet the suspension and your steering keep the average motion forward. The bounce amplitude stops growing because harder bounces push the tires into dips that absorb them.

Large-step training is the fast drive. Early on the road smooths (loss falls fast), which lets the car go effectively faster over sharper bumps (sharpening). Then the bounce sets in: the loss jitters up and down each step while the moving average keeps descending. Backing off to a crawl removes the bounce and also the speed.

How It Actually Works

Phase 1: progressive sharpening. From initialization, λmax⁡\lambda_{\max} climbs steadily while the loss falls smoothly. The network enters sharper territory as features grow: larger weights mean larger curvature. This continues until λmax⁡\lambda_{\max} reaches 2/η2/\eta.

Phase 2: edge of stability. Sharpness hovers at or just above 2/η2/\eta. The loss stops decreasing monotonically and oscillates with period two (up one step, down the next), while its running average keeps falling over tens of steps. The oscillation itself regulates sharpness: overshooting flattens the local region, undershooting sharpens it, forming a feedback loop around the ceiling.

Worked example

Quadratic L(w)=12λw2L(w) = \frac{1}{2}\lambda w^2 with λ=3\lambda = 3 and η=1\eta = 1. The ceiling is 2/1=22/1 = 2, and λ=3>2\lambda = 3 > 2, so classical theory predicts divergence: factor ∣1−3∣=2|1 - 3| = 2, doubling each step with sign flips. Start w0=1w_0 = 1: w1=1−3=−2w_1 = 1 - 3 = -2, w2=−2+6=4w_2 = -2 + 6 = 4. Diverges, as advertised. Now the neural twist: suppose sharpness adapts, falling to λ=1.9\lambda = 1.9 after an overshoot. Then factor ∣1−1.9∣=0.9|1 - 1.9| = 0.9 and steps contract again. Real training alternates between these two local pictures, which is why the loss wobbles around a downward trend instead of exploding.

A realistic scale: η=0.1\eta = 0.1 gives ceiling 2020. Cohen's experiments show sharpness climbing from about 55 to 2020 over early training, then hovering at 2020 to 2222 for the rest, with loss oscillating visibly per step but falling per epoch.

Code

def gd_path(lam: float, eta: float, w0: float, steps: int) -> list[float]:    w, path = w0, [w0]    for _ in range(steps):        w = w - eta * lam * w  # gradient of 0.5 * lam * w^2        path.append(round(w, 3))    return path

print(gd_path(1.5, 1.0, 1.0))  # -> stable: 1.0, -0.5, 0.25, ... shrinksprint(gd_path(3.0, 1.0, 1.0))  # -> unstable: 1.0, -2.0, 4.0, ... doubles

Curvature 1.51.5 sits below the ceiling 22 and converges; 3.03.0 sits above and diverges on a fixed quadratic. Nets survive above only because curvature adapts.

Watch Out For

Mistaking edge oscillations for divergence

At the edge, single-step loss spikes look alarming: up 10%, down 12%, up again. Engineers kill such runs as diverged and restart with smaller steps, landing in a sharp minimum. Watch the epoch average, not the step: if the average falls over 50 steps while sharpness hovers at 2/η2/\eta, you are at the edge, not diverging.

Measuring sharpness with too little data

Sharpness estimates on a single small batch are noisy enough to hide the ceiling entirely. The hover around 2/η2/\eta appears only in full-batch or large-batch estimates (thousands of samples). If your sharpness trace looks like static, grow the probe batch before concluding the edge is absent.

The Quick Version

  • Gradient descent is classically stable only while sharpness stays below 2/η2/\eta.
  • Real training sharpens progressively until it hits that ceiling, then hovers at it.
  • At the edge the loss oscillates per step but trends down per epoch.
  • The oscillation regulates itself: overshoots flatten, undershoots sharpen.
  • With η=0.1\eta = 0.1, sharpness climbs from about 55 to the ceiling 2020 and stays there.
  • Judge edge runs by the epoch average, and measure sharpness on large batches.