karpathy/mingpt

A minimal PyTorch re-implementation of the OpenAI GPT (Generative Pretrained Transformer) training

24,514 stars Python 9 components

13 hidden assumptions · 7-stage pipeline · 9 components

Like any codebase, this ml training makes assumptions it never checks — most are routine. The ones worth your attention are below, in plain language with what to do about each.

Trains and runs GPT language models from scratch using PyTorch

Data enters the system either as raw text (converted by BPETokenizer or CharDataset into integer token ID sequences) or as synthetically generated integer sequences (AdditionDataset). The Trainer's DataLoader samples random batches of (x, y) LongTensor pairs — each a window of block_size tokens. The GPT model embeds each token ID into an n_embd-dimensional vector, adds positional embeddings, then passes the sequence through n_layer transformer Blocks (each doing masked self-attention + MLP with residual connections). The final hidden states are projected to vocab_size logits; cross-entropy loss against y is backpropagated through the entire network. The AdamW optimizer updates all weights. During generation (inference), the model runs forward passes one token at a time, sampling the next token from the logits at the last position, appending it to the sequence, and repeating — a loop that continues for max_new_tokens steps.

Under the hood, the system uses 4 feedback loops, 4 data pools, 7 control points to manage its runtime behavior.

A 9-component ml training. 9 files analyzed. Data flows through 7 distinct pipeline stages.

Hidden Assumptions

Most of what this code assumes is routine. These 3 are the ones most likely to cause trouble here — in plain terms, with what to do about each. The rest are minor; they're under "Show everything".

Worth a check

The model only understands word positions up to the maximum sequence length it was trained on. The authors note that one design choice might let the model handle longer sequences, but they did not demonstrate this for the kind of position handling used here, so feeding longer passages is untested territory.

What to do: Keep your inputs and generated outputs within the maximum sequence length the model was set up for, and treat anything longer as unvalidated.

From the paper

“We chose the sinusoidal version because it may allow the model to extrapolate to sequence lengths longer than the ones encountered during training.”

Read it in the paper · Model Architecture - Positional Encoding ↗
Worth a check

The way the model reads a sequence requires comparing every token to every other token, so the effort grows sharply as sequences get longer. The authors say this is only efficient for relatively short sequences and flagged handling very long inputs as unsolved future work.

What to do: Avoid feeding very long sequences and expect cost and memory to rise steeply with length, since the authors only validated the efficient regime of shorter sequences.

From the paper

“self-attention layers are faster than recurrent layers when the sequence length n 𝑛 n is smaller than the representation dimensionality d 𝑑 d”

Read it in the paper · Why Self-Attention ↗
Good to know

The strong results reported came with particular training settings, including techniques to prevent the model from memorizing the data. The authors note these were important; training without them can lead to worse, over-fitted results that won't match the paper's reported quality.

What to do: If you change or drop the regularization and learning-rate warmup settings, expect results to diverge from the paper's reported quality, especially on smaller data.

From the paper

“bigger models are better, and dropout is very helpful in avoiding over-fitting”

Read it in the paper · Model Variations ↗
Show everything (10 more)
Contract

The model needs to know how many unique characters (or words) are in your text before it's built, and that number comes from reading the text itself. There's no automatic handshake between the two steps — if you swap in a different text file or forget to wire them together, the model either crashes immediately or quietly trains with a mismatched internal table, producing garbage or incompatible saved files.

What to do: After loading your dataset and before building the model, double-check that the model's vocabulary size is set to exactly the number of unique characters your text contains — if those two numbers don't match, nothing downstream will be correct.

projects/chargpt/chargpt.py:CharDataset.__init__ and mingpt/model.py:GPT.__init__
Scale

The entire text file you point this at gets loaded into memory all at once before training starts. If your file is large — say, a novel-length corpus or anything over a few hundred megabytes on a modest laptop — the program will either crash with an out-of-memory error or slow to a crawl before you ever see a training step. There's no warning.

What to do: Before starting a long training run, check that your text file is well within the free RAM on your machine; as a rough guide, keep the file size under a quarter of your available memory.

projects/chargpt/chargpt.py:CharDataset.__init__
Environment

