Feed-Forward Networks
After attention mixes information across positions, each position still needs its own nonlinear update. A feed-forward network widens it, bends it, then squeezes it back.
Why Does This Exist?
Self-attention is a weighted average of value vectors. However clever the weights, it only routes and blends information that already exists across positions. It cannot bend one position into something new on its own.
That gap is why every transformer block has a second sublayer. Attention decides what to gather from where. The feed-forward network decides what to do with what was gathered, one position at a time.
One prerequisite keeps this page short. An activation function bends a number, such as ReLU keeping positives and zeroing negatives. The skip wiring around the block lives in residual connections. This page covers the widen, bend, and squeeze in between.
Think of It Like This
A prep cook with one bowl per order
Dinner service shares ingredients across the kitchen. One cook walks the line tasting pots and gathering a spoon of this and a spoon of that for each order. That gathering round is attention.
Then the cook returns to the station, closes off one bowl at a time, chops, seasons, and reduces it into something the raw spoons were not. Every bowl gets the same recipe steps, but no bowl borrows from another bowl during this part. That solo work is the feed-forward network.
Each gathered spoon mix is one token vector, the wider counter space is the hidden dimension, chopping and seasoning is the nonlinearity, and plating back into the same bowl size is the down projection.
Where it stops: a cook tastes and improvises per bowl. A feed-forward network applies the same fixed matrices at every position with no judgment about the content.
How It Actually Works
Call the token vector with dimension . Call the up matrix with shape by , where is the wider hidden size, often four times . Call its bias. Call the elementwise bend such as ReLU or GELU. Call the down matrix with shape by and its bias. The sublayer is:
The same , , , run at every position. One row never reads another row here. Mixing across positions belongs only to self-attention. Transforming within a position belongs only here. The block output then adds this result back to its input through residual connections, which is why shapes must match at in and out.
Worked example: 2 values widen to 4 then return to 2
Take so and . Pick with columns , , , written as rows . Pick .
Multiply and add:
Apply ReLU, which keeps positives and zeroes the rest:
Pick with rows , , , and . Project down:
That pair is the diagram headline: input and on the left, hidden , , and in the middle, output and on the right.
Why the same weights at every position
Sharing keeps the parameter count fixed as sequences grow and forces one clear job per sublayer. A multi-layer perceptron trained alone sees whole rows. Here the same small perceptron sees each row separately. Change row two of the input and only row two of the output moves.
Code
Rebuild the worked example with plain Python and confirm and .
def relu_vec(v: list[float]) -> list[float]: return [a if a > 0.0 else 0.0 for a in v]
def ffn_step(x: list[float], w1: list[list[float]], b1: list[float], w2: list[list[float]], b2: list[float]) -> list[float]: pre: list[float] = [x[0] * w1[0][j] + x[1] * w1[1][j] + b1[j] for j in range(4)] hid: list[float] = relu_vec(pre) out: list[float] = [hid[0] * w2[0][k] + hid[1] * w2[1][k] + hid[2] * w2[2][k] + hid[3] * w2[3][k] + b2[k] for k in range(2)] return out
x: list[float] = [2.0, -1.0]w1: list[list[float]] = [[1.0, 0.0, -1.0, 2.0], [0.0, 1.0, 1.0, -1.0]]b1: list[float] = [0.0, 1.0, 3.0, 0.0]w2: list[list[float]] = [[1.0, 0.0], [0.0, 1.0], [1.0, 1.0], [0.5, 0.0]]b2: list[float] = [0.0, 1.0]
print(ffn_step(x, w1, b1, w2, b2)) # -> [4.5, 1.0]The printout matches the hand arithmetic. Change to and only the same row result moves, which proves no cross position read.
Watch Out For
Assuming this sublayer mixes positions
Attention sits right before it, so it is easy to credit this step with cross token work. By construction and touch one row at a time with shared weights.
Symptom: an edit to token three is expected to shift token five through this sublayer alone. Fix: trace the matrices row by row as in self-attention for mixing versus here for solo transforms, and test by changing one input row.
Starving the hidden width to save parameters
The width looks wasteful, so a custom build cuts it to . Accuracy drops because the bend has no room to reshape the content.
Symptom: training loss stalls while attention maps look sensible. Fix: keep the expansion for working space and save parameters in residual connections or fewer layers instead, where the cut hurts less.
The Quick Version
- A feed-forward network transforms one position at a time with no reads across positions.
- Our input 2 and minus 1 widens to 2, 0, 0 and 5 after adding bias and applying ReLU.
- Those four hidden values project down to 4.5 and 1.0 through the second matrix plus bias.
- The same two matrices run at every position, so longer sequences add no new weights here.
- Attention routes between positions while this sublayer bends within a position.
- Most transformer parameters sit in these two matrices, not in the attention projections.