Vol. 05 · LLM Foundations
LLM Internals: From Token to Logit to Answer
The full path from the input request to the next token: tokenization, embeddings, positional encoding, self-attention, MLP, normalization, KV-cache, logits, softmax and sampling parameters - parsed atomically.
Input Pipeline
The text cannot be fed directly to the neural network; it must be divided into integer indexes. A token is not always a word: GPT-4 uses ~100k tokens, where a token can be a syllable, a suffix, or a single character.
Source-complete noteshow it works BPE (Byte-Pair Encoding) · facts
text# Training BPE dictionary
1. Start with the character alphabet (bytes/unicode)
2. Count the frequency of all pairs of neighboring tokens
3. Merge the most common pair → new token
4. Repeat until the desired vocab_size
# Inference
"Hello world" → [15496, 995] # GPT-2
"hello" → […, …, …] # Cyrillic alphabet often breaks down to a larger number of tokensfacts
- GPT-4: ~100k tokens
- Llama3: tiktoken BPE
- 1 token ≈ 0.75 words (EN)
- Cyrillic: 1 word = 3–5 tokens
Each index token turns into a vector of dense numbers through a lookup table (weight matrix). This is the first learning layer - the model itself learns what each token means in the space of meanings.
Source-complete notesmechanics · properties
textE ∈ ℝ^[vocab_size × d_model] # embedding matrix
token_id = 15496 # "Hello"
x = E[token_id] # → vector ℝ^d_model
# GPT-3: d_model=12288, vocab=50257
# Llama-3 70B: d_model=8192, vocab=128256
# Parameters only in E:
50257 × 768 = 38.6M # GPT-2 baseproperties
- king − man + woman ≈ queen
- dot product = semantic proximity
- the same E is often used on the output (weight tying)
The Attention mechanism itself is invariant to the order of tokens - it “sees” a set of vectors without the concept of “before/after”. Positional embeddings add position information directly to the token vector.
Source-complete notesthree approaches · modern standard
textSinusoidal (original Transformer):
PE(pos,2i) = sin(pos / 10000^2i/d_model)
PE(pos,2i+1) = cos(pos / 10000^2i/d_model)
→ fixed, not trained
Learned (GPT-2, BERT):
pos_emb ∈ ℝ^[max_seq × d_model]
→ trainable, limited by max_seq
RoPE (Llama, Mistral, GPT-NeoX):
→ rotation of Q,K in space by an angle θ·pos
→ relative positions, extrapolation
→ the best standard for todaymodern standard
- RoPE — Llama, Mistral, Qwen
- ALiBi - length extrapolation
- Learned - limited by max_seq when learning
- Sinusoidal - deprecated
The final vector at the input of each transformer block is the sum of token embedding + positional embedding. It is this vector that “travels” through all layers, gradually enriching itself with context.
Attention Mechanism
Self-attention allows each token to “look” at all other tokens in the sequence and collect information in a weighted manner. This is the key mechanism by which the word "key" in the context of "door key" will be represented differently than in the context of "forest key".
Choose the token whose query is being resolved.
Source-complete notescomplexity
complexity
- O(n²·d) in memory and time from context length
- Flash Attention - tile-based, O(n) memory
- d_k = d_model / num_heads
Three weight matrices W_Q, W_K, W_V project the same input vector into three different “roles”. This allows the model to simultaneously decide “what I want to ask,” “what I have,” and “what to return in response.”
Source-complete notesdimensions
textd_model = 4096 # Llama-3 8B
num_heads = 32
d_k = d_v = 128 # d_model / num_heads
W_Q: [4096 × 128] per head
W_K: [4096 × 128] per head
W_V: [4096 × 128] per head
W_O: [4096 × 4096] # output proj (all heads)
# Parameters in one attention layer:
4 × 4096² = 67M parametersInstead of one attention, several (heads) are executed in parallel. Each head learns to pay attention to different aspects: one to syntactic connections, another to coreference, and a third to positional patterns.
Source-complete notesalgorithm · variations
pythonfor h in range(num_heads):
head_h = Attention(
Q = x W_Q_h,
K = x W_K_h,
V = x W_V_h
) # → [seq × d_k]
# Concatenation of heads:
concat = cat([head_0, …, head_h]) # [seq × d_model]
out = concat W_O # [seq × d_model]variations
- MHA is a classic, all Q/K/V are unique
- GQA — grouped query (Llama-3, Mistral)
- MQA - one K/V for all Q heads
In autoregressive (decoder-only) models, token i should not see tokens j > i. This is implemented by the mask: score(i,j) = −∞ for j > i, after softmax such positions receive a weight ≈ 0.
Source-complete noteswhere there is no mask
where there is no mask
- BERT (encoder-only) - sees the entire context
- T5 (encoder-decoder) - encoder without mask
After softmax, the attention weights α_ij indicate how much token i “focuses” on token j. Row sum = 1. Different heads form different focus patterns - this can be visualized and interpreted.
Source-complete notesinterpretation tools
interpretation tools
- BertViz
- Transformer Lens
- not always = explanation, more of a heuristic
Transformer Block
Each transformer block (layer) is two subblocks with residual connections. First, multi-head attention “mixes” information between positions, then MLP “thinks” about each position independently. The 32-layer model runs the input tensor through this structure 32 times.
Source-complete notestypical block parameters
typical block parameters
- Llama-3 8B: 32 blocks, d=4096
- Llama-3 70B: 80 blocks, d=8192
- each block: ~340M parameters (70B)
After attention, each token vector is processed independently by a two-layer neural network. MLP expands the dimension (×4 or ×8/3 for SwiGLU), applies nonlinearity, then contracts back. This is where the “actual memory” of the model is stored.
Source-complete notesclassic FFN vs SwiGLU · interpretation
text# Original Transformer (ReLU):
FFN(x) = ReLU(x W₁ + b₁) W₂ + b₂
d_ff = 4 × d_model # expansion factor
# SwiGLU (Llama, PaLM - modern standard):
FFN(x) = SiLU(x W₁) ⊙ (x W₂) W₃
# three matrices: gate, up, down
d_ff = 8/3 × d_model ≈ 11k for d=4096
# Parameters in MLP (Llama-3 8B):
3 × 4096 × 11008 = 135M per blockinterpretation
- ~⅔ model parameters - in MLP
- attention — routing, MLP — storage
- MoE replaces one MLP with multiple sparse
After each subblock, activations are normalized - the mean is subtracted, divided by std, then scaled by the trainable γ and β. This stabilizes learning and speeds up convergence, especially in deep networks.
Source-complete notesformula · normalization position
texLayerNorm(x) = γ · (x − μ) / (σ + ε) + β
μ = mean(x) # by d_model
σ = std(x) # by d_model
ε = 1e-6 # numerical stability
γ,β # trainees (scale/shift)
# RMSNorm (Llama, T5) - without subtracting the average:
RMSNorm(x) = γ x / RMS(x)
→ faster, quality is no worsenormalization position
- Pre-Norm (before attention) - more stable
- Post-Norm (original) - requires warmup
- RMSNorm — Llama 2/3, Mistral, Qwen
Each subblock adds its value to the input (skip connection). This solves the problem of vanishing gradient in deep networks - the gradient can flow directly through the skip path. Without residuals, it is almost impossible to train 32+ layers.
Source-complete noteswhy does this work
tex# Regular network:
x₃₂ = f₃₂(f₃₁(…f₁(x₀)…))
∂L/∂x₀ = ∏ᵢ ∂fᵢ/∂xᵢ ← product, → 0
# With residual:
x_{i+1} = x_i + F_i(x_i)
∂L/∂x₀ = Σᵢ 1 + ∂F_i/∂x_i ← sum, stable
# Each layer only learns the CORRECTION to x,
# not a full conversion → easier to optimizeThe original Transformer had an encoder + decoder. Modern LLMs are almost always decoder-only. Key difference: decoder uses a causal mask and generates tokens autoregressively; encoder sees the full context at once.
Source-complete notestable
| Type | Attention | Task | Example |
|---|---|---|---|
| Encoder-only | Bidirectional (no mask) | Classification, embeddings | BERT, RoBERTa |
| Decoder-only | Causal (lower triangle) | Text generation | GPT, Llama, Mistral |
| Encoder-Decoder | Cross-attention encoder→decoder | Translation, summarization | T5, BART, mT5 |
Output & Sampling
After the last transformer block, the vector of the last token is projected into dictionary space. The result is a vector of logits of length vocab_size: raw, unnormalized scores for each possible next token.
Source-complete notesmechanics
textx_last ∈ ℝ^d_model # last token vector
# LM Head = linear layer (usually without bias):
logits = x_last W_lm_headᵀ
# W_lm_head ∈ ℝ^[vocab_size × d_model]
# often = E (embedding matrix) - weight tying
logits ∈ ℝ^vocab_size # for example 128256
# Example logits (top 5):
"the" → 4.21
"a" → 3.87
"some" → 2.14
"this" → 1.95
"your" → 1.32 …Softmax turns logits into a probability distribution: it exponentially amplifies large values and normalizes the sum to 1. It is from this distribution that the next token is sampled.
Source-complete notesformula
texP(token_i) = exp(logit_i) / Σⱼ exp(logit_j)
# Example:
logits: [4.21, 3.87, 2.14, …]
exp: [67.4, 47.9, 8.5, …]
sum: 124.6 + …
probs: [0.32, 0.23, 0.04, …] ← sum = 1
# Numerical stability:
# subtract max(logits) before exp
P(i) = exp(logit_i - max) / Σ exp(logit_j - max)Temperature divides the logits before softmax, controlling the "sharpness" of the distribution. T < 1 - the model is more confident and deterministic. T > 1 - the distribution is smoothed, the output is more random and varied.
Source-complete noteseffect · recommendations
textP(i) = softmax( logits / T )
T = 0.0 → greedy decoding (argmax, determin.)
T = 0.3 → conservative, low variability
T = 0.7 → balance (OpenAI default)
T = 1.0 → “raw” model distribution
T = 1.5 → high randomness, creative chaos
T → ∞ → uniform (everything is equally likely)recommendations
- code / factual: T = 0.0–0.3
- chat: T = 0.6–0.8
- creative: T = 0.9–1.2
- T > 1.5 - often incoherent text
Filters before sampling: cut off the “tail” of the distribution with unlikely tokens. Apply sequentially after temperature, until the final sample.
Source-complete notesalgorithms
textop-k: leave only the top k tokens by probability
k=50 → only 50 best options
# problem: the tail can only be 3 tokens
top-p (nucleus): leave the minimum set
with total probability ≥ p
p=0.9 → take tokens while Σprob < 0.9
# adaptive to the model's "confidence"
min-p: cut off tokens below the threshold
min_p * p_max (Mistral recommendation)
# less aggressive than top-p
# Typical chain:
logits → /T → top-k → top-p → softmax → sampleHow exactly do you select a token from the distribution? Three fundamentally different approaches with different trade-offs between quality, variety and speed.
Source-complete notestable
| Method | Idea | + | − |
|---|---|---|---|
| Greedy | argmax(probs) | quickly, determin. | repetition, suboptimal |
| Beam Search | B parallel hypotheses | more global greedy | slow, generic output |
| Sampling | sample(probs) | variety | need T, top-p tuning |
| Contrastive | max(sim_score) | anti-repetition | slower sampling |
LLM generates one token per step, then adds it to the context and repeats. This is called autoregressive decoding. Each new token is completely dependent on all previous ones - hence the name "language model".
Source-complete notesgeneration cycle
pythoncontext = tokenize("Hi! How are you?")
while not stop_condition:
logits = model.forward(context) # whole pass
logits = logits[-1] # only last pose
probes = sample(logits, T, top_p)
token = draw(probs)
context.append(token) # context grows!
# stop: EOS token or max_new_tokens
# problem: O(n²) without KV-cacheSystem Mechanics
With autoregressive generation without optimization, K and V would have to be recalculated for all previous tokens at each step. KV-Cache saves the already calculated K and V, adding only a new token - this is a tens of times faster.
Source-complete notesoptimizations on top of KV-cache
optimizations on top of KV-cache
- GQA/MQA - less K/V goals → less cache
- PagedAttention (vLLM) - virtual memory for KV
- Sliding Window - Mistral, limits cache
- Prefill phase: all prompt tokens together
- Decode phase: one token at a time
LLM inference is divided into two fundamentally different stages according to the nature of the load. Prefill - compute-bound, decode - memory-bound. This affects optimization of batching and GPU utilization.
Source-complete notestable
| Prefill | Decode | |
|---|---|---|
| What | Prompt processing | Token generation |
| Input data | N tokens in parallel | 1 token per step |
| Bottleneck | Compute (matmul) | Memory (KV load) |
| Metrica | TTFT (time-to-first-token) | TPS (tokens/sec) |
Model parameters are stored in various floating point formats. The choice of format directly affects memory, speed and accuracy. Quantization allows the 70B model to run on a consumer GPU.
Source-complete notesformats and memory
textfloat32 (fp32): 4 bytes/parameter #learn
bfloat16 (bf16): 2 bytes/parameter # inference standard
float16 (fp16): 2 bytes/parameter # legacy, overflow risk
int8 (Q8): 1 byte/parameter # easy quantization
int4 (Q4): 0.5 bytes/parameter # GGUF, bitsandbytes
int2/1: 0.25 bytes # BitNet (experimental)
# Llama-3 70B parameters = 70×10⁹
bf16: 140 GB Q4: 35 GB Q2: 17 GBTraining & Backprop
Direct pass: input tokens go through all layers, the output is the predicted probabilities for the next token. The loss function compares them with true tokens - this is cross-entropy loss.
Source-complete notesalgorithm
pythonx₀ = token_emb(tokens) + pos_emb
for l in range(num_layers):
x_l = transformer_block_l(x_{l-1})
logits = lm_head(x_last) # [seq × vocab]
probes = softmax(logits)
# Teacher forcing: compare with true tokens
loss = cross_entropy(probs[:-1], tokens[1:])
# predict token t+1 given t, t-1, …, t0After calculating the loss, the gradients are propagated in the opposite direction through all layers (chain rule). Each parameter receives a signal on how to change its value to reduce loss.
Source-complete noteschain rule in action · gradient problems
tex∂L/∂W = ∂L/∂ŷ · ∂ŷ/∂z · ∂z/∂W
# Simplified for layer l:
δ_l = δ_{l+1} · ∂f_l/∂x_l # layer gradient l
∂L/∂W_l = δ_l x_{l-1}ᵀ # gradient of weights
# For attention (simplified):
∂L/∂W_Q = ∂L/∂out · ∂out/∂α · ∂α/∂score · Kᵀ
# Optimizer (AdamW):
W ← W - lr m̂ / (√v̂ + ε) - λ Wgradient problems
- vanishing: → 0 (deep networks without residuals)
- exploding: → ∞ (gradient clipping)
- residuals + LayerNorm solve both
Adam tracks the first (moment) and second (variance) moments of the gradients for an adaptive learning rate for each parameter. AdamW adds weight decay as a separate member, and not through gradient - this is important for regularization.
Source-complete notesAdamW algorithm
texg = ∂L/∂W # gradient
m = β₁ m + (1-β₁) g # 1st moment (momentum)
v = β₂·v + (1-β₂)·g² # 2nd moment (variance)
m̂ = m / (1-β₁ᵗ) # bias correction
v̂ = v / (1-β₂ᵗ)
W ← W (1-lr λ) - lr m̂/(√v̂+ε) # weight decay separately
# Typical LLM hyperparameters:
β₁=0.9, β₂=0.95, ε=1e-8, λ=0.1
lr: 3e-4 (pretrain) → 1e-5 (finetune)Full cycle of LLM training: from tokenizing the corpus to updating the weights. Each step critically affects the final quality of the model. Modern LLMs are trained on trillions of tokens.
Big Picture: Token Path
A summary diagram of everything that happens from the moment the text is received until the next token is issued. When generating a response of 200 tokens, this path goes through 200 times (prefill 1 time, decode 199 times).
No dead end
Keep moving through the map.
Continue in sequence, switch to a related guide, or return to the seven-track learning map.