01

How Large Language Models Work

From characters to coherent text — the architecture, the math, and the magic.

1. Architecture

The pipeline end-to-end

2. Tokenization

Byte-level BPE

3. Embeddings

Tokens become vectors

4. Attention

Q, K, V & multi-head

5. Output Layer

Logits → next token

Start with a concrete question

Can a small model learn next-character prediction while being unable to see future targets?

Your goal

Understand the data, the rule, and the required result. Then run the complete Python program and modify one thing at a time.

No companion presentation is required. Python syntax and any mathematical notation are explained as they are introduced.

Vocabulary you need before the algorithm

Token

One character ID in this example.

Teacher forcing

Known earlier tokens provide context for next-token targets.

Causal mask

Only current/past positions may be attended to.

Autoregressive generation

Append one prediction and use it as later context.

Work one example before writing code

Input 'the cat sat.' is shifted by one character to form targets. Logits have shape (B,T,V). The causality test changes later token IDs and requires all earlier logits to stay the same.

Trace each step and name what changed. Ask which fact stayed true and what should happen on an empty, invalid, or boundary input.

02 / Overall Architecture

The LLM Pipeline END TO END

The neural computation includes matrix multiplications, lookups, normalization, nonlinear activations, and softmax. Tokenization and runtime control are additional algorithms.

Input Text "The cat sat" Tokenizer Byte-BPE → token IDs Embeddings + Position vectors ∈ ℝᵈ N × TRANSFORMER BLOCKS Multi-Head Self-Attention Add & LayerNorm Feed-Forward Network (MLP) Add & LayerNorm repeat 32–96+ times Output Head Linear → Softmax → probabilities "on" next token

Input

Teaching excerpt · complete runnable lab available from “Code”
"The cat sat"

Output (next token)

Teaching excerpt · complete runnable lab available from “Code”
"on"   (P = 0.42)
"down" (P = 0.21)
"in"   (P = 0.11)
…
03 / Scope

Three Architecture Families SETTING SCOPE

Decoder-only transformers are common for autoregressive text generation. Encoder-decoder and other designs also exist; undisclosed proprietary architectures should not be assumed.

Encoder-only

BERT family

Block N … Block 1 Embeddings ↔ bidirectional attention

Sees: every token sees every other (full context).

Good at: classification, embeddings, search/retrieval, NER, fill-in-blank.

Bad at: generating text — no native left-to-right notion.

Examples: BERT, RoBERTa, DeBERTa, sentence-transformers

Encoder–Decoder

Original Transformer / T5 family

ENC N … ENC 1 embed input DEC N … DEC 1 embed output (so far) ↔ cross-attention enc → dec

Sees: encoder bidirectional · decoder causal · decoder cross-attends to encoder.

Good at: translation, summarization, structured input → structured output.

Encoder-decoder cost depends on the allocation of encoder/decoder layers, width, and cross-attention. It is not intrinsically twice a decoder-only model at a fixed total parameter budget.

Examples: T5, FLAN-T5, BART, original Transformer (Vaswani 2017)

Decoder-only ← we focus here

GPT family — modern LLMs

Block N … Block 1 Embeddings → causal attention only

Sees: each token sees only past tokens (left-to-right).

Good at: text generation, chat, code, reasoning, agents — anything autoregressive.

Why it won: simpler, scales beautifully, one model handles all tasks via prompting.

Examples: GPT-4, Claude, Llama, Mistral, Gemini, Qwen, DeepSeek

Why Decoder-Only Won

Many chat systems use decoder-only transformers, but this is not a universal architectural requirement. Public model documentation is the basis for any named architecture claim.

04 / Tokenization
You are here Input Text Tokenizer Embeddings N × Transformer Blocks Output Head Next Token

What is Tokenization? TEXT → NUMBERS

Neural networks operate on numbers — not characters or words. Tokenization is the bridge.

The Job of a Tokenizer

  • Convert raw text into a sequence of integer IDs the model can ingest.
  • Reverse the process: turn the model's predicted IDs back into text (detokenization).
  • Maintain a fixed vocabulary — every ID maps to one token.

Three Approaches

  • Character-level — tiny vocab (~100), but very long sequences.
  • Word-level — short sequences, but huge vocab and tons of "unknown" words.
  • Subword (BPE, WordPiece, SentencePiece) — best of both. Common words stay whole, rare words split into pieces. This is what modern LLMs use.

What Tokenization Looks Like

Input (text)
Teaching excerpt · complete runnable lab available from “Code”
"The cat sat on the mat."
Step 1 — split into tokens
The cat sat on the mat .
Step 2 — map to IDs
Teaching excerpt · complete runnable lab available from “Code”
[791, 8415, 7731, 389, 279, 5634, 13]

The model never sees "cat" — it sees the integer 8415. The output will be another integer, which gets detokenized back to text.

Why It Matters

  • Token count = cost (you pay per token in API calls).
  • Token count = context window limit.
  • Bad tokenization → poor performance on rare languages, code, math.
05 / Tokenization
You are here Input Text Tokenizer Embeddings N × Transformer Blocks Output Head Next Token

Byte-Level BPE BYTE PAIR ENCODING

The tokenization family behind GPT-2 onward, Llama 3, and most modern LLMs. (Llama 1–2 used SentencePiece BPE with byte fallback — same idea.)

How It Works

  1. Start with raw bytes (256 base symbols → handles every Unicode character, emoji, language).
  2. Find the most frequent adjacent byte pair in the training corpus.
  3. Merge it into a new token. Repeat thousands of times.
  4. Vocabulary grows to ~50k–200k learned merges.

Why Byte-Level?

  • No "unknown token" — every input encodes.
  • Language-agnostic. Works on code, emoji, rare scripts.
  • Common words → single token. Rare words → multiple sub-tokens.

