Task: Natural Language Processing | Architecture: Generative Pre-trained Transformer (GPT-2)
| Parameter | Value |
|---|---|
| Parameters | 162,419,712 |
| Model size | 619.58 MB |
| Context length | 256 |
| Embedding dimension | 768 |
| Attention heads | 12 |
| Transformer layers | 12 |
This project replicates the GPT-2 language model [1] from scratch using PyTorch, following the architectural specification of the original paper. All core components — tokenization, embedding, multi-head causal self-attention, feed-forward layers, and layer normalization — are implemented as standalone modules. The model is trained on the Harry Potter corpus [8] using a standard language modeling objective. Code reference follows Raschka [9].
The model follows the standard decoder-only Transformer pipeline. For an input sequence of token indices, the forward pass proceeds as:
where
Text is tokenized using OpenAI's Byte Pair Encoding (BPE) scheme [7] via tiktoken with the gpt2 vocabulary (
A simpler whitespace-and-punctuation tokenizer (CustomTokenizer) is also implemented for reference, operating over the corpus vocabulary only.
The input representation is formed by summing token and positional embeddings [3]:
where both are learned lookup tables:
The positional embedding is learned rather than fixed, consistent with the GPT-2 specification.
Scaled dot-product attention is computed as [2]:
where
Setting future positions to
For multi-head attention with
In the implementation, the head split is performed via .view(B, T, h, d_k).transpose(1, 2) rather than running
The feed-forward sublayer uses the tanh approximation of the Gaussian Error Linear Unit (GELU) [6]:
This is the exact approximation used in the original GPT-2 codebase, and is reproduced verbatim in Activation.py.
Each sublayer applies pre-norm layer normalization [5] with learnable parameters
where:
(biased estimator, unbiased=False), and
The position-wise feed-forward network expands the embedding dimension by a factor of 4 before projecting back:
where
Each of the
The corpus is chunked into training samples using a sliding window over the full token sequence. Given a tokenized corpus of length
Each target is the input shifted by one position, encoding the next-token prediction objective directly in the dataset construction. When
The corpus is split 90/10 into train and validation sets prior to windowing, and fed to the model in batches of 32.
The model is trained with a standard language modeling objective — cross-entropy loss over next-token prediction. For a sequence of targets
Perplexity, reported as a secondary metric, is the exponentiated average negative log-likelihood:
The optimizer is AdamW with learning rate ./model/ after each training run and automatically reloaded on subsequent runs.
0_main.py
│
├── Typewriter.py
├── Tokenizer.py
├── DataLoader.py
└── GPT.py
│
├── Tokenizer.py
├── Embedding.py
│ ├── Tokenizer.py
│ └── DataLoader.py
├── MaskDropout.py
│ └── Softmax.py
├── LayerNorm.py
└── TransformerBlock.py
├── MultiHeadAttention.py
│ └── MaskDropout.py
│ └── Softmax.py
├── Activation.py
├── LayerNorm.py
└── MaskDropout.py
└── Softmax.py
1_chat.py
Note: SelfAttention.py is not part of the active pipeline; it is retained as a pedagogical progression from simplified → trainable → batched self-attention, illustrating the derivation of the full MultiHeadAttention implementation.
Training
python 0_main.pyTrains the model for 100 epochs on harrypotter.txt, evaluating every 5 steps and printing a sample generation after each epoch. Saves the final checkpoint to ./model/GPT2-epoch-100.pt. If a checkpoint already exists in ./model/, it is loaded automatically before training resumes.
Inference
python 1_chat.pyLoads the latest checkpoint and opens an interactive prompt. Input is prefixed as Voldemort: <input>. Harry: and the model generates a continuation of up to 100 tokens using greedy decoding (argmax). Type stop to exit.
Note: Inference uses a context window of 50 tokens, smaller than the training context length of 256. Only the most recent 50 tokens are visible to the model during generation.
[1] Radford, A., Wu, J., Child, R., Luan, D., Amodei, D., & Sutskever, I. (2019). Language Models are Unsupervised Multitask Learners. OpenAI Blog. https://cdn.openai.com/better-language-models/language_models_are_unsupervised_multitask_learners.pdf
[2] Vaswani, A., Shazeer, N., Parmar, N., Uszkoreit, J., Jones, L., Gomez, A. N., Kaiser, L., & Polosukhin, I. (2017). Attention Is All You Need. NeurIPS 2017. https://arxiv.org/abs/1706.03762
[3] Mikolov, T., Sutskever, I., Chen, K., Corrado, G., & Dean, J. (2013). Distributed Representations of Words and Phrases and their Compositionality. NeurIPS 2013. https://arxiv.org/abs/1310.4546
[4] Srivastava, N., Hinton, G., Krizhevsky, A., Sutskever, I., & Salakhutdinov, R. (2014). Dropout: A Simple Way to Prevent Neural Networks from Overfitting. JMLR, 15(1), 1929–1958. https://www.cs.toronto.edu/~rsalakhu/papers/srivastava14a.pdf
[5] Ba, J. L., Kiros, J. R., & Hinton, G. E. (2016). Layer Normalization. arXiv:1607.06450. https://arxiv.org/abs/1607.06450
[6] Hendrycks, D., & Gimpel, K. (2016). Gaussian Error Linear Units (GELUs). arXiv:1606.08415. https://arxiv.org/abs/1606.08415
[7] Gage, P. (1994). A new algorithm for data compression. C Users Journal, 12(2). https://dl.acm.org/doi/abs/10.5555/177910.177914
[8] Kudła, M. Harry Potter all books (preprocessed). Kaggle. https://www.kaggle.com/datasets/moxxis/harry-potter-lstm
[9] Raschka, S. Build a Large Language Model (From Scratch). GitHub. https://github.com/rasbt/LLMs-from-scratch