TransformersLesson 3 of 10
One block: attention and a feed-forward network
Attention scores, weights, and weighted sums
Each word is now a row of numbers — a [3 × 4] grid for “the cat sat” (rows = tokens, columns = vector entries). A block transforms that grid using attention and a feed-forward network.
Attention combines information across positions
Section titled “Attention combines information across positions”So far each row started from a token and its position. Attention lets each word take a weighted mix of the others — learned projections determine the weights; a large weight is not a guarantee of relevance. In this decoder block, attention is the operation that mixes information across token positions.
How does it calculate the weights? Each row is projected into a query, key, and value. Dot a query with each allowed key, divide by the square root of the key width, and apply softmax to turn the scores into weights. Use those weights to combine the value rows. And it’s masked so a position can use itself and earlier positions but cannot use future tokens.
One weighted mixture, with numbers
Section titled “One weighted mixture, with numbers”Suppose two allowed positions have scores and . Softmax turns their exponentials, 1 and 3, into weights and . If their value vectors are and , the attention output is
Queries and keys determined how much weight each position received. Values supplied what was mixed. The mixture is a vector with the same number of entries as each value vector.
The matrix notation for the same operation
For n positions, Q and K each have shape . Their product has shape : one score for each query–key pair. Divide by , mask future positions, and apply softmax to each row. Multiplying those weights by V produces the weighted value vectors.
The causal mask M contains zero at allowed positions and at blocked positions, giving blocked positions zero weight. This is one head; multiple heads are combined with an output projection.
Optional contrast: an encoder can use later context
A useful contrast is bidirectional attention, as used in an encoder. In that setting a word can use later context too. Consider: “The chicken didn’t cross the road because it was too tired.” What is it?
Many heads at once
Section titled “Many heads at once”A block runs several attention heads in parallel, each with learned query, key, and value projections. They can represent different relationships; they are not assigned fixed linguistic jobs in advance.
see this in PyTorch
Each token builds a query, key, and value, scores how much it should attend to every earlier token, and mixes their values into its new representation.
# Single-head self-attention: every token decides how much to "look at" the others.import torchimport torch.nn as nnimport torch.nn.functional as F
seq, d_model = 4, 8 # 4 tokens, each a vector of size 8x = torch.randn(seq, d_model) # x: (seq, d_model) -- our input tokens
# Each token asks a Question, offers a Key (label), and carries a Value (content).to_q = nn.Linear(d_model, d_model) # learns to build queriesto_k = nn.Linear(d_model, d_model) # learns to build keysto_v = nn.Linear(d_model, d_model) # learns to build values
q = to_q(x) # q: (seq, d_model) -- what each token is looking fork = to_k(x) # k: (seq, d_model) -- what each token offers to be matched onv = to_v(x) # v: (seq, d_model) -- the info each token will share
d_k = q.size(-1) # size of each query/key vectorscores = q @ k.mT / d_k ** 0.5 # scores: (seq, seq) -- how much each token looks at each other; divide by the square root of key width
# Causal mask: a token may only look at itself and tokens before it, never the future.mask = torch.triu(torch.ones(seq, seq, dtype=torch.bool), diagonal=1) # True = futurescores = scores.masked_fill(mask, float('-inf')) # block the future with -inf
weights = F.softmax(scores, dim=-1) # weights: (seq, seq) -- each row sums to 1 (attention %)out = weights @ v # out: (seq, d_model) -- each token = weighted blend of valuesTransform each row
Section titled “Transform each row”After attention, the same feed-forward network is applied separately to each row. It transforms the contextual information already in that row; it does not read another row directly.
The shape never changes
Section titled “The shape never changes”A block takes a [3 × 4] grid and hands back a [3 × 4] grid — same shape out as in. So you can feed it into another block, and another.
Here is the route through the pre-normalized block used in this guide:
| Operation | What changes | Shape for three tokens |
|---|---|---|
| Normalize, then attention | Mix information across allowed token positions | 3 × 4 → 3 × 4 |
| Add the original input | Add matching entries | 3 × 4 |
| Normalize, then feed-forward | Transform features within each row; widen to 8 internally | 3 × 4 → 3 × 8 → 3 × 4 |
| Add the input to that sublayer | Add matching entries again | 3 × 4 |
The complete token-flow explorer lets you select a token and inspect every calculation. First, the next lesson explains what happens inside the feed-forward network.
see this in PyTorch
A transformer block stacks two simple moves, each added back onto the input: first the tokens look at each other (attention), then each token row is transformed separately (the feed-forward net).
# A transformer block: each token gets refined by (1) looking at other tokens,# then (2) applying the same feed-forward function to each row. We keep both as submodules and just wire them up.import torch.nn as nn
class TransformerBlock(nn.Module): def __init__(self, d_model, attention, ffn): super().__init__() self.norm1 = nn.LayerNorm(d_model) # steadies values before attention self.norm2 = nn.LayerNorm(d_model) # steadies values before the ffn self.attention = attention # tokens look at each other self.ffn = ffn # the same function for each row
def forward(self, x): # x: (batch, seq, d_model) # pre-norm: normalize FIRST, then add the result back onto x (residual). # residual addition retains a direct path for x; later updates can still cancel information. x = x + self.attention(self.norm1(x)) # step 1: mix info across tokens x = x + self.ffn(self.norm2(x)) # step 2: transform each row return x # x: (batch, seq, d_model), same shape in = outNext: inside the FFN.
Sources · 3
- Vaswani, Ashish, et al. “Attention Is All You Need.” NeurIPS, 2017. arXiv:1706.03762.
- Jurafsky, Daniel, and James H. Martin. Speech and Language Processing (3rd ed. draft, 2026), Chapter 8: “Transformers.”
- Levesque, Hector J., Ernest Davis, and Leora Morgenstern. “The Winograd Schema Challenge.” KR, 2012.