FFN Activation Functions › Activation Functions in Practice

Michael BrenndoerferUpdated June 14, 20254 min read
Language AI HandbookAll chapters
Part 15 of 67Transformer Blocks
Chapter 108FFN Activation Functions › Activation Functions in Practice
Reading section 8 of 15 · Activation Functions in Practice · 4 minSection 8 of 15 · 4 min

Book contents

Section 8 of 15 · 4 min read

FFN Activation Functions › Activation Functions in PracticeOne section · Sections in this chapter8 / 15 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let's simulate how these activations behave in an actual FFN layer, processing typical transformer hidden states:

In[20]:
Code
# Simulate FFN behavior with different activations
np.random.seed(42)

d_model = 768
d_ff = 3072
batch_size = 1000

# Initialize FFN weights (shared across activations)
# Use standard deviation of 1/sqrt(d_model) to produce pre-activations with std ~1
W1 = np.random.randn(d_model, d_ff) / np.sqrt(d_model)
b1 = np.zeros(d_ff)
W2 = np.random.randn(d_ff, d_model) * np.sqrt(2.0 / (d_ff + d_model))
b2 = np.zeros(d_model)

# Generate input batch (simulating post-attention representations)
X = np.random.randn(batch_size, d_model)

# Compute pre-activations
pre_act = X @ W1 + b1

# Apply each activation
activations_dict = {
    "ReLU": relu(pre_act),
    "GELU": gelu_exact(pre_act),
    "SiLU": silu(pre_act),
}


# Compute statistics
def compute_stats(hidden):
    """Compute activation statistics."""
    return {
        "mean": hidden.mean(),
        "std": hidden.std(),
        "sparsity": (np.abs(hidden) < 0.01).mean(),
        "negative_fraction": (hidden < 0).mean(),
    }


stats = {name: compute_stats(h) for name, h in activations_dict.items()}
Out[21]:
Console
Hidden layer statistics by activation function:

Activation       Mean        Std     Sparsity     Negative
----------------------------------------------------------
ReLU           0.3991     0.5843       50.4%        0.0%
GELU           0.2823     0.5884        2.0%       50.0%
SiLU           0.2068     0.5601        1.6%       50.0%

The statistics reveal important differences. ReLU creates the sparsest representations (50% of values near zero) and has no negative outputs. GELU and SiLU have less sparsity and a small fraction of negative outputs. The mean activation is higher for the smooth activations because they don't hard-threshold negative inputs to zero.

These differences in representation density have downstream consequences. The second FFN weight matrix W2W_2 projects the hidden representation back down to dmodeld_{\text{model}}. With ReLU, half of the 3072 hidden dimensions are exactly zero, so the matrix multiplication effectively only involves the non-zero dimensions. With GELU or SiLU, most of the 3072 dimensions carry non-zero values, meaning the projection must process a denser representation. This is slightly more expensive but also means the network uses more of its representational capacity on each forward pass.

Out[22]:
Visualization
Histogram of ReLU activations showing large spike at zero and positive tail.
ReLU hidden activations from a BERT-sized FFN layer (768 input dim, 3072 hidden dim). The distribution shows a large concentration near zero (all zeroed negative pre-activations) with a positive-only tail. This shows the 50% sparsity characteristic of ReLU for normally distributed inputs.
Histogram of GELU activations showing smooth distribution with small negative tail.
GELU hidden activations from the same FFN configuration. The distribution is smoother and more symmetric, with a small left tail of negative values. The peak near zero is less pronounced than ReLU's, indicating lower sparsity but richer representation of mildly negative features.
Histogram of SiLU activations showing smooth distribution with moderate negative tail.
SiLU hidden activations showing a distribution similar to GELU but with a slightly more pronounced negative tail. The left tail extends further than GELU's. This reflects SiLU's deeper negative dip around x = -1.28.