Implement or adapt a Transformer attention component. Your discussion and implementation should address all of the following:
Use this PyTorch interface:
class MultiHeadSelfAttention(nn.Module):
def __init__(self, hidden_dim: int, num_heads: int):
...
def forward(
self,
x: torch.Tensor,
attention_mask: torch.Tensor | None = None,
) -> torch.Tensor:
...
Assume x has shape (batch, seq, hidden_dim) and the returned tensor has the same shape. The mask must be usable for either padding positions or causal attention.
Keep the dimension changes explicit, especially when converting projected queries, keys, and values into separate attention heads. Explain how a padding mask differs in broadcast behavior from a mask that blocks access to future positions.
Masks must affect the logits before normalization; applying one only after softmax is not acceptable.
Input:
hidden_dim = 6
num_heads = 2
x.shape = (3, 4, 6)
attention_mask = None
Output:
output.shape = (3, 4, 6)
The batch contains three sequences of four tokens, and the output retains the input's batch, sequence, and hidden dimensions.
Input:
hidden_dim = 4
num_heads = 2
x.shape = (1, 3, 4)
attention_mask.shape = (1, 1, 1, 3)
attention_mask = [1, 1, 0]
Output:
output.shape = (1, 3, 4)
The third token is unavailable as a key/value position for every query, while the result still has one sequence of length three and four hidden features.
Input:
hidden_dim = 8
num_heads = 4
x.shape = (2, 5, 8)
attention_mask.shape = (1, 1, 5, 5)
attention_mask[i, j] = 1 when j <= i, otherwise 0
Output:
output.shape = (2, 5, 8)
Each position can use itself and earlier positions only, and the mask broadcasts across both batches and heads.
hidden_dim must be divisible by num_heads.hidden_dim // num_heads.(batch, seq, hidden_dim) before projection.