Self-Attention & Transformers

See queries, keys and values combine into an attention heatmap for a sentence

AdvancedSelf-AttentionQuery/Key/ValueSoftmaxMulti-Head

Query token (click to inspect)

Heads

d_model = 8, d_k = d_model / heads = 8. Embeddings are hashed from each word and the W_Q, W_K, W_V weights come from a seeded random generator, so the patterns are illustrative rather than learned. The arithmetic is exact.

Queries, keys and values

Q = X·WQ, K = X·WK, V = X·WV, each 6×8. X is the embedding plus positional encoding of each token.

Q

01234567
the1.32-0.76-1.770.10-0.29-0.23-0.480.92
cat-0.78-1.64-2.230.720.491.780.832.44
sat-0.82-0.22-1.221.88-0.160.900.081.53
on-0.07-0.51-2.591.33-0.580.91-0.030.76
the1.35-0.06-0.201.60-0.82-0.78-1.680.08
mat0.620.74-0.560.22-0.58-0.81-0.04-0.52

K

01234567
the-0.78-2.44-1.60-1.370.231.52-0.630.37
cat-0.44-1.69-1.23-2.06-0.261.27-0.19-1.31
sat-0.90-0.88-2.30-0.621.081.650.00-0.91
on-2.20-0.75-2.50-1.832.180.42-0.740.23
the-1.90-1.67-2.40-1.251.420.810.131.22
mat-0.11-0.07-1.091.010.860.87-0.270.17

V

01234567
the1.520.83-1.601.090.31-0.320.971.65
cat0.25-1.170.072.130.482.09-0.123.29
sat-0.850.480.09-0.261.301.96-1.570.25
on-1.612.51-0.510.860.021.57-0.190.65
the0.192.09-1.83-0.750.420.32-0.12-0.13
mat0.151.48-0.27-0.980.81-0.74-0.80-1.74

Scores QKᵀ / √dk

Dot product of each query row with each key row, divided by √8 = 2.83.

scores

thecatsatonthemat
the1.320.480.690.621.190.63
cat3.671.122.853.174.921.95
sat0.85-0.870.880.681.731.49
on1.820.582.061.372.251.64
the-1.09-1.46-1.40-2.20-2.090.27
mat-1.14-0.52-0.54-0.92-1.22-0.20

Attention weights = softmax(scores)

Each row sums to 1. Click a row to make it the query.

thecatsatonthematthe0.260.110.140.130.230.13cat0.170.010.080.100.600.03sat0.140.020.140.110.330.26on0.180.050.230.110.280.15the0.140.100.100.050.050.56mat0.110.200.190.130.100.27

What "cat" attends to

Line thickness is the attention weight. The output for this token is the weighted sum of the value rows.

querykeysthe0.17catcat0.01sat0.08on0.10the0.60mat0.03

z(cat) = 0.17·v(the) + 0.01·v(cat) + 0.08·v(sat) + 0.10·v(on) + 0.60·v(the) + 0.03·v(mat)

= [0.15, 1.73, -1.43, -0.19, 0.44, 0.46, -0.07, 0.28]

Transformer block

Attention mixes information between tokens; the feed-forward layer processes each token on its own.

token embeddings + positional encodingMulti-head attentionAdd & NormFeed-forward (per token)Add & Normoutput (same shape: n × d_model)residualresidualrepeated N times

Positional encoding

Attention ignores order, so each position adds a unique pattern of sines and cosines. Turn it off and repeated words get identical rows in Q, K and V.

-101012345678910
dim 0 (sin, fast)dim 1 (cos, fast)dim 2 (sin, slower)dim 3 (cos, slower)Shaded columns: positions used by your sentence. Dims 4–7 change even more slowly.

How self-attention works

In self-attention every token looks at every other token in the same sentence and decides how much of each to mix into its own representation. Each token's embedding is projected three ways: a query (what am I looking for?), a key (what do I contain?) and a value (what do I pass on?).

The match between a query and a key is their dot product. Dividing by √dk and applying softmax turns a row of scores into weights that sum to 1, and the token's output is the weighted sum of all value vectors. A Transformer stacks this with a small feed-forward network, residual connections and layer normalisation, and repeats the block many times.

The weights in this demo are random but seeded, so the patterns will not look like a trained model's (which might link "sat" strongly to "cat"). What is real is the computation: every number on screen follows the formulas below.

Key formulas

Scaled dot-product attention

