Part 6 of 84 min read

Shortcut Connections

A way to mitigate vanishing gradients in deep neural networks by creating alternative shortcut paths for the flow of gradients during the backward pass in training.

LLMGPTshortcut connectionsresidualtrainingfundamentalsplayground

A shortcut connection adds the input of a network block to its output:

y=x+F(x)y = x + F(x)

xx is the block's input, F(x)F(x) is what its layers compute from it, and yy is what the block passes on. It is also called a skip connection or a residual connection. Following He et al., who introduced it, a network with shortcuts is a residual network, and the same network without them is a plain network.

depth5 layers

Shortcuts matter here: the backward pass is where the gradient reaches each block, and where it vanishes in the plain network.

plain: no shortcuts
x
block 1Linear(3, 3) → GELU
block 2Linear(3, 3) → GELU
block 3Linear(3, 3) → GELU
block 4Linear(3, 3) → GELU
block 5Linear(3, 1) → GELU
y
L = MSE(y, t)
forward pass
backward pass
residual: with shortcuts
x
block 1Linear(3, 3) → GELU
block 2Linear(3, 3) → GELU
block 3Linear(3, 3) → GELU
block 4Linear(3, 3) → GELU
block 5Linear(3, 1) → GELU
y
L = MSE(y, t)
forward pass
backward pass
the input, the outputs, the target and the loss: one example
−+
x[1, 3]
1.000.00-1.00
y, plain[1, 1]
0.189
y, residual[1, 1]
0.472
t[1, 1]
0.00
L, plainscalar
0.0357
L, residualscalar
0.223
block 1: F₁(h₀) = GELU(h₀ @ W₁ + b₁)−+
h₀ = x, plain[1, 3]
1.000.00-1.00
h₀ = x, residual[1, 3]
1.000.00-1.00
W₁[3, 3],b₁[3]
z₁[1]z₁[2]z₁[3]
h₀[1]-0.280.090.06
h₀[2]0.45-0.44-0.37
h₀[3]0.18-0.220.00
b0.22-0.12-0.44
F₁(h₀), plain[1, 3]
-0.09720.109-0.134
F₁(h₀), residual[1, 3]
-0.09720.109-0.134

Subscripts number the block, brackets an entry from 1: W₁[2, 1] is row 2, column 1 of block 1's W, and z₁[1] the first value of its z.

block 1: the gradient for its own weights, ∂L/∂W₁, and the one it hands down, ∂L/∂h₀plainresidual|v|, log scale, 7.31e-7 to 0.384
∂L/∂W₁, plain[3, 3],∂L/∂b₁[3]
z₁[1]z₁[2]z₁[3]
h₀[1]3.88e-41.78e-42.58e-4
h₀[2]000
h₀[3]-3.88e-4-1.78e-4-2.58e-4
b3.88e-41.78e-42.58e-4
∂L/∂W₁, residual[3, 3],∂L/∂b₁[3]
z₁[1]z₁[2]z₁[3]
h₀[1]0.130.04840.00207
h₀[2]000
h₀[3]-0.13-0.0484-0.00207
b0.130.04840.00207
∂L/∂h₀, plain[1, 3]
-7.72e-57.31e-73.07e-5
∂L/∂h₀, residual[1, 3]
0.3840.1110.0225
both gradients at every block, from the loss downplainresiduallog axis, 1e-10 to 1e2
handed down: mean |∂L/∂h|
plainresidual∂L/∂y0.3780.9440.04820.1550.01920.1460.00770.2269.14e-40.1673.62e-50.172
for its weights: mean |∂L/∂W|
plainresidual0.03530.5460.004650.02930.002970.09362.39e-40.05841.83e-40.04
block 1, plain1.83e-4
block 1, residual0.04
residual ÷ plain218×
examples, n1
both networks in python: needs torch

Shortcuts address the vanishing gradient problem: during training, gradients shrink as they pass backward through the network, so the earliest layers barely learn.

The optimizer updates each weight using its gradient, which measures how strongly, and in which direction, a change in that weight affects the loss. Backpropagation computes these gradients by sending a gradient backward from the loss to the input, through every operation in the network. A block is any consecutive group of these operations whose output has the same shape as its input, such as any layer but the last in the card above, or one sublayer in GPT-2 together with its normalization and dropout.

