Skip to content
mlmentorship

Stable Softmax

Convert logits to probabilities without numeric overflow.

Published · 3 min read ·Role-specific ·Intermediate

30-second answer map

Visual first · depth when needed

Subtract the row maximum, exponentiate nonpositive shifts, and divide by their shared sum.

Preparing the visual…

ML breadth · active recall

Practice before you read

8 minutes. Explain the mechanism, why it works, when it fails, and one alternative.

How practice works

ML breadth · closed-book attempt

Stable Softmax

Explain the mechanism, why it works, when it fails, and one alternative.

08:00recommended time

Closing or reloading clears the scratchpad. Only score, weak rubric dimensions, attempt count, and retry date can be stored locally.

Convert logits to probabilities without numeric overflow.

Start with the concrete trace below. It shows the state the algorithm must carry as it runs.

Problem trace

Stable Softmax: Subtract the row maximum, exponentiate nonpositive shifts, and divide by their shared sum.

Input and goalConvert logits to probabilities without numeric overflow.
Find the maximum logitFor logits [1000,1001,999], the row maximum is 1001. Direct exp(1001) can overflow.
10000max = 1001100119992

Recognize it
Use it when exponentials normalize scores into probabilities and large positive logits may overflow even though only relative differences should affect the result.
Keep true
Subtracting one constant from every row element leaves every exponential ratio unchanged; after max shifting, the largest exponent is exp(0)=1 and none can overflow.
Reuse it
Before exponentiating normalized scores, exploit shift invariance to center at the maximum; preserve the reduced dimension so broadcasting back across the row is explicit.
Read it this way: For logits [1000,1001,999], the row maximum is 1001. Direct exp(1001) can overflow. Step through the frames to watch the state change. The last frame shows the answer or the stopping condition.

Pattern: Shift before exponentiation.

Simple idea: Softmax does not change when the same value is subtracted from every logit. Subtract the largest logit so every exponent is at most 1.

import numpy as np

def stable_softmax(logits: np.ndarray, axis: int = -1) -> np.ndarray:
   shifted = logits - np.max(logits, axis=axis, keepdims=True)
   exponentials = np.exp(shifted)
   return exponentials / np.sum(exponentials, axis=axis, keepdims=True)

Cost: time and output space.