Example

Input
Teaching excerpt · complete runnable lab available from “Code”
"unbelievable"
Step 1 — bytes
Teaching excerpt · complete runnable lab available from “Code”
u n b e l i e v a b l e
Step 2 — apply learned merges
un believ able
Step 3 — token IDs
Teaching excerpt · complete runnable lab available from “Code”
[403, 8265, 480]

IDs are illustrative — real splits vary by tokenizer.

A whole sentence
Teaching excerpt · complete runnable lab available from “Code”
"The cat sat on the mat."
The cat sat on the mat .

Note the leading spaces — whitespace is part of the token.

06 / Tokenization — special tokens
You are here Input Text Tokenizer Embeddings N × Transformer Blocks Output Head Next Token

Special Tokens RESERVED VOCABULARY

Tokenizer/API policy and trusted role serialization control reserved tokens. Reservation alone does not prevent prompt injection.

Not all tokens come from text. The vocabulary also reserves control tokens that structure the conversation, mark boundaries, and trigger behaviors.

What Are Special Tokens?

  • Reserved IDs in the vocabulary that don't appear in normal text.
  • Added after BPE training — typically a few hundred slots (e.g. IDs 128000–128255 in Llama 3).
  • To the model they're just integers — but it's fine-tuned to give them specific meanings.
  • Special-token handling depends on tokenizer and API serialization policy. Trusted role boundaries must be constructed by the runtime; token reservation alone does not prevent prompt injection.

Common Categories

<|begin_of_text|> — start of sequence (BOS)
<|end_of_text|> — end of sequence (EOS)
<|start_header_id|> <|end_header_id|> — role markers
<|eot_id|> — end of turn
<|tool_call|> <|tool_result|> — function calling (names vary by model)
<|pad|> — padding for batching

Chat Template — A Real Example

What you write (API call)
Teaching excerpt · complete runnable lab available from “Code”
messages = [
  {"role": "system",    "content": "You are helpful."},
  {"role": "user",      "content": "Hi!"},
  {"role": "assistant", "content": "Hello!"}
]
What the model actually sees (tokenized)
Teaching excerpt · complete runnable lab available from “Code”
<|begin_of_text|>
<|start_header_id|>system<|end_header_id|>
You are helpful.<|eot_id|>
<|start_header_id|>user<|end_header_id|>
Hi!<|eot_id|>
<|start_header_id|>assistant<|end_header_id|>
Hello!<|eot_id|>
As token IDs
Teaching excerpt · complete runnable lab available from “Code”
[128000, 128006, 9125, 128007, 271, 2675, 527, 11190, 13,
 128009, 128006, 882, 128007, 271, 13347, 0, 128009,
 128006, 78191, 128007, 271, 9906, 0, 128009]

IDs ≥ 128000 are special. 271 is the "\n\n" that follows every header. The model learns: "after assistant<|end_header_id|>, generate a reply, then emit <|eot_id|> to stop."

Why This Matters

Roles & structure

System / user / assistant boundaries are just tokens. The model learns to respect them during fine-tuning.

Stop conditions

Generation halts when the model emits <|eot_id|> or <|end_of_text|> — that's how the API knows the turn is done.

Tool use & modes

Function calling, vision inputs, and reasoning modes all get their own special tokens to switch the model's behavior.

07 / Embeddings
You are here Input Text Tokenizer Embeddings N × Transformer Blocks Output Head Next Token

Tokens → Vectors SEMANTIC SPACE

Each token ID indexes into a learned table of high-dimensional vectors.

The Embedding Table

A matrix E ∈ ℝV × d where:

  • V = vocabulary size (e.g. 128,000)
  • d = model dimension (e.g. 4,096) — the length of every vector inside the model
Teaching excerpt · complete runnable lab available from “Code”
token_id  →  E[token_id]  ∈  ℝ⁴⁰⁹⁶

  403  →  [ 0.12, -0.84, 0.31, …, 0.07]
 8265  →  [-0.45,  0.22, 0.91, …,-0.18]
  480  →  [ 0.67, -0.13, 0.04, …, 0.55]

Plus a positional encoding is added so the model knows token order.

What is "Model Dimension" (d)?

The residual stream has width d. Attention heads have width d_k, FFN intermediates have width d_ff, and vocabulary logits have width V; not every internal vector has the same width.

Where d Shows Up

ComponentShape
Token embedding tableV × d
Position embeddingN_max × d (learned only — RoPE adds none)
Q / K / V / O projectionsMHA: d×d; GQA/MQA K/V projections can be narrower
FFN hidden layerd × 4d (expand) → 4d × d (project back). Llama's SwiGLU uses 3 matrices of d × ≈8/3·d — same total.
LayerNormd (γ) + d (β)
LM headd × V

Hidden state shape stays (B, T, d) from input embedding all the way to the LM head — every block preserves it.

Typical Sizes in Real Models

ModeldHeads × head_dim
GPT-2 small (124M)76812 × 64
GPT-2 XL (1.5B)1,60025 × 64
Llama-2 7B4,09632 × 128
Llama-2 70B8,19264 × 128
GPT-3 175B12,28896 × 128

Trade-off: doubling d roughly quadruples compute and memory (most weights are d × d or d × 4d). Bigger d = more capacity but quadratically (not exponentially) more cost per layer. Note d must divide evenly by the number of attention heads.

Vector Arithmetic Works

Teaching excerpt · complete runnable lab available from “Code”
vec("king") − vec("man") + vec("woman") ≈ vec("queen")
vec("Paris") − vec("France") + vec("Italy") ≈ vec("Rome")

Direction in the space encodes meaning. Distance encodes similarity.

Vector Space (2D projection)

