Scaled Dot-Product Attention › Dot Product Similarity

Michael BrenndoerferMay 25, 20256 min read
Language AI HandbookAll chapters
Part 13 of 67Self-Attention
Chapter 92Scaled Dot-Product Attention › Dot Product Similarity
Reading section 2 of 15 · Dot Product Similarity · 6 minSection 2 of 15 · 6 min

Book contents

Section 2 of 15 · 6 min read

Scaled Dot-Product Attention › Dot Product SimilarityOne section · Sections in this chapter2 / 15 ↓
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

The first step in scaled dot-product attention is measuring how relevant each key is to each query. We need a similarity function that is fast to compute, works well in high dimensions, and integrates naturally with gradient-based learning. The choice of similarity measure is not obvious, and it is worth understanding why the dot product was selected over alternatives.

One alternative is Euclidean distance: we could measure how close two vectors are in space and use closeness as a proxy for relevance. But Euclidean distance is expensive to compute for all n2n^2 query-key pairs, and minimizing distance does not naturally integrate with the maximize-gradient training objective. Another alternative is cosine similarity, which normalizes vectors to unit length before taking the dot product. Cosine similarity is interpretable, but the normalization step is expensive and, more importantly, the model cannot learn to increase similarity by scaling the magnitude of its projections, which turns out to be a useful degree of freedom.

The dot product satisfies all three requirements: it is fast to compute via matrix multiplication, its geometric interpretation aligns naturally with learned representations, and it allows the model to control similarity through both the angle and the magnitude of projection vectors. For two vectors q\mathbf{q} and k\mathbf{k} of dimension dkd_k, the dot product is:

q⋅k=∑l=1dkqlkl\mathbf{q} \cdot \mathbf{k} = \sum_{l=1}^{d_k} q_l k_l

where:

  • qlq_l: the ll-th component of the query vector q\mathbf{q}
  • klk_l: the ll-th component of the key vector k\mathbf{k}
  • dkd_k: the shared dimension of queries and keys

Geometrically, the dot product measures how much two vectors point in the same direction. If q\mathbf{q} and k\mathbf{k} are unit vectors, the dot product equals their cosine similarity. For general vectors, it equals ∥q∥∥k∥cos⁡θ\|\mathbf{q}\| \|\mathbf{k}\| \cos\theta, where θ\theta is the angle between them. Large positive values mean strong alignment; values near zero mean orthogonality; negative values mean the vectors point in opposite directions.

This geometric interpretation makes the dot product a natural fit for attention. During training, the model learns to align queries with relevant keys in the projected space. A position attending to relevant content will have its query vector pointing in a direction similar to the key vectors of those relevant positions. The projection matrices WQW^Q and WKW^K are jointly trained to produce this alignment: WQW^Q shapes queries so they point toward relevant key directions, and WKW^K shapes keys so they point toward the directions of queries that should attend to them. The learning process carves out a shared geometric space where relevance is encoded as directional alignment.

Why does this formula make sense? Notice that the dot product is bilinear: it is linear in q\mathbf{q} holding k\mathbf{k} fixed, and linear in k\mathbf{k} holding q\mathbf{q} fixed. This bilinearity is what makes the gradient flow back through both queries and keys cleanly during backpropagation. It is also what allows the score matrix to be computed as a single matrix multiplication, which modern hardware executes at peak efficiency.

The Score Matrix

In practice, we want to compute similarities between all nn queries and all nn keys simultaneously. Processing them one pair at a time would require n2n^2 separate dot product computations, which is slow and wasteful. Instead, we exploit the fact that all pairwise dot products can be expressed as a single matrix multiplication. This is a perfect job for linear algebra: the score matrix is:

S=QKTS = QK^T

where:

  • Q∈Rn×dkQ \in \mathbb{R}^{n \times d_k}: the query matrix, each row is one query vector
  • KT∈Rdk×nK^T \in \mathbb{R}^{d_k \times n}: the transposed key matrix, each column is one key vector
  • S∈Rn×nS \in \mathbb{R}^{n \times n}: the raw score matrix, where Sij=qi⋅kjS_{ij} = \mathbf{q}_i \cdot \mathbf{k}_j

Entry SijS_{ij} tells us how much position ii's query matches position jj's key. The entire matrix of n2n^2 scores is computed with a single matrix multiplication, which modern hardware executes very efficiently using BLAS routines that achieve near-peak floating-point throughput. This is one of the key reasons dot-product attention is so fast in practice: the score computation maps directly onto hardware-optimized matrix multiply.

The shape of SS also matters. For a sequence of nn tokens, SS has n2n^2 entries. Entry (i,j)(i, j) captures the directional alignment between token ii's query and token jj's key. The diagonal entries SiiS_{ii} capture each token's self-similarity, while off-diagonal entries capture cross-token dependencies. Before any normalization, these are called "logits" (by analogy with classification logits): raw unnormalized scores that will be converted into probabilities by softmax.

In[3]:
Code
import numpy as np

np.random.seed(42)

n = 5  # sequence length
d = 8  # input embedding dimension
d_k = 4  # query/key dimension
d_v = 4  # value dimension

# Simulated input embeddings
X = np.random.randn(n, d)

# Simulated projection matrices (normally trained)
W_Q = np.random.randn(d, d_k) * 0.3
W_K = np.random.randn(d, d_k) * 0.3
W_V = np.random.randn(d, d_v) * 0.3

# Project to Q, K, V
Q = X @ W_Q  # (n, d_k)
K = X @ W_K  # (n, d_k)
V = X @ W_V  # (n, d_v)

# Compute raw scores
S = Q @ K.T  # (n, n)
Out[4]:
Console
Shapes:
  Q: (5, 4), K: (5, 4), V: (5, 4)
  Score matrix S = QK^T: (5, 5)

Raw score matrix (entry [i,j] = similarity of query i with key j):
[[-0.047  1.305  1.059 -1.327  2.064]
 [ 0.343 -0.085 -0.362  0.668 -0.201]
 [ 0.245 -0.748 -0.738  0.392 -0.534]
 [ 0.13   0.704  0.36  -0.434  1.3  ]
 [-0.321  0.48   0.57   0.277 -0.431]]

Each row of the score matrix corresponds to one token's query matched against all keys. Notice that the scores span a wide range and include negative values. These raw scores aren't attention weights yet; we need two more transformations before they are usable. In particular, the values here are on a scale where some entries are several times larger than others, which would cause softmax to produce extremely uneven weight distributions. Before we apply softmax, we need to address the scale problem.