The first time you use the text-generation tokenizer, it downloads two vocabulary files from the internet and saves them. If your connection drops mid-download, the broken files are kept and reused every time after that — no re-download happens. The program won't tell you anything is wrong, but the text the model generates will be garbled because it's working from an incomplete rulebook.

What to do: If generation results look strange after a first run, delete the cached vocabulary files and let them re-download cleanly on a stable connection.

mingpt/bpe.py:BPETokenizer.__call__
Contract

Loading pretrained GPT-2 weights relies on a fixed list of internal names that must match exactly what the HuggingFace library uses. If you install a newer version of that library and the names changed even slightly, the load either crashes or silently puts weights in the wrong places — the model then generates nonsense, indistinguishable from a model that loaded correctly.

What to do: Pin the HuggingFace transformers library to the version the project was developed against, and don't upgrade it without re-testing pretrained weight loading.

mingpt/model.py:GPT.from_pretrained
Scale

If you start training without telling it how many steps to run, and without writing special stopping code, the training loop will run essentially forever — there's no default stopping point. It will quietly consume your compute resources until you manually kill it.

What to do: Always set a maximum number of training steps explicitly in your config before starting a run.

mingpt/trainer.py:Trainer.run
Domain

The arithmetic dataset splits problems into training and testing groups using a shuffle that must happen after the random seed is set. If the seed isn't set first, the split is random each run and some test problems may have been trained on — making the accuracy score on the test set meaninglessly optimistic and different every run.

What to do: Make sure the random seed is set at the very top of your script, before any dataset is created, to guarantee a consistent and uncontaminated train/test split.

projects/adder/adder.py:AdditionDataset.__init__
Ordering

The model has a hard limit on how long an input sequence can be, set at construction time. If you write your own code that feeds the model a sequence longer than that limit — even by one token — it crashes with a confusing error. The built-in generation tool handles this correctly, but any custom use doesn't.

What to do: When writing your own inference code, always trim your input to the model's maximum sequence length before passing it in.

mingpt/model.py:CausalSelfAttention.__init__ and forward
Domain

When overriding settings from the command line, values are interpreted as Python expressions. On Windows, folder paths with backslashes can be silently misread — for example, a backslash followed by certain letters gets turned into a special character. The program won't warn you, and you'll get a confusing file-not-found error later.

What to do: On Windows, use forward slashes in any path you pass as a command-line override, or wrap the path in quotes and double the backslashes.

mingpt/utils.py:CfgNode.merge_from_args
Contract

The training optimizer decides which parts of the model to apply regularization to based purely on a simple rule about the shape of each parameter. If you add your own layers to the model, some of them may accidentally get regularized when they shouldn't, which can quietly hurt training quality without any error or warning.

What to do: If you extend the model with custom layers, verify that the optimizer is applying regularization only to the parameters you intend — check the printed parameter group sizes match your expectations.

mingpt/model.py:GPT.configure_optimizers
Temporal

The tokenizer saves results in a memory cache to avoid repeating work. This cache is never cleared and never limited in size. For short scripts this is fine, but for a long-running service or very large text, the cache keeps growing until your machine runs out of memory.

What to do: For long-running deployments, periodically restart the tokenizer instance or replace the cache with a size-limited version to prevent gradual memory growth.

mingpt/bpe.py:Encoder.bpe

Open the standalone hidden-assumptions report for mingpt →

How Data Flows Through the System