king queen prince cat dog tiger run walk jump king → queen 2D projection of 4096-D embedding space
royalty
animals
actions
08 / Positional Encoding
You are here Input Text Tokenizer Embeddings N × Transformer Blocks Output Head Next Token

Positional Encoding INJECTING ORDER

Without positions and without an order-dependent mask, attention is permutation-equivariant. Causal masking changes prefix sets and the applicable symmetry.

Content-only unmasked self-attention is permutation-equivariant: permuting inputs permutes corresponding outputs. It is not invariant, and a causal mask itself changes the permitted symmetry.

The Problem

Attention weights only depend on token content, not order. So we have to add the order back in explicitly.

Teaching excerpt · complete runnable lab available from “Code”
x_i = embedding(token_i) + position_vector(i)
       └─ what it is ─┘   └─── where it sits ───┘

Same shape as the embedding (ℝᵈ), so addition just works. The transformer now receives both signals fused into one vector.

Three Flavors

1. Sinusoidal — Vaswani 2017 (original Transformer)

Fixed sin/cos at exponentially varying frequencies. No parameters. Defined for any length — though quality still drops past the lengths seen in training.

2. Learned — BERT, GPT-2

A second embedding table indexed by position (0…N−1). Trained via backprop. Cannot extrapolate past max length.

3. RoPE — Llama, GPT-NeoX, modern LLMs

Rotary Position Embedding — rotates Q and K vectors by an angle proportional to position. Encodes relative distance directly. Extends to longer contexts with scaling tricks (position interpolation, NTK / YaRN).

Learned Encoding — A Second Embedding Table

Treat each position like a token. Allocate a second lookup table P ∈ ℝN_max × d and train it via backprop alongside everything else.

Teaching excerpt · complete runnable lab available from “Code”
position_id  →  P[position_id]  ∈  ℝᵈ

  0  →  [ 0.21, -0.74, 0.55, …, -0.08]   ← learned
  1  →  [-0.13,  0.62, 0.41, …,  0.27]   ← learned
  2  →  [ 0.08, -0.31, 0.92, …, -0.44]   ← learned
  …
N_max−1 →  [ …                         ]

Each input vector becomes the sum of two learned lookups:

token_id = 8415 "cat" → E[8415] ∈ ℝᵈ token embedding + position = 1 2nd token in seq → P[1] ∈ ℝᵈ learned position vector ═ x = E[8415] + P[1] final input to transformer

Trade-offs

  • Pro: the model learns whatever position pattern is most useful — no hand-designed math.
  • Pro: simple to implement — same lookup mechanism as token embeddings.
  • Con: costs an extra N_max × d parameters (e.g. 2048 × 4096 ≈ 8M).
  • A learned position table has a finite allocated size. Access beyond it raises an index error unless the table is extended; newly initialized positions have not learned useful behavior.

This is why many modern open LLMs (Llama, Mistral, Qwen, GPT-NeoX) moved to RoPE — position encoded as a rotation, which depends only on relative distance and stretches to longer sequences more gracefully.

09 / Attention
You are here Input Text Tokenizer Embeddings N × Transformer Blocks Output Head Next Token

Self-Attention: Q, K, V THE CORE MECHANISM

Every token asks every other token: "how much should I pay attention to you?"

The Three Roles

From each token's embedding x, three vectors are projected via learned weight matrices:

Teaching excerpt · complete runnable lab available from “Code”
Q = x · W_Q   ← "what am I looking for?"
K = x · W_K   ← "what do I offer?"
V = x · W_V   ← "what info do I carry?"

The Attention Formula

Teaching excerpt · complete runnable lab available from “Code”
Attention(Q, K, V) = softmax( Q·Kᵀ / √d ) · V
  • Q · Kᵀ — dot product = compatibility score for every token pair
  • / √d — scale to keep gradients stable (d = d_k, per-head size)
  • softmax — turn scores into a probability distribution (the "attention weights")
  • · V — weighted sum of values = context-aware new representation

Worked Example (causal — future tokens masked)

Sentence: "The cat sat on the mat"

When processing "sat", it can only attend to itself and prior tokens.

The cat sat on the mat 0.10 0.75 0.15 MASKED MASKED MASKED "sat" attends most to "cat" (the subject) future tokens "on / the / mat" are blocked by the causal mask
Output for "sat"
Teaching excerpt · complete runnable lab available from “Code”
new_representation = 0.10·V_The + 0.75·V_cat + 0.15·V_sat
                   (future V_on, V_the, V_mat are excluded)
09 / Attention — by hand

Attention on a 3-Token Toy WORKED EXAMPLE

Real models use dk = 128. Here dk = 2, so every number fits on one slide and you can check it with a calculator.

Intuition First — A Soft Dictionary Lookup

  • Q is the question a token asks. K is the label on each other token. V is what's inside.
  • A normal dictionary returns the one exact match. Attention returns a blend of all values, weighted by how well each label matches.

Setup (WK = WV = I, WQ doubles dim 2)

tokenxQ = xWQK = V = x
The[1, 0][1, 0][1, 0]
cat[0, 1][0, 2][0, 1]
sat[1, 1][1, 2][1, 1]

Result for Every Row (causal)

queryweights on (The, cat, sat)output
The1.00, —, —[1.00, 0.00]
cat0.20, 0.80, —[0.20, 0.80]
sat0.14, 0.28, 0.58[0.72, 0.86]

Each row sums to 1. "The" can only see itself. "sat" pulls most from itself and "cat" — its best-matching keys.

The "sat" Row, Step by Step

Teaching excerpt · complete runnable lab available from “Code”
1. scores   q·k   = [1·1+2·0, 1·0+2·1, 1·1+2·1]
                  = [1, 2, 3]
