The Softmax Function

Softmax converts any list of numbers (logits) into probabilities that sum to 1. It's the bridge between raw model scores and interpretable outputs.

softmax(z_i) = exp(z_i) / sum(exp(z_j) for all j)

Interactive: Type logits, see probabilities

Enter comma-separated numbers (logits) to see the softmax output. Try values like 2, 1, 0.1 or 5, 5, 5 (equal inputs = equal probabilities).

Optional labels (comma-separated):

Bar chart

Key properties

Always sums to 1 No matter what inputs you give, the outputs are a valid probability distribution.
Amplifies differences The exponential makes large logits dominate. A logit of 10 vs 1 doesn't produce a 10:1 ratio, but roughly 8100:1.
Shift-invariant Adding the same constant to all logits doesn't change the output. softmax([2,1,0]) = softmax([102,101,100]).

Cross-Entropy Loss

The loss function that tells the model how wrong its predictions are. Lower is better. Used in almost every classification and language model.

L = -sum( y_true[i] * log(y_pred[i]) )

Intuition

Imagine the correct next token is "cat" (probability should be 1.0). Cross-entropy measures how surprised the model is by the truth.

Confident & correct P("cat") = 0.95 => Loss = -log(0.95) = 0.05 (small)
Uncertain P("cat") = 0.50 => Loss = -log(0.50) = 0.69 (medium)
Confident & wrong P("cat") = 0.01 => Loss = -log(0.01) = 4.61 (huge penalty!)

Interactive: Predicted vs Actual

Drag the slider to change the model's predicted probability for the correct class.

0.70
Loss = 0.36

The curve shows -log(p): loss explodes as probability approaches 0. The model is harshly punished for being confidently wrong.


Gradient Descent & Backpropagation

How neural networks learn: compute the loss, figure out which weights caused it (backprop), then nudge them in the right direction (gradient descent).

The training loop

Input batch
Forward pass
compute predictions
Compute loss
cross-entropy
Backward pass
compute gradients
Update weights
w = w - lr * grad

Gradient Descent: The Ball on a Hill

Imagine standing on a hilly landscape where elevation = loss. You want to reach the lowest point. The gradient tells you which direction is "uphill," so you step the opposite way.

w_new = w_old - learning_rate * gradient

learning_rate = step size (too big: overshoot, too small: slow)

Click to place the ball, then watch it roll downhill via gradient descent. The red dot follows the gradient to the minimum.

0.15

Backpropagation: Computing Gradients Efficiently

Backpropagation applies the chain rule of calculus from the loss backward through each layer. It computes how much each weight contributed to the error.

Loss = 2.34
dL/dOutput
Layer 3 (output)
dL/dL3 × dL3/dL2
Layer 2 (hidden)
dL/dL2 × dL2/dL1
Layer 1 (input)

Each layer receives the gradient from above, multiplies by its own local gradient (chain rule), and passes the result down. This way every weight knows its contribution to the final loss.


The Chain Rule & Computation Graphs

Backpropagation is just the chain rule applied to a computation graph. Every neural network operation becomes a node in this graph.

Example: y = sigmoid(w*x + b)

x = 2.0
input
w = 0.5
weight
\
multiply
w*x = 1.0
|
add (+b)
1.0 + 0.3 = 1.3
b = 0.3
bias
|
sigmoid
sig(1.3) = 0.786
|
y = 0.786

Chain rule in action (backward pass)

To compute dL/dw, we chain the derivatives from the loss back to w:

dL/dw = dL/dy × dy/dz × dz/dw
where z = w×x + b,   y = sigmoid(z)
Step by step:
dy/dz = sigmoid(z) × (1 − sigmoid(z)) = 0.786 × 0.214 = 0.168
dz/dw = x = 2.0
dL/dw = dL/dy × 0.168 × 2.0 = dL/dy × 0.336

Each node in the graph only needs to know its own local derivative. Backpropagation chains them together automatically. This is why frameworks like PyTorch can compute gradients for arbitrarily complex models.

Why this matters for LLMs:

A model like LLaMA 3 70B has billions of parameters. The computation graph has millions of nodes. Backpropagation computes all gradients in one backward pass (same cost as the forward pass), making training feasible.


Activation Functions: ReLU, GELU, SwiGLU

Non-linear functions applied after linear transformations. Without them, stacking layers would be pointless — a stack of linear layers is just one big linear layer.

Interactive: Compare activation functions

Hover over the graph to see input/output values. Toggle functions on/off.

ReLU
f(x) = max(0, x)

