Transformer Architecture
The 2017 paper that introduced the transformer had a blunt thesis: attention was the useful part of the encoder-decoder architecture, so delete everything else. No recurrence, no convolution ā just attention and simple feedforward layers, stacked. Removing the sequential dependency of recurrence is what turned scale from a research curiosity into an engineering problem that money and hardware could actually solve.
Removing recurrence makes every position computable in parallel, which is what turned scale from a research problem into an engineering one.

The motivation: recurrence blocks parallelismā
Recurrent Neural Networks's hidden state requires to already exist ā an inherently sequential dependency chain that no amount of extra hardware can shortcut. Self-attention (from Attention Mechanism) computes every position's output from the whole sequence simultaneously, with no such dependency ā every position's attention computation can run in parallel on modern hardware.
The encoder blockā
Each encoder layer: multi-head self-attention (see Self-Attention in Depth), a residual connection (Skip Connections and Depth) plus layer normalisation (Normalization Layers), then a position-wise feed-forward network, another residual connection plus normalisation.
The decoder blockā
Each decoder layer adds a third component beyond the encoder's two: masked self-attention (attending only to earlier positions in the output, enforcing the autoregressive constraint), cross-attention (queries from the decoder, keys/values from the encoder's output ā the direct architectural descendant of Attention Mechanism's original mechanism), and a feed-forward network ā each wrapped in its own residual-plus-normalisation.
The causal mask, and why it's requiredā
At training time, the decoder sees the entire target sequence at once (for parallelism) ā but at position , it must only be allowed to attend to positions , or it could trivially "cheat" by looking at the very token it's supposed to be predicting. The causal mask sets attention scores for all future positions to before the softmax, so their attention weight becomes exactly zero.
The position-wise feed-forward networkā
Applied identically and independently to each position ā no interaction between positions happens in this sub-layer (that's entirely attention's job). Typically expands to a much larger intermediate dimension (e.g. the model dimension) before projecting back down, and holds the majority of a transformer's total parameters.
Pre-norm vs. post-normā
As covered in Normalization Layers, modern transformers overwhelmingly use pre-norm (normalise before each sub-layer, with an unmodified residual stream carrying through) rather than the original paper's post-norm ā pre-norm trains substantially more reliably at depth, without requiring the careful warmup schedule post-norm needs to avoid early divergence.
The full data path for one tokenā
Token id ā embedding lookup ā add positional encoding (Positional Encodings) ā through encoder (or decoder) layers, each applying self-attention then feed-forward, both wrapped in residual+norm ā final layer normalisation ā (for a language model) a linear projection to vocabulary size, followed by softmax to produce next-token probabilities.
Parameter count accountingā
For a model dimension , feed-forward dimension , and attention heads: attention's projections contribute roughly parameters per layer; the feed-forward network contributes roughly parameters per layer ā the feed-forward block alone typically accounts for close to two-thirds of a transformer layer's parameters.
| Symbol | Meaning |
|---|---|
| (or ) | the model's hidden/embedding dimension |
| number of encoder or decoder layers | |
| number of attention heads | |
| sequence length |
The O(n²) cost in sequence lengthā
Self-attention computes a score between every pair of positions ā scores for a sequence of length , and correspondingly compute and memory for the attention matrix itself. This quadratic scaling is the direct reason context-window length is expensive to extend, and motivates the efficient-attention variants covered in Self-Attention in Depth.
What the transformer removed that we sometimes missā
Recurrence gave RNNs a built-in inductive bias toward sequential/local structure, learned "for free" without needing data to teach it ā transformers have no such built-in bias and must learn positional/sequential structure entirely from data and the positional encoding scheme, which is part of why transformers typically need more training data to reach comparable performance on small-data tasks. Recurrence also naturally supports true streaming (processing one token at a time with state) in a way a full-context-attending transformer does not, without extra machinery like the KV caching covered in Self-Attention in Depth.
Code: an encoder block from scratch, verified against nn.TransformerEncoderLayerā
import torch
import torch.nn as nn
class TransformerEncoderBlockFromScratch(nn.Module):
def __init__(self, d_model, n_heads, d_ff):
super().__init__()
self.attn = nn.MultiheadAttention(d_model, n_heads, batch_first=True)
self.norm1 = nn.LayerNorm(d_model)
self.ffn = nn.Sequential(nn.Linear(d_model, d_ff), nn.GELU(), nn.Linear(d_ff, d_model))
self.norm2 = nn.LayerNorm(d_model)
def forward(self, x):
# pre-norm: normalise before the sub-layer, residual stream stays unmodified
attn_out, _ = self.attn(self.norm1(x), self.norm1(x), self.norm1(x))
x = x + attn_out
x = x + self.ffn(self.norm2(x))
return x
torch.manual_seed(0)
d_model, n_heads, d_ff, seq_len, batch = 64, 8, 256, 10, 4
x = torch.randn(batch, seq_len, d_model)
block = TransformerEncoderBlockFromScratch(d_model, n_heads, d_ff)
output = block(x)
print("from-scratch block output shape:", output.shape)
# --- Compare parameter count against the built-in layer ---
builtin = nn.TransformerEncoderLayer(d_model, n_heads, d_ff, batch_first=True, norm_first=True)
n_params_scratch = sum(p.numel() for p in block.parameters())
n_params_builtin = sum(p.numel() for p in builtin.parameters())
print(f"from-scratch params: {n_params_scratch}, built-in params: {n_params_builtin}")
# --- Parameter breakdown per sub-layer ---
attn_params = sum(p.numel() for p in block.attn.parameters())
ffn_params = sum(p.numel() for p in block.ffn.parameters())
print(f"attention params: {attn_params}, feed-forward params: {ffn_params}"
f" (ratio: {ffn_params/attn_params:.2f}x)")
See alsoā
- Attention Mechanism ā the operation this entire architecture is built from.
- Self-Attention in Depth ā multi-head attention, masking, and the O(n²) cost examined closely.
- Positional Encodings ā how position is injected, since attention itself carries no notion of order.