A from-scratch, educational implementation of Transformer and Large Language Model fundamentals in Python.
HowLLMsWork is a learning-oriented implementation that builds the core path from tokens to next-token generation step by step.
The goal is not to reproduce the scale or performance of production LLMs. The goal is to make the internal mechanics of a language model explicit, inspectable, testable, and understandable.
Focus: understand the mathematics, data flow, training loop, autoregressive inference, sampling, and KV-cache mechanics by implementing them directly.
The project follows the conceptual pipeline:
Text
↓
Tokenization
↓
Vocabulary
↓
Training examples
↓
Token embeddings
↓
Positional information
↓
Q / K / V projections
↓
Scaled dot-product attention
↓
Multi-head attention
↓
Residual connection + normalization
↓
Feed-forward network
↓
Transformer decoder
↓
Vocabulary projection
↓
Logits
↓
Cross-entropy loss
↓
Backpropagation
↓
Parameter updates
↓
Next-token prediction
↓
Autoregressive generation
↓
Sampling
↓
KV cache
↓
Prefill / decode
The repository implements these ideas as small Python components rather than hiding them behind a high-level deep-learning framework.
- Vocabulary management
- Tokenization
- Language-model examples
- Causal next-token targets
- Batching
- Simple context language model
- Positional context language model
- Transformer language model
- Cached Transformer language model
- Token embeddings
- Positional encoding
- Query / Key / Value projection
- Scaled dot-product attention
- Multi-head attention
- Residual connections
- Layer normalization
- Feed-forward networks
- Transformer decoder block
- Vocabulary projection
- Causal language-model objective
- Cross-entropy loss
- Gradient computation
- Backpropagation through Transformer components
- Parameter updates
- Training loops
- Evaluation
- Next-token prediction
- Greedy generation
- Temperature sampling
- Top-K sampling
- Top-P / nucleus sampling
- Pluggable sampling strategies
- Generation backends
- KV cache
- Cached attention
- Cached multi-head attention
- Cached Transformer backbone
- Prefill / decode API
- Cached generation
- Sliding context-window handling
A token ID is mapped to a learned vector:
where:
-
$E \in \mathbb{R}^{V \times d}$ is the embedding matrix -
$V$ is the vocabulary size -
$d$ is the model dimension -
$e_i \in \mathbb{R}^{d}$ is the representation of token$i$
For a sequence of
Self-attention by itself does not inherently encode token order, so positional information is added to token representations.
A classical sinusoidal positional encoding is:
and the input to the Transformer can be written as:
The repository also contains simplified positional mechanisms in some educational components. The important idea is the same: the model needs information about where a token occurs.
For hidden states
where:
and
These projections create three different views of the same hidden states:
- Query: what this position is looking for
- Key: what this position offers for matching
- Value: what information this position contributes
The core attention equation is:
The scaling factor prevents the dot products from growing too large as the dimension increases.
For a causal decoder, future positions must not be visible. Conceptually this is implemented with a causal mask:
where masked future positions receive a value approaching
Given logits
The resulting probabilities satisfy:
and:
Instead of using one attention operation, a Transformer uses multiple heads:
The heads are concatenated:
and projected:
The project contains both a trainable multi-head implementation and a cached incremental version.
A sublayer output is combined with its input:
This allows information to flow through the network while making deeper optimization easier.
The repository explicitly models residual operations because they are an important part of the Transformer computation graph.
For a vector
Then:
where
A Transformer feed-forward block can be represented as:
where
Conceptually, the attention mechanism mixes information between positions, while the feed-forward network transforms each position's representation.
A simplified decoder block follows the pattern:
followed by:
and another normalization step depending on the exact block formulation.
The project intentionally keeps these operations explicit so the intermediate values can be inspected.
The final hidden representation is projected into vocabulary space:
where:
$H \in \mathbb{R}^{n \times d}$ $W_{out} \in \mathbb{R}^{d \times V}$
giving:
Each row contains the logits for predicting the next token at that position.
For a sequence:
tokens: x₁ x₂ x₃ x₄
the training objective shifts the sequence:
input: x₁ x₂ x₃
target: x₂ x₃ x₄
The model learns:
for each position
The complete autoregressive objective is:
Usually the mean is taken over the training positions.
For a target token
For a sequence of
A lower loss means the model assigns higher probability to the correct target tokens.
Training computes gradients of the loss with respect to model parameters:
The parameters are then updated using a simple gradient-descent rule:
where
The repository includes explicit backward implementations for important Transformer components so the gradient flow can be studied rather than treated as a black box.
At inference time the model produces a vocabulary-sized logit vector:
A deterministic approach chooses:
This is the basic greedy strategy.
But generation does not have to be deterministic.
Temperature rescales logits:
and probabilities become:
Interpretation:
-
$T < 1$ : sharper distribution -
$T = 1$ : original distribution -
$T > 1$ : flatter distribution
The project demonstrates this effect explicitly.
Top-K sampling keeps only the
Let
All other token probabilities are removed and the remaining probabilities are renormalized:
Then sampling occurs only from the reduced candidate set.
Top-P, or nucleus sampling, sorts candidates by probability and retains the smallest set whose cumulative probability reaches
Tokens outside the nucleus are removed before sampling.
This allows the candidate set to adapt to the shape of the current distribution.
Suppose the prompt is:
the cat drinks milk
Generation proceeds iteratively:
prompt
↓
Transformer
↓
next-token logits
↓
sampling strategy
↓
new token
↓
append token
↓
repeat
Mathematically:
The sequence is therefore generated one token at a time.
The repository contains both direct generation and cached generation paths.
A major cost of autoregressive generation is repeatedly recomputing keys and values for tokens that have already been processed.
Without a KV cache:
Step 1 → process token 1
Step 2 → process tokens 1..2 again
Step 3 → process tokens 1..3 again
Step 4 → process tokens 1..4 again
...
With a KV cache:
Prompt
↓
Prefill
↓
store K/V
↓
new token
↓
compute only new K/V
↓
append to cache
↓
attend against cached K/V
For each attention head, the cache stores:
and:
For the new query
The key point is that previous
The repository makes the two inference phases explicit.
The complete prompt is processed:
prompt = [x₁, x₂, x₃, x₄]
↓
cache contains K/V for all prompt positions
The engine returns the logits needed to generate the next token.
Only a newly generated token is processed:
x₅
↓
new Q/K/V
↓
append K/V
↓
attend over cached history
↓
predict x₆
This gives the conceptual interface:
prefill(prompt)
↓
next logits
↓
decode(next_token)
↓
next logits
↓
decode(...)
The cache cannot grow without bounds when a fixed context size is being enforced.
For a context size of 4:
[0, 1, 2, 3]
then, after generation:
[1, 2, 3, 4]
then:
[2, 3, 4, 5]
The repository keeps the low-level Transformer backbone bounded while the generation layer replays the latest context window when necessary.
This makes the distinction explicit:
Backbone
↓
fixed context contract
Generation layer
↓
sliding-window policy
That separation is intentional.
HowLLMsWork/
│
├── src/
│ │
│ ├── tokenization/
│ │ ├── tokenizer.py
│ │ └── vocabulary.py
│ │
│ ├── attention/
│ │ ├── qkv_projection.py
│ │ ├── scaled_dot_product.py
│ │ ├── multi_head.py
│ │ ├── kv_cache.py
│ │ ├── cached_attention.py
│ │ └── cached_multi_head.py
│ │
│ ├── llm/
│ │ ├── language_model.py
│ │ ├── simple_language_model.py
│ │ ├── positional_language_model.py
│ │ ├── transformer_backbone.py
│ │ ├── transformer_language_model.py
│ │ ├── cached_transformer_backbone.py
│ │ └── cached_transformer_language_model.py
│ │
│ ├── training/
│ │ ├── dataset.py
│ │ ├── batch.py
│ │ ├── causal_examples.py
│ │ ├── language_model_objective.py
│ │ ├── language_model_training.py
│ │ ├── model_evaluation.py
│ │ ├── positional_language_model_training.py
│ │ ├── transformer_training_bridge.py
│ │ └── transformer_session_client.py
│ │
│ ├── inference/
│ │ ├── next_token.py
│ │ ├── sampling.py
│ │ ├── sampling_strategy.py
│ │ ├── top_k_sampling.py
│ │ ├── top_p_sampling.py
│ │ ├── transformer_inference.py
│ │ ├── cached_transformer_inference.py
│ │ ├── generator.py
│ │ ├── cached_generator.py
│ │ ├── prefill_decode.py
│ │ ├── generation_backend.py
│ │ ├── legacy_generation_backend.py
│ │ ├── cached_generation_backend.py
│ │ └── unified_generator.py
│ │
│ └── experiments/
│ └── step-by-step demonstrations
│
└── tests/
└── automated unit and integration tests
The easiest way to understand the repository is to follow the concepts in this order.
Start with:
src/tokenization/
src/experiments/tokenization_demo.py
Understand how text becomes token IDs.
Explore:
src/training/dataset.py
src/training/causal_examples.py
Understand why the input and target sequences are shifted.
Explore:
src/llm/simple_language_model.py
src/llm/positional_language_model.py
This gives a simpler baseline before introducing attention.
Study:
src/attention/qkv_projection.py
src/attention/scaled_dot_product.py
src/attention/multi_head.py
Recommended demonstrations:
qkv_projection_demo.py
scaled_attention_demo.py
multi_head_attention_demo.py
Move to:
src/llm/transformer_language_model.py
Then inspect the Transformer backbone and decoder components represented in the project.
Explore:
src/training/language_model_objective.py
src/training/language_model_training.py
src/training/transformer_training_bridge.py
The central loop is:
forward
↓
loss
↓
gradient
↓
parameter update
↓
repeat
Explore:
src/inference/next_token.py
src/experiments/next_token_demo.py
Study:
src/inference/generator.py
src/inference/sampling_strategy.py
Then compare:
Greedy
Temperature
Top-K
Top-P
Finally study:
src/attention/kv_cache.py
src/attention/cached_attention.py
src/attention/cached_multi_head.py
and:
src/inference/prefill_decode.py
src/inference/cached_generator.py
This is where the project moves from basic Transformer mechanics into the mechanics of efficient autoregressive inference.
The repository contains focused executable demonstrations.
python -m src.experiments.tokenization_demopython -m src.experiments.objective_demopython -m src.experiments.qkv_projection_demopython -m src.experiments.scaled_attention_demopython -m src.experiments.multi_head_attention_demopython -m src.experiments.next_token_demopython -m src.experiments.temperature_demopython -m src.experiments.top_k_demopython -m src.experiments.top_p_demopython -m src.experiments.kv_cache_demopython -m src.experiments.prefill_decode_demopython -m src.experiments.cached_generation_demopython -m src.experiments.cached_sampling_demopython -m src.experiments.end_to_end_transformer_trainingOther experiments are available under src/experiments/.
The project is heavily test-driven.
Run the complete test suite:
python -m pytestThe current repository contains 209 automated tests covering:
- tokenization
- datasets
- causal examples
- model objectives
- Transformer components
- Q/K/V projection
- attention
- multi-head attention
- KV cache
- cached inference
- prefill/decode
- generation
- sampling
- sliding-window generation
- integration boundaries
The repository uses:
- Python 3.12+
- NumPy
- pytest
- Ruff
- mypy
Run all quality checks:
python -m pytest
python -m ruff check .
python -m mypy srcAt the time this README was prepared, the repository baseline was:
209 passed
Ruff: clean
Mypy: clean
The project follows a few principles.
Important mathematical operations are implemented as explicit Python components rather than hidden behind a high-level model API.
Major parts expose simple boundaries such as:
token_ids → hidden states
hidden states → logits
logits → token
Attention, sampling, cache behavior, training objectives, and generation paths are individually tested.
The project intentionally favors readability and inspectability over production-scale optimizations.
The repository separates:
Model
Training
Inference
Generation
Sampling
Caching
This makes it easier to reason about where a particular behavior belongs.
This is not a production-scale LLM.
It does not attempt to reproduce the scale or engineering complexity of systems such as GPT-class foundation models.
It does not provide:
- large-scale distributed training
- GPU kernels
- CUDA optimization
- tensor parallelism
- pipeline parallelism
- mixed-precision production training
- billion-parameter models
- web-scale datasets
- production inference serving
- fault-tolerant distributed infrastructure
Those are different engineering problems.
The purpose of this repository is to understand the algorithmic and mathematical foundations beneath those systems.
Some implementations in the project are intentionally simplified so that the underlying idea remains visible.
For example:
- datasets are small and educational
- dimensions are tiny compared with real LLMs
- some positional mechanisms are simplified
- some Transformer components are implemented for clarity rather than maximum performance
- generation backends are designed to demonstrate architecture rather than production serving
- the KV-cache sliding-window behavior uses replay at the generation layer rather than implementing a fully optimized production cache eviction strategy
These choices are deliberate.
The project is about understanding the mechanism, not pretending that a small NumPy implementation is equivalent to a production model stack.
Using a high-level framework makes it easy to call a language model.
Building the important pieces yourself forces the full computation to remain visible:
token
↓
embedding
↓
position
↓
Q/K/V
↓
attention
↓
multi-head composition
↓
residual + normalization
↓
feed-forward
↓
logits
↓
probabilities
↓
loss
↓
gradients
↓
parameter update
That makes it possible to inspect not only the final prediction, but also the intermediate representations and transformations that produced it.
The core educational implementation is complete enough to demonstrate the main Transformer-to-LLM pipeline:
- ✅ Tokenization
- ✅ Causal language modeling
- ✅ Embeddings
- ✅ Positional information
- ✅ Q/K/V projection
- ✅ Scaled dot-product attention
- ✅ Multi-head attention
- ✅ Residual connections
- ✅ Layer normalization
- ✅ Feed-forward network
- ✅ Transformer language model
- ✅ Cross-entropy objective
- ✅ Backpropagation
- ✅ Training
- ✅ Next-token prediction
- ✅ Greedy generation
- ✅ Temperature sampling
- ✅ Top-K sampling
- ✅ Top-P sampling
- ✅ KV cache
- ✅ Cached attention
- ✅ Prefill / decode
- ✅ Cached generation
- ✅ Sliding context-window generation
- ✅ Automated tests
- ✅ Static type checking
- ✅ Linting
If you remember only one thing from this repository, it should be this:
And during generation:
with Transformer inference providing the probability distribution and the generation strategy deciding how the next token is selected.
For efficient autoregressive decoding:
That is the central journey this repository is designed to make understandable.
Add an open-source license before publishing the repository publicly.
For example, choose an appropriate license such as MIT if that matches your intended usage and contribution model.
Before publishing, add a LICENSE file to the repository and update this section with the chosen license.
This README describes the repository as it stood after the latest validation run:
Tests: 209 passed
Ruff: clean
Mypy: clean
The numbers should be refreshed whenever the implementation changes materially.