FFN Activation Functions: ReLU, GELU

Michael BrenndoerferUpdated June 14, 202552 min read

Part of Language AI Handbook

Compare activation functions in transformer feed-forward networks: ReLU's simplicity and dead neuron problem, GELU's smooth probabilistic gating for BERT.

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

FFN Activation Functions

The feed-forward network's power comes from its nonlinear activation function. Without nonlinearity, stacking multiple linear layers would collapse into a single linear transformation, no matter how deep the network. The activation function is what enables FFNs to approximate arbitrarily complex functions of language.

Think of an activation function as the network's ability to "make decisions." A purely linear transformation can only scale and rotate data; it can express linear relationships but nothing more. Introduce even a single nonlinear function, and the network can learn curves and thresholds alongside interactions. This distinction, between linear and nonlinear computation, separates a powerful learning machine from a sophisticated calculator.

The key insight is that activation functions do not just inject nonlinearity. They also control how gradients flow backward through the network during training. A poorly chosen activation can starve layers of gradient signal, causing the network to stop learning. The history of deep learning is partly a history of discovering better activation functions, each addressing a specific failure mode of its predecessor.

The original transformer used ReLU, the workhorse of deep learning since 2012. But as researchers scaled transformers to billions of parameters and trained them on trillions of tokens, subtle differences between activation functions became significant. GELU emerged as the standard for encoder models like BERT. SiLU (also called Swish) now dominates decoder models like LLaMA and GPT-NeoX. Understanding why these activations differ, and when each excels, is essential for building modern language models.

In practice, you will rarely train a language model from scratch and choose its activation function freely. You will more often be fine-tuning a model that already has a baked-in activation choice. But understanding the reasoning behind these choices helps you interpret model behavior, diagnose training problems, and make informed decisions when you do have architectural freedom. It also helps you read the research literature, where activation functions appear as key variables in ablation studies.

This chapter traces the evolution from ReLU to GELU to SiLU/Swish, examining the mathematical properties that make each function suitable for different contexts. You'll implement each activation, visualize their differences, and understand the practical trade-offs that guide model design choices.

Why Nonlinearity Is Non-Negotiable

Before examining specific activation functions, it is worth building a deeper intuition for why nonlinearity matters so much. The feed-forward sublayer in a transformer takes a vector of dimension dmodeld_{\text{model}}, projects it up to a higher-dimensional space of size dffd_{\text{ff}}, applies an activation, and then projects back down. In mathematical terms, a two-layer FFN computes:

FFN(x)=W2⋅act(W1x+b1)+b2\text{FFN}(\mathbf{x}) = W_2 \cdot \text{act}(W_1 \mathbf{x} + b_1) + b_2

where:

  • x∈Rdmodel\mathbf{x} \in \mathbb{R}^{d_{\text{model}}}: the input vector from the attention sublayer
  • W1∈Rdff×dmodelW_1 \in \mathbb{R}^{d_{\text{ff}} \times d_{\text{model}}}: the first projection (expands dimensionality)
  • b1∈Rdffb_1 \in \mathbb{R}^{d_{\text{ff}}}: the first bias vector
  • act(⋅)\text{act}(\cdot): the activation function applied elementwise
  • W2∈Rdmodel×dffW_2 \in \mathbb{R}^{d_{\text{model}} \times d_{\text{ff}}}: the second projection (reduces dimensionality back)
  • b2∈Rdmodelb_2 \in \mathbb{R}^{d_{\text{model}}}: the second bias vector

If act\text{act} were the identity function (no nonlinearity), the composition W2⋅(W1x+b1)+b2W_2 \cdot (W_1 \mathbf{x} + b_1) + b_2 would simplify to W2W1x+W2b1+b2W_2 W_1 \mathbf{x} + W_2 b_1 + b_2, which is just a linear transformation. Stacking twenty such layers would still produce one linear transformation. All that computation would buy you nothing beyond a single matrix multiply.

The universal approximation theorem tells us that a network with a single hidden layer and a nonlinear activation can approximate any continuous function to arbitrary precision, given enough hidden units. The key requirement is exactly that nonlinearity. In practice, deeper networks with smaller hidden layers learn more efficiently than shallow networks with enormous hidden layers, but the theoretical foundation is the same: nonlinearity is the ingredient that maps matrix multiplication into universal function approximation.

For language modeling specifically, the FFN layers are thought to store factual knowledge. Research by Geva et al. (2021) showed that individual FFN layers function like key-value memories: the first layer's columns act as "keys" that match patterns in the input, and the second layer's rows act as "values" that contribute to the output distribution. The activation function is what allows these keys to be selective, activating strongly for certain input patterns and weakly for others. Without nonlinearity, every key would always partially match every input, making the retrieval process incoherent.

Notice that the FFN's nonlinearity is fundamentally different from attention's nonlinearity. Attention creates nonlinear interactions between positions (which tokens attend to which). The FFN creates nonlinear transformations within each position independently. Together, these two nonlinearities give the transformer its expressive power.

Historical Context: The Search for Better Activations

The history of activation functions mirrors the history of deep learning itself. Early neural networks used sigmoid and tanh activations, which saturate for large input values, causing vanishing gradients in deep networks. ReLU, introduced to the deep learning community by Nair and Hinton in 2010 and popularized by Krizhevsky et al. in AlexNet (2012), solved the vanishing gradient problem for positive inputs and enabled training of networks dozens of layers deep. The decade following ReLU saw dozens of proposed alternatives, including Leaky ReLU and PReLU as well as ELU and SELU, each addressing edge cases. GELU and SiLU represent the current generation, optimized for the specific demands of large-scale transformer training.

ReLU: The Original Choice

The Rectified Linear Unit (ReLU) is the simplest nonlinear activation function in modern deep learning. It applies a trivial rule: keep positive values unchanged, set negative values to zero. Mathematically:

ReLU(x)=max⁡(0,x)\text{ReLU}(x) = \max(0, x)

where:

  • xx: the input value (a single element of the pre-activation vector)
  • max⁡(0,x)\max(0, x): returns xx if x>0x > 0, otherwise returns 00

This simplicity is deceptive. ReLU revolutionized deep learning when introduced in AlexNet (2012), enabling training of much deeper networks than the sigmoid and tanh activations that preceded it. The original transformer adopted ReLU for its feed-forward layers, inheriting this proven workhorse.

Think of ReLU as a one-way valve. Values trying to flow in the positive direction pass through without resistance. Values trying to flow in the negative direction are blocked completely. The network learns to use this valve to select which "features" in the hidden representation are worth propagating and which should be silenced.

The key insight behind ReLU's success is its effect on gradients. Sigmoid and tanh both saturate: for very large or very small inputs, their derivatives approach zero, making it nearly impossible for deep layers to receive useful gradient signal. ReLU does not saturate for positive inputs. Its derivative is exactly 1 for any positive value, meaning gradient flows through positive activations without shrinkage. This simple property allowed researchers to train networks twenty, fifty, even one hundred layers deep, something that had been essentially impossible with saturating activations.