2. scale    ÷ √2  = [0.71, 1.41, 2.12]
3. softmax        = [0.14, 0.28, 0.58]
4. blend V  0.14·[1,0] + 0.28·[0,1] + 0.58·[1,1]
                  = [0.72, 0.86]

Run It Yourself (plain Python)

Teaching excerpt · complete runnable lab available from “Code”
import math
Q = [[1, 0], [0, 2], [1, 2]]          # The, cat, sat
K = V = [[1, 0], [0, 1], [1, 1]]

def softmax(v):
    m = max(v); e = [math.exp(x - m) for x in v]
    return [x / sum(e) for x in e]

for t in range(3):                     # query position
    s = [sum(q * k for q, k in zip(Q[t], K[j])) / math.sqrt(2)
         for j in range(t + 1)]        # causal: keys 0..t only
    w = softmax(s)
    out = [sum(w[j] * V[j][c] for j in range(t + 1)) for c in range(2)]
    print(t, [round(x, 2) for x in w], [round(x, 2) for x in out])

Common mistake: softmax over the wrong axis. Normalize across keys (each row sums to 1), not across queries. Subtracting max(v) first keeps exp from overflowing.

09 / Attention — deep dive
You are here Input Text Tokenizer Embeddings N × Transformer Blocks Output Head Next Token

Why divide by √d? SCALED DOT-PRODUCT

The sqrt(d_k) scaling controls score variance under the stated initialization assumptions and helps prevent saturation. Alternative normalizations and optimization choices exist; training is not mathematically impossible without this exact factor.

The Math: Variance Grows With d

Assume each component of Q and K is roughly independent with mean 0 and variance 1. Then for a single dot product:

Teaching excerpt · complete runnable lab available from “Code”
q · k = Σᵢ qᵢ · kᵢ      (i = 1 … d)

E[q · k]    = 0
Var[q · k]  = d
Std[q · k]  = √d

So as d grows (typical: 64, 128, even 256 per head), the raw scores q·k grow proportionally to √d.

For d=128 → typical scores ≈ ±11 (so 22 is just a 2σ score). For d=4096 → typical scores ≈ ±64.

The Fix

Teaching excerpt · complete runnable lab available from “Code”
softmax( Q·Kᵀ / √d ) · V
  • Dividing by √d rescales the variance back to 1, regardless of head size.
  • Softmax now operates in its sensitive range — outputs are spread across tokens, not collapsed to one.
  • Gradients flow → the model can learn attention patterns.
  • Trains stably across model sizes — same formula whether d=64 or d=512.

The Failure Mode: Softmax Saturation

Softmax is exponential. When inputs are large, one value dominates:

Without /√d   (d=128)
Teaching excerpt · complete runnable lab available from “Code”
scores  = [22, 8, 14, 17]
softmax = [0.99, 0.00, 0.00, 0.01]
0.99 0.00 0.00 0.01

≈ one-hot. Gradient ≈ 0. Model can't learn.

With /√d   (÷ √128 ≈ 11.3)
Teaching excerpt · complete runnable lab available from “Code”
scores  = [1.94, 0.71, 1.24, 1.50]
softmax = [0.41, 0.12, 0.20, 0.27]
0.41 0.12 0.20 0.27

Soft distribution. Gradients flow. Model learns.

Intuition in One Line

Dot products grow with dimension. Softmax of large numbers is brittle. /√d keeps the input to softmax dimension-independent — so the same architecture works whether you have 64 or 4096 features per head.

09 / Attention — causal mask
You are here Input Text Tokenizer Embeddings N × Transformer Blocks Output Head Next Token

No Peeking: The Causal Mask FUTURE TOKENS BLOCKED

LLMs are trained to predict the next token. If a token could see future tokens, training would be trivial — and inference would break.

Why It's Required

  • Training objective: predict token t+1 from tokens 1…t. If token t could see t+1, the model would just copy the answer — learning nothing.
  • Inference consistency: at generation time, future tokens literally don't exist yet. The model must work with only the prefix.
  • Parallel training: the mask lets us compute predictions for all positions at once in a single forward pass — instead of running the model N times.

How It's Implemented

After computing Q·Kᵀ / √d but before softmax, add a mask matrix M:

Teaching excerpt · complete runnable lab available from “Code”
scores = Q·Kᵀ / √d
scores = scores + M           ← M is upper-triangular −∞
attn   = softmax(scores)      ← e^−∞ = 0, so future is gone
output = attn · V

Setting masked entries to −∞ (not 0) means after softmax they become exactly 0 — and the visible probabilities still sum to 1.

The Mask Matrix (Q rows × K columns)

Each row = one query token. Each column = a key it might attend to. Green = allowed, red = masked (−∞).

The cat sat on the mat KEY (what we attend to →) The cat sat on the mat QUERY (token doing the asking ↓) ✓ −∞ −∞ −∞ −∞ −∞ ✓ ✓ −∞ −∞ −∞ −∞ ✓ ✓ ✓ −∞ −∞ −∞ ✓ ✓ ✓ ✓ −∞ −∞ ✓ ✓ ✓ ✓ ✓ −∞ ✓ ✓ ✓ ✓ ✓ ✓ Lower-triangular = visible. Upper-triangular = masked.

Key Insight

Encoder models (BERT) use bidirectional attention — every token sees every other token. Decoder LLMs (GPT, Llama, Claude) use causal attention — each token sees only the past. That mask — plus the training objective (masked-word filling vs. next-token prediction) — is the main difference between a representation model and a generator.

10 / Multi-Head Attention
You are here Input Text Tokenizer Embeddings N × Transformer Blocks Output Head Next Token

Multi-Head Attention PARALLEL PERSPECTIVES

Multiple heads supply several learned projections/attention distributions. A head can participate in many patterns; one relationship per head is not a representational restriction.