Data enters the system either as raw text (converted by BPETokenizer or CharDataset into integer token ID sequences) or as synthetically generated integer sequences (AdditionDataset). The Trainer's DataLoader samples random batches of (x, y) LongTensor pairs — each a window of block_size tokens. The GPT model embeds each token ID into an n_embd-dimensional vector, adds positional embeddings, then passes the sequence through n_layer transformer Blocks (each doing masked self-attention + MLP with residual connections). The final hidden states are projected to vocab_size logits; cross-entropy loss against y is backpropagated through the entire network. The AdamW optimizer updates all weights. During generation (inference), the model runs forward passes one token at a time, sampling the next token from the logits at the last position, appending it to the sequence, and repeating — a loop that continues for max_new_tokens steps.

  1. Encode raw input to token IDs — For text inputs, BPETokenizer.encode() maps each byte of the UTF-8 string to a unicode character via bytes_to_unicode(), splits into word-chunks using the GPT-2 regex pattern, then applies BPE merge rules in bpe_ranks priority order to produce a list of integer token IDs. For CharDataset, each character is looked up in stoi. For AdditionDataset, digits 0-9 are used directly as token IDs. (config: data.block_size, data.ndigit)
  2. Sample batch from DataLoader — Trainer.run() wraps the Dataset in a DataLoader with a RandomSampler set to sample 1e10 items with replacement — effectively infinite. Each call to next(data_iter) yields a (x, y) tuple where both are LongTensor[batch_size, block_size]. x is the input context and y is the target (x shifted left by one, so position i of y is the correct next token after position i of x). [TokenSequenceBatch → TokenSequenceBatch] (config: trainer.batch_size, trainer.num_workers)
  3. Embed tokens and positions — GPT.forward() looks up each token ID in self.transformer.wte (an nn.Embedding of shape vocab_size × n_embd) and each position index (0..T-1) in self.transformer.wpe (an nn.Embedding of shape block_size × n_embd). The two embeddings are added elementwise to produce a FloatTensor[B, T, n_embd], then dropout is applied. [TokenSequenceBatch → EmbeddedSequence] (config: model.n_embd, model.vocab_size, model.block_size +1)
  4. Transformer block stack (N layers of attention + MLP) — The embedded sequence is passed sequentially through n_layer Block modules stored in self.transformer.h. Each Block first applies LayerNorm, then CausalSelfAttention: a fused linear projects to Q/K/V (each split into n_head heads of size n_embd/n_head), computes (Q @ K^T) / sqrt(head_dim), masks the upper triangle to -inf to prevent future-token access, softmax-normalizes, dropout-applies, multiplies by V, and projects back. The attention output is residually added to the input. Then LayerNorm → Linear(n_embd → 4*n_embd) → NewGELU → Linear(4*n_embd → n_embd) → residual add. [EmbeddedSequence → EmbeddedSequence] (config: model.n_layer, model.n_head, model.n_embd +2)
  5. Project to vocabulary logits and compute loss — After the final Block, GPT applies a LayerNorm (self.transformer.ln_f) then the language model head (self.lm_head: a linear layer with shape n_embd × vocab_size, with weights tied to the token embedding matrix wte). This produces logits of shape [B, T, vocab_size]. If targets y are provided, F.cross_entropy is computed over the flattened [B*T, vocab_size] logits against [B*T] targets, yielding a scalar loss. [EmbeddedSequence → LogitsAndLoss] (config: model.vocab_size)
  6. Backpropagate and update weights — Trainer.run() calls loss.backward() to compute gradients through the entire model via PyTorch autograd. torch.nn.utils.clip_grad_norm_ clips the global gradient norm to grad_norm_clip (default 1.0) to prevent exploding gradients. self.optimizer.step() then applies the AdamW update (configured with separate weight decay: 0.1 for weight matrices, 0.0 for biases and LayerNorm params, as split by GPT.configure_optimizers). iter_num increments, timing stats update, and 'on_batch_end' callbacks fire. [LogitsAndLoss] (config: trainer.learning_rate, trainer.betas, trainer.weight_decay +1)
  7. Autoregressive generation (inference) — GPT.generate() takes an initial token sequence idx of shape [B, T], then loops max_new_tokens times. Each iteration: crops idx to the last block_size tokens if it's too long, calls GPT.forward() to get logits, slices only the last position's logits [B, vocab_size], optionally divides by temperature, optionally keeps only the top-k values (setting the rest to -inf), applies softmax, and samples or argmax-selects one token ID per batch item. The new ID is appended to idx and the loop repeats. [TokenSequenceBatch → TokenSequenceBatch] (config: model.block_size)

Data Models

The data structures that flow between stages — the contracts that hold the system together.

