LLM Developer DocsLLM Docs

Loading the search index ...

↑ ↓ move, Enter open, Esc close

GitHub

Architecture

The forward pass as transformer.forward runs it, the parameters init_params creates, and the files in the repository.

The model is a decoder-only transformer with pre-LayerNorm blocks, written with NumPy and hand-written backward functions. This page shows its structure. The pages it links to hold the equations.

The forward pass

transformer.forward takes one sequence of ids and returns logits and a cache for the backward pass. It first checks the length (transformer.py:156). The longest allowed sequence is 32 tokens, and training and generation use windows of 16.

Each box below is one function call or inline expression, in code order. The name in code font is what the code runs there. The boxes in the frame are the body of block_forward, which forward calls once per layer.

Embeddings
Blocks, repeated num_layers = 2 times transformer.py:162
  1. 2. LayerNorm 1layer_norm_forward(T, d)(16, 32)transformer.py:133
  2. 3. Attentionattention_forward(T, d)(16, 32)transformer.py:134
  3. 4. Residual addX + att(T, d)(16, 32)transformer.py:135
  4. 5. LayerNorm 2layer_norm_forward(T, d)(16, 32)transformer.py:136
  5. 6. Feed-forwardffn_forward(T, d)(16, 32)transformer.py:137
  6. 7. Residual addX1 + ff(T, d)(16, 32)transformer.py:139
Output
  1. 8. Final LayerNormlayer_norm_forward(T, d)(16, 32)transformer.py:165
  2. 9. Linear headXf @ params["head_w"] + params["head_b"](T, V)(16, V)transformer.py:166
  3. 10. Logitsreturn logits(T, V)(16, V)transformer.py:168
Plain names have no page yet. The link under each name opens the real lines at the pinned commit.

Each block reads normalised copies, and each residual adds to the value that was not normalised. T is at most 16. V depends on the corpus, so it stays a symbol.

What is not inside forward

forward stops at the logits (transformer.py:168). It applies no softmax to them and computes no loss.

StepWhere it runsLine
Cross-entropy, in trainingloss_and_gradstransformer.py:173
Cross-entropy, in validationvalidation_losstrain.py:31
Softmax on logits, in generationsample_nextgenerate.py:11

The only softmax inside forward turns attention scores into attention weights (transformer.py:83). Attention covers it.

Parameters

init_params returns a flat dictionary of arrays. The names below come from calling it. b{l} is the block index, starting at 0, so b0.wq is the query weights of the first block. The "With config" column uses config.py. V depends on the corpus, so it stays a symbol. head_w is a separate matrix (transformer.py:40).

