Position-Wise Feed-Forward Networks

Imagine a Transformer block that only mixes information between tokens through attention. There is no room for a token to individually transform its own representation. Attention decides what to look at. But something else must decide what to do with what was seen. That is the job of the position-wise feed-forward network (FFN). This is the sublayer sitting right after Multi-Head Attention in every encoder and decoder block. It accounts for roughly two-thirds of a Transformer's parameters. Yet it is often glossed over as "just an MLP." The problem the FFN solves is simple to state. Attention output is a weighted average of value vectors. This is fundamentally a linear, position-agnostic mixing operation across the sequence. Linear mixing alone cannot introduce new features or sharpen decisions. It also cannot perform non-linear transformations of a token's content. Every real system built on Transformers — BERT, GPT, T5, Vision Transformers — relies on the FFN. It injects non-linearity and expands representational capacity, token by token, independent of sequence length. The big picture is that a Transformer block alternates between two very different kinds of computation. These are attention (token-to-token, linear-in-values, permutation-equivariant mixing) and the FFN (token-to-itself, non-linear, position-independent transformation). This division of labor makes the architecture both expressive and parallelizable. Attention handles context, the FFN handles computation. This article gives a precise definition of the position-wise FFN. It works through its algorithmic structure and parameter count. It derives its computational complexity honestly. It traces its role in the residual stream. Finally, it previews how it interacts with the surrounding Transformer Encoder Stack.

Key takeaways
  • The FFN is a two-layer MLP applied identically and independently to every token's vector — hence "position-wise."
  • It supplies the non-linearity that attention (a linear combination of values) cannot provide on its own.
  • The hidden dimension is typically 4× the model dimension, giving the FFN most of a Transformer's parameters.
  • It runs in parallel across all positions: no cross-token computation happens inside it.
  • Complexity is O(n⋅d⋅dff)O(n \cdot d \cdot d_{ff}) time and O(d⋅dff)O(d \cdot d_{ff}) parameter space, linear in sequence length nn.
  • Residual connections and layer normalization wrap the FFN, stabilizing training of deep stacks.
The core insight

The position-wise feed-forward network applies the exact same non-linear MLP to every token vector independently, giving each position the computational depth that linear attention alone cannot provide.

The idea

The position-wise feed-forward network is a sublayer that maps each token's dd-dimensional vector through a two-layer multilayer perceptron (MLP), with a non-linear activation between the layers, applied identically at every sequence position. Formally, for a token representation x∈Rdx \in \mathbb{R}^d,

FFN(x)=W2 σ(W1x+b1)+b2\text{FFN}(x) = W_2 \, \sigma(W_1 x + b_1) + b_2

where W1∈Rdff×dW_1 \in \mathbb{R}^{d_{ff}\times d}, W2∈Rd×dffW_2 \in \mathbb{R}^{d \times d_{ff}}, and σ\sigma is a non-linear activation — originally ReLU, later GELU or Swish/SwiGLU variants in modern models. The inner dimension dffd_{ff} is usually 4d4d (e.g. d=512,dff=2048d=512, d_{ff}=2048 in the original Transformer; d=768,dff=3072d=768, d_{ff}=3072 in BERT-base). The crucial qualifier is "position-wise": the same weight matrices W1,W2W_1, W_2 are reused at every position 1,…,n1, \dots, n in the sequence, but there is no interaction between positions inside this sublayer. If the input to the block is a matrix X∈Rn×dX \in \mathbb{R}^{n \times d} (one row per token), the FFN is applied row-by-row:

FFN(X)i,:=FFN(Xi,:)for each i=1,…,n.\text{FFN}(X)_{i,:} = \text{FFN}(X_{i,:}) \quad \text{for each } i = 1, \dots, n.

This is mathematically equivalent to two back-to-back 1×11{\times}1 convolutions across the sequence dimension — a framing used in the original "Attention Is All You Need" paper, which is exactly why it is called position-wise rather than sequence-wise: it behaves like a kernel-size-1 convolution, sliding independently over each position with shared weights. Contrast this with the Multi-Head Attention sublayer that precedes it in each block: attention computes a weighted combination of other tokens' values, an operation that is linear in the value vectors and whose only source of adaptivity is the attention weights themselves. Stacking two linear operations (attention's value-mixing, followed by nothing) would collapse into an expressively limited linear map. The FFN's non-linearity σ\sigma is what allows the network to compute genuinely new features — logical AND/OR-like combinations, thresholding, sparse activation patterns — that a purely linear attention mechanism cannot represent. This is the same reason a plain linear regression stack collapses to a single linear layer without non-linear activations between them; the FFN is where the Transformer earns its non-linear expressive power.

