A word can matter in more than one way. Multi-head attention lets a transformer compute several attention representations in parallel, so different heads can attend to different positions and patterns. In self-attention, the queries, keys, and values all come from the input sequence’s embeddings. A decoder can also attend to an encoder’s output: its target sequence supplies the queries, while the encoder output supplies the keys and values. section 9.1.2 section 9.2.3
The computation builds on scaled dot-product attention:
- Project embeddings into queries, keys, and values. Query–key scores, scaled by 1/√dₖ and passed through softmax, weight the values. section 9.1.2
- Run this computation across multiple heads, concatenate their outputs, and apply an output projection. The number of heads is an architecture hyperparameter; the original transformer used eight heads with 64-dimensional keys and values. section 9.1.2
- Place the attention layer alongside a feedforward layer and normalization in a stackable transformer block. Encoder and decoder stacks use attention in different positions. section 9.2.2
Figure 9.4 Multi-head self-attention
The diagram shows the parallel heads and how their results become one projected output. section 9.1.2
Inspect attention during translation
For a translation experiment, load aligned German–English sentences, tokenize the two languages, and pass source and target sequences to a PyTorch transformer. A customized decoder layer calls self-attention with the target three times, then calls multi-head attention with the target and encoder memory. It returns the latter attention weights for inspection. In the supplied test example, a German sentence about a mother and her son produces an English translation; plotted weights put source and output tokens on the axes. The plots help inspect what the model attended to, but a translation still needs evaluation on held-out data. section 9.2.3
Listing 9.5 Inspecting attention weights with customized TransformerDecoderLayer
>>> from torch import Tensor >>> from typing import Optional, Any >>> class CustomDecoderLayer(nn.TransformerDecoderLayer): ... def forward(self, tgt: Tensor, memory: Tensor, ... tgt_mask: Optional[Tensor] = None, ... memory_mask: Optional[Tensor] = None, ... tgt_key_padding_mask: Optional[Tensor] = None ... ) -> Tensor: ... """Like decode but returns multi-head attention weights.""" ... tgt2 = self.self_attn( ... tgt, tgt, tgt, attn_mask=tgt_mask, ... key_padding_mask=tgt_key_padding_mask)[0] ... tgt = tgt + self.dropout1(tgt2) ... tgt = self.norm1(tgt) ... tgt2, attention_weights = self.multihead_attn( ... tgt, memory, memory, #1 ... attn_mask=memory_mask, ... key_padding_mask=mem_key_padding_mask, ... need_weights=True) ... tgt = tgt + self.dropout2(tgt2) ... tgt = self.norm2(tgt) ... tgt2 = self.linear2( ... self.dropout(self.activation(self.linear1(tgt)))) ... tgt = tgt + self.dropout3(tgt2) ... tgt = self.norm3(tgt) ... return tgt, attention_weights #2
The decoder-layer code makes the distinction between target self-attention and attention over encoder memory concrete. section 9.2.3