The Idea

  • Split the embedding into h smaller chunks (e.g. 32 heads × 128 dims = 4096).
  • Each head gets its own W_Q, W_K, W_V — learns a different pattern.
  • Run all heads in parallel.
  • Concatenate outputs → project back to model dim with W_O.

What Different Heads Learn

  • Head A: syntactic — subject ↔ verb agreement
  • Head B: coreference — pronoun ↔ noun ("she" → "Alice")
  • Head C: positional — previous-token attention
  • Head D: semantic — topic / entity tracking

These specializations emerge during training — nobody assigns them.

Input embeddings (d=4096) Head 1 syntax Q₁ K₁ V₁ d_head=128 Head 2 coreference Q₂ K₂ V₂ d_head=128 Head 3 position Q₃ K₃ V₃ d_head=128 … Head 32 semantic Q₃₂ K₃₂ V₃₂ d_head=128 Concat (32 × 128 = 4096) Linear projection W_O
11 / Output Layer
You are here Input Text Tokenizer Embeddings N × Transformer Blocks Output Head Next Token

From Hidden State to Token THE LM HEAD

After N transformer blocks, the final hidden state of the last token gets projected back to the vocabulary.

Final hidden state h ∈ ℝ⁴⁰⁹⁶ (last token) Linear (LM head) h · W_lm W_lm ∈ ℝ⁴⁰⁹⁶ × ¹²⁸ᴷ Logits ℓ ∈ ℝ¹²⁸ᴷ raw scores per token Softmax (÷ T) P(token) = e^ℓᵢ / Σ e^ℓⱼ probability distribution Sample argmax / top-k / top-p / temperature

Logits (raw)

Teaching excerpt · complete runnable lab available from “Code”
token       logit
─────────────────
"on"        8.21
"down"      7.52
"in"        6.87
"upon"      6.42
"quietly"   6.08
…           …

After Softmax

Teaching excerpt · complete runnable lab available from “Code”
token       P
─────────────────
"on"        0.42
"down"      0.21
"in"        0.11
"upon"      0.07
"quietly"   0.05
…           …  (sums to 1.0)

Check: 0.42 / 0.21 = 2 = e8.21 − 7.52. Only logit differences matter.

Sampling Strategy

  • argmax → always pick "on" (deterministic, boring)
  • temperature → divide logits by T; T<1 = sharper, T>1 = more random
  • top-k → sample only from top k tokens
  • top-p → smallest set summing to ≥ p

Then Repeat — Autoregressive Generation

Teaching excerpt · complete runnable lab available from “Code”
"The cat sat"           → "on"
"The cat sat on"        → "the"
"The cat sat on the"    → "mat"
"The cat sat on the mat"→ "."     (stop)

Each generated token is appended to the input and the whole pipeline runs again. That's it. That's an LLM.

12 / Putting It All Together

Every Block IS a Neural Network SAME SHAPE, DIFFERENT ROLE

Every layer in the LLM is a small neural network — dots = neurons, lines = learned weights. The structure varies (linear, with-activation, parallel branches), but the substrate is identical.

FULL MODEL 12 87 5 Token IDs integers (not yet a NN — input data) Embedding Layer one-hot input → dense vector + Positional Encoding another NN, summed into the embedding attn ffn Transformer Block × N NN blocks, repeated 12–96+ times → expanded on the right Final LayerNorm NN with element-wise normalization LM Head — Linear NN d → V projection (no activation) Softmax parameter-free NN: normalize to Σ=1 "on" Next Token sampled, then fed back as input ONE TRANSFORMER BLOCK (EXPANDED) LayerNorm parameter-light NN — normalize each token's vector to mean 0, variance 1, then re-scale Q (linear NN) K (linear NN) V (linear NN) softmax QKᵀ/√d · V + W_O linear NN Multi-Head Self-Attention composite of 4 linear NNs (Q, K, V, output projection W_O) + a parameter-free routing op (softmax of Q·Kᵀ/√d) params: 4 × (d × d) per block + Add (Residual Connection) output += input — lets gradients flow through deep stacks LayerNorm same NN structure, applied again before the FFN ↑ GeLU activation Feed-Forward Network (MLP) classic 3-layer NN: input → hidden (4×) → output applied independently to every token — pure pattern matching params: 2 × (d × 4d) per block — ~⅔ of all model parameters + Add (Residual Connection) output of FFN added back to its input
12 / By the Numbers

Where the Parameters and Memory Go WORKED ARITHMETIC

Incremental decoding computes new Q,K,V. Cache persistence, context eviction, summaries, and cross-session storage are serving/application decisions.

Counting weights by hand is the best way to make the architecture concrete. Every number below can be checked with a calculator.

GPT-2 Small: V = 50,257 · d = 768 · L = 12 · Nmax = 1,024

PieceFormulaParams
Token embeddingsV·d38,597,376
Position embeddingsNmax·d786,432
Attention (per block)d·3d + 3d + d·d + d2,362,368
FFN (per block)d·4d + 4d + 4d·d + d4,722,432
2 LayerNorms (per block)4d3,072
12 blocks12 × 7,087,87285,054,464
Final LayerNorm2d1,536
LM headtied to token embeddings0
Total124,439,808

That's the famous "124M". Rule of thumb for the blocks alone: 12·L·d² = 12·12·768² ≈ 85M.

KV Cache Size

Teaching excerpt · complete runnable lab available from “Code”
bytes per token = 2 (K and V) × layers × kv_heads
                  × head_dim × bytes_per_number
Model (fp16)Per token4,096 tokens
Llama-2 7B (32 layers, 32 KV heads × 128)0.5 MB2 GB
Llama-2 70B (80 layers, 8 KV heads × 128)320 KB1.25 GB

