DEPTH 00 / 09
LATENT STACK TRANSFORMER · INTERNALS · LAB

Decoder-Only · Dense · 2025

INSIDE THE
TRANSFORMER

A layer-by-layer dissection of the architecture running underneath modern language models — embeddings, attention, residual streams, and the forward pass that turns tokens into meaning.

0Parameters
0Decoder Layers
0Attention Heads
0Context Tokens
SCROLL TO DESCEND
Query Key Value Softmax Multi-Head Attention Residual Stream Layer Norm Positional Encoding Feed Forward GELU RoPE KV Cache Causal Mask Top-K Sampling Logits

A note before you descend

The transformer architecture is the modern basis for large language models, and the concepts around it are dense enough that several people have already studied them thoroughly. It's one of the powerful architectures born out of a genuinely phenomenal paper, "Attention Is All You Need" — though the model proposed there was an encoder-decoder built for translation. The LLMs you actually talk to today run a slightly different variant: auto-regressive, decoder-only models.

This breakdown was originally meant to sit inside the Interpretability chapter of my open-draft book, Safe AI — but the material is dense enough on its own that I didn't want it to become a bottleneck for anyone trying to finish that chapter. So it lives here instead, built purely for the aesthetic of it, an excuse to try some new visuals. Probably the best way to remember something is to study it visually — in that spirit, here's this.

Before you take the deep dive: if you find my writing out of scope, or you just don't like the way I present content, here's where most of this understanding actually comes from. All of the below are absolutely fabulous, and I cannot recommend them enough:

GENERATING TOKENS

Before the vectors: tokenization

A transformer block can only take matrices as input, so the first real question is what representation of raw text actually conveys its essence. We start by dividing text into chunks called tokens. The obvious first guess is to split based on words — older NLP pipelines did exactly that — but that captures less nuance than we'd like, and it isn't free: it's a trade-off between vocabulary size and sequence length, and both heavily affect a model's cost and capability. Wait a minute …. Before we jump ahead of ourselves let us establish some definitions first so that we are all on the same page. Definition alert !!! Jump to Definitions

Split word-by-word and the vocabulary explodes the moment you account for misspellings, inflected forms, and out-of-vocabulary words. Drop all the way to character- or byte-level and the vocabulary shrinks to a tidy 256 bytes — but the sequence length balloons to roughly four times the word-level length, and any single character carries too little information on its own, pushing that burden onto later layers.

GPT-3's 50,257-token vocabulary is chosen to sit precisely at that sweet spot: the model can process roughly 4× more text in the same context window than a character-level model would allow, while still representing any possible Unicode string without ever needing an <UNK> token — and it also helps with reconstructing word boundaries. I have built a simple tool you can play around with which tells you how different sentences are split and which token represents a particular number in the vocab: Tokenization Tool

Byte‑pair encoding – mathematical formulation

GPT-3 uses a byte-level byte-pair encoding (BPE) it operates on raw bytes (UTF-8), which naturally handles any Unicode text, and it pre‑splits text with a regex before merging. The tokenization process has two stages. The tokenizer is defined by a fixed regular expression pre‑splitter, an ordered list of merge rules \(\mathcal{M}\), and a vocabulary \(\mathcal{V}\).

Training

  1. Pre‑tokenization. The corpus is split into a multiset of chunks \(C = \{c_1, c_2, \dots\}\) using the regex pattern.
    Each chunk \(c\) is converted to a byte sequence \(\mathbf{b} = (b_1,\dots,b_\ell),\ b_i \in \{0,\dots,255\}\).
  2. Initial vocabulary. \(\mathcal{V}_0 = \{0,1,\dots,255\}\). Every token is a single byte.
  3. For \(k = 1\) to \(N\) (with \(N = 50\,000\)):
    • Count frequencies of all adjacent token pairs \((p,q)\) across all current token sequences.
    • Choose the most frequent pair: \[ (p_k, q_k) = \arg\max_{(p,q)} \text{freq}(p,q). \]
    • Create a new token \(r_k = 255 + k\) and update the vocabulary: \[ \mathcal{V}_k = \mathcal{V}_{k-1} \cup \{r_k\}. \]
    • Apply the merge: in every token sequence, replace every occurrence of the contiguous pair \((p_k,q_k)\) by \(r_k\).
  4. Final vocabulary. \(\mathcal{V} = \mathcal{V}_0 \cup \{r_1,\dots,r_N\} \cup \{\texttt{<|endoftext|>}\}\), with \(|\mathcal{V}| = 50\,257\).
  5. Merge rule list. The ordered list \[ \mathcal{M} = \big((p_1,q_1)\to r_1,\; (p_2,q_2)\to r_2,\; \dots,\; (p_N,q_N)\to r_N\big) \] is the sole parameter that defines encoding and decoding.

Encoding a new text string \(s\)

  1. Regex split. \(s \mapsto (c_1, c_2, \dots, c_m)\).
  2. Byte encode each chunk. \(c_j \mapsto \mathbf{b}^{(j)} = (b_1^{(j)},\dots,b_{\ell_j}^{(j)}),\ b_i^{(j)} \in \{0,\dots,255\}\).
  3. Apply merge list. For each chunk’s byte sequence \(\mathbf{t} \gets \mathbf{b}^{(j)}\), iterate through \(\mathcal{M}\) in order: \[ \text{for } (p,q)\to r \text{ in } \mathcal{M},\ \text{replace every leftmost occurrence of } (p,q) \text{ in } \mathbf{t} \text{ by } r. \] This yields a sequence of tokens from \(\mathcal{V}\).
  4. Concatenate chunk token sequences. The final token list is the input to the transformer.

Formally, the encoder is a deterministic function \(\operatorname{encode}: \Sigma^* \to \mathcal{V}^*\) where \(\Sigma\) is the set of Unicode characters.

Decoding

Given a sequence \((t_1,\dots,t_k),\ t_i \in \mathcal{V}\):

  1. Recursively expand each token \(t_i\) by reversing the merge rules (if \(t_i = r_k\), replace it by \((p_k,q_k)\)) until only bytes remain.
  2. Concatenate all resulting bytes and decode the UTF‑8 byte stream: \(\operatorname{decode}: \mathcal{V}^* \to \Sigma^*\).

Other tokenization schemes in use