Simplest. Zero for negatives, identity for positives. Fast but "dead neurons" can occur (gradient = 0 for x < 0).

Used in: Earlier CNNs, some feed-forward layers

GELU
f(x) = x * Phi(x)

Smooth approximation of ReLU. Allows small negative values through. Probabilistic interpretation: gates by how likely x is positive under a Gaussian.

Used in: GPT-2, BERT, many modern models

SwiGLU
f(x) = Swish(xW1) * (xW2)

Combines Swish activation with a gating mechanism (GLU). Uses two linear projections — one is activated, one is a gate. State-of-the-art in modern LLMs.

Used in: LLaMA, Mistral, Gemma, PaLM


KL Divergence

Measures how one probability distribution differs from another. Crucial for knowledge distillation, VAEs, and understanding cross-entropy.

KL(P || Q) = sum( P(x) * log(P(x) / Q(x)) )

Interactive: Compare two distributions

Adjust the Q distribution sliders to see how KL divergence changes. P (true distribution) is fixed.

P (true): [0.7, 0.2, 0.1]

Fixed reference distribution

0.40
0.35
Q[2] is computed as 1 - Q[0] - Q[1]
KL(P || Q) = 0.00

Blue bars: P (true). Red bars: Q (model). When they match, KL = 0.

KL = 0 Distributions are identical. Perfect match.
KL is asymmetric KL(P||Q) != KL(Q||P). It measures the cost of using Q to approximate P, not vice versa.

The Jacobian Matrix

When a function takes a vector in and produces a vector out, the Jacobian captures all partial derivatives at once. It's the multi-dimensional version of a derivative.

What it looks like

For a function f: R^n -> R^m, the Jacobian is an m x n matrix of partial derivatives:

J = | df1/dx1 df1/dx2 ... df1/dxn | | df2/dx1 df2/dx2 ... df2/dxn | | ... ... ... ... | | dfm/dx1 dfm/dx2 ... dfm/dxn |

Row i, column j tells you: "How much does output i change when input j wiggles?"

Concrete example: Softmax Jacobian

For softmax with 3 inputs, the Jacobian is 3x3. Each entry tells you how changing one logit affects each output probability.

Diagonal (green): increasing logit i increases probability i. Off-diagonal (red): increasing logit i decreases other probabilities (they must sum to 1).

Why it matters in deep learning:

During backpropagation, the gradient at each layer is multiplied by that layer's Jacobian. If Jacobian entries are consistently less than 1, gradients shrink (vanish). If greater than 1, they explode. This is why activation functions, normalization, and residual connections are so carefully designed.


Eigenvalues & Dimensionality Reduction

Eigenvectors point along the directions in your data; each one's eigenvalue (a scalar) tells you how much information that direction carries. PCA uses them together to compress high-dimensional data while preserving the important structure.

Interactive PCA Visualization

The blue dots are 2D data points. The colored arrows show the principal components — the directions of maximum variance. The longer the arrow, the larger the eigenvalue.

Click "New Data" to generate a different distribution and see how the principal components change.

How it works

Step 1: Covariance matrix Compute how each dimension varies with every other dimension.
Step 2: Eigendecomposition Find the eigenvectors (directions) and eigenvalues (importance) of the covariance matrix.
Step 3: Project Keep the top-k eigenvectors (largest eigenvalues). Project data onto these axes. You've reduced dimensions while keeping the most variance.
In LLM context:

When you see 2D t-SNE or PCA plots of embeddings, they're projecting 768+ dimensional vectors down to 2D using these techniques. The eigenvalues tell you how much information is lost. Tools like LoRA also exploit low-rank structure — many weight matrices have a few dominant eigenvalues, meaning most of their "knowledge" lives in a low-dimensional subspace.


How Residual Connections Help Gradients

Without residual (skip) connections, deep networks can't train. The gradient signal dies before reaching early layers. Residuals provide a highway for gradients.

Gradient magnitude through layers

This chart shows how gradient magnitude changes as you go deeper into a network. Without residuals, gradients shrink exponentially. With residuals, they stay healthy.

Without residuals With residuals

Why it works

Without residual: output = f(input) gradient must pass through f With residual: output = input + f(input) gradient = 1 + df/dinput The "1" term means gradients always have a direct path through the skip connection, even if f's gradient is tiny.
Vanishing gradient problem In a 96-layer network without residuals, if each layer's gradient multiplier is 0.9, the gradient at layer 1 is 0.9^96 ≈ 0.00004. Essentially zero. Layer 1 can't learn.
With residuals The skip connection adds 1 to the gradient at each layer. Even if the transformation's gradient is 0, the skip connection preserves the signal. The gradient at layer 1 stays close to 1.0.
Bonus: learning residuals is easier Each layer only needs to learn a small refinement (delta) on top of the existing representation. Learning "change this by +0.01" is easier than learning "reconstruct this entire representation from scratch."

