Pre-training is a coupled system, not a single optimization call. The tokenizer defines the sequence, the corpus defines the experience, the transformer defines how context moves, and the runtime determines how much learning fits inside the budget.
Learn one conditional distribution at every position.
Given a token sequence x₁ … xₜ, an autoregressive model assigns a probability to the sequence by factorizing it into next-token conditionals. Training minimizes the negative log-probability of the observed next token at every valid position.
p(x₁:T) = ∏ₜ pθ(xₜ | x<t)The same parameters solve every position. Each prefix becomes a training example.
Lpre = − (1 / N) Σₜ log pθ(xₜ | x<t)N counts non-padding target tokens across the global batch.
For an input tensor X with shape [B,T], the targets are the same stream shifted by one position. The model returns logits [B,T,V], where V is vocabulary size. Cross-entropy compares every logit vector against one target ID. This simple interface should remain visible throughout the implementation; most silent failures are violations of shapes, masks, shifts, or token counts around it.
# one contiguous token stream, sampled at offset s
x = tokens[s : s + B*T].view(B, T)
y = tokens[s + 1 : s + B*T + 1].view(B, T)
logits = model(x) # [B, T, V]
loss = cross_entropy(logits.view(-1, V), y.view(-1))Tokenization is the model’s first architecture decision.
A Python string is a sequence of Unicode code points, but a model vocabulary needs a finite alphabet that can represent every possible input. UTF-8 supplies that foundation: each code point becomes one to four bytes, and the 256 byte values guarantee coverage. UTF-16 and UTF-32 are valid encodings, but UTF-8 is compact for common web text and matches the byte-level design used by GPT-style tokenizers.
Byte-pair encoding begins with those 256 byte IDs. It counts consecutive pairs in the training sample, merges the most frequent pair into a new ID, rewrites the sequence, and repeats until the vocabulary reaches its target size. The training loop produces two inseparable artifacts: an ordered merge table and the byte sequence associated with every token ID.
ids = list(training_text.encode("utf-8"))
merges = {}
while 256 + len(merges) < target_vocab:
pair_counts = count_adjacent_pairs(ids)
pair = max(pair_counts, key=pair_counts.get)
new_id = 256 + len(merges)
ids = merge_every_occurrence(ids, pair, new_id)
merges[pair] = new_idCompression ratio is a diagnostic, not the only goal. It compares UTF-8 bytes with produced tokens and reveals whether the learned vocabulary fits the corpus. A tokenizer trained on one language or domain can fragment another into long byte sequences, consuming context and compute before the Transformer sees the content.
compression = number_of_UTF8_bytes / number_of_tokensReport it by language, domain, and text type rather than as one global average.
Encoding begins from bytes and applies only merges that exist in the learned table, always respecting merge rank. Decoding recursively expands a token ID back to its byte sequence and then uses UTF-8 decoding. A token may contain bytes that are not a valid standalone character; validity is restored only after neighboring token bytes are concatenated.
The regex, special tokens, and vocabulary size change model behavior.
Production BPE tokenizers usually split text with a regular expression before applying merges. This prevents merges across selected categories such as letters, numbers, punctuation, or whitespace. GPT-2 and later tokenizers use different split patterns, which means the same BPE idea can produce different segment boundaries and compression.
Special tokens represent boundaries or control structure: end of text, conversation roles, tool calls, document separators, or padding. They must be added through an explicit reserved-token policy. Encoding ordinary text should reject or escape them unless the caller intentionally enables them; otherwise user text can impersonate control structure.
Controls merge boundaries
Unexpected whitespace or script fragmentation
Every string is representable
Rare text expands into many tokens
Shorter sequences
Larger embedding and output matrices
May merge equivalent forms
Can destroy exact round trips
Explicit document and role structure
Collision with ordinary text
SentencePiece commonly trains from Unicode text and can use BPE or unigram segmentation, while GPT-style libraries expose byte-level BPE with model-specific regex rules. The library name does not define the model contract; the serialized vocabulary, ordered merges, normalization rules, split pattern, and special-token policy do.
Adding new vocabulary entries after pre-training is not free. The new embedding and output rows have not learned from the original token stream, while existing text may be resegmented into unfamiliar units. Measure any proposed prompt compression against model quality, not token count alone.
Tokenizer artifacts explain several visible model quirks: spelling and character counting can be hard when a word is one token; arithmetic strings fragment irregularly; spaces may be attached to following words; capitalization can change segmentation; non-English text can consume more positions. Multimodal systems generalize the idea by quantizing image, audio, or video features into discrete or token-like representations, but the coverage and compression contract remains.
for sample in adversarial_text_suite:
ids = tokenizer.encode(sample, allowed_special=set())
assert tokenizer.decode(ids) == sample
assert all(0 <= token_id < vocab_size for token_id in ids)
assert reserved_ids.isdisjoint(tokenizer.encode(user_text))Build a data pipeline whose decisions can be audited.
The pre-training corpus determines which patterns receive gradient updates. A production pipeline normally moves through source registration, parsing, language and quality signals, exact and approximate deduplication, policy filtering, benchmark decontamination, mixture weighting, tokenization, and immutable sharding.
Keep document boundaries explicit. Concatenating documents without an end marker teaches false transitions between unrelated texts. Packing several documents into one context improves utilization, but the token stream must retain boundary tokens and the training manifest must retain source membership.
document_id, source_idLineage and later removal
content_hash, cluster_idExact and near-duplicate accounting
language, quality_scoresMixture analysis and filtering
license, policy_flagsUsage and governance boundaries
token_count, shard, offsetReproducible packing and sampling
pipeline_versionExact rebuild of every decision
Split evaluation documents before approximate deduplication, then remove overlapping clusters from training. Otherwise a benchmark can look strong because the model saw a paraphrase, duplicate, or answer-bearing neighbor. Report mixture weights in tokens—not only documents or bytes—because tokens are what produce optimizer updates.
Small teaching corpora can be loaded as one contiguous token array. Production corpora should be stored as immutable token shards with enough metadata to reproduce every boundary and sampling decision. Advance the shard cursor deterministically and checkpoint it with the optimizer state.
Each batch is a set of shifted windows from one token stream.
For a contiguous token array, sample a start position and take T+1 IDs. The first T become the input; the same window shifted by one becomes the target. Stacking B windows creates inputs and targets with shape [B,T]. A held-out split must use different documents or shards, not adjacent windows from the same duplicated source.
window = tokens[start : start + T + 1]
x = window[:-1] # [T]
y = window[1:] # [T]
x_batch = stack([sample_window(train_tokens) for _ in range(B)])
y_batch = stack([sample_targets(train_tokens) for _ in range(B)])Token embeddings produce [B,T,C]; positional information is added or applied inside attention; a stack of blocks preserves [B,T,C]; the language-model head projects to [B,T,V].
token_ids, targetsB × T
hiddenB × T × C
q, k, vB × H × T × D; D = C/H
attention_scoresB × H × T × T
logitsB × T × V
The 124M GPT-2 configuration is a useful reference point: 12 blocks, width 768, 12 attention heads, context 1024, and vocabulary 50,257. Assert every shape and calculate the parameter count from the configuration at startup. A wrong bias, untied output matrix, or incorrect feed-forward width can add millions of parameters while leaving the code visually plausible.
Causal attention is learned, content-addressed communication.
A bigram language model is the minimum baseline: the current token indexes a row of logits for the next token. It cannot use an earlier subject, indentation level, or open delimiter because no context crosses positions. Averaging previous embeddings adds context but treats every previous position identically. Self-attention makes the aggregation depend on the content at both ends of the connection.
For hidden states X ∈ ℝT×C, learned projections create queries, keys, and values. Query-key dot products measure which previous positions matter to each current position. The scores are scaled, future positions are masked, a softmax produces weights, and the weights mix value vectors.
A = softmax((QKᵀ / √D) + M)Mij = −∞ when j > i; otherwise it is zero.
Y = A VEach row of Y is a learned weighted combination of permitted positions.
q, k, v = project(x).chunk(3, dim=-1)
q, k, v = split_heads(q), split_heads(k), split_heads(v)
scores = (q @ k.transpose(-2, -1)) / sqrt(head_dim)
scores = scores.masked_fill(causal_mask == 0, -inf)
weights = softmax(scores, dim=-1)
y = merge_heads(weights @ v)Multiple heads perform the same operation in smaller subspaces and concatenate their results. The attention weights move information only within a sequence; the batch dimension contains independent examples. Because attention sees a set unless position is added, token and positional embeddings must meet before the first block or position must be injected into the attention calculation.
With dense attention, the score matrix grows as T². Longer context therefore increases both memory and computation sharply. Fused attention kernels avoid writing the full score matrix to high-bandwidth memory, which changes feasibility without changing the mathematical result.
Match the reference architecture before optimizing it.
GPT-2 124M uses a token embedding, learned absolute position embedding, 12 Transformer blocks, a final layer normalization, and a vocabulary projection. Each block contains causal multi-head attention and a four-times wider MLP with GELU. The attention implementation commonly stores the query, key, and value projections in one combined matrix, then splits the result.
x = token_embedding(ids) + position_embedding(positions)
for block in blocks:
x = x + block.attn(block.ln_1(x))
x = x + block.mlp(block.ln_2(x))
logits = lm_head(final_norm(x))The model width must divide evenly across attention heads. GPT-2’s width 768 and 12 heads produce a head width of 64. The MLP expands 768 → 3072 → 768. Combined projection weights from another framework may require transposition when copied into a local Linear layer; compare every parameter name and shape before loading.
The token embedding and language-model head share the same weight matrix. Weight tying saves parameters and makes input and output token geometry use one table. Confirm object identity or shared storage, not merely equal initialization.
vocab_size50,257
context_length1,024
layers / heads / width12 / 12 / 768
mlp_width3,072
parametersApproximately 124 million
The model ends with logits [B,T,V]. During training, every position contributes a target. During sampling, only the final position chooses the next token, which is appended before repeating the forward loop.
Initialize for depth, then prove parity with a known checkpoint.
GPT-2 initializes most linear and embedding weights from a normal distribution with standard deviation near 0.02, with biases at zero and normalization scales at one. Residual branches accumulate through depth, so the output projections of attention and the MLP receive an additional depth-dependent scale.
std_residual = 0.02 / √(2 × number_of_layers)Two residual branches per layer motivate the factor of two.
A strong architecture test is to load the released GPT-2 124M weights and compare logits for a fixed token prefix. Resolve transposed Conv1D-style matrices, tied embeddings, and parameter-name mapping once. If the logits match within tolerance, the module hierarchy, shapes, norms, attention mask, and output head are jointly validated.
Matches reference configuration
Wrong bias, width, or untied head
All names and shapes consumed once
Missing or transposed matrices
Numerical parity within tolerance
Functional architecture mismatch
Loss falls toward zero
Shift, mask, or gradient error
Define the optimizer in global tokens, not device-local batches.
AdamW maintains first and second moments for every trainable tensor and applies decoupled weight decay. Matrix weights receive decay; biases, normalization parameters, and often embeddings are placed in explicit non-decay groups. Fused AdamW can combine many elementwise updates into fewer kernels when the device supports it.
A GPT-style schedule usually warms the learning rate linearly, then decays it with a cosine or similar schedule toward a lower floor. Warmup prevents early updates from overwhelming uncalibrated activations and optimizer moments. Global gradient clipping limits rare spikes before the optimizer step.
G = world_size × micro_batch × sequence_length × accumulation_stepsEvery learning-rate, loss, and throughput comparison should state G.
optimizer.zero_grad(set_to_none=True)
for micro_step in range(accumulation_steps):
x, y = next_batch()
with autocast(dtype=bf16):
loss = model(x, y) / accumulation_steps
loss.backward()
grad_norm = clip_grad_norm_(model.parameters(), max_norm)
optimizer.step()
scheduler.step()Gradient accumulation recreates a large logical batch from several micro-batches. Divide the loss before backpropagation so the accumulated gradient is an average. When using distributed data parallelism, synchronize gradients only on the final micro-step; earlier micro-steps can accumulate locally.
Record optimizer hyperparameters with the checkpoint: learning-rate schedule, warmup steps, decay floor, betas, epsilon, weight decay, clipping threshold, batch tokens, and total trained tokens. A weight file without these cannot explain the run.
Speed comes from reducing memory movement and idle work.
The GPT-2 reproduction lecture improves one implementation step by step: enable TensorFloat-32 for suitable matrix multiplications, use bfloat16 autocasting to reduce traffic while preserving range, compile the graph to remove Python overhead and fuse kernels, use fused causal attention, choose matrix-friendly dimensions, and fuse optimizer work. Each change is timed against the same batch.
Tensor-core arithmetic and memory traffic
Loss parity and numerical stability
Python overhead and kernel fusion
Compile cost, graph breaks, steady-state speed
Avoids materializing the full score matrix
Mask and output parity
Improves matrix-kernel tiling
Added vocabulary rows remain unreachable as targets
Combines optimizer kernels
Parameter-group and update parity
Padding GPT-2’s vocabulary projection from 50,257 to a hardware-friendly multiple such as 50,304 can make the largest matrix multiplication more efficient, even though it adds unused rows. Inputs and targets must still use only real tokenizer IDs.
Distributed data parallelism places one complete model replica on each accelerator. Rank r consumes a disjoint token range, computes local gradients, and participates in all-reduce. Divide the desired global batch by world_size × B × T to obtain the accumulation count, and assert that the division is exact.
tokens_per_microstep = B * T * world_size
assert global_batch_tokens % tokens_per_microstep == 0
accumulation_steps = global_batch_tokens // tokens_per_microstep
loader_offset = rank * B * T
loader_stride = world_size * B * TThe checkpoint must capture the data cursor, rank-independent random state policy, model, optimizer, scheduler, global token count, and scaler state when applicable. On resume, compare the next batch IDs and next loss with an uninterrupted run.
Train and held-out loss vs. tokens
Optimization and overfitting
Gradient norm, skipped steps, NaN count
Stability failures
Tokens/s, utilization, memory, step time
Pipeline bottlenecks
Source and language token shares
Mixture drift
A base model is complete only when the run is explainable.
Before scaling, overfit one tiny batch, compare attention against a slow reference, round-trip the tokenizer, load a known GPT-2 checkpoint, resume from a local checkpoint, and confirm that one-device and multi-device updates agree within tolerance. Then run a short fixed-token experiment that produces a known loss curve and throughput envelope.
During the full run, evaluate held-out token loss at a fixed interval and use a deterministic number of validation batches. Add capability evaluations whose scoring rule is explicit. HellaSwag, for example, compares candidate completion losses after masking the shared context; the candidate with the lowest normalized completion loss is selected. Keep such benchmark scores separate from the training objective and audit contamination.
Sampling is a qualitative probe. Use fixed prompts, seeds, temperature, top-k policy, and maximum length so checkpoint comparisons are meaningful. A coherent sample can reveal catastrophic data or masking errors, but it cannot replace held-out loss or task evaluation.
run_manifestCode revision, configuration, hardware, numerical mode, kernels, dependencies
data_manifestSources, filters, mixture, tokenizer, shards, splits, contamination checks
checkpointWeights, optimizer, scheduler, random state, data cursor, global tokens
learning_curvesTrain and validation loss by tokens, gradient norm, learning rate
system_profileTokens per second, utilization, memory, communication, step-time distribution
evaluation_reportDomain losses, fixed tasks, sampling configuration, failure examples
Performance is part of intelligence.
A system that wastes half its accelerator time learns less under the same budget. Throughput, stability, and recoverability determine how many hypotheses can be tested and how much clean learning can be purchased. The pre-training artifact is therefore more than weights: it is the tokenizer, data manifest, model configuration, checkpoint state, and evidence that the run behaved as intended.
14:56–42:47 ↗Unicode, UTF encodings, byte-pair encoding, implementation, and compression
42:47–01:26:28 ↗Decoding, encoding, regex splits, model-specific rules, and special tokens
01:28:42–02:13:34 ↗SentencePiece, vocabulary design, new tokens, multimodal units, quirks, and recommendations
07:52–42:13 ↗Corpus exploration, token splits, batches, bigram baseline, training loop, and contextual averaging
13:47–01:22:18 ↗GPT-2 module, weight loading, logits, sampling, batches, loss, weight tying, and initialization
01:22:18–03:23:10 ↗Precision, compilation, fused attention, optimizer, schedule, accumulation, DDP, and data
03:23:10–04:01:26 ↗Validation, HellaSwag, sampling, result comparison, and final run summary
- Timestamped transcript — Let’s build the GPT Tokenizer ↗
- Timestamped transcript — Let’s build GPT from scratch ↗
- Timestamped transcript — Let’s reproduce GPT-2 ↗
- Attention Is All You Need ↗
- Language Models are Unsupervised Multitask Learners ↗
- Scaling Laws for Neural Language Models ↗
The three lecture transcripts define this chapter’s implementation coverage. The papers, released configurations, and explicit verification tests are used to check the technical details. The resulting chapter is a synthesis, not a transcript reproduction.