SchemeHow it worksUsed by
Byte-pair encoding (BPE)Starts from characters/bytes, iteratively merges the most frequent adjacent pair. Byte-level variants guarantee no out-of-vocabulary tokens.GPT, LLaMA, Mistral, Falcon
WordPieceSimilar to BPE, but merges are chosen by a language-model likelihood objective rather than pure frequencyBERT
Unigram LM (SentencePiece)Treats tokenization as probabilistic segmentation, choosing a vocabulary that maximizes corpus likelihood under a unigram modelT5 and others
SentencePieceA library implementing BPE or Unigram directly over a raw character stream — no word pre-tokenization, handy for languages without spacesMultiple model families

Choosing a vocabulary and a tokenization scheme is still a tricky, mostly iterative and intuition-driven craft. It's also worth noting that tokenization has been used as a method for jailbreaking LLMs via adversarial inputs, and that certain anomalous "glitch tokens" cause undocumented failures in GPT-style models — some break determinism even at temperature zero. Further reading here. As we'll see later, the architecture is built around maximizing the probability of the next token rather than chasing a single ground truth — so the model is always giving its best approximation for the next possible word

The below tool illustrates how the merges will be carried out. we took a simple sequences run runner and the appropriate merging is illustrated below click on next step until embeddings appear


BPE Encoding → Embedding Lookup

Click Next Step to begin

New string: "run runner"  (split into "run" and " runner")

Merge list 𝒜: (114,117)→256, (256,110)→257, (257,32)→258, (258,114)→259, (259,117)→260, (260,110)→261

After encoding: each token ID looks up a 4‑dim embedding vector from the matrix.

Encoding Scheme

Why geometry, not just lookup

Once tokens exist, each is mapped to a vector via a learned embedding matrix We ∈ ℝV×d — one column per vocabulary entry which are intialized randomly in the begining and fine tuned entirely through training process. GPT-3 embeds into 12,288 dimensions and each of the directions in that space has real semantic content rather than arbitrary coordinates.

if we map these embeddings to a higher dimensional space and subtract the embedding for Paris from France, then add that difference to Tokyo; the resulting vector lands remarkably close to Japan. This suggests the model has implicitly learned a direction that encodes the relationship “is the capital city of,” allowing it to recover a country from its capital as neatly as it recovers a queen from a king.

Vector Arithmetic: Paris – France + Tokyo ≈ Japan

Click Next Step to explore the analogy

Embedding space (2D projection) — France, Paris, Tokyo, Japan are word vectors.
The direction France − Paris captures "capital‑country" relationship. Adding it to Tokyo yields a point remarkably close to Japan.

Dot products give a way to measure this quantitatively: geometrically, a dot product is positive when two vectors point in similar directions, near zero when they're roughly perpendicular, and negative when they point apart. Taking the difference between the embeddings of cats and cat gives something like a "plurality direction" — dotting it against plural nouns scores consistently higher than against their singular counterparts, and dotting it against the embeddings for the numerals 1, 2, 3… gives steadily increasing values, as if plurality itself were a quantity the model can measure.

EMBED_DIM = 4096 PROJECTED TO 2D
Royalty / People
Animals
Verbs / Actions
Numbers / Quantities
Punctuation / Control

Embeddings in different Modalities

Tokenizing and embedding isn't unique to language — a sequence of tokens is a generic enough representation that many different data types can be tokenized and fed straight into a transformer, rather than requiring a bespoke architecture per modality (CNNs for images, RNNs for sequences, DeepSets for sets, and so on). That also means you don't need handcrafted architectures for mixing modalities — everything can just become one big set of tokens. Below table contains some of the most popular endoing scheme for some popular models for different use cases

