Field Notes

GELU vs ReLU: The Smooth Gate Inside GPT

GELU vs ReLU: The Smooth Gate Inside GPT

The first useful difference is visible at −1

While implementing Chapter 4 of Build a Large Language Model (From Scratch), I reached a function called GELU. Calling it an activation function was accurate but insufficient. I wanted to know what happened to an actual number.

I evaluated GELU and ReLU over the same five inputs:

x = torch.tensor([-2.0, -1.0, 0.0, 1.0, 2.0])

ReLU(x) = [0.0000, 0.0000, 0.0000, 1.0000, 2.0000]
GELU(x) = [-0.0454, -0.1588, 0.0000, 0.8412, 1.9546]

The decisive value is -1. ReLU returns exactly 0. GELU returns approximately -0.1588. GELU reduces the negative input strongly, but it does not necessarily erase it.

Python code sampling GELU and ReLU from negative three to positive three beside the resulting curves
The actual Chapter 4 experiment: 100 inputs from −3 to 3 reveal GELU's shallow negative dip and ReLU's hard zero line.

In the screenshot, torch.linspace(-3, 3, 100) creates 100 evenly spaced inputs for both functions. The shared range makes the contrast honest: ReLU stays exactly on zero throughout the negative side, while GELU dips slightly below zero and curves smoothly back through the origin. On the positive side, both rise with the input, but GELU approaches that straight line gradually.

ReLU is a hard rule

ReLU is short for rectified linear unit. Its complete rule is:

ReLU(x) = max(0, x)

If x is negative, return zero. If x is positive, return x. Applied to one value at a time:

ReLU(-1) = max(0, -1) = 0
ReLU( 2) = max(0,  2) = 2

This produces the sharp corner in the plot. Everything to the left of zero lies flat on zero; everything to the right follows a straight line.

GELU is a smooth, input-dependent gate

GELU also processes every value independently, but it does not make a binary keep-or-delete decision. It computes a multiplier from the input and then multiplies the input by it:

GELU(x) = x × gate(x)

For x = 1, the gate is approximately 0.841192:

1 × 0.841192 = 0.841192

For x = -1, the gate is approximately 0.1588:

-1 × 0.1588 = -0.1588

The gate is computed from x. It is not a stored weight and GELU owns no trainable parameters.

A numeric comparison showing ReLU zeroing negative inputs while GELU retains small negative outputs
Same five inputs, different behavior: ReLU cuts at zero while GELU changes strength smoothly.

The tanh approximation, one operation at a time

The Chapter 4 implementation uses this approximation:

GELU(x) ≈ 0.5 × x × (
    1 + tanh(
        √(2 / π) × (x + 0.044715 × x³)
    )
)

It looks dense because several small operations are written on one line. For x = 1, I can unfold it:

x³                              = 1.000000
x + 0.044715x³                  = 1.044715
√(2/π)                          = 0.797885
0.797885 × 1.044715             = 0.833562
tanh(0.833562)                  = 0.682384
0.5 × (1 + 0.682384)            = 0.841192
x × 0.841192                    = 0.841192

The last-but-one result is the gate. The final line multiplies the original input by that gate.

The GELU tanh approximation evaluated step by step for x equal to one
At x = 1, the tanh expression computes a gate of 0.841192; multiplying by x gives the GELU output.

What tanh contributes

The hyperbolic tangent function maps any real input to a value between -1 and 1. GELU adds one and multiplies by one half:

gate = 0.5 × (1 + tanh(...))

That converts the tanh output into a smooth gate between zero and one. Strongly negative inputs receive gates close to zero. Positive inputs receive increasingly larger gates. The transition is gradual rather than a hard switch at zero.

Zakaria in a navy hoodie operating a hard ReLU cutoff machine and a smooth GELU attenuation machine
ReLU is the hard gate on the left. GELU is the smooth machine on the right: a small negative signal can survive, while larger positive signals pass almost unchanged.

Where GELU sits inside GPT

In the Chapter 4 feed-forward network, GELU sits between two trainable linear layers:

nn.Linear(768, 3072)
GELU()
nn.Linear(3072, 768)

The first linear layer expands every token from 768 to 3072 features. GELU changes each of those 3072 values independently and preserves the shape. The second linear layer projects the token back to 768 features:

(batch, tokens, 768)
→ Linear
(batch, tokens, 3072)
→ GELU
(batch, tokens, 3072)
→ Linear
(batch, tokens, 768)

The two linear layers own trainable weights and biases. GELU does not. It still matters during training because its transformation affects the values flowing forward and the gradients flowing backward.

The mental model I am keeping

ReLU applies a hard rule: negative becomes zero, positive passes unchanged. GELU computes a smooth gate from each input. Negative values can survive in reduced form, and positive values pass more strongly as they grow.

The useful shorthand is not “GELU is complicated ReLU.” It is:

ReLU: hard cutoff
GELU: input × smooth gate(input)

The implementation is visible in Raschka's official Chapter 4 code. PyTorch documents the same tanh GELU approximation and the exact ReLU rule.