Number Formats: FP32, FP16, BF16, INT8, INT4

Every weight, activation, and gradient in an LLM is a number stored in a specific format. The format you choose determines memory usage, speed, and whether training converges at all.

How floating point numbers work

A floating point number has three parts: sign (positive or negative), exponent (the scale/magnitude), and mantissa (the precision). More bits = more precision and range, but more memory.

FP32 32 bits • 4 bytes per value
S
1 sign 8 exponent 23 mantissa

Full precision. Used for optimizer states (Adam momentum/variance). Range: ±3.4×10³⁸, ~7 decimal digits of precision.

FP16 16 bits • 2 bytes
S
1s 5 exp 10 mantissa

More precision, less range. Range: ±65,504. Can overflow during training if gradients spike. Good for inference weights.

BF16 16 bits • 2 bytes
S
1s 8 exp 7 mantissa

Same range as FP32, less precision. Range: ±3.4×10³⁸. Truncated FP32 — just drop the bottom 16 mantissa bits. Preferred for training because it never overflows.

INT8 8 bits • 1 byte

Integer, not floating point. 8 bits stored in two's complement (no separate sign bit — the top bit carries weight -128). Range: -128 to 127. Requires calibration (scale + zero-point). 4× smaller than FP32. Good for inference with minimal quality loss.

INT4 / NF4 4 bits • 0.5 bytes

Only 16 possible values (4 bits, no separate sign bit). NF4 (QLoRA) uses a normal distribution to pick 16 optimal quantization levels. 8× smaller than FP32. Used for QLoRA and aggressive inference compression.

When to use each format

FormatBitsSize (7B model)PrecisionBest for
FP323228 GB~7 digitsOptimizer states (Adam), loss accumulation
BF161614 GB~2 digitsTraining (forward/backward pass) — same range as FP32
FP161614 GB~3 digitsInference weights, mixed-precision training with loss scaling
INT887 GB256 levelsPost-training quantization for inference
INT4 / NF443.5 GB16 levelsQLoRA fine-tuning, aggressive compression

Why BF16 won the training format war

FP16 has only 5 exponent bits — its max value is 65,504. During training, gradient magnitudes can exceed this, causing overflow → NaN → training crash. BF16 has 8 exponent bits (same as FP32) so it handles the same range of magnitudes. The tradeoff: BF16 has less precision (7 mantissa bits vs FP16's 10), but in practice neural networks are robust to this — the gradient noise during training is larger than the precision loss.

Mixed precision training: Forward and backward pass in BF16 (fast, memory-efficient), but optimizer states and loss accumulation in FP32 (precise). This gives you the speed of 16-bit with the stability of 32-bit. Used by every modern LLM training run.

Interactive: See what precision loss looks like

Type a number and see how each format represents it. Notice how FP16 and BF16 round differently, and how INT8 snaps to the nearest of 256 levels.

Try: 0.1, 65535, 100000, 0.000001

Walkthrough: Mixed precision training pipeline

Every modern LLM training step uses multiple formats simultaneously. Click each stage to see what format is used and why:

What is quantization?

Quantization reduces the number of bits used to store each weight, trading precision for smaller model size and faster inference. It's like rounding — you lose some detail, but the model still works surprisingly well.

Concrete example: quantizing one weight
FP32 (original)
0.78431372
32 bits — infinite precision
4 bytes per weight
INT8 (quantized)
194 out of 256
8 bits — 256 possible values
1 byte per weight (4× smaller)
How it works: Find the range of all weights (e.g., -1.5 to +1.5), then map that range onto 256 integer levels (0-255). Each weight snaps to the nearest level. To use the weight, scale back: 194/255 × 3.0 - 1.5 = 0.7824 — close to 0.7843, but not exact. The error (0.002) is the cost of compression.
Why it matters: memory savings
FP32
28 GB
7B model
FP16/BF16
14 GB
2× smaller
INT8
7 GB
4× smaller
INT4
3.5 GB
8× smaller
A 70B model in FP32 needs ~280 GB (multiple GPUs). In INT4 it fits in ~35 GB (one GPU). This is why quantization is essential for deploying large models.

Interactive: Quantization quality loss

Drag the slider to see how reducing bits affects a set of model weights. The red bars show the error between original and quantized values.

16