Transformer Block Assembly › A Worked Example: Token by Token
Language AI HandbookAll chapters Transformer Block Assembly › A Worked Example: Token by TokenOne section · Sections in this chapter6 / 16 ↓
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
To truly understand how data flows through a transformer block, let's trace a small concrete example with actual numbers. We'll use a tiny configuration so you can follow each computation:
- : each token is represented by just 4 numbers
- : we split attention into 2 parallel heads
- : the FFN's hidden layer has 8 dimensions
- : our sequence contains 3 tokens
# Tiny configuration for hand-traceable computation
d_model_tiny = 4
n_heads_tiny = 2
d_ff_tiny = 8
seq_len_tiny = 3
# Create a tiny transformer block with standard FFN for simplicity
class TinyTransformerBlock:
"""Minimal transformer block for demonstration."""
def __init__(self):
self.d_model = d_model_tiny
# RMSNorm weights (all ones for simplicity)
self.norm1_weight = np.ones(d_model_tiny)
self.norm2_weight = np.ones(d_model_tiny)
# Attention weights (small random values)
scale = 0.5
self.W_q = np.random.randn(d_model_tiny, d_model_tiny) * scale
self.W_k = np.random.randn(d_model_tiny, d_model_tiny) * scale
self.W_v = np.random.randn(d_model_tiny, d_model_tiny) * scale
self.W_o = np.random.randn(d_model_tiny, d_model_tiny) * scale
# FFN weights
self.W1 = np.random.randn(d_model_tiny, d_ff_tiny) * scale
self.W2 = np.random.randn(d_ff_tiny, d_model_tiny) * scale
def rmsnorm(self, x, weight):
"""Apply RMSNorm."""
rms = np.sqrt(np.mean(x**2, axis=-1, keepdims=True) + 1e-6)
return weight * (x / rms)
def attention(self, x):
"""Simplified single-head attention for demonstration."""
Q = x @ self.W_q
K = x @ self.W_k
V = x @ self.W_v
# Attention scores
scale = np.sqrt(self.d_model)
scores = (Q @ K.T) / scale
attn_weights = softmax(scores, axis=-1)
# Apply attention
context = attn_weights @ V
output = context @ self.W_o
return output, attn_weights
def ffn(self, x):
"""Standard FFN with GELU."""
hidden = gelu(x @ self.W1)
output = hidden @ self.W2
return output, hidden
def forward_with_intermediates(self, x):
"""Forward pass returning all intermediate values."""
intermediates = {"input": x.copy()}
# First sublayer: attention
x_norm1 = self.rmsnorm(x, self.norm1_weight)
intermediates["after_norm1"] = x_norm1.copy()
attn_out, attn_weights = self.attention(x_norm1)
intermediates["attention_output"] = attn_out.copy()
intermediates["attention_weights"] = attn_weights.copy()
h = x + attn_out # Residual
intermediates["after_residual1"] = h.copy()
# Second sublayer: FFN
h_norm = self.rmsnorm(h, self.norm2_weight)
intermediates["after_norm2"] = h_norm.copy()
ffn_out, ffn_hidden = self.ffn(h_norm)
intermediates["ffn_hidden"] = ffn_hidden.copy()
intermediates["ffn_output"] = ffn_out.copy()
y = h + ffn_out # Residual
intermediates["output"] = y.copy()
return y, intermediates
# Create tiny block and sample input
tiny_block = TinyTransformerBlock()
x_tiny = np.array(
[[1.0, 0.5, -0.3, 0.8], [-0.2, 0.7, 0.4, -0.1], [0.6, -0.4, 0.2, 0.5]]
)
# Forward pass with intermediates
output_tiny, intermediates = tiny_block.forward_with_intermediates(x_tiny)Transformer Block Walkthrough ============================================================ Input (3 tokens, 4 dimensions): [[ 1. 0.5 -0.3 0.8] [-0.2 0.7 0.4 -0.1] [ 0.6 -0.4 0.2 0.5]] --- ATTENTION SUBLAYER --- After RMSNorm (normalized input): [[ 1.421 0.711 -0.426 1.137] [-0.478 1.673 0.956 -0.239] [ 1.333 -0.889 0.444 1.111]] Attention weights (which tokens attend to which): [[0.057 0.828 0.114] [0.323 0.326 0.35 ] [0.101 0.77 0.129]] Attention output: [[-0.501 0.291 0.083 -0.598] [-0.255 0.105 -0.12 -0.139] [-0.467 0.271 0.056 -0.548]] After residual connection (input + attention output): [[ 0.499 0.791 -0.217 0.202] [-0.455 0.805 0.28 -0.239] [ 0.133 -0.129 0.256 -0.048]] --- FFN SUBLAYER --- After RMSNorm: [[ 1.017 1.613 -0.442 0.413] [-0.915 1.617 0.563 -0.48 ] [ 0.831 -0.808 1.601 -0.303]] FFN hidden activations (8 dimensions, showing first 4): [[ 0.217 -0.144 0.387 0.409] [ 1.806 1.054 0.741 -0.005] [-0.061 -0.11 0.814 0.56 ]] FFN output: [[ 1.075 -1.042 0.461 -0.019] [ 1.119 0.147 -0.608 -0.293] [ 0.307 0.067 -0.009 0.341]] --- FINAL OUTPUT --- Block output (intermediate + FFN output): [[ 1.574 -0.251 0.244 0.184] [ 0.664 0.953 -0.328 -0.532] [ 0.439 -0.062 0.246 0.292]]
Let's visualize how each token's representation evolves through the block stages:

Understanding the Attention Weights
The attention weights matrix shows how each token attends to others in the sequence. Let's visualize this pattern:

Each row in the attention matrix sums to 1.0 (due to softmax normalization). If we denote the attention weight from token to token as , then:
where is the sequence length. This constraint ensures that each token's output is a valid weighted average of the value vectors from all positions. No attention weight can be negative, and the weights form a probability distribution over the sequence.
Tracing the Residual Contribution
One key insight from residual connections is that the output preserves information from the input. Let's quantify this:
# Decompose the output into residual and sublayer contributions
input_contribution = x_tiny # Original input (passes through both residuals)
attn_contribution = intermediates["attention_output"]
ffn_contribution = intermediates["ffn_output"]
# The output is: input + attention_output + ffn_output
reconstructed = input_contribution + attn_contribution + ffn_contributionOutput Decomposition ================================================== Original input contribution: Magnitude: 1.8682 Attention sublayer contribution: Magnitude: 1.1856 FFN sublayer contribution: Magnitude: 2.0974 Reconstruction check (should match output): Match: True Relative contributions: Input: 36.3% Attention: 23.0% FFN: 40.7%

The decomposition reveals how each component contributes to the final representation. Mathematically, the output can be written as:
where:
- : the original input, which passes through both residual connections unchanged
- : the attention sublayer's contribution
- : the FFN sublayer's contribution
The original input remains a large part of the output. This shows how residual connections preserve information while allowing sublayers to add refinements. This additive structure also explains why gradients flow easily backward through transformer blocks: they can bypass the nonlinear sublayers entirely via the residual paths.