Transformer, explained · Part 4 of 7 · Covers §4, Table 1
Why Self-Attention: Cost, Steps and Paths
Section 4 compares self-attention with recurrent and convolutional layers on three measures: work per layer, steps that must wait for each other, and the distance a signal must travel. We compute every column of Table 1 in Python, check the counts with a FLOP counter, and build the restricted attention window in code.
Attention Is All You Need. Ashish Vaswani, Noam Shazeer, Niki Parmar, Jakob Uszkoreit, Llion Jones, Aidan N. Gomez, Łukasz Kaiser, Illia Polosukhin. NeurIPS 2017, 2017. arXiv:1706.03762
Parts 2 and 3 showed what the Transformer is. Section 4 answers a different question: why build a layer out of attention, and not out of a recurrent or a convolutional network? The paper gives three measures and one table, and we put real numbers into each cell.
Three measures for a layer
Table 1, cell by cell
The first column: work per layer
Where do the two formulas come from? Count the multiplications.
A recurrent layer updates its memory once for each word. Each update multiplies a vector of numbers by a matrix, which is multiplications. With words, that is .
Self-attention compares each word with each other word. That is pairs. Each comparison is a dot product of two vectors of numbers, so it costs multiplications. The mix of the values costs the same again. So the core of attention costs about .
Read the ratio on the right. The two costs share a factor , so it cancels. What is left is : sentence length over vector size. When is smaller than , attention does less work. When is larger, the recurrent layer does less.
A tiny example you can check by hand. Take words and vectors of numbers. The attention scores are a grid of 16 dot products, each with 3 multiplications: 48 in all. The recurrent layer does 4 updates of multiplications: 36 in all. The ratio is , which is exactly .
Now the real sizes, with as in the base model. Our script tx_part4.py prints the counts:
| self-attention | recurrent | convolution , | restricted , | ratio | |
|---|---|---|---|---|---|
| 10 | 51,200 | 2,621,440 | 7,864,320 | 81,920 | 0.020 |
| 27 | 373,248 | 7,077,888 | 21,233,664 | 221,184 | 0.053 |
| 100 | 5,120,000 | 26,214,400 | 78,643,200 | 819,200 | 0.195 |
| 512 | 134,217,728 | 134,217,728 | 402,653,184 | 4,194,304 | 1.000 |
| 2,048 | 2,147,483,648 | 536,870,912 | 1,610,612,736 | 16,777,216 | 4.000 |
| 8,192 | 34,359,738,368 | 2,147,483,648 | 6,442,450,944 | 67,108,864 | 16.000 |
For a sentence of 27 tokens, the attention core needs about 19 times less work than a recurrent layer. The two meet at exactly . At 8,192 tokens, attention needs 16 times more.
One honest note. Table 1 counts only the part that differs between the layers. A real attention layer also multiplies every vector by the matrices , , and of Part 3. That adds about , the same size as a recurrent layer. A real recurrent layer, such as an LSTM, also has several matrices. So the table compares the parts that grow differently, and leaves out the shared terms.
Counting the work in code
The counts above came from formulas. Now we count them from code, in two ways. Method 1 reads the shapes and multiplies, with one comment for each term. Method 2 runs the real layers under FlopCounterMode, a tool of PyTorch that watches every matrix product and adds up its cost.
def count_self_attention(n, d):
"""Multiply-adds of one self-attention layer, from the shapes (one head, as in Table 1)."""
scores = n * n * d # Q K^T: n x n dot products, d multiply-adds each
mix = n * n * d # weights V: n output rows, each a sum of n value vectors of d numbers
proj = 4 * n * d * d # x W_Q, x W_K, x W_V and the output W_O: n vectors times a d x d matrix, four times
return scores + mix, proj
def count_recurrent(n, d):
"""Multiply-adds of one recurrent layer h_t = tanh(x_t W + h_{t-1} U)."""
chain = n * d * d # h_{t-1} U: one d x d product per position, one after another
inp = n * d * d # x_t W: the input part, also d x d per position
return chain, inp
def count_conv(n, d, k):
"""Multiply-adds of one convolution with kernel width k, d channels in and out."""
return k * n * d * d # each of n outputs mixes k input vectors, each with a d x d matrix
def count_separable(n, d, k):
"""Multiply-adds of a depthwise separable convolution: a per-channel window, then a d x d mix."""
depthwise = k * n * d # each channel has its own k numbers: no mixing across channels
pointwise = n * d * d # a 1 x 1 convolution mixes the d channels at each position
return depthwise + pointwise
def flops(fn, *args):
"""FLOPs that PyTorch's own counter sees (it counts 2 FLOPs per multiply-add)."""
with FlopCounterMode(display=False) as fc:
fn(*args)
return fc.get_total_flops()The layers themselves are short functions. attention_core is the part of self-attention that Table 1 counts. recurrent_chain is the part of a recurrent layer that must wait. The convolutions come from PyTorch.
def attention_core(q, k, v):
"""The part of self-attention that Table 1 counts: the scores and the mix. q, k, v: (n, d)."""
return torch.softmax(q @ k.T / math.sqrt(q.shape[1]), dim=-1) @ v # (n, d)
def recurrent_chain(xw, U):
"""The part of a recurrent layer that must wait: h_t = tanh(xw_t + h_(t-1) U), one position after another."""
h = torch.zeros(xw.shape[1])
for t in range(xw.shape[0]): # h_t needs h_(t-1): no way to compute the steps at the same time
h = torch.tanh(xw[t] + h @ U)
return h # the last memory (d,)
n, d, k = 27, 512, 3 # 27 tokens, the base model's d
x = torch.randn(n, d) # the input: n vectors of d numbers
Wq, Wk, Wv, Wo, W, U = (torch.randn(d, d) / math.sqrt(d) for _ in range(6))
conv = torch.nn.Conv1d(d, d, k, padding=1, bias=False) # contiguous kernel of width 3
depthwise = torch.nn.Conv1d(d, d, k, padding=1, groups=d, bias=False) # one small window per channel
pointwise = torch.nn.Conv1d(d, d, 1, bias=False) # mixes the channels
xc = x.T.unsqueeze(0) # Conv1d wants (batch, channels d, length n)Then we count each part both ways, with tokens and :
core, proj = count_self_attention(n, d)
chain, inp = count_recurrent(n, d)
with torch.no_grad():
q, kk, v = x @ Wq, x @ Wk, x @ Wv
rows = [ # (part of the layer, Method 1: our count, Method 2: what PyTorch's counter sees)
('self-attention core (QK^T and weights V)', core, flops(attention_core, q, kk, v)),
('self-attention projections W_Q W_K W_V W_O', proj, flops(lambda: (x @ Wq, x @ Wk, x @ Wv, x @ Wo))),
('recurrent chain h U (n steps)', chain, flops(recurrent_chain, x @ W, U)),
('recurrent input x W', inp, flops(lambda: x @ W)),
('convolution, k = 3', count_conv(n, d, k), flops(conv, xc)),
('separable convolution, k = 3', count_separable(n, d, k), flops(lambda: pointwise(depthwise(xc)))),
]part of the layer our count counter / 2 equal
self-attention core (QK^T and weights V) 746,496 746,496 True
self-attention projections W_Q W_K W_V W_O 28,311,552 28,311,552 True
recurrent chain h U (n steps) 7,077,888 7,077,888 True
recurrent input x W 7,077,888 7,077,888 True
convolution, k = 3 21,233,664 21,233,664 True
separable convolution, k = 3 7,119,360 7,119,360 True
Table 1 terms: n^2 d = 373,248, n d^2 = 7,077,888, k n d^2 = 21,233,664, k n d + n d^2 = 7,119,360Every row agrees to the last digit. So the formulas of Table 1 are not estimates. They are exact counts of the matrix products, without the constant factors. One of those factors is worth seeing. The attention core costs , not , because it has two products of that size: the scores and the mix. Big-O notation drops the 2.
The second lesson is the honest note above, now in numbers. The four projections of attention cost 28,311,552 multiply-adds, the attention core only 746,496. For a short sentence, the projections are most of the cost of a real attention layer, and the term is small. Table 1 leaves them out, because a recurrent layer has the same kind of term. It is the input product , the row with 7,077,888.
Counting the waiting steps in code
We can measure this column too. PyTorch records every operation of a computation in a graph, to compute the gradients later. Each operation points to the operations whose results it used. The longest chain in that graph is the number of steps that must run one after another, whatever the hardware.
def graph_depth(t):
"""The longest chain of operations from the output back to the inputs, in PyTorch's autograd graph.
Operations on one chain must run one after another; operations on different chains can run in parallel."""
memo = {}
def depth(node):
if node is None:
return 0
if node not in memo:
parents = [p for p, _ in node.next_functions]
memo[node] = 1 + max([depth(p) for p in parents] or [0])
return memo[node]
return depth(t.grad_fn)
def self_attention_layer(x, Wq, Wk, Wv):
"""A whole self-attention layer (one head): the projections, then the core."""
return attention_core(x @ Wq, x @ Wk, x @ Wv) # (n, d)
def recurrent_layer(x, W, U):
"""A whole recurrent layer: the input part x W for all positions at once, then the chain."""
return recurrent_chain(x @ W, U)We give the weights requires_grad=True, so that PyTorch records the graph. Then we measure both layers for four lengths:
dd = 64
Aq, Ak, Av, Wr, Ur = (torch.randn(dd, dd, requires_grad=True) for _ in range(5))
for n2 in [4, 16, 64, 256]:
xs = torch.randn(n2, dd)
da = graph_depth(self_attention_layer(xs, Aq, Ak, Av))
dr = graph_depth(recurrent_layer(xs, Wr, Ur))== 2. Sequential operations: longest chain in the autograd graph (d = 64)
n self-attention recurrent
4 7 20
16 7 80
64 7 320
256 7 1280For self-attention, the longest chain has 7 operations at every length: a few matrix products, the scale and the softmax. A longer sentence makes each product larger, but it does not add a link to the chain. The recurrent chain grows in step with : 20 operations for 4 tokens, 1,280 for 256 tokens. This is the second column of Table 1, measured: against . A GPU can run the many multiplications inside one attention product at the same time. It cannot start step 2 of the recurrent chain before step 1 ends.
The tokenizer gives these lengths:
2. Real sentence lengths (Marian en-de tokenizer, d_model = 512)
n = 8 words = 6 n/d = 0.016 The cat sat on the mat.
n = 7 words = 5 n/d = 0.014 Attention is all you need.
n = 13 words = 12 n/d = 0.025 The agreement on the European Economic Area was signed in Au...
n = 23 words = 17 n/d = 0.045 Self-attention layers are faster than recurrent layers when ...
n = 33 words = 26 n/d = 0.064 Learning long-range dependencies is a key challenge in many ...
n = 39 words = 32 n/d = 0.076 The Transformer allows for significantly more parallelizatio...
tokens of sentence 4: ▁Self - at tention ▁layers ▁are ▁faster ▁than ▁recurrent ▁layers ▁when ▁the ▁sequence ▁length ▁is ▁smaller ▁than ▁the ▁representation ▁dimension ality . </s>Even the longest sentence, at 39 tokens, has . So for this sentence the attention core does about 13 times less work than a recurrent layer of the same width. Each count includes the end-of-sentence token </s>. These six sentences are our own choice, not the paper's test set. They show the order of size, not an average over a corpus.
Time on a real computer
Counting multiplications is one thing. Does a real computer agree? Our script times one self-attention step against one recurrent loop, both with . The attention step includes the three projections to queries, keys and values. The recurrent loop does its input projection for all words at once, then runs the chain of updates.
3. Wall time on CPU (4 threads), d = 512, median of 7 runs, milliseconds
n self-attn recurrent loop steps attn steps rnn
16 0.059 0.156 1 16
64 0.147 0.573 1 64
256 0.578 2.449 1 256
1024 4.568 8.970 1 1024
4096 42.882 37.640 1 4096At 16 tokens, attention takes 0.059 ms and the loop takes 0.156 ms. At 1,024 tokens, attention is still about two times faster. At 4,096 tokens, the term wins and attention becomes the slower one, as the arithmetic predicts.
Two limits apply to this test. It ran on a laptop CPU with 4 threads, in PyTorch, with random weights and one batch of one sentence. A GPU runs the large matrix products of attention much faster, but cannot speed up the 4,096 small waiting steps of the loop. So we expect the gap at short lengths to be larger on a GPU, not smaller. We did not measure a GPU here.
The path between two words
The third column of Table 1 is about learning, not speed. The figure below draws the three layers for a sentence of 8 words. The bold lines show the shortest route from word 1 to the output at word 8.
Why does a long path hurt? During training, the network learns from the gradient: a signal that says how much each input should change. In a recurrent layer, the gradient from word 8 to word 1 passes through 7 updates. Each update multiplies it by a matrix and by the slope of the tanh function. If these factors are a little smaller than 1, the product shrinks fast, like .
We measured this. Our script builds a recurrent network with tanh and a single self-attention layer, both with random weights. Each has vectors of 64 numbers and reads 50 positions. We compute the size of the gradient of the last output with respect to each input, and take the mean of 20 random networks.
4. Gradient norm |d out_last / d x_i| (random weights, d = 64, n = 50), mean of 20 seeds
input i distance RNN self-attn
1 49 1.55e-09 2.97e-01
10 40 7.31e-08 2.76e-01
20 30 5.23e-06 2.13e-01
30 20 6.06e-04 1.77e-01
40 10 4.66e-02 2.14e-01
45 5 5.58e-01 3.11e-01
49 1 3.33e+00 1.99e-01
50 0 5.02e+00 1.87e+00The numbers tell the story of the third column. In the recurrent network, the first word has a gradient of , about three billion times smaller than the last word. In practice, such a word cannot teach the network anything. In the attention layer, the first word gets 0.297 and the 49th word gets 0.199. Distance does not matter.
This test has limits too. The weights are random, not trained. A trained LSTM keeps gradients much better than our plain tanh network, because its gates were designed for this problem. And the input at distance 0 is larger in attention because it also feeds the query of the last position. The test shows the shape of the effect, not the size it has in a real trained model.
The path length in code
The figure above draws the paths for 8 words. Code can count them for any length. The idea is to describe one layer by a table of links. Entry is True if output reads input directly. A stack of layers is the product of their tables, with True and False in place of numbers. We stack layers until the last output depends on the first input.
def window_links(n, offsets):
"""links[i, j] = True if output i of one layer reads input j directly, for j = i + offset.
A one-sided kernel of width 3 has offsets (0, -1, -2); dilated by 3 it has (0, -3, -6)."""
links = torch.zeros(n, n, dtype=torch.bool)
for i in range(n):
for o in offsets:
if 0 <= i + o < n:
links[i, i + o] = True
return links
def layers_to_connect(n, layer_links):
"""Stack layers until the last output depends on the first input; return how many layers that takes.
layer_links(L) gives the links of layer L. The dependency of a stack is the boolean product of its layers."""
reach = torch.eye(n, dtype=torch.bool) # before any layer, each position depends only on itself
for L in range(1, 10 * n):
reach = (layer_links(L).int() @ reach.int()) > 0
if reach[n - 1, 0]: # output n now depends on input 1
return L
return None
def recurrent_path(n):
"""x_1 enters the memory at h_1. To reach the output z_n = h_n, it must pass h_1 -> h_2 -> ... -> h_n."""
t, hops = 1, 0 # x_1 is now inside h_1
while t < n:
t += 1 # one recurrent step: h_t is computed from h_(t-1)
hops += 1
return hopsEach layer type is one table of links. Self-attention links every pair. A convolution with kernel 3 links each output to its own position and the two before it. A dilated convolution spreads the same three links further apart in each layer: 1, 3 and 9 positions. Restricted attention links each output to the inputs centred on it.
k = 3
for n2 in [8, 27, 64, 100]:
full = torch.ones(n2, n2, dtype=torch.bool)
p_attn = layers_to_connect(n2, lambda L: full) # every query sees every key
p_conv = layers_to_connect(n2, lambda L: window_links(n2, [0, -1, -2])) # kernel 3, contiguous
p_dil = layers_to_connect(n2, lambda L: window_links(n2, [0, -k ** (L - 1), -2 * k ** (L - 1)])) # dilation 1, 3, 9, ...
p_loc = layers_to_connect(n2, lambda L: window_links(n2, range(-2, 3))) # window of r = 5, centred
p_loc17 = layers_to_connect(n2, lambda L: window_links(n2, range(-8, 9))) # window of r = 17, centred
p_rnn = recurrent_path(n2) n self-attn recurrent conv k=3 dilated k=3 restricted r=5 r=17
8 1 7 4 2 4 1
27 1 26 13 3 13 4
64 1 63 32 4 32 8
100 1 99 50 5 50 13Read the row for . Self-attention needs 1 layer, the recurrent layer 26 steps, the contiguous convolution 13 layers and the dilated convolution 3. These are the counts of Table 1: , , and . The last two columns are restricted attention. With a window of 5, it needs 13 layers, because each layer reaches only 2 positions further. A window of 17 reaches 8 positions per layer and needs 4: the of the last row of Table 1.
Our convolutions are one-sided: each output looks only at its own position and earlier ones. The decoder of ByteNet, reference [18] of the paper, works this way. Their reach matches the of the paper exactly. A centred kernel reaches as many positions, but half of them on each side.
When sentences get long
Restricted attention in code
The window is one more mask, like the decoder mask of Part 3. Query may look at key only if the two are at most positions apart. Here is the mask for 10 tokens and a window of , built with two loops:
n2, r = 10, 5
half = r // 2
local = torch.zeros(n2, n2, dtype=torch.bool)
for i in range(n2):
for j in range(n2):
local[i, j] = abs(i - j) <= half # key j is inside the window of query iwindow mask (1 = may look):
1 1 1 0 0 0 0 0 0 0
1 1 1 1 0 0 0 0 0 0
1 1 1 1 1 0 0 0 0 0
0 1 1 1 1 1 0 0 0 0
0 0 1 1 1 1 1 0 0 0
0 0 0 1 1 1 1 1 0 0
0 0 0 0 1 1 1 1 1 0
0 0 0 0 0 1 1 1 1 1
0 0 0 0 0 0 1 1 1 1
0 0 0 0 0 0 0 1 1 1
allowed pairs: 44 of 100 (at most r = 5 per row)The mask is a band along the diagonal. Each row has at most 5 ones. The rows at the two ends have fewer, because their window runs past the end of the sentence. Our attention function with this mask gives the same output as the library with the same mask.
def masked_attention(q, k, v, mask):
"""Equation 1 with a mask: computes all n x n scores, then hides the ones outside the window."""
scores = q @ k.T / math.sqrt(q.shape[-1])
scores = scores.masked_fill(~mask, float('-inf'))
return torch.softmax(scores, dim=-1) @ vThere is a catch. The masked version still computes all scores, and then throws most of them away. It saves no work at all. To get the of Table 1, the code must compute only the scores inside the window. banded_attention does that. For each query, it cuts out the keys and values of its window with unfold, and computes dot products.
def banded_attention(q, k, v, r):
"""The same result, but each query computes only its r scores: n * r work in place of n * n."""
n, d = q.shape
half = r // 2
kp = F.pad(k, (0, 0, half, half)) # (n + 2 half, d): zero rows before the start and after the end
vp = F.pad(v, (0, 0, half, half))
k_win = kp.unfold(0, r, 1) # (n, d, r): for query i, the keys i - half ... i + half
v_win = vp.unfold(0, r, 1) # (n, d, r)
scores = (q.unsqueeze(1) @ k_win).squeeze(1) / math.sqrt(d) # (n, r): r dot products per query
pos = torch.arange(n).unsqueeze(1) + torch.arange(r) - half # (n, r): the real position of each window slot
scores = scores.masked_fill((pos < 0) | (pos >= n), float('-inf')) # slots outside the sentence are padding
w = torch.softmax(scores, dim=-1) # (n, r)
return (v_win @ w.unsqueeze(2)).squeeze(2) # (n, d): mix the r values of each window
q, kk, v = torch.randn(n2, 16), torch.randn(n2, 16), torch.randn(n2, 16)
o_mask = masked_attention(q, kk, v, local)
o_lib = F.scaled_dot_product_attention(q, kk, v, attn_mask=local)
o_band = banded_attention(q, kk, v, r)
d_lib = (o_mask - o_lib).abs().max().item() # Method 1 (our mask) against Method 2 (the library)
d_band = (o_band - o_mask).abs().max().item() # the banded version against the masked onemasked (ours) vs F.scaled_dot_product_attention(attn_mask): max abs difference 0.0e+00
banded (n * r scores) vs masked (n * n scores): max abs difference 1.2e-07The banded version matches the masked one to , and the masked one matches the library exactly. Now count the work. We ran both versions under the FLOP counter, with and a window of 17:
d3, r3 = 64, 17
for n3 in [256, 1024, 4096]:
q, kk, v = torch.randn(n3, d3), torch.randn(n3, d3), torch.randn(n3, d3)
mask = (torch.arange(n3).unsqueeze(1) - torch.arange(n3)).abs() <= r3 // 2
fm = flops(masked_attention, q, kk, v, mask)
fb = flops(banded_attention, q, kk, v, r3) # window of 17: 8 on each side and the centreFLOPs of the two versions, d = 64, r = 17 (FlopCounterMode, matrix products only)
n masked n x n banded n x r ratio
256 16,777,216 1,114,112 15
1024 268,435,456 4,456,448 60
4096 4,294,967,296 17,825,792 241At 256 tokens, the band does 15 times less work than the full table. At 4,096 tokens, it does 241 times less, and the gap keeps growing with . That is the gain the authors had in mind. The price is the path: the table of the previous section showed that a window of 17 needs 13 layers to connect 100 tokens.
Why not convolutions?
Our script counts the layers that a kernel of width 3 needs to connect the first and the last word:
5. Layers needed to connect position 1 and position n (kernel k = 3)
n = 10: contiguous 5 layers, dilated 3 layers, self-attention 1 layer, RNN 9 steps
n = 27: contiguous 13 layers, dilated 3 layers, self-attention 1 layer, RNN 26 steps
n = 100: contiguous 50 layers, dilated 5 layers, self-attention 1 layer, RNN 99 steps
n = 512: contiguous 256 layers, dilated 6 layers, self-attention 1 layer, RNN 511 stepsThe formulas behind the counts: contiguous layers reach positions, and dilated layers reach . For , contiguous layers need , and dilated layers need , because . Self-attention needs 1 layer for every .
The tables of links in the path section are one method to count these layers. A second method uses real layers. We build a stack of random one-sided convolutions and ask autograd whether the last output depends on the first input at all. If the gradient is exactly 0, no path exists.
def conv_stack_grad(n, L, dilated=False):
"""Method 2: a real stack of L random one-sided Conv1d layers (kernel 3). Is d out_n / d x_1 zero?"""
g = torch.Generator().manual_seed(L)
x = torch.randn(1, 8, n, generator=g, requires_grad=True) # 8 channels, n positions
h = x
for l in range(L):
dil = 3 ** l if dilated else 1
w = torch.randn(8, 8, 3, generator=g)
h = F.pad(h, (2 * dil, 0)) # pad on the left only: output t reads t, t - dil, t - 2 dil
h = torch.tanh(F.conv1d(h, w, dilation=dil))
h[0, :, n - 1].sum().backward() # the last output ...
return x.grad[0, :, 0].abs().sum().item() # ... and its gradient at the first input
for L, dil in [(12, False), (13, False), (2, True), (3, True)]:
gsum = conv_stack_grad(27, L, dil)Method 2: real stacks of random Conv1d layers, n = 27, |d out_27 / d x_1|
contiguous 12 layers: 0.000e+00 (not connected)
contiguous 13 layers: 7.305e-05 (connected)
dilated 2 layers: 0.000e+00 (not connected)
dilated 3 layers: 3.317e-02 (connected)With 12 contiguous layers, the gradient is exactly 0: no path exists from word 1 to the output at word 27. With 13 layers, a path appears. The dilated stack connects in 3 layers and not in 2. Both methods give the counts of the formulas above.
With , the term becomes , the cost of self-attention, and is the cost of a point-wise feed-forward layer. The Transformer pays what the cheapest full-width convolution would pay, and gets a path of length 1.
A side benefit: we can look inside
The three wishes, scored
Put the three measures side by side for a typical sentence of 27 tokens and :
| layer | work of the core | waiting steps | longest path |
|---|---|---|---|
| self-attention | 373,248 | 1 | 1 |
| recurrent | 7,077,888 | 27 | 27 |
| convolution, , contiguous | 21,233,664 | 1 | 13 layers |
| convolution, , dilated | 21,233,664 | 1 | 3 layers |
For sentences, self-attention wins all three columns. That is the whole argument of Section 4. It does not say that attention is better for every input. For very long inputs, the cost returns, and the authors say so.
The paper and the code
Paper (arXiv 1706.03762) · Official code (tensor2tensor) · Our scripts for this series

