Scaled Dot-Product Attention - Transformer Architecture
Implement Scaled Dot-Product Attention in Python with NumPy. medium exercise with examples, hints, and instant feedback. No setup or signup required.
Scaled Dot-Product Attention
What you'll build
The core operation of every Transformer: each query takes a weighted average of the values, with weights given by how well it matches each key. Return both the output and the attention weights.
Background
Attention is a differentiable lookup table. A query q_i is compared with every key k_j by a dot product; a softmax turns those scores into a probability distribution over positions; the output is the matching mixture of value vectors. Dividing by sqrt(d_k) keeps the scores at unit scale: for random vectors with unit-variance entries, q · k has variance d_k, and without the scaling the softmax saturates and its gradients vanish.
Formula
S = Q @ K.T / sqrt(d_k) # (seq_len, seq_len) scores
A = softmax(S, axis=-1) # each row sums to 1
out = A @ V # (seq_len, d_v)
Q: queries,K: keys,V: values;d_k = Q.shape[-1]- Row
iofAholds queryi's weights over all keys
Inputs
Q: float array(seq_len, d_k)K: float array(seq_len, d_k)V: float array(seq_len, d_v)
Expected Return Format
A tuple (output, attention_weights):
output:(seq_len, d_v)attention_weights:(seq_len, seq_len), non-negative, rows sum to 1
Edge cases and conventions
- Use a numerically stable softmax: subtract each row's max before
np.exp. - No mask here (every query sees every key). Masking is the Causal Attention Mask problem.
Constraints
NumPy only, fully vectorised (no Python loops over positions).
Follow-up
- Why
sqrt(d_k)and notd_k? What happens to the gradients of a saturated softmax? - Time and memory are
O(seq_len^2 · d)andO(seq_len^2). Which later problems (Online Softmax, KV Cache) attack each?
Examples
Input: Q = K = np.array([[1.0, 0.0], [0.0, 1.0]]), V = np.array([[1.0, 2.0], [3.0, 4.0]]) Output: output = [[1.6605, 2.6605], [2.3395, 3.3395]], attention_weights = [[0.6698, 0.3302], [0.3302, 0.6698]]
Scores are [[0.7071, 0], [0, 0.7071]] after dividing by sqrt(2); each query leans toward the key it matches.
Input: Q = np.zeros((3, 4)), any K, V = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]]) Output: every output row = [3.0, 4.0]
All scores are 0, so the weights are uniform (1/3) and each output is the mean of V.
Practice in Python
import numpy as np
def scaled_dot_product_attention(Q, K, V):
"""
Compute scaled dot-product attention.
Args:
Q: Queries (seq_len, d_k)
K: Keys (seq_len, d_k)
V: Values (seq_len, d_v)
Returns:
(output, attention_weights):
output: (seq_len, d_v)
attention_weights: (seq_len, seq_len), each row sums to 1
"""
# Your code here
pass
The interactive editor loads with Python and NumPy. Run your implementation against 6 tests, or use the progressive hints.