Number the blocks 1 to nn from the input, so block kk computes yk=Fk(xk)y_k = F_k(x_k). Going backward, each block multiplies the gradient it receives by its derivative ∂Fk/∂xk\partial F_k/\partial x_k, which says how much its output changes when its input changes, and passes the result to the block below. In a plain network, the gradient that comes out of the first block is therefore a product, with one factor per block:

∂L∂x1=∂L∂yn∂Fn∂xn⋯∂F2∂x2∂F1∂x1\frac{\partial L}{\partial x_1} = \frac{\partial L}{\partial y_n} \frac{\partial F_n}{\partial x_n} \cdots \frac{\partial F_2}{\partial x_2} \frac{\partial F_1}{\partial x_1}

If every factor halves the gradient, after 10 blocks it is 0.510≈0.0010.5^{10} \approx 0.001 of its starting size, and after 20 about one millionth. This is exponential decay: the gradient shrinks by the same fraction at every block, so the early blocks are updated by almost nothing.

With shortcuts, block kk computes yk=xk+Fk(xk)y_k = x_k + F_k(x_k), and each factor becomes 1+∂Fk/∂xk1 + \partial F_k/\partial x_k:

∂L∂x1=∂L∂yn(1+∂Fn∂xn)⋯(1+∂F1∂x1)\frac{\partial L}{\partial x_1} = \frac{\partial L}{\partial y_n} \left(1 + \frac{\partial F_n}{\partial x_n}\right) \cdots \left(1 + \frac{\partial F_1}{\partial x_1}\right)

The 1 is the shortcut's derivative, the derivative of xkx_k with respect to itself; when xkx_k is a vector, it is the identity matrix. Multiplied out, the product contains the term ∂L/∂yn\partial L/\partial y_n on its own: the gradient at the last block's output, unchanged. When the ∂Fk/∂xk\partial F_k/\partial x_k are small, each factor stays close to 1 instead of close to 0, so each block changes the gradient only a little instead of shrinking it. A shortcut does not change how a block computes its weight gradients; it changes the gradient the block receives, from which they are computed.

Limits

The shapes must match. x+F(x)x + F(x) is defined only when F(x)F(x) has the shape of xx. Where a block changes the shape, the shortcut must change it too, usually with a projection that has its own weights, so its derivative is no longer the identity.

Gradients can still vanish or grow. Depending on ∂Fk/∂xk\partial F_k/\partial x_k, a factor 1+∂Fk/∂xk1 + \partial F_k/\partial x_k can be close to 0 or larger than 1. The values passed between blocks can grow too. Each block adds the output of its FkF_k to a running sum, the residual stream, which after the last block is yn=x1+F1(x1)+⋯+Fn(xn)y_n = x_1 + F_1(x_1) + \cdots + F_n(x_n), so its size tends to grow with the number of blocks. This is why residual networks are paired with normalization and careful initialization.

The effective depth is shorter than the drawn depth. Multiplying out the product gives one term for each path down the network: at each block, a path takes either the 1, along the shortcut, or ∂Fk/∂xk\partial F_k/\partial x_k, through FkF_k. With two blocks:

∂L∂x1=∂L∂y2(1+∂F2∂x2+∂F1∂x1+∂F2∂x2∂F1∂x1)\frac{\partial L}{\partial x_1} = \frac{\partial L}{\partial y_2} \left(1 + \frac{\partial F_2}{\partial x_2} + \frac{\partial F_1}{\partial x_1} + \frac{\partial F_2}{\partial x_2} \frac{\partial F_1}{\partial x_1}\right)

A path through many blocks multiplies many factors ∂Fk/∂xk\partial F_k/\partial x_k and shrinks like the plain network's product. Veit et al. show that most of the gradient during training comes from short paths: in their network of 54 blocks, from paths through 5 to 17 of them.

References

Sebastian Raschka, Build a Large Language Model (From Scratch) (Manning, 2024). Section 4.4 builds the network the card runs, which the code exported above follows.

Kaiming He et al., Deep Residual Learning for Image Recognition (2015). The paper the method comes from.

Andreas Veit, Michael Wilber and Serge Belongie, Residual Networks Behave Like Ensembles of Relatively Shallow Networks (2016). The path view of a residual network, and which paths carry its gradient.

Search

Search pages, articles, and resources