NameShapeWith configScalarsRole
tok_embtransformer.py:21(V, d)(V, 32)V × d32VToken table: one row per vocabulary id.Starts as Xavier uniform. Weight decay.
pos_embtransformer.py:22(T_max, d)(32, 32)T_max × d1,024Position table: one row per position.Starts as Xavier uniform. Weight decay.
b{l}.ln1_gtransformer.py:29(d,)(32,)d32 per blockLayerNorm 1 scale, before attention.Starts as ones. No decay.
b{l}.ln1_btransformer.py:29(d,)(32,)d32 per blockLayerNorm 1 shift, before attention.Starts as zeros. No decay.
b{l}.wqtransformer.py:32(d, d)(32, 32)d × d1,024 per blockQuery projection weights.Starts as Xavier uniform. Weight decay.
b{l}.bqtransformer.py:33(d,)(32,)d32 per blockQuery projection bias.Starts as zeros. No decay.
b{l}.wktransformer.py:32(d, d)(32, 32)d × d1,024 per blockKey projection weights.Starts as Xavier uniform. Weight decay.
b{l}.bktransformer.py:33(d,)(32,)d32 per blockKey projection bias.Starts as zeros. No decay.
b{l}.wvtransformer.py:32(d, d)(32, 32)d × d1,024 per blockValue projection weights.Starts as Xavier uniform. Weight decay.
b{l}.bvtransformer.py:33(d,)(32,)d32 per blockValue projection bias.Starts as zeros. No decay.
b{l}.wotransformer.py:32(d, d)(32, 32)d × d1,024 per blockAttention output projection weights.Starts as Xavier uniform. Weight decay.
b{l}.botransformer.py:33(d,)(32,)d32 per blockAttention output projection bias.Starts as zeros. No decay.
b{l}.ln2_gtransformer.py:35(d,)(32,)d32 per blockLayerNorm 2 scale, before feed-forward.Starts as ones. No decay.
b{l}.ln2_btransformer.py:35(d,)(32,)d32 per blockLayerNorm 2 shift, before feed-forward.Starts as zeros. No decay.
b{l}.ff1_wtransformer.py:36(d, F)(32, 128)d × F4,096 per blockFirst feed-forward layer weights.Starts as Xavier uniform. Weight decay.
b{l}.ff1_btransformer.py:36(F,)(128,)F128 per blockFirst feed-forward layer bias.Starts as zeros. No decay.
b{l}.ff2_wtransformer.py:37(F, d)(128, 32)d × F4,096 per blockSecond feed-forward layer weights.Starts as Xavier uniform. Weight decay.
b{l}.ff2_btransformer.py:37(d,)(32,)d32 per blockSecond feed-forward layer bias.Starts as zeros. No decay.
lnf_gtransformer.py:39(d,)(32,)d32Final LayerNorm scale.Starts as ones. No decay.
lnf_btransformer.py:39(d,)(32,)d32Final LayerNorm shift.Starts as zeros. No decay.
head_wtransformer.py:40(d, V)(32, V)V × d32VLinear head weights. A separate matrix, not tied to tok_emb.Starts as Xavier uniform. Weight decay.
head_btransformer.py:40(V,)(V,)VVLinear head bias.Starts as zeros. No decay.

Symbols: T_max is context_len, d is emb_dim, F is ff_dim, L is num_layers and V is the vocabulary size.

Summing the "Scalars" column gives the total. The first line is collected from the shapes above and the second puts in the values from config.py.

Implemented
N=2Vd+Tmax⁡d+2d+V+L(4d2+2dF+9d+F)N = 2V d + T_{\max} d + 2d + V + L\left(4d^{2} + 2d F + 9d + F\right)
N=65 V+26,496N = 65\,V + 26{,}496

count_params in transformer.py sums the array sizes, but only tests call it. Training does not print this number.

Repository map

Every Python file at the pinned commit. The links open the file in the Code Explorer.

FileLinesWhat it holds
checkpoint.py46Saves and loads weights, model shape and tokenizer.
config.py44Hyperparameters as module-level constants, plus two import-time assertions.
data.py43Corpus loading, encoding, the split, sliding windows and batches.
generate.py35Sampling and the generation loop.
gradcheck.py33Finite-difference gradient checker. Only tests use it.
ops.py60Operations with hand-written backward functions.
optim.py50Adam with decoupled weight decay, the learning-rate schedule and gradient clipping.
talk.py30Interactive inference entry point.
tests/test_checkpoint.py76Checkpoint tests.
tests/test_data.py83Data pipeline tests.
tests/test_generate.py82Sampling tests.
tests/test_gradcheck.py39Gradient-check tests.
tests/test_ops.py88Operation tests.
tests/test_optim.py49Optimizer, schedule and clipping tests.
tests/test_tokenizer.py50Tokenizer tests.
tests/test_transformer.py110Model tests.
tokenizer.py119BPE tokenizer that works on characters: pre-tokens, merges, encode and decode.
train.py85Training entry point.
transformer.py190Parameters, the forward pass and the backward pass.

data/corpus.csv and checkpoints/ are git-ignored, so they are not part of the commit. A fresh clone needs a corpus before it can train.