The 70B model caches less per token: it shares K/V across query heads (grouped-query attention, GQA). Multiply by batch size to see why serving is memory-bound.

Compute Rules of Thumb (N = parameter count)

  • Generating one token ≈ 2N FLOPs (one multiply + one add per weight).
  • Training on one token ≈ 6N FLOPs (forward 2N + backward ≈ 4N).
  • So a 7B model needs ≈ 14 GFLOPs per generated token (ignoring attention over the context).
Check yourself: GPT-2 Medium has d = 1,024 and L = 24. Estimate its size.

Blocks: 12·24·1024² ≈ 302M. Embeddings: 50,257·1,024 + 1,024·1,024 ≈ 52.5M. Total ≈ 355M — the published size.

13 / How Chatbots Are Trained

From Base Model to Chatbot THREE TRAINING STAGES

Everything we've covered so far produces a base model — a giant next-token predictor. It knows facts and language but doesn't know how to talk. Three more training stages turn it into ChatGPT / Claude / Llama-Chat.

1. Pre-training ~10T tokens · months · 1000s of GPUs · ~$10–100M 2. Supervised Fine-Tuning (SFT) 10K–1M curated examples · days · ~$10K–100K 3. RLHF / DPO (Alignment) human preferences · weeks · ~$100K–$1M Chatbot ready to ship

1. Pre-training → Base Model

Data: trillions of tokens from web, books, code, papers.

Objective: predict the next token. Nothing else.

Result: knows facts & language but continues text — doesn't follow instructions.

If you ask a base model:
Teaching excerpt · complete runnable lab available from “Code”
Prompt: "What is 2+2?"

Output: "What is 2+2?
A) 3   B) 4   C) 5
Answer key: B
Question 2: What is …"

It mimics text it's seen — like a textbook, not a helper.

2. Supervised Fine-Tuning (SFT)

Data: thousands to millions of curated (instruction, response) pairs — written by humans, and increasingly generated by models and checked by humans.

Objective: same next-token loss, but on chat-formatted conversations with special tokens.

Result: learns the chat format, follows instructions, knows when to stop.

After SFT:
Teaching excerpt · complete runnable lab available from “Code”
Prompt: "What is 2+2?"

Output: "2 + 2 equals 4."

Same model weights, mostly intact — SFT just nudges it toward helpful conversational behavior.

3. RLHF / DPO → Aligned Chatbot

Data: humans rank pairs of model responses ("A is better than B").

Objective: push the model toward responses humans prefer (helpful, honest, harmless). RLHF: reward model + RL. DPO: learns straight from the pairs.

Result: politer, safer, refuses bad requests, admits uncertainty, follows tone.

After RLHF:
Teaching excerpt · complete runnable lab available from “Code”
Prompt: "How do I hack my
        neighbor's wifi?"

Output: "I can't help with
unauthorized access. If it's
your own network, here's
how to recover credentials…"

Post-training can alter style, instructions, reasoning, and factual behavior. It is not restricted to tone, and it does not guarantee retention or improvement of knowledge.

The Big Picture

For classic chat models, the vast majority of compute went into pre-training (the architecture from this whole talk). Newer "reasoning" models spend a growing share on reinforcement learning after SFT. Either way, the later stages are what makes the difference between a "fancy autocomplete" and something you can actually have a conversation with.

14 / How Chatbots Run

The Chat Inference Loop WHAT HAPPENS WHEN YOU PRESS SEND

Incremental decoding computes new Q,K,V. Cache persistence, context eviction, summaries, and cross-session storage are serving/application decisions.

A multi-turn conversation is just repeated next-token prediction over a growing prompt — wrapped in special tokens, streamed back one token at a time.

The Round Trip

1 · USER TYPES "What's the capital of France?" 2 · APPLY CHAT TEMPLATE <|begin_of_text|> <|sys_start|>You are a helpful assistant.<|eot|> <|user_start|>What's the capital of France?<|eot|> <|asst_start|> ← model generates from here 3 · TOKENIZE [128000, 128006, 9125, 128007, 2675, 527, …, 128006] 4 · AUTOREGRESSIVE GENERATION (LOOP) forward pass → logits → sample → "The" forward pass → logits → sample → " capital" forward pass → logits → sample → " of" … forward pass → logits → sample → " Paris" forward pass → logits → sample → <|eot|> ← STOP 5 · DETOKENIZE & STREAM TO USER "The capital of France is Paris." next user turn

Multi-Turn Conversation

A stateless transformer function consumes a supplied context. The application or serving runtime may persist conversation state, summaries, or reusable KV prefixes across turns.

Teaching excerpt · complete runnable lab available from “Code”
turn 1 prompt:
  <sys>… <user>Hi</> <asst>

turn 2 prompt:
  <sys>… <user>Hi</>
  <asst>Hello!</>
  <user>How are you?</> <asst>

turn 3 prompt:
  <sys>… <user>Hi</>
  <asst>Hello!</>
  <user>How are you?</>
  <asst>Doing well!</>
  <user>What did I say first?</> <asst>

This is why long conversations get expensive — the prompt grows unboundedly until it hits the context window limit.

The KV Cache — Why Streaming Is Fast

Naïve approach: re-run the full forward pass for the whole prompt on every new token. Cost: O(N²) per token, O(N³) per response.

Incremental decoding computes new Q, K, and V for the new position and reuses cached past K/V. Context length still contributes attention work.

Teaching excerpt · complete runnable lab available from “Code”
per-token cost:
  without KV cache  →  O(N²)
  with KV cache     →  O(N)

The KV cache is why first token is slow ("prefill") but subsequent tokens are fast ("decode"). It also dominates GPU memory in long conversations.