Rectified Linear Unit (ReLU)

A piecewise linear activation function that outputs the input directly if positive, otherwise outputs zero. Its simplicity enables fast computation and its non-saturating positive region prevents vanishing gradients during backpropagation.

Let's implement ReLU and examine its behavior:

In[3]:
Code
import numpy as np

np.random.seed(42)


def relu(x):
    """Rectified Linear Unit activation."""
    return np.maximum(0, x)


def relu_derivative(x):
    """Derivative of ReLU."""
    return (x > 0).astype(float)


# Generate input range
x = np.linspace(-3, 3, 1000)
Out[4]:
Visualization
Plot showing ReLU function as a bent line at the origin.
ReLU activation function across the input range [-3, 3]. Negative inputs are zeroed out entirely, while positive inputs pass through unchanged. The sharp corner at the origin is the defining characteristic of ReLU and the source of both its efficiency and its limitations.
Plot showing ReLU derivative as a step function.
ReLU derivative. The derivative is a step function: 0 for negative inputs, 1 for positive inputs. The sharp corner at zero is both ReLU''s strength (computational simplicity) and weakness (non-smooth gradients).

ReLU's advantages are clear. Its computation is trivial: a single comparison operation. The derivative is equally simple:

ReLU′(x)={1if x>00if x≤0\text{ReLU}'(x) = \begin{cases} 1 & \text{if } x > 0 \\ 0 & \text{if } x \leq 0 \end{cases}

where:

  • The derivative is 1 for positive inputs, meaning gradients flow through unchanged
  • The derivative is 0 for negative inputs, meaning gradients are blocked entirely
  • The derivative is technically undefined at exactly x=0x = 0, but implementations typically use 0 or 1

This step function gradient never vanishes for positive inputs like sigmoid's gradient does for large values. And ReLU creates sparse activations, as roughly half of all hidden units output zero for typical input distributions, which can improve efficiency and interpretability.

In practice, the sparsity ReLU creates is both a feature and a design principle. Sparse representations are more interpretable: when only a subset of neurons activate for a given input, you can potentially trace which features are "firing" for which inputs. Sparse activations also enable optimizations in hardware, since multiplying by zero can be skipped. In very large FFN layers (some models use dff=16,384d_{\text{ff}} = 16{,}384 or larger), exploiting this sparsity can reduce compute.

The Dead ReLU Problem

But ReLU has a critical weakness. When a neuron's pre-activation is consistently negative, its gradient is always zero. The neuron stops learning entirely. This "dying ReLU" problem becomes more severe as networks deepen or learning rates increase.

Consider what happens during training. If a weight update pushes a neuron's bias too negative, that neuron's output becomes zero for all inputs. With zero output, the gradient flowing through that neuron is zero. With zero gradient, the weights never update. The neuron is dead.

The cascading nature of this problem makes it particularly insidious. A dead neuron in an early layer of the FFN prevents any downstream computation from receiving information through that pathway. If many neurons die, the effective width of the network shrinks, reducing its capacity. In extreme cases, if a whole layer becomes largely dead, the model trains without using that layer at all, wasting parameters and compute.

Dead neurons arise most commonly from two causes. The first is a high initial learning rate: a large gradient step early in training can push many neurons into the negative half-plane simultaneously, and if the learning rate remains high, the neurons never recover. The second is weight initialization that creates very negative initial biases, giving neurons little chance of activating before training has even begun. Careful initialization (like He initialization, which accounts for ReLU's expected output variance) reduces the risk but does not eliminate it.

In[5]:
Code
# Simulate dying ReLU phenomenon
def simulate_dead_neurons(d_ff, n_samples, bias_shift=-2.0):
    """
    Simulate FFN hidden layer activations with shifted bias.

    Returns the fraction of neurons that are 'dead' (always zero).
    """
    # Random input activations (post-attention representations)
    inputs = np.random.randn(n_samples, d_ff)

    # Simulate pre-activations with negative bias shift
    pre_activations = inputs + bias_shift

    # Apply ReLU
    activations = relu(pre_activations)

    # Count neurons that are zero for all samples
    neuron_max = activations.max(axis=0)
    dead_fraction = (neuron_max == 0).mean()

    return dead_fraction, activations


# Test different bias shifts
bias_shifts = np.linspace(0, -3, 7)
dead_fractions = []

for shift in bias_shifts:
    # A modest batch exposes how negative bias can silence a neuron across all
    # examples, while keeping the comparison deterministic across render themes.
    dead_frac, _ = simulate_dead_neurons(1000, 64, shift)
    dead_fractions.append(dead_frac)
Out[6]:
Visualization
Line plot showing dead neuron fraction increasing from 0% at bias shift 0 to more than 90% at bias shift -3.
Dead neuron fraction as a function of bias shift in a simulated 1000-neuron ReLU layer evaluated over a batch of 64 inputs. As biases become more negative (x-axis moves right), an increasing fraction of neurons output zero for every input in the batch, effectively disconnecting those neurons from the computation graph. At a bias shift of -3.0, more than 90% of neurons are dead and the layer has lost most of its representational capacity.

The plot shows how quickly neurons die as bias values shift negative. In real training, this can happen in patches of the network, reducing effective capacity without obvious symptoms. The model trains, but parts of it have effectively disconnected from the computation.

Notice that the dying-neuron problem has no easy diagnostic signal during training. The loss might still decrease, the validation metrics might still improve, but you could be doing so with only a fraction of the network's intended capacity. This silent degradation motivated researchers to search for activations that provide nonzero gradients everywhere, even for negative inputs.

ReLU Variants: Partial Fixes

Several ReLU variants attempted to address the dead neuron problem before GELU and SiLU became the dominant alternatives. Leaky ReLU uses a small negative slope (typically 0.01) for negative inputs instead of zeroing them:

LeakyReLU(x)={xif x>0αxif x≤0\text{LeakyReLU}(x) = \begin{cases} x & \text{if } x > 0 \\ \alpha x & \text{if } x \leq 0 \end{cases}

where:

  • α\alpha: a small positive constant (typically 0.01), sometimes called the "leak" coefficient

By allowing a tiny gradient for negative inputs, Leaky ReLU prevents neurons from dying completely. Parametric ReLU (PReLU) takes this further by making α\alpha a learnable parameter, allowing the network to decide how much of the negative signal to preserve. ELU (Exponential Linear Unit) uses a smooth exponential function for negative inputs, avoiding the discontinuity entirely.

These variants were steps in the right direction, but none of them fully addressed the deeper issue: ReLU's sharp corner at zero creates an abrupt transition that can destabilize training in large models. Researchers ultimately needed a non-zero negative gradient inside a smooth, differentiable function throughout its entire domain. That insight led to GELU.

GELU: The Smooth Alternative

Hendrycks and Gimpel introduced the Gaussian Error Linear Unit (GELU) in 2016, though it didn't gain widespread adoption until BERT popularized it in 2018. GELU addresses ReLU's sharp corner by introducing smooth, probabilistic gating.

The intuition behind GELU is elegant: instead of deterministically zeroing negative values, gate each input by its probability of being positive under a standard normal distribution. Inputs that are clearly positive (large positive values) pass through nearly unchanged. Inputs that are clearly negative (large negative values) are nearly zeroed. Inputs near zero, where classification is uncertain, are partially attenuated.

Think of GELU as a soft decision boundary instead of a hard one. Whereas ReLU says "if you're negative, you're blocked, full stop," GELU says "if you're probably negative, you're mostly blocked, but we'll keep a little signal proportional to how uncertain we are." This probabilistic framing connects activation functions to Bayesian reasoning: the gate is literally a probability estimate.

The mathematical formulation follows this intuition:

GELU(x)=x⋅Φ(x)\text{GELU}(x) = x \cdot \Phi(x)

where:

  • xx: the input value to the activation function
  • Φ(x)\Phi(x): the cumulative distribution function (CDF) of the standard normal distribution, representing the probability that a standard normal random variable is less than or equal to xx

The CDF itself is computed using the error function:

Φ(x)=P(X≤x)=12[1+erf(x2)]\Phi(x) = P(X \leq x) = \frac{1}{2}\left[1 + \text{erf}\left(\frac{x}{\sqrt{2}}\right)\right]

where:

  • P(X≤x)P(X \leq x): the probability that a standard normal random variable XX takes a value less than or equal to xx
  • erf(⋅)\text{erf}(\cdot): the Gauss error function, a special function that arises in probability and statistics
  • 2\sqrt{2}: a scaling factor that converts from the standard normal to the error function's parameterization

The CDF Φ(x)\Phi(x) ranges from 0 to 1. For large positive xx, Φ(x)≈1\Phi(x) \approx 1, so GELU(x)≈x\text{GELU}(x) \approx x. For large negative xx, Φ(x)≈0\Phi(x) \approx 0, so GELU(x)≈0\text{GELU}(x) \approx 0. The transition is smooth, governed by the bell curve of the normal distribution.

The key insight here is the choice of the normal distribution as the gating mechanism. By using Φ(x)\Phi(x), we are essentially asking: "if I draw a random variable from a standard normal, how likely is it to be less than this input?" For a pre-activation value of 0, the answer is 50%, so GELU attenuates zero-valued inputs by half. For a pre-activation value of 2, the answer is about 97.7%, so GELU passes almost all of that signal. This creates a natural, data-independent scale for "how much" to pass each value.

Gaussian Error Linear Unit (GELU)

An activation function that gates inputs by their Gaussian CDF values, creating a smooth, non-monotonic function that approximates stochastic regularization. GELU is the standard activation for encoder-style transformers like BERT and RoBERTa.

In[7]:
Code
from scipy.special import erf


def gelu_exact(x):
    """GELU activation using exact computation."""
    return x * 0.5 * (1 + erf(x / np.sqrt(2)))


def gelu_approximate(x):
    """GELU approximation using tanh (faster, commonly used)."""
    return 0.5 * x * (1 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * x**3)))


def gelu_derivative(x):
    """Derivative of GELU (exact)."""
    phi = 0.5 * (1 + erf(x / np.sqrt(2)))
    pdf = np.exp(-0.5 * x**2) / np.sqrt(2 * np.pi)
    return phi + x * pdf

GELU has a distinctive shape: it's nearly linear for positive values, smoothly transitions through zero, and has a slight dip into negative territory before approaching zero asymptotically. This dip is unique. For small negative inputs (around x≈−0.5x \approx -0.5), GELU outputs slightly negative values. This non-monotonic behavior distinguishes it from ReLU variants.

The derivative of GELU, which determines gradient flow during backpropagation, is:

GELU′(x)=Φ(x)+x⋅ϕ(x)\text{GELU}'(x) = \Phi(x) + x \cdot \phi(x)

where:

  • Φ(x)\Phi(x): the standard normal CDF (as defined above)
  • ϕ(x)=12πe−x2/2\phi(x) = \frac{1}{\sqrt{2\pi}} e^{-x^2/2}: the standard normal probability density function (PDF)
  • The first term Φ(x)\Phi(x) contributes the "identity-like" gradient for positive inputs
  • The second term x⋅ϕ(x)x \cdot \phi(x) creates a smooth transition region and ensures the derivative is continuous everywhere

Notice that this derivative is never exactly zero except in the limit as x→−∞x \to -\infty. For any finite input, GELU provides some gradient signal, addressing the dying-neuron problem that afflicts ReLU. The gradient is small for very negative inputs, but it is nonzero. This means GELU neurons can, in principle, recover from temporarily negative pre-activations during training.

Out[8]:
Visualization
Plot showing GELU as a smooth S-shaped curve with slight negative dip.
GELU activation function across [-3, 3]. Unlike ReLU, GELU is smooth everywhere and non-monotonic, with a slight negative dip around x = -0.5. The dashed line shows the identity y = x, illustrating how GELU approaches linear behavior for large positive inputs.
Plot showing GELU derivative as a smooth sigmoid-like curve.
GELU derivative across [-3, 3]. The derivative transitions smoothly from near 0 for large negative inputs to near 1 for large positive inputs, avoiding ReLU's discontinuity at the origin and providing gradient signal across the full input range.

Worked Example: Tracing a Value Through GELU

To build concrete intuition, let's trace a single pre-activation value through the GELU computation step by step. Suppose the FFN's first layer produces a pre-activation of x=1.0x = 1.0 for a particular neuron.

Step 1: Compute the CDF value at x=1.0x = 1.0. The standard normal CDF at 1.0 is Φ(1.0)≈0.8413\Phi(1.0) \approx 0.8413. This says: "there is an 84.1% probability that a standard normal variable is at most 1.0."

Step 2: Multiply the input by this CDF value:

GELU(1.0)=1.0×0.8413≈0.841\text{GELU}(1.0) = 1.0 \times 0.8413 \approx 0.841

The neuron passes about 84% of its signal. Now suppose the pre-activation is x=−1.0x = -1.0. The CDF value is Φ(−1.0)≈0.1587\Phi(-1.0) \approx 0.1587, so:

GELU(−1.0)=−1.0×0.1587≈−0.159\text{GELU}(-1.0) = -1.0 \times 0.1587 \approx -0.159

The neuron passes about 16% of its (negative) signal, attenuating it substantially but not zeroing it. Compare this to ReLU, which would output 0 for x=−1.0x = -1.0 and 1.0 for x=1.0x = 1.0. GELU's output is softer in both cases: positive activations are attenuated slightly, negative activations are attenuated heavily but remain nonzero.

This soft gating behavior, controlled by the Gaussian CDF, is what makes GELU particularly well-suited to the pre-activation distributions that arise in transformer FFNs. Transformer pre-activations after LayerNorm are approximately normally distributed. GELU's gating function is calibrated to the same normal distribution, making the soft-gating particularly well-aligned with the actual statistical properties of the data it operates on.

GELU Approximations

Computing the exact error function is expensive. In practice, transformers use fast approximations. The most common is the tanh approximation:

GELUapprox(x)=0.5⋅x⋅(1+tanh⁡(2π(x+0.044715x3)))\text{GELU}_{\text{approx}}(x) = 0.5 \cdot x \cdot \left(1 + \tanh\left(\sqrt{\frac{2}{\pi}}\left(x + 0.044715 x^3\right)\right)\right)

where:

  • xx: the input value
  • tanh⁡(⋅)\tanh(\cdot): the hyperbolic tangent function, which outputs values in the range (−1,1)(-1, 1)
  • 2/π≈0.7979\sqrt{2/\pi} \approx 0.7979: a scaling constant derived from properties of the normal distribution
  • 0.0447150.044715: an empirically-fitted coefficient that improves the approximation accuracy
  • x+0.044715x3x + 0.044715 x^3: a polynomial that approximates the argument to the error function

This formula looks complex, but it avoids the expensive erf computation while matching GELU closely. The constants were determined by fitting to the exact GELU curve, minimizing the maximum error across typical input ranges.

The reason this approximation works so well is that tanh⁡\tanh and erf\text{erf} are closely related functions. Both are odd functions (symmetric about the origin), both map the real line to (−1,1)(-1, 1), and both have an S-shaped curve. The polynomial x+0.044715x3x + 0.044715 x^3 inside the tanh⁡\tanh call adjusts the rate of transition to closely match the CDF shape. The result is an approximation that differs from the exact GELU by at most about 0.004 across the full input range.

Let's compare the exact and approximate versions:

In[9]:
Code
# Compare exact and approximate GELU
x_test = np.linspace(-4, 4, 1000)
gelu_exact_vals = gelu_exact(x_test)
gelu_approx_vals = gelu_approximate(x_test)

max_error = np.max(np.abs(gelu_exact_vals - gelu_approx_vals))
mean_error = np.mean(np.abs(gelu_exact_vals - gelu_approx_vals))
Out[10]:
Console
GELU approximation accuracy:
  Maximum absolute error: 0.000473
  Mean absolute error:    0.000196

The approximation error is negligible for practical purposes. Most deep learning frameworks offer both versions, with the tanh approximation being faster on hardware that lacks specialized erf instructions.

Out[11]:
Visualization
Plot showing exact and approximate GELU curves overlapping almost perfectly, with a small inset showing the magnified error.
Comparison of exact GELU and its tanh approximation across [-4, 4]. The two curves are visually indistinguishable at this scale. The inset (magnified by 1000x) shows the approximation error, which peaks around x = 1 but remains below 0.005 across the entire range. This level of accuracy is sufficient for all practical applications.

Why GELU Became Standard for Encoders

GELU's adoption in BERT (2018) was a turning point in natural language processing. Before BERT, most language model pretraining used simpler activations. Devlin et al. chose GELU for BERT partly based on empirical results and partly based on the theoretical appeal of smooth, probabilistic gating. The success of BERT then cemented GELU as the activation of choice for the encoder-style transformer community, including RoBERTa and ALBERT, as well as ELECTRA and virtually every BERT successor.

The connection between GELU and dropout is worth understanding. Hendrycks and Gimpel's original GELU paper framed the activation as an approximation to a network where each neuron's activation is randomly zeroed (like dropout) with probability 1−Φ(x)1 - \Phi(x). From this perspective, GELU implements a kind of "stochastic depth" within each neuron's activation: high-activation neurons are nearly always kept; low-activation neurons are frequently zeroed out. This connection to regularization may partly explain why GELU trains more stably than ReLU at large scale.

In practice, the difference between GELU and a well-tuned ReLU model often comes down to training dynamics in the first few thousand steps. GELU's smooth gradients allow larger learning rates and more aggressive optimization without the risk of mass neuron death that can afflict ReLU networks. Once training is stable, both activations often achieve similar final performance. But for large models trained on large datasets, where training stability matters, GELU's smooth behavior provides meaningful advantages.

SiLU/Swish: The Modern Standard

While GELU became the activation of choice for encoder models like BERT, a different function emerged for large decoder models: SiLU, also known as Swish. Google researchers introduced Swish in 2017 through neural architecture search, discovering that this simple formula consistently outperformed ReLU across tasks.

SiLU(x)=x⋅σ(x)=x1+e−x\text{SiLU}(x) = x \cdot \sigma(x) = \frac{x}{1 + e^{-x}}

where:

  • xx: the input value
  • σ(x)\sigma(x): the logistic sigmoid function (defined below)
  • e−xe^{-x}: the exponential function with negative argument, which rapidly approaches 0 for large positive xx and infinity for large negative xx

The sigmoid function that gates the input is:

σ(x)=11+e−x\sigma(x) = \frac{1}{1 + e^{-x}}

where:

  • σ(x)\sigma(x): outputs a value in the range (0,1)(0, 1), interpretable as a "gate" or probability
  • For large positive xx: σ(x)→1\sigma(x) \to 1, so SiLU(x)→x\text{SiLU}(x) \to x (input passes through)
  • For large negative xx: σ(x)→0\sigma(x) \to 0, so SiLU(x)→0\text{SiLU}(x) \to 0 (input is suppressed)
  • At x=0x = 0: σ(0)=0.5\sigma(0) = 0.5, so SiLU(0)=0\text{SiLU}(0) = 0

The name "SiLU" stands for Sigmoid Linear Unit. This stresses its structure: the input multiplied by its sigmoid. "Swish" was Google's original branding. The two names refer to the same function, though the literature isn't always consistent.

Notice the structural similarity between SiLU and GELU. Both functions multiply the input xx by a "gate" value that lies between 0 and 1. In GELU, the gate is Φ(x)\Phi(x), the Gaussian CDF. In SiLU, the gate is σ(x)\sigma(x), the logistic sigmoid. The Gaussian CDF and the logistic sigmoid are very similar functions: both are S-shaped, both map the real line to (0,1)(0, 1), and both equal 0.5 at x=0x = 0. The key difference is their transition rate: the logistic sigmoid transitions slightly more quickly, leading to SiLU's slightly deeper negative dip compared to GELU.

SiLU / Swish

An activation function defined as SiLU(x)=x⋅σ(x)\text{SiLU}(x) = x \cdot \sigma(x), where σ\sigma is the sigmoid function. SiLU is smooth, non-monotonic, and unbounded above. It has become the standard activation for decoder-style transformers like LLaMA and Mistral, as well as GPT-NeoX.

In[12]:
Code
def sigmoid(x):
    """Logistic sigmoid function."""
    return 1 / (1 + np.exp(-np.clip(x, -500, 500)))


def silu(x):
    """SiLU/Swish activation."""
    return x * sigmoid(x)


def silu_derivative(x):
    """Derivative of SiLU."""
    sig = sigmoid(x)
    return sig + x * sig * (1 - sig)

SiLU shares GELU's smooth, non-monotonic character. Both have a slight negative dip, both transition smoothly through zero, and both asymptotically approach the identity for large positive values. But the functions differ subtly: SiLU's dip is slightly deeper (minimum around -0.28 compared to GELU's -0.17), and its transition is slightly sharper.

The derivative of SiLU, derived using the product rule, is:

SiLU′(x)=σ(x)+x⋅σ(x)⋅(1−σ(x))\text{SiLU}'(x) = \sigma(x) + x \cdot \sigma(x) \cdot (1 - \sigma(x))

where:

  • σ(x)\sigma(x): the sigmoid function
  • σ(x)(1−σ(x))\sigma(x)(1 - \sigma(x)): the derivative of the sigmoid, which has a bell-shaped curve centered at x=0x = 0
  • The first term σ(x)\sigma(x) approaches 1 for large positive xx, giving gradient 1
  • The second term x⋅σ(x)(1−σ(x))x \cdot \sigma(x)(1 - \sigma(x)) adds a positive contribution near x=1.5x = 1.5, allowing SiLU's derivative to exceed 1
Out[13]:
Visualization
Plot showing SiLU as a smooth curve with negative dip.
SiLU/Swish activation function across [-3, 3]. Like GELU, SiLU is smooth and non-monotonic with a negative dip for moderately negative inputs. The dip is slightly deeper than GELU's, reaching approximately -0.28 at x ≈ -1.28.
Plot showing SiLU derivative as a smooth curve peaking above 1.
SiLU derivative across [-3, 3]. The derivative peaks slightly above 1.0 near x = 1.28, meaning SiLU can amplify gradients for moderate positive inputs, a property not shared by GELU.

Worked Example: Tracing a Value Through SiLU

Let's trace the same pre-activation values through SiLU to compare with GELU. For x=1.0x = 1.0:

σ(1.0)=11+e−1≈11+0.368≈0.731\sigma(1.0) = \frac{1}{1 + e^{-1}} \approx \frac{1}{1 + 0.368} \approx 0.731 SiLU(1.0)=1.0×0.731≈0.731\text{SiLU}(1.0) = 1.0 \times 0.731 \approx 0.731

For x=−1.0x = -1.0:

σ(−1.0)=11+e1≈11+2.718≈0.269\sigma(-1.0) = \frac{1}{1 + e^{1}} \approx \frac{1}{1 + 2.718} \approx 0.269 SiLU(−1.0)=−1.0×0.269≈−0.269\text{SiLU}(-1.0) = -1.0 \times 0.269 \approx -0.269

Compare these to GELU's values: GELU(1.0) ≈\approx 0.841 and GELU(-1.0) ≈\approx -0.159. SiLU attenuates positive inputs more (0.731 vs. 0.841) and amplifies negative inputs more (-0.269 vs. -0.159). The logistic sigmoid transitions more quickly than the Gaussian CDF, which explains SiLU's larger negative dip and slightly sharper transition region.

In practice, these differences are small enough that switching between GELU and SiLU rarely changes model quality measurably unless you are training at very large scale. The choice between them is largely a matter of convention and ecosystem: GELU for BERT-like models, SiLU for LLaMA-like models.

Why Decoder Models Prefer SiLU

The preference for SiLU in decoder models like LLaMA and Mistral, as well as Falcon, isn't fully understood theoretically, but several factors contribute:

  1. Simpler computation: SiLU requires only sigmoid and multiplication, avoiding the error function or its approximations. On modern hardware with fast sigmoid implementations, this can be faster than GELU.

  2. Slightly stronger gradients: SiLU's derivative can exceed 1 (peaking around 1.1 near x=1.5x = 1.5), which may help gradient flow in very deep networks. GELU's derivative is bounded between 0 and 1.

  3. Empirical performance: In large-scale experiments at the scale of modern LLMs, SiLU consistently matches or slightly outperforms GELU for autoregressive language modeling. The differences are small but measurable.

  4. Pairing with gated linear units: Modern architectures like LLaMA use SiLU specifically within gated linear unit (GLU) variants, where the activation's properties interact with the gating mechanism. We'll explore this in the next chapter.

The historical sequence helps explain this preference. When Meta released LLaMA in early 2023 and described using SiLU in their SwiGLU-based FFN, SiLU's adoption accelerated rapidly across the open-source community. Mistral and Falcon, along with Gemma and Phi, were among the subsequent open models that followed the LLaMA architecture closely, including its SiLU activation. This created a strong network effect: the ecosystem of model weights, fine-tuning code, and inference libraries all assumed SiLU, making it the path of least resistance for new decoder models.

Historical Context: Neural Architecture Search and Swish

SiLU's discovery followed an unusual path. Rather than being derived mathematically or from theoretical principles, Swish was found by automated neural architecture search. Google researchers trained a reinforcement learning agent to propose activation function formulas, evaluating each on a held-out task. The agent discovered that x⋅σ(βx)x \cdot \sigma(\beta x) (a parameterized version of SiLU where β\beta is learnable or fixed at 1) consistently outperformed ReLU. When β=1\beta = 1, this reduces to the standard SiLU. This discovery highlighted that neural architecture search can find useful building blocks that human intuition might overlook, and contributed to growing interest in automated ML research tools.

Activation Function Comparison

With all three activations implemented, let's visualize them together to understand their relationships and differences:

Out[14]:
Visualization
Plot showing three activation functions overlaid with ReLU as a bent line and GELU and SiLU as smooth curves with negative dips.
Comparison of ReLU, GELU, and SiLU activation functions across [-3, 3]. All three share the property of being approximately linear for large positive inputs and approximately zero for large negative inputs. The critical differences lie in the transition region near zero: ReLU has a sharp corner and no negative outputs, while GELU and SiLU are smooth with slight negative dips that provide gradient signal even for mildly negative pre-activations.

The three functions converge for large positive inputs, approaching the identity function. They also converge for large negative inputs, approaching zero. The critical differences are in the transition region around zero, where:

  • ReLU has a sharp corner with discontinuous derivative
  • GELU has a smooth transition with a slight negative dip (minimum ≈−0.17\approx -0.17 at x≈−0.5x \approx -0.5)
  • SiLU has a smooth transition with a deeper negative dip (minimum ≈−0.28\approx -0.28 at x≈−1.28x \approx -1.28)

Let's also compare the derivatives, which directly affect gradient flow during training:

Out[15]:
Visualization
Plot showing three derivative curves with ReLU as a step function and GELU and SiLU as smooth sigmoid-like curves.
Derivatives of ReLU, GELU, and SiLU across [-3, 3]. ReLU''s derivative is a discontinuous step function that is either 0 or 1. GELU and SiLU have smooth derivatives that transition gradually from near 0 to near 1. Both smooth functions mildly overshoot 1 for positive inputs: GELU peaks earlier and slightly higher, while SiLU peaks later and lower.

The derivative plots show that both smooth activations can mildly amplify gradients for certain positive inputs. GELU's derivative peaks at approximately 1.1291.129 near x=1.41x = 1.41, while SiLU's derivative peaks at approximately 1.1001.100 near x=2.40x = 2.40. Let's examine SiLU's broader amplification region in more detail:

Out[16]:
Visualization
Line plot of SiLU derivative with shaded region showing where derivative exceeds 1.0.
Gradient amplification region for SiLU across [-2, 4]. The shaded region shows where the SiLU derivative exceeds 1.0, meaning gradients are mildly amplified rather than attenuated during backpropagation. The derivative crosses 1 near x = 1.28, peaks near x = 2.40, and remains just above 1 through the displayed range while tapering back toward 1.

Quantitative Comparison

Let's compute specific properties that affect neural network behavior:

In[17]:
Code
def analyze_activation(name, func, x_range):
    """Compute key properties of an activation function."""
    y = func(x_range)

    # Find minimum value and location
    min_idx = np.argmin(y)
    min_val = y[min_idx]
    min_x = x_range[min_idx]

    # Sparsity: fraction of outputs that are zero or near-zero
    sparsity = (np.abs(y) < 0.01).mean()

    # Linearity for positive inputs (correlation with y=x)
    pos_mask = x_range > 0.5
    correlation = np.corrcoef(x_range[pos_mask], y[pos_mask])[0, 1]

    return {
        "name": name,
        "min_value": min_val,
        "min_location": min_x,
        "sparsity": sparsity,
        "linearity": correlation,
    }


x_analysis = np.linspace(-5, 5, 10000)
activations = [
    ("ReLU", relu),
    ("GELU", gelu_exact),
    ("SiLU", silu),
]

results = [
    analyze_activation(name, func, x_analysis) for name, func in activations
]
Out[18]:
Console
Activation function properties:

Function      Min Value   Min Location     Sparsity    Linearity
-----------------------------------------------------------------
ReLU             0.0000          -5.00       50.1%     1.000000
GELU            -0.1700          -0.75       23.6%     0.999769
SiLU            -0.2785          -1.28        0.4%     0.999823

Key observations from this analysis:

  • Minimum value: ReLU never goes negative (by definition). GELU's dip is mild (-0.17), while SiLU dips deeper (-0.28). These negative values can help with regularization but also introduce potential instabilities.

  • Sparsity: All three create some degree of sparsity (outputs near zero), but ReLU is the most sparse for normally distributed inputs. This sparsity can aid interpretability and efficiency.

  • Linearity: All three are highly linear for positive inputs (correlation > 0.999 with y=xy = x), which helps preserve signal magnitude through deep networks.

The sparsity difference between ReLU and the smooth activations is worth dwelling on. For a standard normal input distribution, ReLU produces exactly 50% sparsity by zeroing all negative values. GELU and SiLU produce much less sparsity because they only attenuate negative inputs, not zero them. This means GELU and SiLU FFN layers operate with denser intermediate representations, which requires slightly more memory bandwidth during training and inference.

The Negative Dip: Feature or Bug?

GELU and SiLU's negative regions deserve closer examination. For certain negative inputs, these activations produce negative outputs rather than zero. This seems counterintuitive: why would we want activations to sometimes invert their input's sign?

The negative dip is a form of implicit regularization. Consider what happens during training: inputs that land in the dip region (roughly −2<x<0-2 < x < 0 for GELU) get attenuated but not eliminated. This "soft gating" provides a richer gradient signal than ReLU's hard zero, potentially helping the network escape local minima.

Think of it this way: a neuron in a deep network exists among neighboring neurons, all contributing to the next layer's computation. If one neuron's activation is slightly negative, zeroing it (as ReLU does) eliminates any information that neuron might carry. Allowing it to pass a small negative value (as GELU and SiLU do) preserves that signal, even if attenuated. The downstream layer can then decide whether to amplify or further attenuate it.

The key insight is that the sign of a pre-activation carries information. A pre-activation of −0.3-0.3 is different from a pre-activation of −3.0-3.0 in a meaningful way: the first suggests the input is borderline, while the second suggests a strong mismatch with the learned pattern. GELU and SiLU preserve this distinction by scaling the output proportionally. ReLU treats both identically: output zero.

Out[19]:
Visualization
Zoomed plot of the negative dip region showing GELU and SiLU curves dipping below zero before asymptoting to zero for large negative inputs.
Detailed view of the negative dip region for GELU and SiLU across [-4, 1]. GELU reaches its minimum of approximately -0.17 at x = -0.52, while SiLU reaches its deeper minimum of approximately -0.28 at x = -1.28. The dashed zero line provides reference. These negative outputs preserve gradient signal for mildly negative inputs, enabling neurons to recover from transient negative phases during training.

The magnitude of the dip matters for training dynamics. A deeper dip (SiLU) means stronger negative signals for moderately negative inputs, which could either help or hurt depending on the task. Empirically, both work well, with the choice often coming down to which pairs better with other architectural decisions (like gated linear units).

There is also a connection between the negative dip and the activation's non-monotonicity. A monotonic function never decreases as its input increases: ReLU and the standard sigmoid are monotonic. GELU and SiLU are non-monotonic: they decrease slightly in the range around x≈−0.5x \approx -0.5 before increasing again. This non-monotonicity is unusual for activation functions and was initially viewed with some skepticism. The practical evidence, however, is that non-monotonic activations train comparably to or better than monotonic alternatives at large scale, suggesting that the strict monotonicity constraint imposed by ReLU and its variants was unnecessarily restrictive.

Activation Functions in Practice

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.

Computational Efficiency

Activation function speed matters at scale. When processing billions of tokens through trillion-parameter models, even small differences in activation computation time accumulate. The helper below shows how to benchmark the implementations. For the visualization, we use one fixed reference measurement so the data remains identical across light, dark, and transparent render variants:

In[23]:
Code
import time


def benchmark_activation(func, x, n_iterations=100):
    """Benchmark activation function speed."""
    # Warmup
    for _ in range(10):
        _ = func(x)

    # Timed runs
    start = time.perf_counter()
    for _ in range(n_iterations):
        _ = func(x)
    elapsed = time.perf_counter() - start

    return elapsed / n_iterations * 1000  # ms per call


# Representative ratios from one NumPy/SciPy CPU reference run. Keeping this
# measurement fixed prevents theme-rendering overhead from changing the bars.
benchmarks = {
    "ReLU": 1.0,
    "GELU (exact)": 32.0,
    "GELU (approx)": 29.5,
    "SiLU": 6.8,
}
Out[24]:
Visualization
Horizontal bar chart showing relative computation times with ReLU fastest at 1x and exact GELU slowest.
Representative NumPy/SciPy CPU timing ratios for activation functions on a 1024x4096 tensor, normalized relative to ReLU. ReLU is fastest because it requires only a comparison operation. In this reference measurement, SiLU is faster than both GELU variants and exact GELU using SciPy's error function is slowest. Exact ratios depend on the software and hardware; dedicated GPU implementations commonly use fused kernels and can differ substantially.

In this reference measurement, ReLU is fastest because it is just a comparison operation. The exact GELU using erf is slowest. The GELU tanh approximation is faster than exact GELU but still slower than SiLU. SiLU is relatively fast because sigmoid has efficient implementations on most hardware.

In practice, these differences are often dwarfed by memory bandwidth limitations and matrix multiplication costs. The activation function typically accounts for less than 1% of total FFN compute time. Still, at sufficient scale, even small improvements matter.

This reference measurement represents NumPy/SciPy CPU performance and does not reflect production GPU performance. On modern GPUs, activation functions are often "fused" into preceding or following operations using custom CUDA kernels. A fused kernel computes the matrix multiplication and activation in a single pass over memory, reducing the number of times data must be read from GPU memory. With fused operations, the relative cost of more expensive activations (like exact GELU) shrinks significantly, because the bottleneck shifts from computation to memory bandwidth. This is one reason why the exact GELU is feasible in production despite its higher computational cost.

Which Activation Should You Use?

The choice of activation function depends on your model architecture and use case. Here's a practical guide:

Use ReLU when:

  • You need maximum computational efficiency and simplicity
  • You're building a custom architecture and want a well-understood baseline
  • Your model is small enough that the dead neuron problem is manageable
  • You're working with older codebases or frameworks that expect ReLU

Use GELU when:

  • You're building an encoder model (BERT, RoBERTa, ELECTRA style)
  • You want compatibility with pretrained encoder models
  • You prioritize smooth gradients over computational efficiency
  • You're fine-tuning an existing GELU-based model

Use SiLU when:

  • You're building a decoder model (GPT, LLaMA, Mistral style)
  • You're using gated linear units (SwiGLU, GeGLU) in your FFN
  • You want the slight performance edge that modern LLMs have demonstrated
  • You need a smooth activation but prefer simpler computation than GELU

The empirical differences between GELU and SiLU are often small. Unless you're training at massive scale where every fraction of a percent matters, either smooth activation will likely work well. The more important choice is between ReLU (with its dead neuron risk) and the smooth alternatives.

When fine-tuning a pretrained model, you should essentially always match the activation used during pretraining. Changing from GELU to SiLU in the middle of a model's lifecycle would invalidate the learned weight distributions, since those weights were optimized under the original activation's gradient behavior. The weights are not activation-function agnostic: they encode information in a way that is calibrated to how the original activation transforms inputs. Switching activations after pretraining would require substantial additional training to re-adapt all the weights.

Out[25]:
Visualization
Three-column table visualization showing ReLU, GELU, and SiLU with their key properties and typical use cases.
Summary comparison of activation functions used in transformer FFNs, organized by formula, transition behavior, key limitation, computational speed, and typical use cases. GELU and SiLU both offer smooth gradients over ReLU, but differ in computational implementation and the models that have adopted them as standards.

Implementation: Configurable Activation Module

Let's create a configurable activation function module that supports all three options, following patterns used in modern transformer libraries:

In[26]:
Code
class Activation:
    """
    Configurable activation function for FFN layers.

    Supports ReLU, GELU (exact and approximate), and SiLU.
    """

    ACTIVATIONS = {
        "relu": lambda x: np.maximum(0, x),
        "gelu": lambda x: x * 0.5 * (1 + erf(x / np.sqrt(2))),
        "gelu_approximate": lambda x: (
            0.5 * x * (1 + np.tanh(np.sqrt(2 / np.pi) * (x + 0.044715 * x**3)))
        ),
        "silu": lambda x: x * (1 / (1 + np.exp(-np.clip(x, -500, 500)))),
    }

    def __init__(self, activation_type="gelu"):
        """
        Initialize the activation function.

        Args:
            activation_type: One of "relu", "gelu", "gelu_approximate", "silu"
        """
        if activation_type not in self.ACTIVATIONS:
            raise ValueError(
                f"Unknown activation: {activation_type}. "
                f"Choose from {list(self.ACTIVATIONS.keys())}"
            )

        self.activation_type = activation_type
        self._func = self.ACTIVATIONS[activation_type]

    def __call__(self, x):
        """Apply the activation function."""
        return self._func(x)

    def __repr__(self):
        return f"Activation({self.activation_type})"


# Test the module
test_input = np.array([-2, -1, 0, 1, 2])
Out[27]:
Console
Activation module test:

Input: [-2 -1  0  1  2]

Activation(relu)               → [0 0 0 1 2]
Activation(gelu)               → [-0.046 -0.159  0.     0.841  1.954]
Activation(gelu_approximate)   → [-0.045 -0.159  0.     0.841  1.955]
Activation(silu)               → [-0.238 -0.269  0.     0.731  1.762]

In practice, PyTorch makes this even simpler. You can specify the activation using torch.nn.functional.gelu, torch.nn.functional.silu, or torch.nn.ReLU(). Hugging Face's Transformers library defines an ACT2FN dictionary that maps string names like "gelu", "silu", and "relu" to their corresponding functions, allowing model architectures to be configured via a single string parameter in the model config. This pattern lets you swap activations without changing the model code, which is convenient for ablation experiments.

Limitations and Impact

The choice of activation function in transformer FFNs has subtle but measurable effects on model behavior. While the differences between GELU and SiLU are often small in benchmarks, they compound across billions of forward passes during training.

One underappreciated limitation is numerical stability. GELU's error function and SiLU's exponential can both produce numerical issues at extreme input values. Production implementations include clipping (as we did for sigmoid) and sometimes use mixed-precision computation to balance speed and stability. The clipping range matters: a range that is too tight may introduce bias by capping legitimate high-magnitude activations, while a range that is too loose may fail to prevent overflow in edge cases.

Another consideration is hardware optimization. Modern GPUs and TPUs have specialized circuits for certain operations. The sigmoid function in SiLU benefits from this optimization on many accelerators. GELU's error function is less universally optimized, which partly explains the trend toward SiLU in recent models despite GELU's theoretical elegance. When Facebook (Meta) designed the A100-optimized LLaMA architecture, the choice of SiLU over GELU was partly driven by the sigmoid's hardware-friendly properties.

The impact of activation functions extends beyond raw performance. The smooth gradients of GELU and SiLU allow for more stable training at large batch sizes and high learning rates. This mattered when scaling transformers to billions of parameters, where training instability becomes a serious concern. Models trained with ReLU show higher variance in loss curves and more sensitivity to hyperparameter choices than models trained with smooth activations. At very large scale, this training instability has real costs: a training run that diverges and must be restarted wastes compute that can cost millions of dollars.

One limitation that receives less attention is the activation function's interaction with weight initialization. Standard initialization schemes like Glorot (Xavier) or He initialization make assumptions about the activation function's expected output variance. He initialization, for example, is derived specifically for ReLU by accounting for the fact that half of ReLU's outputs are zero. Using He initialization with GELU or SiLU produces slightly incorrect scaling, since those activations do not zero out exactly half of their inputs. This mismatch is usually small enough not to matter in practice, especially with modern normalization schemes like LayerNorm, but it illustrates that the choice of activation cannot be fully decoupled from other initialization and normalization decisions.

A deeper limitation is our incomplete theoretical understanding of why smooth activations outperform ReLU at large scale. The empirical evidence is consistent and reproducible: GELU and SiLU generally work better than ReLU for large language models. But the precise mechanism remains incompletely understood. Is it the smoothness? The non-monotonicity? The probabilistic interpretation? The negative dip? Ablation studies can test individual hypotheses, but the interactions between activation choice and initialization, plus normalization and optimizer, are complex enough that clean causal explanations are hard to establish. This uncertainty is a reminder that even core architectural choices in modern deep learning rest partly on empirical foundations rather than pure theory.

Looking forward, activation functions continue to evolve. Gated linear units (covered in the next chapter) combine activation functions with multiplicative gating, creating even richer nonlinearities. The field hasn't settled on a final answer, and future architectures may introduce entirely new activation functions that further improve on the current options. There is ongoing research into activation functions with adaptive behavior, where the function's shape is conditioned on context or learned per-layer. Whether such approaches will yield practical improvements beyond SiLU and GELU at current scales remains an open question.

Summary

Activation functions inject nonlinearity into the feed-forward network, enabling transformers to learn complex functions of language. This chapter traced the evolution from ReLU to GELU to SiLU, examining the mathematical properties that make each suitable for different contexts.

Key takeaways:

  • ReLU (max⁡(0,x)\max(0, x)) is the simplest activation, with zero computation cost beyond a comparison. Its sharp corner creates sparse representations but risks "dead neurons" that stop learning entirely. ReLU was used in the original transformer but has been largely superseded in modern architectures.

  • GELU (x⋅Φ(x)x \cdot \Phi(x)) smoothly gates inputs by their Gaussian CDF values. The result is a differentiable, non-monotonic function with a slight negative dip around x≈−0.5x \approx -0.5. GELU became the standard for encoder models (BERT, RoBERTa) and remains widely used.

  • SiLU/Swish (x⋅σ(x)x \cdot \sigma(x)) multiplies inputs by their sigmoid values. Like GELU, it's smooth and non-monotonic, but with a deeper negative dip and slightly simpler computation. SiLU has become the standard for decoder models (LLaMA, Mistral, GPT-NeoX).

  • Practical choice: The differences between GELU and SiLU are often small in practice. Choose based on your model family (encoder vs. decoder), compatibility with pretrained models, or specific architectural requirements like gated linear units.

  • Computational efficiency: ReLU is fastest, followed by SiLU, then GELU approximation, then exact GELU. However, activation computation is typically less than 1% of total FFN time, so speed rarely drives the choice.

  • Never change activations during fine-tuning: Pretrained model weights encode information calibrated to the original activation. Switching activations mid-lifecycle requires retraining to re-adapt the weights.

The next chapter examines gated linear units (GLUs), which combine activation functions with multiplicative gating to create even more expressive FFN architectures. Variants like SwiGLU and GeGLU have become standard in state-of-the-art models like LLaMA and PaLM.

Key Parameters

When configuring activation functions for transformer FFNs, these parameters and choices affect model behavior:

  • activation_type: The activation function to use. Common options are "relu", "gelu", "gelu_approximate", and "silu". Choose based on your model architecture: GELU for encoder models (BERT-style), SiLU for decoder models (LLaMA-style), or ReLU for maximum simplicity and speed.

  • approximate (for GELU): Whether to use the tanh approximation instead of the exact error function. The approximation is faster on most hardware and introduces negligible error (< 0.005 maximum). Most production systems use the approximation.

  • inplace (framework-specific): Some frameworks allow in-place activation to reduce memory usage. This modifies the input tensor directly rather than creating a new output tensor. Use with caution, as it can cause issues with gradient computation if the original values are needed.

  • numerical clipping (for SiLU/sigmoid): Input values should be clipped to prevent overflow in the exponential function. A range of [−500,500][-500, 500] is typical. Most deep learning frameworks handle this automatically, but custom implementations should include explicit clipping.

  • dtype considerations: Activation functions behave differently at different precisions. At float16 or bfloat16, the dynamic range is limited, which can cause issues with the exponential in SiLU or the error function in GELU. Mixed-precision training typically keeps activations in higher precision for stability.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about activation functions in transformer feed-forward networks.

FFN Activation Functions Quiz

Question 1 of 100 of 10 completed
What is the primary advantage of ReLU over sigmoid and tanh activations?

Comments

No comments yet. Be the first to share your thoughts!

Reference

Citation details

Cite or share this article.

BIBTEXAcademic
@misc{brenndoerfer2025ffnactivation, author = {Michael Brenndoerfer}, title = {FFN Activation Functions: ReLU, GELU}, year = {2025}, url = {https://mbrenndoerfer.com/writing/ffn-activation-functions}, organization = {mbrenndoerfer.com}, note = {Accessed: 2026-09-23} }
APAAcademic
Michael Brenndoerfer (2025). FFN Activation Functions: ReLU, GELU. Retrieved from https://mbrenndoerfer.com/writing/ffn-activation-functions
MLAAcademic
Michael Brenndoerfer. "FFN Activation Functions: ReLU, GELU." 2026. Web. September 23, 2026. <https://mbrenndoerfer.com/writing/ffn-activation-functions>.
CHICAGOAcademic
Michael Brenndoerfer. "FFN Activation Functions: ReLU, GELU." Accessed September 23, 2026. https://mbrenndoerfer.com/writing/ffn-activation-functions.
HARVARDAcademic
Michael Brenndoerfer (2025) 'FFN Activation Functions: ReLU, GELU'. Available at: https://mbrenndoerfer.com/writing/ffn-activation-functions (Accessed: September 23, 2026).
SimpleBasic
Michael Brenndoerfer (2025). FFN Activation Functions: ReLU, GELU. https://mbrenndoerfer.com/writing/ffn-activation-functions

About the author

Continue with the full handbook

This chapter is part of Language AI Handbook. Use the handbook page to browse the complete table of contents and continue reading in sequence.

Explore Language AI Handbook
Newsletter

Stay up to date

Get articles, book updates, and news delivered to your inbox.

No spam, unsubscribe anytime.

or

Join the community

Sign in to remove popups, track your reading progress, and join the discussion.