Katkat

Implement Multi-Head Self-Attention

UberMachine Learning Engineer · Technical Screen

Last updated

This question tests the ability to implement the multi-head self-attention mechanism from scratch using PyTorch, focusing on tensor manipulation, linear projections, and the scaled dot-product attention formula.

Multi-Head Self-Attention Implementation

Implement a MultiHeadAttention class using PyTorch. The class should map an input tensor x of shape (batch_size, seq_len, embed_dim) to an output of the same shape. You must perform the following steps:

  1. Linear Projections: Apply three separate linear layers to transform the input into Queries (Q), Keys (K), and Values (V).
  2. Head Splitting: Reshape the Q, K, and V tensors into (batch_size, num_heads, seq_len, head_dim), where head_dim = embed_dim / num_heads.
  3. Scaled Dot-Product Attention: Compute Attention(Q, K, V) = softmax((Q @ K.transpose(-2, -1)) / sqrt(head_dim)) @ V.
  4. Concatenation and Projection: Concatenate the heads back together and pass them through a final linear output layer.

Function Signature

import torch
import torch.nn as nn

class MultiHeadAttention(nn.Module):
    def __init__(self, embed_dim, num_heads):
        super().__init__()
        # Initialize your linear layers and parameters here
        pass

    def forward(self, x):
        # x shape: (batch_size, seq_len, embed_dim)
        # returns: (batch_size, seq_len, embed_dim)
        pass

Constraints

  • embed_dim must be divisible by num_heads.
  • Use torch.softmax for the attention scores.

Example

mha = MultiHeadAttention(embed_dim=512, num_heads=8)
x = torch.randn(32, 10, 512) # batch=32, seq_len=10, dim=512
output = mha(x)
# output.shape should be (32, 10, 512)

Edge Cases

  • Consider scenarios where seq_len is 1.
  • Ensure the implementation handles arbitrary batch_size.

Hints

Hint 1

Use `torch.transpose` or `permute` to arrange dimensions so that matrix multiplication occurs across the sequence length dimension for each head independently.

Hint 2

The scaling factor `1 / sqrt(head_dim)` is crucial for preventing the softmax input from becoming too large and saturating gradients.

Hint 3

Remember to reshape/flatten the final attention output back to `(batch_size, seq_len, embed_dim)` before the final linear projection.

Follow-up questions

  • How would you integrate a padding mask into the attention calculation to ignore specific tokens in the sequence?
  • Why is multi-head attention generally preferred over single-head attention with a larger hidden dimension?
  • How would the memory complexity scale if the sequence length doubles?