Sampling Knobs You Control

  • temperature — 0 = deterministic; 1 = balanced; >1 = wild
  • top-p / top-k — restrict the candidate pool before sampling
  • max_tokens — hard cap on response length
  • stop sequences — additional strings that halt generation
15 / How Agents Work

From Chatbot to Agent LLM IN A LOOP + TOOLS + MEMORY

Incremental decoding computes new Q,K,V. Cache persistence, context eviction, summaries, and cross-session storage are serving/application decisions.

A chatbot answers in one shot. An agent is the same model wrapped in a loop, given tools to take actions in the world and memory to remember things across turns.

User Goal "Book me a flight to NYC" Assemble Prompt system + tool defs + memory + history + user message (grows each iteration) LLM think → decide Tool call or done? done Final Answer return to user call tool Execute Tool API · code · DB · search append tool result to prompt & loop again

How Tools Change the Prompt

Tools are added declaratively in the system prompt as JSON schemas. The model is fine-tuned (during SFT) to emit special tokens that signal a tool call.

Plain chatbot prompt
Teaching excerpt · complete runnable lab available from “Code”
<|sys|>You are a helpful assistant.<|/>
<|user|>What's the weather in SF?<|/>
<|asst|>
Agent prompt — same thing + tool block
Teaching excerpt · complete runnable lab available from “Code”
<|sys|>You are a helpful assistant.

You have access to the following tools:
[
  {
    "name": "get_weather",
    "description": "Get current weather for a city",
    "parameters": {
      "city": {"type": "string", "required": true}
    }
  },
  { "name": "search_web", … },
  { "name": "send_email", … }
]<|/>
<|user|>What's the weather in SF?<|/>
<|asst|>
Model emits a tool call (not text)
Teaching excerpt · complete runnable lab available from “Code”
<|tool_call|>
{"name": "get_weather", "args": {"city": "SF"}}
<|/tool_call|>
Runtime executes & appends result
Teaching excerpt · complete runnable lab available from “Code”
<|tool_result|>
{"temp_f": 62, "conditions": "foggy"}
<|/tool_result|>
<|asst|>It's 62°F and foggy in SF right now.<|/>

The model never actually called an API — it just emitted text. The agent runtime parses the tool-call tokens, executes the real function, and stuffs the result back into the prompt. Same loop the chatbot uses, just with extra structured turns.

How Memory Is Added

The model itself is stateless — it forgets everything between API calls. "Memory" is built around the model by reading/writing to external stores and stuffing relevant bits back into the prompt.

1
Short-term: conversation history

Retained conversation context is bounded and incurs processing/storage cost; the runtime decides what to retain, resend, cache, or summarize.

2
Compression: summarization

When history grows too long, ask the model to summarize old turns and replace them with the summary. Lossy but cheap.

3
Semantic: vector DB / RAG

Embed past conversations (or external docs) into vectors. At each turn, retrieve the top-k most similar chunks and inject them. Scales to millions of facts.

4
Persistent: files / state tools

Give the agent read_memory / write_memory tools. The model decides what to save and when to recall — explicit and durable across sessions.

Memory injected as a prompt prefix
Teaching excerpt · complete runnable lab available from “Code”
<|sys|>You are a helpful assistant.

## Recalled memory (relevant facts):
- User prefers metric units
- User is based in Berlin
- Last conversation: discussed Python tooling

[tool definitions…]<|/>
<|user|>What's the weather?<|/>

Many transformer applications serialize retrieved memory into context text. Other designs use structured inputs, cached vectors, multimodal representations, or recurrent state; memory is not universally extra text.

Key Insight

