Split heads, merge heads, multi-head attention
Lesson: Multi-Head AttentionCheat sheetTrack: Transformers from scratch
Problem
Multi-head attention runs several small attentions in parallel. Each head sees one slice of the features. The heads are then joined again and mixed by an output projection. The hard part is the reshape: a wrong axis order gives the right shape but the wrong numbers.
Task
Write three functions.
split_heads(x, n_heads)turnsxof shape(B, T, D)into an array of shape(B, H, T, d_head), withH = n_headsandd_head = D / H. Headhholds the columnsh * d_headto(h + 1) * d_head - 1ofx, in the same order. Sosplit_heads(x, H)[b, h, t, j]equalsx[b, t, h * d_head + j].merge_heads(x)is the inverse. It turns(B, H, T, d_head)into(B, T, H * d_head), so thatmerge_heads(split_heads(x, H))equalsx.multi_head_attention(x, w_q, w_k, w_v, w_o, n_heads)returns the output and the attention weights of multi-head self-attention.
The steps of multi_head_attention are:
- Q = x W_q, K = x W_k, V = x W_v. There are no bias terms.
- Split Q, K and V into heads.
- In each head, compute \mathrm{softmax}(Q_h K_h^T / \sqrt{d_{head}})\, V_h. The softmax is over the key axis. There is no mask.
- Merge the head outputs and multiply by W_o.
Input and output
| Name | Type | Shape | Meaning |
|---|---|---|---|
x | float array | (B, T, D) | a batch of B sequences of T tokens with D features |
n_heads | int | the number of heads H | |
w_q, w_k, w_v | float array | (D, D) | the query, key and value projections |
w_o | float array | (D, D) | the output projection |
split_headsreturns a float array of shape(B, H, T, D / H).merge_headstakes an array of shape(B, H, T, d_head)and returns a float array of shape(B, T, H * d_head).multi_head_attentionreturns a tuple(output, weights).outputhas shape(B, T, D).weightshas shape(B, H, T, T), andweights[b, h, i, j]is the weight that queryigives to keyjin headh.
Constraints
1 <= B, T <= 16and1 <= H <= D <= 64.- When
Dis not divisible byn_heads,split_headsraisesValueError. - Activations can be large. The output must stay finite.
- Do not change the input arrays.
- Use numpy only.
Examples
pythonx = np.arange(8.0).reshape(1, 2, 4)
split_heads(x, 2)text[[[[0. 1.]
[4. 5.]]
[[2. 3.]
[6. 7.]]]]merge_heads of that result gives x again.
pythonx = np.array([[[1.0, 0.0, 0.0, 1.0], [0.0, 2.0, 1.0, 0.0], [1.0, 1.0, 1.0, 1.0]]])
eye = np.eye(4)
output, weights = multi_head_attention(x, eye, eye, eye, eye, 2)textoutput
[[[0.80222419 0.79666372 0.59888791 0.80222419]
[0.23208206 1.72252957 0.80222419 0.59888791]
[0.59888791 1.20333628 0.75174492 0.75174492]]]
weights
[[[[0.40111209 0.19777581 0.40111209]
[0.04538836 0.76791794 0.1866937 ]
[0.19777581 0.40111209 0.40111209]]
[[0.40111209 0.19777581 0.40111209]
[0.19777581 0.40111209 0.40111209]
[0.24825508 0.24825508 0.50348984]]]]Hints
4 hintsSolution and notes
LockedPass every check with Submit to open the reference solution and the notes.
Run it on your computer
Clone the repository, install numpy and run the same checks in a terminal:
python3 scripts/practice/run.py multi-head-split-merge my_answer.py
python3 scripts/practice/run.py multi-head-split-merge my_answer.py --examplesStart my_answer.py from starter.py.
Your code is saved in this browser as you type. On a phone you can read and edit the code, but the checks are easier to use on a desktop.
Keyboard shortcuts
- Ctrl Enter
- Run the examples
- Ctrl Shift Enter
- Submit
- Tab / Shift Tab
- Indent or outdent the lines
- Esc then Tab
- Move the focus out of the editor
- Alt H
- Show the next hint
On a Mac, use Cmd in place of Ctrl.
Select Run to check the examples, or Submit to run every check.