TokenSequenceBatch mingpt/trainer.py
Tuple of (x: LongTensor[batch_size, block_size], y: LongTensor[batch_size, block_size]) where x is the input token IDs and y is x shifted left by one position (each position's label is the next token)
Created by each Dataset's __getitem__ slicing a chunk of block_size+1 tokens, split into x and y; consumed by GPT.forward() which computes cross-entropy loss between predicted logits and y.
GPTConfig mingpt/model.py
CfgNode with fields: model_type (str, e.g. 'gpt2', 'gpt-nano'), n_layer (int), n_head (int), n_embd (int, embedding dimension), vocab_size (int), block_size (int, max sequence length), embd_pdrop (float), resid_pdrop (float), attn_pdrop (float)
Created via GPT.get_default_config(), populated with a model_type which triggers lookup of preset (n_layer, n_head, n_embd) values in GPT.__init__; frozen after model construction.
TrainerConfig mingpt/trainer.py
CfgNode with fields: device (str), num_workers (int), max_iters (int or None), batch_size (int, default 64), learning_rate (float, default 3e-4), betas (tuple (0.9, 0.95)), weight_decay (float, default 0.1), grad_norm_clip (float, default 1.0)
Created by Trainer.get_default_config(), merged with project overrides, and read throughout Trainer.run() to control DataLoader construction, optimizer hyperparameters, and stopping condition.
EmbeddedSequence mingpt/model.py
FloatTensor[batch_size, sequence_length, n_embd] — each token ID has been looked up in the token embedding table and summed with its positional embedding
Computed in GPT.forward() by adding token_embedding (from nn.Embedding of size vocab_size × n_embd) and position_embedding (from nn.Embedding of size block_size × n_embd), then dropout-regularized before entering the Block stack.
AttentionWeights mingpt/model.py
FloatTensor[batch_size, n_head, seq_len, seq_len] — a matrix where entry [b, h, i, j] is the attention score from position i to position j in head h; upper triangle is masked to -inf before softmax to enforce causality
Computed inside CausalSelfAttention.forward() as (Q @ K^T) / sqrt(head_dim), masked with a lower-triangular buffer registered at init time, softmax-normalized, dropout-applied, then used to weight-sum V.
LogitsAndLoss mingpt/model.py
Tuple of (logits: FloatTensor[batch_size, seq_len, vocab_size], loss: scalar FloatTensor or None) — logits are unnormalized log-probabilities over the vocabulary at each position; loss is cross-entropy averaged over all positions when targets are supplied
Output of GPT.forward(); if targets (y) are provided, loss is computed via F.cross_entropy and returned for backprop; during generation, only logits at the last position are used for sampling the next token.
BPETokenizerState mingpt/bpe.py
Encoder instance holding: encoder dict (str→int, 50257 entries), decoder dict (int→str), bpe_ranks dict (tuple→int, ~50000 merge rules), byte_encoder/byte_decoder (int↔unicode char, 256 entries), pat regex for pre-tokenization splitting
Loaded once by BPETokenizer.__call__() which downloads encoder.json and vocab.bpe from OpenAI's CDN on first use and caches them locally; used to encode prompt strings into LongTensor[1, seq_len] for generation.

System Behavior

How the system operates at runtime — where data accumulates, what loops, what waits, and what controls what.

Data Pools

BPE Vocabulary Cache (file-store)
encoder.json (50,257 token→ID mappings) and vocab.bpe (~50,000 BPE merge rules) downloaded from OpenAI's CDN on first use and cached at a local path. BPETokenizer checks for file existence before downloading, so this is a write-once persistent cache.
Training Dataset (in-memory) (in-memory)
AdditionDataset stores all possible addition problems as a list of integer tensors in RAM. CharDataset stores the entire input text file as a Python string. Both are accessed by index via the DataLoader's RandomSampler.
Model Checkpoint (file-store)
Project callbacks triggered on 'on_batch_end' can save model.state_dict() to the work_dir (e.g. ./out/adder/model.pt). The checkpoint stores all learned parameters (embedding tables, attention weights, MLP weights, LayerNorm scales) as a dict of named tensors.
Config and Args Log (file-store)
setup_logging() writes sys.argv to args.txt and the full CfgNode config serialized as JSON to config.json in the work_dir. Written once at training start, useful for reproducing runs.

Feedback Loops

Delays

Control Points

Technology Stack

PyTorch (framework)
Core tensor computation, autograd (backpropagation), nn.Module system for model definition, DataLoader for batching, and AdamW optimizer
Python (runtime)
Primary implementation language for all model, training, and tokenization code
regex (re) (library)
GPT-2's pre-tokenization regex pattern in bpe.py uses the `regex` library (not stdlib `re`) for Unicode property escapes like \p{L} (letters) and \p{N} (numbers)
requests (library)
Downloads GPT-2 BPE vocab files (encoder.json, vocab.bpe) from OpenAI's CDN in bpe.py on first use
numpy (library)
Used in set_seed() for reproducibility seeding and minor array operations in bpe.py
HuggingFace transformers (library)
Used in GPT.from_pretrained() to load official GPT-2 weights, and in the test suite to verify minGPT's outputs match HuggingFace's exactly
setuptools (build)
Packages mingpt as an installable Python library via pip install -e .
unittest (testing)
Test framework for test_huggingface_import.py which validates that minGPT produces identical logits and generated sequences to HuggingFace's GPT-2 implementation

Key Components

Explore the interactive analysis

See the full architecture map, data flow, and code patterns visualization.

Analyze on CodeSea

Compare minGPT

Related Ml Training Repositories

Frequently Asked Questions

What is minGPT used for?

Trains and runs GPT language models from scratch using PyTorch karpathy/mingpt is a 9-component ml training written in Python. Data flows through 7 distinct pipeline stages. The codebase contains 9 files.

How is minGPT architected?

minGPT is organized into 5 architecture layers: Tokenization, Model (Transformer), Training Loop, Configuration, and 1 more. Data flows through 7 distinct pipeline stages. This layered structure keeps concerns separated and modules independent.

How does data flow through minGPT?

Data moves through 7 stages: Encode raw input to token IDs → Sample batch from DataLoader → Embed tokens and positions → Transformer block stack (N layers of attention + MLP) → Project to vocabulary logits and compute loss → .... Data enters the system either as raw text (converted by BPETokenizer or CharDataset into integer token ID sequences) or as synthetically generated integer sequences (AdditionDataset). The Trainer's DataLoader samples random batches of (x, y) LongTensor pairs — each a window of block_size tokens. The GPT model embeds each token ID into an n_embd-dimensional vector, adds positional embeddings, then passes the sequence through n_layer transformer Blocks (each doing masked self-attention + MLP with residual connections). The final hidden states are projected to vocab_size logits; cross-entropy loss against y is backpropagated through the entire network. The AdamW optimizer updates all weights. During generation (inference), the model runs forward passes one token at a time, sampling the next token from the logits at the last position, appending it to the sequence, and repeating — a loop that continues for max_new_tokens steps. This pipeline design reflects a complex multi-stage processing system.

What technologies does minGPT use?

The core stack includes PyTorch (Core tensor computation, autograd (backpropagation), nn.Module system for model definition, DataLoader for batching, and AdamW optimizer), Python (Primary implementation language for all model, training, and tokenization code), regex (re) (GPT-2's pre-tokenization regex pattern in bpe.py uses the `regex` library (not stdlib `re`) for Unicode property escapes like \p{L} (letters) and \p{N} (numbers)), requests (Downloads GPT-2 BPE vocab files (encoder.json, vocab.bpe) from OpenAI's CDN in bpe.py on first use), numpy (Used in set_seed() for reproducibility seeding and minor array operations in bpe.py), HuggingFace transformers (Used in GPT.from_pretrained() to load official GPT-2 weights, and in the test suite to verify minGPT's outputs match HuggingFace's exactly), and 2 more. A focused set of dependencies that keeps the build manageable.

What system dynamics does minGPT have?

minGPT exhibits 4 data pools (BPE Vocabulary Cache, Training Dataset (in-memory)), 4 feedback loops, 7 control points, 3 delays. The feedback loops handle training-loop and convergence. These runtime behaviors shape how the system responds to load, failures, and configuration changes.

What design patterns does minGPT use?

5 design patterns detected: Callback-based training hooks, Hierarchical config with CLI override, Weight tying (embedding and output projection share parameters), Selective weight decay (parameter grouping), Fused QKV projection.

How does minGPT compare to alternatives?

CodeSea has side-by-side architecture comparisons of minGPT with nanogpt. These comparisons show tech stack differences, pipeline design, system behavior, and code patterns. See the comparison pages above for detailed analysis.

Analyzed on June 9, 2026 by CodeSea. Written by .