How it works

Each Transformer block (encoder or decoder) applies attention, then the FFN, each wrapped in a residual connection and a normalization step. The full block, following the standard post-norm formulation, is: ``` z = LayerNorm(x + MultiHeadAttention(x)) y = LayerNorm(z + FFN(z)) ``` (Modern implementations often use pre-norm, applying LayerNorm before the sublayer instead — a design detail orthogonal to the FFN's internal computation.) The FFN sublayer itself, expanded into pseudocode operating on a batch of sequences: ``` function FeedForward(X, W1, b1, W2, b2):

# X: tensor of shape (batch, seq_len, d_model)

# W1: (d_model, d_ff), b1: (d_ff,)

# W2: (d_ff, d_model), b2: (d_model,) H = X @ W1 + b1 # shape (batch, seq_len, d_ff) H = activation(H) # elementwise, e.g. ReLU(x) = max(0, x) Y = H @ W2 + b2 # shape (batch, seq_len, d_model) return Y # same shape as X function TransformerBlock(X, params): A = MultiHeadAttention(X, X, X, params.attn) # self-attention Z = LayerNorm(X + A, params.ln1) F = FeedForward(Z, params.W1, params.b1, params.W2, params.b2) Y = LayerNorm(Z + F, params.ln2) return Y ``` Walking through the shapes makes the "position-wise" property concrete. The input `X` has shape `(batch, seq_len, d_model)`. The matrix multiply `X @ W1` broadcasts `W1` across the `batch` and `seq_len` axes — every one of the `batch seq_len` token vectors is multiplied by the same `W1`, independently. There is no operation here that reads from position ii and position j≠ij \neq i simultaneously; that cross-position mixing already happened in the attention sublayer, and it will happen again in the next block's attention sublayer, but not inside this FFN. Each token's forward pass through the FFN is: 1. <strong>Expand</strong>: project from dmodeld_{model} up to dffd_{ff} (typically 4×4\times wider), giving the network a larger "workspace" to compute intermediate features. 2. <strong>Activate</strong>: apply a pointwise non-linearity (ReLU, GELU, or a gated variant like SwiGLU) to each of the dffd_{ff} hidden units. 3. <strong>Contract</strong>: project back down from dffd_{ff} to dmodeld_{model}, so the output can be added back into the residual stream. Because step 1 and step 3 are ordinary matrix multiplications, and step 2 is elementwise, the entire computation for all nn positions can be done as a single* batched matrix multiply of shape (n,d)×(d,dff)(n, d) \times (d, d_{ff}), rather than nn separate small operations — this is what makes the FFN extremely GPU-friendly.

Figure
One Transformer block: attention mixes across tokens, the FFN transforms each token independently
tokens x₁ x₂ x₃ x₄ Multi-Head Attention mixes info ACROSS positions Add residual + LayerNorm z₁ z₂ z₃ z₄ W1,σ W2 W1,σ W2 W1,σ W2 W1,σ W2 same W1,W2 reused at every position ✗ no line drawn between token boxes: no cross-token mixing Add residual + LayerNorm Output: y₁, y₂, y₃, y₄ → next block

Complexity & correctness

Time complexity. For a single token vector, the FFN performs a d→dffd \to d_{ff} matrix multiply (O(d⋅dff)O(d \cdot d_{ff}) multiply-adds) followed by a dff→dd_{ff} \to d matrix multiply (another O(d⋅dff)O(d \cdot d_{ff})). For a sequence of length nn, since the same computation repeats at every position independently, the total cost is

O(n⋅d⋅dff).O(n \cdot d \cdot d_{ff}).

With dff=4dd_{ff} = 4d, this is O(4nd2)=O(nd2)O(4nd^2) = O(nd^2) — linear in sequence length, in sharp contrast to the self-attention sublayer's O(n2d)O(n^2 d) cost, which grows quadratically because attention must compute pairwise interactions between all n2n^2 token pairs. This asymmetry matters a great deal in practice: for short-to-moderate sequences the FFN can dominate total FLOPs (since d≫1d \gg 1 and nn is small), but for very long sequences attention's n2n^2 term eventually dominates. Understanding which sublayer is the bottleneck is essential when deciding whether to optimize attention (e.g. with sparse or linear-attention variants) or the FFN (e.g. with mixture-of-experts routing).

Space complexity. The parameters are W1∈Rd×dffW_1 \in \mathbb{R}^{d \times d_{ff}} and W2∈Rdff×dW_2 \in \mathbb{R}^{d_{ff}\times d}, giving 2⋅d⋅dff2 \cdot d \cdot d_{ff} weights (plus dff+dd_{ff} + d biases, negligible in comparison). With dff=4dd_{ff} = 4d, this is 8d28d^2 parameters per FFN sublayer, versus roughly 4d24d^2 for the multi-head attention sublayer's four projection matrices (WQ,WK,WV,WOW_Q, W_K, W_V, W_O, each d×dd \times d). This is precisely why the FFN sublayers account for about two-thirds of a Transformer's total parameter count, a fact often surprising to newcomers who assume attention "is" the Transformer. The activation memory during the forward pass requires storing the intermediate hidden tensor of shape (n,dff)(n, d_{ff}), which is 4×4\times larger than the token representations themselves — a real consideration for memory-constrained training, motivating techniques like activation checkpointing.

Correctness / why this design works. The argument for the FFN's necessity is an expressivity argument, not a convergence proof, and it rests on two facts. Fact 1 (attention alone is a linear map in its values). Self-attention output for token ii is ∑jαij(XjWV)\sum_j \alpha_{ij} (X_j W_V), a convex combination (in α\alpha) of linearly-projected value vectors. For fixed attention weights α\alpha, this is an affine function of the token embeddings. Composing this with the residual add and LayerNorm (which is a nonlinear but position-wise renormalization, not a feature-mixing nonlinearity) does not introduce the kind of nonlinear feature interaction that a hidden-layer MLP provides. Fact 2 (a composition of purely linear sublayers cannot exceed linear expressivity). If every sublayer in a stack were linear, the entire depth-LL network would collapse to a single linear transformation (a composition of linear maps is linear), regardless of LL — exactly the classical argument for why deep linear networks gain no expressive power over depth. The universal approximation theorem for two-layer MLPs (Cybenko, 1989; Hornik, 1991) guarantees that a single hidden layer with a suitable non-linearity and sufficient width dffd_{ff} can approximate any continuous function on a compact domain arbitrarily well. Placing such an MLP after every attention sublayer therefore ensures each block can, in principle, compute an arbitrary non-linear transformation of the (already contextualized) token vector, rather than being limited to affine recombination. Together these facts justify the architectural necessity of the FFN: it is not an arbitrary add-on but the component responsible for the network's non-linear representational capacity, applied at the finest possible granularity — one token at a time — so that its cost and expressive power scale with nn rather than n2n^2.

Worked example

Take a toy setting with dmodel=4d_{model} = 4, dff=8d_{ff} = 8, and a 3-token sequence output from the attention + Add&Norm step, Z∈R3×4Z \in \mathbb{R}^{3\times 4}:

Z=(0.5−0.21.00.0−1.00.30.20.40.10.1−0.50.9)Z = \begin{pmatrix} 0.5 & -0.2 & 1.0 & 0.0 \\ -1.0 & 0.3 & 0.2 & 0.4 \\ 0.1 & 0.1 & -0.5 & 0.9 \end{pmatrix}

Suppose W1W_1 and b1b_1 (for illustration, using a simplified 4×84\times 8 weight and a bias of −0.1-0.1 on every hidden unit) produce the pre-activation matrix Hpre=ZW1+b1H_{pre} = ZW_1 + b_1. We do not need the exact numeric entries of W1W_1 to see the mechanism — only that each row of ZZ is multiplied against the same W1W_1, independently:

steptoken 1 (row 1)token 2 (row 2)token 3 (row 3)
input ziz_i[0.5,−0.2,1.0,0.0][0.5,-0.2,1.0,0.0][−1.0,0.3,0.2,0.4][-1.0,0.3,0.2,0.4][0.1,0.1,−0.5,0.9][0.1,0.1,-0.5,0.9]
pre-activation ziW1+b1z_i W_1 + b_1 (shape 8)e.g. [0.9,−0.3,… ][0.9,-0.3,\dots]e.g. [−1.4,0.6,… ][-1.4,0.6,\dots]e.g. [0.2,−0.5,… ][0.2,-0.5,\dots]
after ReLUzero out negativeszero out negativeszero out negatives
output ReLU(⋅)W2+b2\text{ReLU}(\cdot)W_2 + b_2 (shape 4)y1∈R4y_1 \in \mathbb{R}^4y2∈R4y_2 \in \mathbb{R}^4y3∈R4y_3 \in \mathbb{R}^4

The key observation, verified by the shapes above: computing y2y_2 never reads z1z_1 or z3z_3. If we permuted the rows of ZZ (swapped token 1 and token 3), the FFN output rows would simply permute the same way — y1y_1 and y3y_3 would swap, with no numerical change to their values. This is the formal signature of "position-wise": the FFN commutes with row permutations of its input, i.e. it is a permutation-equivariant, in fact a completely position-decoupled, operation. Contrast this with what would happen if we permuted the rows of ZZ before the attention sublayer instead: the attention weights αij\alpha_{ij} depend on pairwise dot products across positions, so permuting rows there changes which tokens attend to which, and the output values genuinely differ in a way that depends on the new arrangement of context, not merely a relabeling. Numerically, using ReLU as σ\sigma, note that token 2's pre-activation had a strongly negative first entry (−1.4-1.4), which gets zeroed — that hidden unit contributes nothing to y2y_2, while it may have contributed heavily to y1y_1 (whose corresponding entry was 0.90.9, i.e. active). This is precisely the non-linear, content-dependent gating that a linear attention-only stack cannot replicate: different tokens activate different subsets of the dff=8d_{ff}=8 hidden units, letting the network compute qualitatively different functions per token content, not just different weighted averages of the same value vectors.

Common pitfalls & traps

PitfallWhy it happensConsequence
Assuming the FFN mixes information across tokensConfusing it with attention, since both are "sublayers" in a blockMisjudging what causes cross-token effects when debugging attribution or interpretability probes
Using too small dffd_{ff} (e.g. dff=dd_{ff}=d)Under-provisioning the "expansion" ratioSeverely bottlenecks the non-linear capacity of the block, hurting downstream task performance
Forgetting the residual connection around the FFNImplementing y=FFN(z)y = \text{FFN}(z) instead of y=LayerNorm(z+FFN(z))y = \text{LayerNorm}(z + \text{FFN}(z))Gradient signal cannot flow directly through identity path; deep stacks fail to train
Sharing FFN weights across layers (not just across positions)Misreading "shared across positions" as "shared across the whole network"Drastically under-parameterizes the model; each layer needs its own W1,W2W_1, W_2
Ignoring FFN's parameter dominance when profilingAssuming attention is the compute/memory bottleneck by defaultWasted optimization effort on attention (e.g. sparsifying it) when the FFN (e.g. via mixture-of-experts) is the real cost driver
Choosing activation without checking model familyAssuming ReLU everywhereGELU, SwiGLU, and gated variants materially change behavior and must match the pretrained checkpoint

Pioneers & history

The FFN sublayer's mathematical ancestry runs through decades of neural network research on multilayer perceptrons and universal approximation, before being crystallized as a specific architectural component in the Transformer.

Origins · 1 minAshish Vaswani and how this was discovered
AV
Ashish Vaswani
b. 1980s (active researcher) · Lead author of "Attention Is All You Need" (2017), the paper introducing the Transformer architecture and its position-wise feed-forward sublayer as a core building block

History
1986
— Rumelhart, Hinton, and Williams popularize backpropagation for training multilayer perceptrons, establishing the MLP as the standard non-linear building block.
1989
— Cybenko proves the universal approximation theorem for single-hidden-layer networks with sigmoidal activations.
1991
— Hornik extends universal approximation to broader classes of activation functions and network architectures.
2017
— Vaswani et al. introduce the Transformer, formalizing the position-wise feed-forward network as the non-linear sublayer following self-attention in every block.
2018–2020
— GELU and Swish activations, and later gated variants (SwiGLU, GeGLU), replace plain ReLU in the FFN of BERT, GPT-2/3, T5, and successors, improving optimization and downstream quality.

Where this matters

Every major Transformer-based system — Multi-Head Attention-based encoders like BERT, decoder-only language models like the GPT family, encoder-decoder systems like T5, and Vision Transformers applied to images — includes a position-wise FFN in each block, making it one of the most reused computational primitives in modern deep learning. Because the FFN dominates parameter count, it is also the primary target of parameter-efficiency and scaling research: Mixture-of-Experts (MoE) architectures such as Switch Transformer and Mixtral replace the single dense FFN with a bank of expert FFNs and a routing mechanism, letting models scale total parameters far beyond what a dense FFN could afford while keeping the compute per token roughly fixed. Techniques like LoRA and adapter modules frequently target the FFN's weight matrices for efficient fine-tuning, since they hold the bulk of the model's task-specific capacity. Understanding the FFN is also a prerequisite for understanding why the Transformer Encoder Stack scales the way it does in both parameters and FLOPs, and it directly informs engineering decisions in production inference systems, where FFN matrix multiplies are the dominant target for kernel fusion, quantization, and hardware-specific optimization (e.g. batching FFN computation across all tokens and all sequences in a batch into one large matrix multiply for maximal GPU utilization).

Ask the tutor: How does replacing the dense FFN with a Mixture-of-Experts layer change the complexity and training dynamics of a Transformer block?

Why is it called "position-wise" instead of just "feed-forward layer"?

The qualifier distinguishes this sublayer from a generic dense layer that might operate on a flattened, whole-sequence representation. "Position-wise" emphasizes that the exact same two-layer MLP, with the same weights, is applied separately to each position in the sequence, as if you sliced the sequence into individual token vectors and ran each one through an identical small network. This is mathematically equivalent to a 1×11{\times}1 convolution across the sequence axis: same kernel, applied at every location, with zero receptive field beyond that single position. The name signals that there is no cross-token computation happening inside this particular sublayer, in contrast to attention.

Does the FFN use different weights at every layer of the Transformer stack?

Yes. "Position-wise" only means the weights are shared across positions within one layer's FFN — not across layers. Each of the LL blocks in a Transformer stack has its own independent W1,b1,W2,b2W_1, b_1, W_2, b_2 for its FFN sublayer. So a 12-layer BERT-base model has 12 separate FFN sublayers, each with its own roughly 8d28d^2 parameters, contributing to the total parameter budget. Only within a single layer's FFN is the same transformation reused for every token in the sequence, which is what keeps the FFN's cost linear in sequence length rather than growing per unique position.

Why is dffd_{ff} usually chosen as 4 times dmodeld_{model}?

The 4× expansion ratio was an empirical choice in the original Transformer paper (512 → 2048) that balances expressive capacity against compute and memory cost, and it has persisted through most subsequent architectures (768 → 3072 in BERT-base, and similar ratios in GPT-family models). A wider hidden layer gives more "room" for the non-linear activation to carve out complex decision boundaries per token, per the universal approximation theorem's dependence on hidden width, but doubling dffd_{ff} roughly doubles FFN FLOPs and parameters. The 4× ratio is a widely validated sweet spot rather than a mathematically derived optimum, and some architectures (e.g. certain gated-activation variants) use a somewhat smaller ratio to compensate for the extra gating projection's added parameters.

How does the FFN interact with the residual stream and LayerNorm?

The FFN's output is never used on its own; it is added back to its own input via a residual connection, then normalized: y=LayerNorm(z+FFN(z))y = \text{LayerNorm}(z + \text{FFN}(z)) in the post-norm formulation, or y=z+FFN(LayerNorm(z))y = z + \text{FFN}(\text{LayerNorm}(z)) in the pre-norm formulation used by most modern large language models. The residual connection lets gradients flow through an identity path during backpropagation, which is essential for training very deep stacks (dozens of layers) without vanishing gradients. LayerNorm rescales each token vector to have stable mean and variance, preventing the FFN's expansion-then-contraction from causing activation magnitudes to explode or shrink across layers.

Is the position-wise FFN the same thing as a fully connected layer in a CNN or plain MLP classifier?

It uses the same basic building block — a linear layer, a non-linearity, another linear layer — but the usage pattern differs. A plain MLP classifier typically takes one fixed-size input vector and produces one output. The Transformer's FFN instead takes a sequence of vectors (one per token) and applies the identical small MLP independently to each one, producing a same-shaped sequence of outputs. It is best understood as a batched, weight-shared MLP applied per token, equivalent to a 1×11{\times}1 convolution along the sequence dimension, rather than a single classifier processing one flattened input.

Why does the FFN dominate parameter count compared to attention?

For model dimension dd, the four attention projection matrices (WQ,WK,WV,WOW_Q, W_K, W_V, W_O) each cost d2d^2 parameters, totaling roughly 4d24d^2 per layer. The FFN, with expansion ratio 4, has two matrices of size d×4dd \times 4d, totaling 8d28d^2 parameters per layer — twice the attention cost. Summed over all layers in a deep Transformer, FFN sublayers typically account for around two-thirds of total parameters, which is why techniques for scaling model capacity efficiently, such as Mixture-of-Experts, specifically target the FFN sublayer rather than attention.

What activation functions are used in the FFN besides ReLU, and why change them?

The original Transformer used ReLU, max⁡(0,x)\max(0, x), which is simple but has a "dead zone" for negative inputs where gradients are exactly zero. GELU (Gaussian Error Linear Unit) offers a smoother, probabilistic gating that tends to improve optimization dynamics and is used in BERT and GPT-2/3. More recent architectures use gated variants like SwiGLU or GeGLU, which introduce an extra elementwise gating projection multiplying the activated branch, empirically improving quality per parameter at the cost of a third weight matrix. The choice of activation must match what a pretrained checkpoint was trained with, since substituting a different activation post hoc changes the function computed by every FFN sublayer.

Before this

Where this leads