Query, Key, Value › Putting It All Together
Language AI HandbookAll chapters Query, Key, Value › Putting It All TogetherOne section · Sections in this chapter6 / 11 ↓
Reading tools Definitions & inline links
Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.
Article links
Make inline references clickable
We've now built up all the components of QKV attention: projections that create specialized representations, dot products that measure compatibility, scaling that ensures stable gradients, and value aggregation that transmits information. Let's combine these into a complete self-attention layer:
class SelfAttention:
"""
Complete self-attention layer with QKV projections.
"""
def __init__(self, embed_dim, d_k, d_v, seed=None):
"""
Initialize projection matrices.
Args:
embed_dim: Dimension of input embeddings
d_k: Dimension of queries and keys
d_v: Dimension of values
seed: Random seed for reproducibility
"""
if seed is not None:
np.random.seed(seed)
# Xavier/Glorot initialization for stable gradients
scale_qk = np.sqrt(2.0 / (embed_dim + d_k))
scale_v = np.sqrt(2.0 / (embed_dim + d_v))
self.W_q = np.random.randn(embed_dim, d_k) * scale_qk
self.W_k = np.random.randn(embed_dim, d_k) * scale_qk
self.W_v = np.random.randn(embed_dim, d_v) * scale_v
self.d_k = d_k
def forward(self, X):
"""
Compute self-attention output.
Args:
X: Input embeddings of shape (seq_len, embed_dim)
Returns:
output: Attention output of shape (seq_len, d_v)
attention_weights: Weights of shape (seq_len, seq_len)
"""
# Project to Q, K, V
Q = X @ self.W_q
K = X @ self.W_k
V = X @ self.W_v
# Scaled dot-product attention
scores = Q @ K.T / np.sqrt(self.d_k)
# Softmax
scores_stable = scores - scores.max(axis=1, keepdims=True)
exp_scores = np.exp(scores_stable)
attention_weights = exp_scores / exp_scores.sum(axis=1, keepdims=True)
# Aggregate values
output = attention_weights @ V
return output, attention_weights# Test the complete layer
embed_dim = 8
d_k = d_v = 6
seq_len = 5
np.random.seed(42)
X = np.random.randn(seq_len, embed_dim)
# Create and apply self-attention layer
attention = SelfAttention(embed_dim, d_k, d_v, seed=123)
output, weights = attention.forward(X)Self-Attention Layer Test ======================================== Input shape: (5, 8) Output shape: (5, 6) Attention weights shape: (5, 5) Attention weight matrix (rows sum to 1): [[0.068 0.446 0.094 0.171 0.221] [0.012 0.476 0.085 0.148 0.28 ] [0.046 0.24 0.251 0.096 0.367] [0.177 0.324 0.158 0.208 0.132] [0.457 0.169 0.077 0.125 0.172]] Row sums: [1. 1. 1. 1. 1.]
The attention weights matrix reveals the core of what self-attention computes: a soft routing table that determines how information flows between positions. Each row sums to exactly 1.0 (confirming proper softmax normalization), and each entry indicates how much one position attends to another.
With random initialization, these patterns are meaningless, just noise from the random projection matrices. But after training on language data, we would see interpretable patterns emerge: determiners attending to nouns, verbs gathering information from subjects and objects, pronouns reaching back to their antecedents.