Tools and memory don't change the model. They change what's in the prompt at each step. An "agent framework" (LangChain, AutoGPT, Claude's tool use API, etc.) is essentially a prompt-building loop that decides what to put in front of the LLM next based on what it just emitted.

16 / Common Mistakes

Myths vs. Reality COMMON MISTAKES

Ideas students (and engineers) often get wrong about LLMs — each one ties back to a slide in this talk.

"The model reads words."
It reads token IDs. That's why it can struggle to count letters in "strawberry" — it never sees the letters.

"It remembers our last chat."
The model is stateless. The app re-sends the history, or injects saved memory into the prompt.

"Temperature 0 means it's sure."
T = 0 just means greedy: always take the top token. The top token can still have P = 0.2.

"Mask future tokens with 0."
Use −∞ before softmax. A 0 score still gets weight e0 = 1, so the future leaks in.

"Attention weights explain the answer."
They show where information moved in one layer, not why. Many heads and layers mix it again.

"A longer context window is free."
The KV cache grows linearly and prompt processing (prefill) grows quadratically with length.

Fine-tuning can change factual and task capabilities as well as behavior. Retrieval supplies additional evidence; it does not replace the need to evaluate factual accuracy.

"The model calls the API."
It only emits text that describes a call. The runtime executes it and pastes the result back.

17 / Check Yourself

Check Yourself CLICK TO REVEAL

Try each one before you open the answer.

1. Why is "unbelievable" split into several tokens, but "the" is one?

BPE merges the most frequent pairs first. "the" is extremely common, so it became one token early. Rare words are built from reusable pieces.

2. Without positional information, how would "dog bites man" differ from "man bites dog"?

Without positional signals and without order-dependent masking, input permutations permute outputs. With causal masking, the allowed prefix sets change, so that simplified statement does not apply unchanged.

3. Scores are [2, 0, 0] with dk = 4. What are the attention weights?

Scale by √4 = 2 → [1, 0, 0]. Softmax → e/(e+2) ≈ 0.58, then 0.21, 0.21.

4. A model has d = 4,096 and 32 heads. What is the head dimension?

4,096 / 32 = 128. The heads split d evenly; after attention their outputs are concatenated back to 4,096.

5. Logits for A and B are 3.0 and 1.0. What is P(A) at T = 1 and T = 2?

T = 1: 1/(1+e−2) ≈ 0.88. T = 2: logits become 1.5 and 0.5, so P(A) = 1/(1+e−1) ≈ 0.73. Higher T flattens the distribution.

6. Why is the first token slow and the rest fast?

The first token needs a full pass over the whole prompt (prefill). After that, K and V are cached, so each new token only runs one position through the model (decode).

7. A 7B model in fp16 — how much memory do the weights alone need?

7 × 109 parameters × 2 bytes ≈ 14 GB, before any KV cache or activations.

8. What turns a base model into a chatbot, if not new architecture?

More training: SFT on chat-formatted examples (with special tokens), then preference tuning (RLHF or DPO). The architecture is unchanged.

18 / Glossary

Glossary TERMS IN ONE PLACE

TokenA chunk of text (word, piece of a word, byte) with an integer ID.
BPEByte Pair Encoding — builds a vocabulary by merging frequent pairs.
EmbeddingThe learned vector for a token; a row of the table E.
d (d_model)Width of every hidden vector in the model.
d_k (head dim)Width of each head's Q and K vectors; d / heads.
RoPERotary position embedding — rotates Q and K by position.
Q, K, VQuery, key, value projections used by attention.
Causal mask−∞ on future positions so a token sees only the past.
Multi-headSeveral attention heads in parallel, then concatenated.
FFN / MLPThe per-token feed-forward network inside each block.
LogitsRaw scores over the vocabulary, before softmax.
TemperatureDivides logits; lower = sharper, higher = more random.
Top-k / top-pSample only from the k best, or the smallest set with mass ≥ p.
KV cacheStored K and V of past tokens so decode skips recomputation.
Prefill / decodeProcessing the prompt at once / generating one token at a time.
Context windowMax tokens the model can attend to in one call.
SFTSupervised fine-tuning on (instruction, response) examples.
RLHF / DPOTuning on human preferences, with or without a reward model.
AgentAn LLM in a loop that can call tools and use memory.
19 / Recap

The Whole Picture ONE SLIDE SUMMARY

The Five Layers We Covered

  1. Architecture — input → tokenizer → embeddings → N transformer blocks → LM head → output
  2. Tokenization — byte-level BPE turns any text into a sequence of integer IDs
  3. Embeddings — IDs index into a learned table of vectors; meaning lives in geometry
  4. Attention — Q·Kᵀ scores, softmax weights, weighted sum of V; multi-head = parallel perspectives
  5. Output — final hidden state → logits → softmax → sampled token; repeat

Key Takeaways

  • An LLM is a giant next-token predictor. Everything else (chat, code, reasoning) emerges from that.
  • Attention is all you need — and it's just three matrix multiplies wrapped in a softmax.
  • Scale matters: more layers, more heads, more parameters, more data → emergent capabilities.
  • Inference is autoregressive — one token at a time, feeding output back as input.

Where to Go Next

  • Attention Is All You Need (Vaswani et al., 2017) — the original paper
  • The Illustrated Transformer — Jay Alammar's visual guide
  • nanoGPT by Karpathy — a full LLM in ~300 lines of code
  • 3Blue1Brown — But what is a GPT? — best video explanation

Questions?

Run a complete program

Can a small model learn next-character prediction while being unable to see future targets?

The file contains its own imports, data generator or fixture, implementation, and checks.

python3 -m venv .venv
source .venv/bin/activate
python -m pip install torch
python llm_tech_talk.py
python llm_tech_talk.py --self-test

Use Python 3.10+. On Windows, activate with .venv\Scripts\Activate.ps1. Required packages: torch>=2.2.

Connect the concept to the implementation

Read the contract first. Identify the inputs, intermediate state, and return value in this excerpt from the verified file.

def forward(self, tokens):
        b, t = tokens.shape
        if t > self.context:
            raise ValueError("sequence exceeds allocated position embeddings")
        x = self.token(tokens) + self.position(torch.arange(t, device=tokens.device))
        q, k, v = self.qkv(self.norm1(x)).chunk(3, dim=-1)
        q, k, v = [value.reshape(b, t, self.heads, self.width//self.heads).transpose(1, 2)
                   for value in (q, k, v)]
        scores = q @ k.transpose(-2, -1) / math.sqrt(self.width//self.heads)
        mask = torch.ones(t, t, dtype=torch.bool, device=tokens.device).triu(1)
        attention = scores.masked_fill(mask, -torch.inf).softmax(-1)
        mixed = (attention @ v).transpose(1, 2).contiguous().reshape(b, t, self.width)
        x = x + self.output(mixed)
        x = x + self.ffn(self.norm2(x))
        return self.head(x)

Compare with an actual run

Vocabulary: '\n .acdeghnorst'
Initial/final next-character loss: 2.84539532661438 0.053476203233003616
Generated: 'the dog ran.\nthe cat sat.\nthe dog ran.\nthe c'
The small corpus demonstrates teacher forcing and causality; it does not establish general language ability.

Output was captured from the complete program running in an isolated temporary directory. Floating-point digits and compressed sizes may vary across versions/hardware.

Check the result, then try an exercise

Verified checks

5 checks passed, including boundary cases and independent reference comparisons where applicable. These executed checks complement the reasoning; they are not a universal proof.

Remove the future mask and rerun the causality check. Why can training loss improve while the generator becomes invalid?

Explain the expected result

Without masking, positions can read the answer token during training. Such information does not exist during left-to-right generation, so low training loss is misleading.

Implement it yourself and validate it

  1. Run the downloadable program and its checks.
  2. Reimplement TinyLM.forward from its contract.
  3. Compare outputs with the reference on valid, empty, and invalid cases.
  4. Explain the limitations of the algorithm or experiment.
All coding labs
Decks