Embedding typeModalityDescriptionUsed by
Learned token embedding (subword)TextLook-up table over BPE/WordPiece tokensBERT, GPT-2/3/4, T5, LLaMA, most standard language transformers
Absolute sinusoidal positional encodingTextFixed sine/cosine functions of positionOriginal Transformer, Transformer-XL (on values), DETR (for images)
Absolute learned positional embeddingTextTrainable vector per positionBERT, GPT, GPT-2
Rotary Position Embedding (RoPE)TextMultiplicative rotation of query/key by a position-dependent angleLLaMA, GPT-NeoX, PaLM, Mistral, Falcon
ALiBiTextAdds a static, non-learned linear bias to attention scores based on distanceSome GPT-style models (e.g. BLOOM's ALiBi option)
Relative position biasTextLearned scalar bias per relative distance, added before softmaxT5, Transformer-XL
Segment / token-type embeddingsTextLearned vector distinguishing sentence A vs. BBERT, XLNet
Patch flatten + linear projectionImageSplit image into patches, flatten, project via a linear layerViT, DeiT, BEiT, MAE, SimMIM
2D sinusoidal positional encodingImageSinusoidal encoding of row and column indicesDETR, some ViT implementations
Learned 2D positional embeddingImageTrainable embedding per (row, col) patch positionViT (original), MAE
Relative 2D position biasImageLearned bias based on relative 2D coordinatesSwin Transformer
CNN feature map tokensImageFeature vectors from a CNN backbone treated as input tokens, projected to dDETR, early ViT hybrids
Pixel-as-token embeddingImageRaw pixel intensities (0–255) mapped to learned embeddings + 2D positioniGPT
Spectrogram patch embeddingAudioPatches of a mel spectrogram flattened and linearly projectedAudio Spectrogram Transformer (AST)
CNN encoder + quantized codebookAudioRaw waveform → conv layers → discrete units from a codebookWav2Vec 2.0, HuBERT, WavLM
3D tubelet patch embeddingVideoSpatio-temporal patches ("tubes") flattened and projected linearlyViViT
Factorised spatial-temporal embeddingVideoSeparate spatial patch embeddings per frame + temporal position per patchTimeSformer
Mini-PointNet patch embedding3D Point CloudGroup points into patches, embed with a shared MLP, add patch-center positional encodingPoint-BERT, Point-MAE
Laplacian / random-walk PE + node projectionGraphNode features projected; structural positional encoding addedGraphormer, SAN, GRPE-based transformers
Sub-series patching + linear projectionTime SeriesUnivariate/multivariate series split into patches, then projectedPatchTST
Feature tokenizer + column embeddingTabularEach numerical feature scaled by a learned vector; categorical features embedded; column id addedFT-Transformer, TabTransformer
Byte-level embeddingTextDirectly embeds raw UTF-8 bytes via a small embedding tableByT5, CANINE
Object query embeddings (learned)Image (Detection)A set of learned vectors interacts with encoder output via cross-attentionDETR (decoder queries)
Cross-attention via latent arrayMultimodalA fixed-size array of learned latents queries raw input (pixels, audio, etc.) to produce tokensPerceiver, Perceiver IO

We'll stay exclusively on text-based input and generative pretrained transformers from here on — other modalities get their own detour elsewhere (link pending).


Converting tokens to Embeddings


Once the tokens are generated using the above bpe algorithm they are converted to embeddings using the below procedure .The transformer does not have a sense of position of the words for example "Only you can do this job poorly." is vastly different from " You can only do this job poorly." if you shuffle the words around they represent completely different things. hence it is incredbly important to capture not just the words but also there positions for this we use 2 different methods to generate embeddings one to capture the position.


Embeddings


The token and positional embeddings are summed element‑wise and then regularised with dropout to form the initial hidden state: \[ \mathbf{h}_0 = \text{Dropout}\bigl( \mathbf{T} + \mathbf{P} \bigr) \qquad \in \mathbb{R}^{B \times T \times d_{\text{model}}} \] This combined tensor is the first input fed to the transformer decoder blocks, where context‑dependent meaning finally emerges.


hidden_state = self.embed_dropout(tok_vec + pos_vec)  # h_0, shape (B, T, d_model)

Generating Regular embeddings

Algorithm 1 · Token (regular) embedding

Learned parameter: \(\mathbf{W}_e \in \mathbb{R}^{V \times d_{\text{model}}}\) – one row per vocabulary entry.

  1. Input: Token indices \(\mathbf{x} \in \{1,\dots,V\}^{B \times T}\) (\(B\) batch, \(T\) sequence length).
  2. For each index \(x_{b,t}\), perform a table look‑up:
    \(\mathbf{t}_{b,t} \leftarrow \text{row } x_{b,t} \text{ of } \mathbf{W}_e\).
  3. Scale all token vectors by \(\sqrt{d_{\text{model}}}\) (GPT‑2 and later):
    \(\mathbf{t}_{b,t} \leftarrow \mathbf{t}_{b,t} \cdot \sqrt{d_{\text{model}}}\).
  4. Output: Tensor \(\mathbf{T} \in \mathbb{R}^{B \times T \times d_{\text{model}}}\).
token_ids = x                                    # (batch, seq_len)
tok_vec = self.token_embedding(token_ids)        # lookup & shape (B, T, d_model)
tok_vec = tok_vec * math.sqrt(self.d_model)  # scaling

Generating Positional embeddings

Algorithm 2 · Learned absolute positional embedding

Learned parameter: \(\mathbf{W}_p \in \mathbb{R}^{T_{\max} \times d_{\text{model}}}\) – one row per absolute position up to \(T_{\max}\).

  1. Input: Sequence length \(T\) (implicit from token_ids).
  2. Generate a vector of position indices \(\mathbf{pos} = [0, 1, \dots, T-1]\).
  3. For each position \(pos_t\), look up the corresponding row:
    \(\mathbf{p}_t \leftarrow \text{row } pos_t \text{ of } \mathbf{W}_p\).
  4. Output: Tensor \(\mathbf{P} \in \mathbb{R}^{T \times d_{\text{model}}}\) (broadcastable over batch).
positions = torch.arange(seq_len, device=x.device)   # (T,)
pos_vec = self.position_embedding(positions)     # (T, d_model), broadcasts over batch
E(xt) = We[xt]  ·  We ∈ ℝV×d
h0 = E(xt) + P(t)

Attention has no inherent sense of order, so sinusoidal signals at geometrically increasing wavelengths are added to every embedding — letting the model recover relative and absolute position from phase alone.

SIN / COS · 2i / d_model POS 0 – 64
PE(pos, 2i) = sin( pos / 100002i/d )
PE(pos, 2i+1) = cos( pos / 100002i/d )

Even dimensions carry sine, odd dimensions carry cosine — wavelengths grow geometrically across the embedding dimension d.

Alternatives to sinusoidal position

SchemeMechanismUsed by
Absolute sinusoidalFixed sine/cosine functions of position, added to the token embeddingOriginal Transformer, Transformer-XL, DETR
Absolute learnedA trainable vector per position, same shape as the sinusoidal versionBERT, GPT, GPT-2
RoPE (rotary)Rotates Q/K by a position-dependent angle instead of adding anything to the embeddingLLaMA, GPT-NeoX, PaLM, Mistral, Falcon — and FORGE-1's own θ=500K scheme
ALiBiA static, non-learned linear bias subtracted from attention scores by distanceBLOOM (optional)
Relative position biasA learned scalar bias per relative distance, added pre-softmaxT5, Transformer-XL

# Embedding lookup — the very first step of the forward pass
# token_embedding:    (vocab_size, d_model)   — one row per vocabulary entry
# position_embedding: (max_seq_len, d_model)  — one row per sequence position
token_ids = x                                        # (batch, seq_len) integer ids
positions = torch.arange(seq_len, device=x.device)   # (seq_len,)
tok_vec = self.token_embedding(token_ids)            # (batch, seq_len, d_model)
pos_vec = self.position_embedding(positions)         # (seq_len, d_model), broadcasts over batch
hidden_state = self.embed_dropout(tok_vec + pos_vec)  # this is h_0

Vector Normalization

The generated embeddings are normalized before passing onto the attention block. The normalization helps get all the embeddings onto a similar scale. the below illustration can help you understand. click on a word and see how the embeeding of a word gets normalized for simplicity we made most of the vector lengths shorter. But the process remains the same mean is calculated and subtracted from the vector and divided by the square root of e+ standard deviation squared. The e term here helps with division by zero error.

Layer Normalization
y = γ + β   with   = (x−μ) / √(σ²+ε)

SELF-ATTENTION

After the layer normalization step is done the embeddings get passed onto the attention block. This is probabaly the phase that defines the classic transformer architecture and distinguishes from rest of the neural nets.

Attention mechanism

GPT‑2 uses multi‑head causal self‑attention in every decoder block. For a given layer, the input is the hidden state h ∈ ℝB×T×d, where B is batch size, T is sequence length, and d is the model dimension. The output is a context‑aware tensor of the same shape, computed as follows.

Multi‑head scaled dot‑product attention with causal masking

  1. Project to queries, keys, and values. A single weight matrix Wc_attn ∈ ℝd×3d and bias bc_attn ∈ ℝ3d produce a combined projection: \[ \text{qkv} = h\,W_{\text{c\_attn}} + b_{\text{c\_attn}} \quad\in \mathbb{R}^{B\times T\times 3d}. \] Split the result into three equally‑sized chunks along the last dimension: \[ Q = \text{qkv}[:,:,\,0:d],\quad K = \text{qkv}[:,:,\,d:2d],\quad V = \text{qkv}[:,:,\,2d:3d]. \]
  2. Divide into heads. Let nheads be the number of attention heads (e.g. 12 for GPT‑2 small) and dk = d / nheads. Reshape and transpose so that each head processes a dk‑dimensional subspace independently: \[ Q \to (B, n_{\text{heads}}, T, d_k),\quad K \to (B, n_{\text{heads}}, T, d_k),\quad V \to (B, n_{\text{heads}}, T, d_k). \]
  3. Compute raw attention scores. For each head, the unnormalised attention logits are the scaled dot products between queries and keys: \[ \text{scores}_{h,i,j} = \frac{1}{\sqrt{d_k}} \sum_{m=1}^{d_k} Q_{h,i,m}\, K_{h,j,m}, \] where h indexes the head, i the query position, and j the key position. In matrix form: \[ S_h = \frac{Q_h K_h^\top}{\sqrt{d_k}} \quad\in \mathbb{R}^{T\times T}. \]
  4. Apply the causal mask. GPT‑2 is an autoregressive model; the i‑th query must only attend to positions j ≤ i. A lower‑triangular mask M is added to the scores: \[ M_{i,j} = \begin{cases} 0, & \text{if } i \geq j,\\ -\infty, & \text{if } i < j. \end{cases} \] The masked scores are: \[ \tilde{S}_h = S_h + M. \]
  5. Softmax normalisation. Convert masked scores to attention probabilities: \[ A_h = \operatorname{softmax}(\tilde{S}_h),\qquad (A_h)_{i,j} = \frac{\exp\big((\tilde{S}_h)_{i,j}\big)}{\sum_{k=0}^{T-1}\exp\big((\tilde{S}_h)_{i,k}\big)}. \] Because of the mask, all probabilities for future positions (j > i) are zero.
  6. Weighted sum of values. For each head, the output is a convex combination of the value vectors: \[ O_h = A_h\, V_h \quad\in \mathbb{R}^{T\times d_k}. \] This is repeated for every head, yielding nheads output tensors.
  7. Concatenate heads and project. The per‑head outputs are concatenated along the dk dimension, restoring the original model size d: \[ O_{\text{concat}} = [O_1; O_2; \dots; O_{n_{\text{heads}}}] \in \mathbb{R}^{B\times T\times d}. \] Finally, a linear projection (with weights Wc_proj ∈ ℝd×d and bias bc_proj ∈ ℝd) produces the attention sub‑layer output: \[ \text{Attn}(h) = O_{\text{concat}}\, W_{\text{c\_proj}} + b_{\text{c\_proj}}. \]
Self-Attention Mechanism Sequence "The cat sat on the mat"
Dwg No. GPT2‑ATT‑01
Dims d₀=4 · n=6
verified ✓
Attention(Q, K, V) = softmax (QKT∕√dk) V
↳ tap a token to make it the query
Attention(Q,K,V) = softmax(QKT/√dk)V · single head, dmodel=dk=4, Q/K/V shown here use fixed diagonal projections for a legible, hand-checkable example.

Integration into the decoder block

HEAD 1 / 8 CAUSAL MASK ON
Attention(Q, K, V) = softmax( QKT / √dh + M ) V
Mij = 0  if j ≤ i,  else  −∞

M is the causal mask — it removes any attention to future positions before the softmax is taken.

The attention equation

Every decoder block projects its normalized hidden state into queries, keys, and values with one fused weight matrix, then splits the result across heads:

# qkv_proj: (d_model, 3 * d_model) — one combined matrix for Q, K, and V
q, k, v = qkv_proj(normed_hidden).split(d_model, dim=-1)
# reshape so each of the H heads attends independently
q = q.view(batch, seq_len, num_heads, head_dim).transpose(1, 2)
k = k.view(batch, seq_len, num_heads, head_dim).transpose(1, 2)
v = v.view(batch, seq_len, num_heads, head_dim).transpose(1, 2)

Scaled dot-product attention with the causal mask:

attention_weights = softmax((q @ k.transpose(-2, -1)) / sqrt(head_dim) + causal_mask)
context = attention_weights @ v

The 1/√d_h scaling keeps dot products from growing too large as head dimension increases, which would otherwise push softmax toward a near one-hot, hard-to-train distribution. The causal mask sets every position j > i to −∞ before the softmax, so token i can only ever attend to itself and whatever came before it — the mechanism that makes autoregressive generation well-defined at all.

Attention module, renamed and commented

Setup happens once, in __init__ — the fused projection, the output projection, dropout, and (only as a fallback) an explicit causal mask buffer:

class MultiHeadSelfAttention(nn.Module):
    """Causal multi-head self-attention with a single fused QKV projection."""
    def __init__(self, cfg):
        super().__init__()
        assert cfg.d_model % cfg.num_heads == 0
        # one matrix produces queries, keys, and values together — cheaper than three separate ones
        self.qkv_proj = nn.Linear(cfg.d_model, 3 * cfg.d_model, bias=cfg.use_bias)
        self.attn_out_proj = nn.Linear(cfg.d_model, cfg.d_model, bias=cfg.use_bias)
        self.attn_drop = nn.Dropout(cfg.drop_prob)
        self.resid_drop = nn.Dropout(cfg.drop_prob)
        self.num_heads = cfg.num_heads
        self.d_model = cfg.d_model
        self.drop_prob = cfg.drop_prob
        # prefer a fused, hardware-optimized attention kernel when available
        self.use_flash = hasattr(F, "scaled_dot_product_attention")
        if not self.use_flash:
            # fallback: an explicit lower-triangular mask, built once and reused
            causal = torch.tril(torch.ones(cfg.max_seq_len, cfg.max_seq_len))
            self.register_buffer("causal_mask", causal.view(1, 1, cfg.max_seq_len, cfg.max_seq_len))

And forward does the actual reshaping, dispatch to the fused kernel when it's available, and the manual fallback path when it isn't:

    def forward(self, x):
        batch, seq_len, d_model = x.size()
        q, k, v = self.qkv_proj(x).split(self.d_model, dim=2)
        head_dim = d_model // self.num_heads
        q = q.view(batch, seq_len, self.num_heads, head_dim).transpose(1, 2)
        k = k.view(batch, seq_len, self.num_heads, head_dim).transpose(1, 2)
        v = v.view(batch, seq_len, self.num_heads, head_dim).transpose(1, 2)
        if self.use_flash:
            # fused kernel — see FORGE-1's spec panel (FlashAttention-3)
            out = F.scaled_dot_product_attention(
                q, k, v, attn_mask=None,
                dropout_p=self.drop_prob if self.training else 0,
                is_causal=True,
            )
        else:
            scores = (q @ k.transpose(-2, -1)) * (1.0 / math.sqrt(head_dim))
            scores = scores.masked_fill(self.causal_mask[:, :, :seq_len, :seq_len] == 0, float("-inf"))
            weights = self.attn_drop(F.softmax(scores, dim=-1))
            out = weights @ v
        out = out.transpose(1, 2).contiguous().view(batch, seq_len, d_model)
        return self.resid_drop(self.attn_out_proj(out))

RESIDUAL STREAM

The residual stream is the place where after the execution of the each block whether it is the mlp block or attention block the model writes to as the data is passed between the layers it under goes massive amount of dimenionsal changes often mapping from higher to lower dimensions and viceversa as we saw in the above section there multiple attention heads and several layers of these repeated pattern. Residual stream helps solve this problem we just simply write to a flow the so called a information highway

ADDITIVE · UNNORMALIZED 12 LAYERS SHOWN

Residual streams play an important role in understanding the internals of the model it helps us grasp how different parts of the blocks interact.
In the context of the residual stream, circuits are naturally framed as the computational pathways that read from and write to this shared, accumulated state. The residual stream acts as a high-dimensional memory where each layer adds information without overwriting the past, and interpretability research uses terminology like features to describe the meaningful directions encoded in its activations—concepts, entities, or grammatical properties that later components can access. Because the stream compresses many features into a single vector through superposition, individual neurons rarely correspond to clean, isolated concepts; instead, circuits are traced by identifying how attention heads and MLP neurons interact through the stream.
Path patching and activation patching are applied directly to the residual stream to measure how specific upstream components shape the representation that downstream heads read, revealing structures like induction circuits that enable in-context learning. The residual stream thus becomes the central interpretive medium: features are written into it, superimposed, and then selectively read out, making it the backbone through which circuits compose complex behaviors.

Residual Stream Mechanism Read / Write / Virtual Weights
Dwg No. RS‑GPT2‑01
Layers 1 · 2 · 3
linear ✓
y = x + W_O^i f^i(W_I^i x)
Residual Stream · A sequence of layers reads from and writes to the stream with linear projections, allowing for effective gradient flow.

FEED-FORWARD NETWORK

Every position is independently expanded to roughly four times the model width, pushed through a GELU nonlinearity, then projected back down — the part of the network most often credited with storing factual knowledge.

d_model → 4× → d_model GELU

Feed‑forward network – mathematical formulation

The feed‑forward network (FFN) is a position‑wise sublayer. It processes each token independently, introduces non‑linearity, and contains the bulk of the model's capacity. The operation is identical for every sequence position.

Standard ReLU FFN (Vaswani et al., 2017)

Two affine transformations with a ReLU activation in between.

Parameters
\(W_1 \in \mathbb{R}^{d_{\text{model}} \times d_{\text{ff}}}\), \(b_1 \in \mathbb{R}^{d_{\text{ff}}}\)
\(W_2 \in \mathbb{R}^{d_{\text{ff}} \times d_{\text{model}}}\), \(b_2 \in \mathbb{R}^{d_{\text{model}}}\)
\(d_{\text{ff}} = 4 \cdot d_{\text{model}}\) in the original paper.

Per‑token operation (for a single vector \(x \in \mathbb{R}^{d_{\text{model}}}\)):

\[ \operatorname{FFN}(x) = \max(0,\; xW_1 + b_1)\,W_2 + b_2 \]

Full sequence (input matrix \(X \in \mathbb{R}^{n \times d_{\text{model}}}\)):

\[ \operatorname{FFN}(X) = \max(0,\; XW_1 + \mathbf{1}_n b_1^T)\,W_2 + \mathbf{1}_n b_2^T \]

This is equivalent to two independent \(1\!\times\!1\) convolutions applied to every token vector.

Variant: GELU activation

Used in BERT, GPT‑2, and many encoder models. Smooth alternative to ReLU:

\[ \operatorname{GELU}(x) = x \cdot \Phi(x) \approx 0.5x\left(1 + \tanh\left[\sqrt{2/\pi}\left(x + 0.044715 x^3\right)\right]\right) \] \[ \operatorname{FFN}_{\text{GELU}}(x) = \operatorname{GELU}(xW_1 + b_1)\,W_2 + b_2 \]

Variant: SwiGLU (gated linear unit)

Modern large language models (PaLM, LLaMA, etc.) use a gated variant with a Swish activation, improving performance while maintaining comparable computational cost.

Parameters
\(W_1, W_3 \in \mathbb{R}^{d_{\text{model}} \times d_{\text{ff}}}\), \(W_2 \in \mathbb{R}^{d_{\text{ff}} \times d_{\text{model}}}\)
(Biases are typically omitted.)

Operation (element‑wise gating):

\[ \operatorname{SwiGLU}(x) = \big(\operatorname{Swish}(xW_1) \;\odot\; xW_3\big)\,W_2 \]

where \(\operatorname{Swish}(z) = z \cdot \sigma(z) = z \cdot \operatorname{silu}(z)\). The inner dimension \(d_{\text{ff}}\) is often rescaled (e.g., \(\frac{8}{3}d_{\text{model}}\)) to keep the parameter count similar to the standard FFN.

Forward pass of the FFN sublayer

Inside a Transformer block, the FFN is wrapped with a residual connection and layer normalisation. Given input \(X\) (after attention and residual):

  1. Normalise. \(\bar{X} = \operatorname{LayerNorm}(X)\)
  2. Compute FFN. \(Y = \operatorname{FFN}(\bar{X})\) using one of the formulations above.
  3. Residual addition. \(X_{\text{out}} = X + Y\)

Key properties

  • No token mixing – the FFN preserves the sequence length \(n\); it operates purely as a per‑token non‑linear feature extractor.
  • Linear complexity in \(n\) – \(O(n \cdot d_{\text{model}} \cdot d_{\text{ff}})\), unlike the quadratic self‑attention. For long sequences the FFN can dominate the per‑token computation.
  • Memory interpretation – the large weight matrices can be viewed as key‑value memories that store linguistic and factual knowledge.
FFN(x) = GELU(x W1 + b1) W2 + b2
GELU(x) = x · Φ(x)

Φ is the standard normal CDF — GELU weighs each input by how likely it is to exceed a random threshold, rather than hard-clipping at zero like ReLU.

Position-wise, in code

Every position runs through the identical MLP independently — no mixing across positions happens here, that already happened in attention. The expand-then-contract shape gives the network room to compute something more elaborate than a single linear pass would allow before compressing back down to the model's working width.


class FeedForward(nn.Module):
    def __init__(self, cfg):
        super().__init__()
        self.mlp_up_proj = nn.Linear(cfg.d_model, 4 * cfg.d_model, bias=cfg.use_bias)
        self.activation = nn.GELU()
        self.mlp_down_proj = nn.Linear(4 * cfg.d_model, cfg.d_model, bias=cfg.use_bias)
        self.drop = nn.Dropout(cfg.drop_prob)
    def forward(self, x):
        x = self.mlp_up_proj(x)
        x = self.activation(x)
        x = self.mlp_down_proj(x)
        return self.drop(x)

THE DECODER BLOCK PIPELINE

Tokenize, embed, add position, then loop through N identical decoder blocks — each a pre-norm attention sub-layer and a pre-norm feed-forward sub-layer, both wrapped in a residual add — before a final projection to vocabulary logits.

× N LAYERS AUTOREGRESSIVE
h = hℓ−1 + Attn( LN(hℓ−1) )
h = h + FFN( LN(h) )
P(xt+1 | x≤t) = softmax( Wu · LN(hL) )

Repeated for ℓ = 1 … L, then the final normalized state is projected onto the vocabulary.

The forward pass

GPT — Generative Pretrained Transformer — is the specific decoder-only recipe this walkthrough builds towards. There are several different types of transformer varaints.

  • layer normalization
  • attention block
  • normalization
  • feed forward layer
  • norm and gets added to residual stream + repeat
  • Assembling the decoder block

    
    class DecoderBlock(nn.Module):
        """One pre-norm attention sub-layer + one pre-norm feed-forward sub-layer,
        each wrapped in its own residual connection."""
        def __init__(self, cfg):
            super().__init__()
            self.pre_attn_norm = LayerNorm(cfg.d_model, cfg.use_bias)
            self.attn = MultiHeadSelfAttention(cfg)
            self.pre_mlp_norm = LayerNorm(cfg.d_model, cfg.use_bias)
            self.mlp = FeedForward(cfg)
        def forward(self, x):
            x = x + self.attn(self.pre_attn_norm(x))   # attention sub-layer, residual add
            x = x + self.mlp(self.pre_mlp_norm(x))      # feed-forward sub-layer, residual add
            return x

    The full model

    Everything upstream — token embedding, position embedding, the stack of decoder blocks, and the tied unembedding head — is assembled once in __init__:

    
    class ForgeTransformer(nn.Module):
        def __init__(self, cfg):
            super().__init__()
            self.cfg = cfg
            self.backbone = nn.ModuleDict(dict(
                token_embedding=nn.Embedding(cfg.vocab_size, cfg.d_model),
                position_embedding=nn.Embedding(cfg.max_seq_len, cfg.d_model),
                embed_dropout=nn.Dropout(cfg.drop_prob),
                layers=nn.ModuleList([DecoderBlock(cfg) for _ in range(cfg.num_layers)]),
                final_norm=LayerNorm(cfg.d_model, cfg.use_bias),
            ))
            self.lm_head = nn.Linear(cfg.d_model, cfg.vocab_size, bias=False)
            # weight tying: the unembedding is literally the embedding matrix, transposed
            self.backbone.token_embedding.weight = self.lm_head.weight

    And forward runs the actual pass — embed, loop through every decoder block, normalize, then either compute a training loss or return only the last position's logits for inference:

    
        def forward(self, token_ids, targets=None):
            batch, seq_len = token_ids.size()
            positions = torch.arange(seq_len, device=token_ids.device)
            tok_vec = self.backbone.token_embedding(token_ids)
            pos_vec = self.backbone.position_embedding(positions)
            x = self.backbone.embed_dropout(tok_vec + pos_vec)
            for layer in self.backbone.layers:
                x = layer(x)
            x = self.backbone.final_norm(x)
            if targets is not None:
                logits = self.lm_head(x)
                loss = F.cross_entropy(logits.view(-1, logits.size(-1)), targets.view(-1), ignore_index=-1)
            else:
                # inference: only the last position's logits are actually needed
                logits = self.lm_head(x[:, [-1], :])
                loss = None
            return logits, loss

    UNEMBEDDING TOKENS

    From hidden state to token: the unembedding pipeline

    At the final layer of the transformer, each position in the sequence holds a hidden state vector \(\mathbf{h} \in \mathbb{R}^{d_{\text{model}}}\). For autoregressive generation, the hidden state at the last position is used to predict the next token. The unembedding matrix \(W_U \in \mathbb{R}^{|\mathcal{V}| \times d_{\text{model}}}\) maps this hidden state to a score for every token in the vocabulary:

    \[ \mathbf{z} = W_U \, \mathbf{h} \qquad \text{(logits)} \]

    Applying the softmax function yields a proper probability distribution:

    \[ P(t_i \mid \text{context}) = \frac{\exp(z_i / \tau)}{\sum_{j=1}^{|\mathcal{V}|} \exp(z_j / \tau)} \]

    where \(\tau\) is the temperature parameter — lower values sharpen the distribution (more deterministic), higher values flatten it (more exploratory). At \(\tau=1\) this is the standard softmax. The model then either selects the token with the highest probability (greedy decoding) or samples from the distribution. Finally, the chosen token ID is decoded back to text by reversing the BPE merge rules — recursively expanding merged tokens until only raw bytes remain, then decoding those bytes via UTF-8.

    The interactive tool below demonstrates this entire pipeline for a single position, using the same 4‑dimensional toy embedding space and small vocabulary introduced in the BPE encoding example above.

    The tool below walks through the unembedding process step by step. We start with a hidden state vector \(\mathbf{h}\) (the output of the transformer for the last position) and trace it all the way back to readable text. Click Next Step to begin.


    Hidden State → Logits → Probability → Token → Text

    Click Next Step to begin the unembedding journey

    Hidden state h: \([0.50,\; 0.60,\; 0.40,\; 0.30]\)  (output from final transformer layer, last position)

    Unembedding matrix \(W_U\): \(12 \times 4\)  ·  one row per token in our toy vocabulary

    Temperature \(\tau = 1.0\) for the softmax (standard scaling)

    Predicted token: 257 → expands to "run" after BPE decoding

    Mathematical formulation of unembedding & decoding

    Step 1 — Logit computation

    Given hidden state \(\mathbf{h} \in \mathbb{R}^{d}\) and unembedding matrix \(W_U \in \mathbb{R}^{|\mathcal{V}|\times d}\):

    \[ z_i = \sum_{k=1}^{d} (W_U)_{i,k} \cdot h_k \quad \text{for } i = 1,\dots,|\mathcal{V}| \]

    Equivalently, \(\mathbf{z} = W_U \mathbf{h}\). Each logit \(z_i\) is the dot product between the hidden state and the learned vector for token \(i\).

    Step 2 — Softmax with temperature

    \[ P(t_i) = \frac{\exp(z_i / \tau)}{\sum_{j=1}^{|\mathcal{V}|} \exp(z_j / \tau)} \]

    As \(\tau \to 0\), the distribution approaches a one-hot encoding of the argmax token (greedy). As \(\tau \to \infty\), it approaches a uniform distribution.

    Step 3 — Token selection

    \[ t^* = \arg\max_i P(t_i) \quad \text{(greedy)} \quad \text{or} \quad t^* \sim P(t) \quad \text{(sampling)} \]

    Step 4 — BPE decoding

    Given token \(t^*\), recursively apply the inverse of the merge list \(\mathcal{M}\):

    1. If \(t^* \leq 255\), it is a raw byte — emit the corresponding UTF-8 byte.
    2. Otherwise, look up the merge rule \((p,q) \to t^*\) in \(\mathcal{M}\) and recursively decode \(p\) then \(q\).
    3. Concatenate all emitted bytes and decode the UTF-8 byte stream to obtain the final string.

    This guarantees a lossless round-trip: decode(encode(s)) = s for any Unicode string \(s\).

    Weight tying: why \(W_U\) often equals \(W_E^\top\)

    In many transformer architectures (including GPT-2/3, LLaMA, and others), the unembedding matrix is not independently learned — instead, it's set to the transpose of the embedding matrix: \(W_U = W_E^\top\). This is called weight tying. The intuition is elegant: if the embedding for "run" is the vector that represents that token in the input space, then the same vector should serve as the detector for that token in the output space. The dot product \(\mathbf{h} \cdot \mathbf{e}_{\text{token}}\) measures how closely the hidden state aligns with that token's learned representation.

    Weight tying reduces the parameter count by \(|\mathcal{V}| \times d_{\text{model}}\) (roughly 50,257 × 768 ≈ 38.6M parameters for a small GPT-style model) and has been shown to improve generalization, especially for rare tokens whose output embeddings would otherwise receive very little gradient signal during training.

    GPT-3 SPECIFICATIONS

    Architecture
    TYPEDecoder-Only Transformer
    PARAMETERS175.0 B
    LAYERS96
    ATTENTION HEADS96
    HIDDEN DIM12,288
    FFN DIM49,152
    POSITION SCHEMELearned Absolute Positional Embeddings
    VOCAB SIZE50,257
    Training & Inference
    TRAINING TOKENS300 B
    CONTEXT WINDOW2,048
    PRECISIONFP16 mixed precision
    OPTIMIZERAdamW, cosine decay
    ATTENTION KERNELCustom CUDA (dense/sparse)
    KV CACHE / SEQ~9.0 GB @ 2,048 (FP16)
    ALIGNMENTNone (zero/few‑shot pretrained)
    TOTAL TRAIN FLOPs~3.15 × 10²³
    C ≈ 6 · N · D

    N is parameter count, D is training tokens — the back-of-envelope compute estimate behind GPT-3's ~3.15 × 10²³ total training FLOPs (175B params × 300B tokens × 6).

    Some of the Key Definitions

    Transformer
    A particular kind of neural network design that uses attention to process sequences. It is the basis from which most of the variants of language models emerged.
    Math: Maps an input sequence \(\mathbf{x}\) to a prediction \(\hat{\mathbf{y}}\) by stacking attention and feed‑forward layers. Minimises loss \(L\) between \(\hat{\mathbf{y}}\) and target \(\mathbf{y}\).

    GPT
    An acronym for Generative Pretrained Transformer. It is a family of models with a certain special kind of transformer architecture.
    Math: A decoder‑only transformer that uses a causal mask \(\text{Mask}\) to prevent attending to future tokens.

    Back propagation
    The standard algorithm for deep learning that adjusts all of the model parameters based on the loss calculated from the training text.
    Math: Computes gradients \(\frac{\partial L}{\partial \theta}\) for all parameters \(\theta\) and updates them via a gradient‑descent step.

    Token
    A small chunk of text. It may or may not be semantically correct; we do not bother.
    Math: Represented by a one‑hot vector \(\mathbf{e}_i \in \{0,1\}^V\), where \(i\) is the token index in the vocabulary.

    Embedding
    The process of turning a token into a vector: a list of numbers that try to capture some form of information regarding the text.
    Math: An embedding matrix \(\mathbf{E} \in \mathbb{R}^{V \times d_{\text{model}}}\) (also denoted \(W_e\)) maps the one‑hot token to a dense vector: \(\mathbf{x}_i = \mathbf{E}^\top \mathbf{e}_i\).

    Vectors
    An ordered list of numbers that points to a point in n‑dimensional space (vn).
    Math: Usually written in bold: \(\mathbf{v} = (v_1, v_2, \dots, v_n)\).

    Dot product
    A way of capturing the product of two vectors that measures how much the two vectors point in the same direction.
    Math: \(\mathbf{u} \cdot \mathbf{v} = \sum_{i=1}^n u_i v_i = \|\mathbf{u}\|\|\mathbf{v}\|\cos\theta\).

    Vocabulary
    The fixed set of all tokens that the model knows about. For GPT‑3 this set contains about fifty thousand items.
    Math: Size denoted by \(V\).

    Attention block
    The place where all the information about the embedding vectors gets exchanged.
    Math: Computes queries \(\mathbf{q}_i\), keys \(\mathbf{k}_i\), values \(\mathbf{v}_i\) via learned weight matrices \(W_Q, W_K, W_V\). The scaled dot‑product attention is \(\operatorname{Attention}(Q,K,V) = \operatorname{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}} + \text{Mask}\right)V\), where \(Q,K,V\) are matrices of queries, keys, values. Multi‑head attention concatenates \(h\) such heads and projects with \(W_O\).

    Multi layer perceptron
    A stage where every vector is processed independently by the same simple network. Here weights are tuned with back propagation to find the optimal ones.
    Math: The feed‑forward network (FFN): \(\operatorname{FFN}(\mathbf{h}) = \operatorname{GELU}(\mathbf{h}\mathbf{W}_1 + \mathbf{b}_1)\mathbf{W}_2 + \mathbf{b}_2\), with \(\mathbf{W}_1, \mathbf{W}_2\) weight matrices and \(\mathbf{b}_1, \mathbf{b}_2\) biases.

    Context size
    The maximum number of tokens the model can look at once.
    Math: Denoted \(N_{\text{ctx}}\).

    Unembedding matrix
    A table of numbers that maps the final, context‑rich vector back into a score for every possible token in the vocabulary.
    Math: \(W_U \in \mathbb{R}^{d_{\text{model}} \times V}\) (often tied to the embedding matrix). The output logits are \(\mathbf{z} = \mathbf{h}_{\text{last}} W_U\).

    Logit
    One of the raw, unnormalised scores produced by the unembedding matrix before they are turned into probabilities.
    Math: The \(i\)‑th logit \(z_i\) for the \(i\)‑th vocabulary token; the vector of all logits is \(\mathbf{z} = (z_1, \dots, z_V)\).

    Softmax
    A function that converts an arbitrary list of numbers into a proper probability distribution, where all values lie between zero and one and add up to one. (The activation function used inside the network is GELU, a smooth variant of ReLU.)
    Math: \(\operatorname{softmax}(\mathbf{z})_i = \frac{e^{z_i}}{\sum_{j=1}^V e^{z_j}}.\)

    Temperature
    A single number that adjusts the softmax distribution. Higher values make the distribution more even and exploratory; lower values make it more concentrated on the most likely token.
    Math: The probabilities become \(\mathbf{p} = \operatorname{softmax}(\mathbf{z}/T)\).

    System prompt
    A predefined context given to the model before the real prompt is passed. It establishes the format of the output, any language changes, the tone of the response, and similar characteristics.
    Math: Prepended to the input sequence \(\mathbf{x}\); the model conditions all predictions on this fixed prefix.

    Fine‑tuning
    An extra round of training on a more specific dataset, after the initial pretraining, to adapt the model to a particular task.
    Math: Additional training that minimises the loss \(L\) on a task‑specific dataset, updating all or some of the parameters \(\theta\).

    Weight
    One of the many adjustable numbers inside the model that control how information is transformed. Weights are learned from data during training.
    Math: An element of a weight matrix, e.g., \(W_Q, W_K, W_V, W_O, \mathbf{W}_1, \mathbf{W}_2\).

    Parameter
    Another name for a weight; any number inside the model that is tuned during training.
    Math: The full set of parameters is often denoted by \(\theta\), encompassing all weights and biases (e.g., \(\gamma, \beta\) in layer norm).

    Tensor
    A generic term for a multi‑dimensional grid of numbers, such as a list of vectors or a stack of matrices.
    Math: Examples: the layer‑wise hidden state \(\mathbf{H}^{(l)} \in \mathbb{R}^{N_{\text{ctx}} \times d_{\text{model}}}\), or the attention weight tensor \(\mathbf{A}\) of shape \(h \times N_{\text{ctx}} \times N_{\text{ctx}}\).

    Jailbreaking LLMs
    The practice of carefully designing prompts that trick a language model into ignoring its safety rules and producing harmful or restricted outputs.
    Math: Finding an adversarial input \(\tilde{\mathbf{x}}\) such that the model assigns high probability to a disallowed target \(\mathbf{y}_{\text{bad}}\), i.e. \(\mathbf{p}(\mathbf{y}_{\text{bad}} \mid \tilde{\mathbf{x}})\) is large, despite the model being fine‑tuned to minimise such probabilities.

    Adversarial Inputs
    Inputs deliberately altered with small, often invisible perturbations that cause a machine‑learning model to make mistakes or produce unintended outputs. In language models, these are crafted prompts that reliably trigger harmful or restricted completions.
    Math: Given an original input \(\mathbf{x}\), an adversarial version \(\tilde{\mathbf{x}} = \mathbf{x} + \delta\) is found such that the model’s prediction \(\hat{\mathbf{y}}(\tilde{\mathbf{x}})\) is dangerously wrong, or the probability of a harmful target \(\mathbf{y}_{\text{bad}}\) becomes high: \(\mathbf{p}(\mathbf{y}_{\text{bad}} \mid \tilde{\mathbf{x}}) \gg \mathbf{p}(\mathbf{y}_{\text{bad}} \mid \mathbf{x})\), often while keeping \(\delta\) almost imperceptible to a human.

    Consider Supporting the Work

    Good writing takes time. If any of this thinking has been useful, interesting, or even just made you pause — a small contribution keeps it going.

    Every contribution is deeply appreciated

    Click the button or scan the QR code — whichever is easier.