Attention(Q, K, V) = softmax(QKᵀ / √dk + M) · V

Q, K, V
X·W_Q, X·W_K, X·W_V, one row per token
d_k
length of each query and key vector
M
mask: 0 where allowed, −∞ for future tokens when causal

Softmax over one row

aij = exp(sij) / Σm exp(sim)

Multi-head attention

MultiHead(X) = Concat(head1, …, headh) · WO, with dk = dmodel / h

Sinusoidal positional encoding

PE(pos, 2i) = sin(pos / 100002i/d), PE(pos, 2i+1) = cos(pos / 100002i/d)

How to use this visualizer

  1. 1
    Type a sentence of up to 8 words. Each word becomes a token with an 8-number embedding.
  2. 2
    Click a token (or a heatmap row) to make it the query. Its row is highlighted in Q, in the scores and in the heatmap.
  3. 3
    Follow the pipeline: Q, K and V, then the scaled scores, then the softmax weights, then the lines and the weighted sum that form the output.
  4. 4
    Turn on the causal mask and watch the upper triangle drop to zero: each token can only attend to itself and earlier tokens.
  5. 5
    Switch to 2 or 4 heads. Each head gets a smaller d_k and its own weights, so the small heatmaps show different patterns.
  6. 6
    Turn positional encoding off with a repeated word (like "the" twice) and notice the two rows become identical.

Key concepts

Query, key, value

Each token asks a question (query), advertises what it contains (key) and offers content to share (value). All three are linear projections of the same embedding.

Scaling by √d_k

Dot products grow with the vector length. Dividing by √d_k keeps the scores in a range where softmax does not saturate and gradients still flow.

Causal mask

Language models must not peek at future tokens, so scores above the diagonal are set to −∞ and get zero weight after softmax.

Multi-head attention

Several smaller heads run in parallel with their own weights. One might track the previous word, another the subject of the sentence.

Residual connections

Each sub-layer adds its output to its input (Add & Norm), which keeps deep stacks of blocks trainable.

Positional encoding

Attention treats the input as a set. Adding a position-dependent signal to each embedding lets the model use word order.

Code

NumPy
import numpy as np

def softmax(x, axis=-1):
    x = x - x.max(axis=axis, keepdims=True)        # numerical stability
    e = np.exp(x)
    return e / e.sum(axis=axis, keepdims=True)

def self_attention(X, W_q, W_k, W_v, causal=False):
    Q, K, V = X @ W_q, X @ W_k, X @ W_v            # (n, d_k) each
    scores = Q @ K.T / np.sqrt(K.shape[-1])         # (n, n)
    if causal:
        n = scores.shape[0]
        scores[np.triu_indices(n, k=1)] = -np.inf    # hide future tokens
    A = softmax(scores, axis=-1)                    # rows sum to 1
    return A @ V, A                                 # outputs and weights

def positional_encoding(n, d):
    pos = np.arange(n)[:, None]
    i = np.arange(0, d, 2)[None, :]
    angle = pos / 10000 ** (i / d)
    pe = np.zeros((n, d))
    pe[:, 0::2], pe[:, 1::2] = np.sin(angle), np.cos(angle)
    return pe

Practice problems

Problem 1: Attention by hand

With d_k = 2, a query q = [1, 0] attends to two keys k₁ = [1, 1] and k₂ = [0, 2] with values v₁ = [1, 0] and v₂ = [0, 1]. Compute the scaled scores, the attention weights and the output.
Show solution

Raw scores: q·k₁ = 1, q·k₂ = 0

Scaled by √2 ≈ 1.414: s = [0.707, 0]

exp: [2.028, 1.000], sum = 3.028

weights a = [0.670, 0.330]

output = 0.670·[1, 0] + 0.330·[0, 1]

z = [0.67, 0.33]

Problem 2: Causal mask and heads

In a causal decoder the second token (index 1) of a three-token sequence has scaled scores [2, 1, 3]. What are its attention weights? Separately, a model with d_model = 512 uses 8 heads: what is d_k and what are the scores divided by?
Show solution

The third token is in the future, so its score becomes −∞: [2, 1, −∞]

exp: [7.389, 2.718, 0], sum = 10.107

weights = [0.731, 0.269, 0]

d_k = 512 / 8 = 64, so scores are divided by √64 = 8

[0.731, 0.269, 0]; d_k = 64, scale 8

Discussion

Questions, ideas or a different approach? Join the conversation.