LLM Developer DocsLLM Docs

Loading the search index ...

↑ ↓ move, Enter open, Esc close

GitHub

transformer.py

Parameters, forward pass, backward pass

Where it fits

14 of 15 functions, classes and methods here are reachable from train.main, and 8 from talk.main.

Pipeline stageStepExplained on
EmbeddingsToken plus position embeddingsembeddings
BlocksLayerNorm 1layernorm
BlocksAttentionattention
BlocksResidual addtransformer block
BlocksLayerNorm 2layernorm
BlocksFeed-forwardtransformer block
BlocksResidual addtransformer block
OutputFinal LayerNormfull model
OutputLinear headfull model
OutputLogitsfull model
Loss and updateSoftmax and cross-entropysoftmax cross entropy
Loss and updateBackwardhow the model learns

Imports and used by

Imports from this repository
ops.py
Other imports
dataclasses, numpy
Imported by
checkpoint.py, generate.py, tests/test_checkpoint.py, tests/test_generate.py, tests/test_gradcheck.py, tests/test_transformer.py, train.py
Used by, in the pipeline
checkpoint.py, generate.py, train.py
Used by, in tests
tests/test_checkpoint.py, tests/test_generate.py, tests/test_gradcheck.py, tests/test_transformer.py

Mathematics used

ConceptWhat this file doesLinesStatus
Parameter countComputes it21, 22, 29, 32, 33, 35, 36, 37, 39, 40, 45Implemented
Starting values of the parametersComputes it21, 22, 29, 32, 33, 35, 36, 37, 39, 40Implemented
Positional embeddingComputes it53Implemented
Token embeddingComputes it53Implemented
Embedding sumComputes it, Calls it53, 158Implemented
Embedding backwardComputes it, Calls it56, 57, 188Implemented
Splitting into headsCalls it, Computes it62, 79Implemented
HeadsComputes it, Writes it in place62, 67, 73, 94Implemented
Merging headsCalls it, Computes it67, 84Implemented
Q, K, V projectionsComputes it75, 76, 77Implemented
Matrix multiplicationWrites it in place75, 76, 77, 85, 113, 116, 166Derived from implementation
Linear layerWrites it in place75, 76, 77, 85, 113, 116, 166Implemented
Scaling by sqrt(d_h)Computes it81Implemented
ScoresComputes it81Implemented
Causal maskComputes it82Implemented
Softmax per rowCalls it83Implemented
SoftmaxCalls it83Implemented
Weighted valuesComputes it84Implemented
Output projectionComputes it85Implemented
Linear layer, backwardWrites it in place, Calls it95, 96, 97, 106, 107, 108, 119, 123, 176Implemented
Attention backwardComputes it, Calls it95, 96, 97, 98, 99, 100, 101, 102, 103, 106, 107, 108, 148Implemented
Softmax, backwardCalls it100Implemented
Feed-forward network, forwardWrites it in place, Computes it113, 114, 116Implemented
ReLUCalls it114Implemented
Feed-forward network, backwardComputes it119, 120, 121, 122, 123, 125, 126Implemented
ReLU, backwardCalls it122Implemented
LayerNormCalls it133, 136, 165Implemented
Pre-LN block, forwardComputes it133, 134, 135, 136, 137, 139Implemented
Residual connections, backwardComputes it143, 144, 147, 148, 149, 152Implemented
LayerNorm, backwardCalls it144, 149, 181Implemented
Autoregressive loopCalls it156Implemented
Whole model, forwardComputes it, Writes it in place156, 162, 165, 166Implemented
Cross-entropyCalls it173Implemented
Cross-entropy, backwardCalls it175Implemented
Whole model, backwardComputes it176, 181, 186, 187, 188Implemented

Functions and constants

Frozen dataclass of the model shape; ln_eps defaults to 1e-5

Reached from
train.main, talk.main
Used by, in the pipeline
checkpoint.load_checkpoint, train.main
Used by, in tests
tests.test_checkpoint.saved, tests.test_checkpoint.test_non_ascii_characters_survive, tests.test_generate.tiny, tests.test_gradcheck.tiny_setup, tests.test_transformer.test_initial_loss_is_close_to_ln_vocab, tests.test_transformer.test_param_count_matches_formula, tests.test_transformer.tiny_cfg

Builds the flat dictionary of parameter arrays

Reached from
train.main
Used by, in the pipeline
train.main
Used by, in tests
tests.test_checkpoint.saved, tests.test_checkpoint.test_non_ascii_characters_survive, tests.test_generate.tiny, tests.test_gradcheck.tiny_setup, tests.test_transformer.test_attention_weights_are_causal_and_normalised, tests.test_transformer.test_future_tokens_do_not_change_earlier_logits, tests.test_transformer.test_grads_have_the_shape_of_params, tests.test_transformer.test_initial_loss_is_close_to_ln_vocab, tests.test_transformer.test_overfit_one_window, tests.test_transformer.test_param_count_matches_formula
Calls
ops.xavier_uniform
Mathematics
Parameter count, Starting values of the parameters

Multi-head causal self-attention; returns output and cache

Reached from
train.main, talk.main
Used by, in the pipeline
transformer.block_forward
Used by, in tests
tests.test_transformer.test_attention_weights_are_causal_and_normalised
Calls
ops.softmax_forward, transformer.merge_heads, transformer.split_heads
Mathematics
Heads, Q, K, V projections, Matrix multiplication, Linear layer, Splitting into heads, Scaling by sqrt(d_h), Scores, Causal mask, Softmax per row, Softmax, Merging heads, Weighted values, Output projection

Whole model on one sequence; returns logits and cache

Reached from
train.main, talk.main
Used by, in the pipeline
generate.generate, train.validation_loss, transformer.loss_and_grads
Used by, in tests
tests.test_checkpoint.test_loaded_model_gives_identical_logits, tests.test_transformer.test_future_tokens_do_not_change_earlier_logits
Calls
ops.layer_norm_forward, transformer.block_forward, transformer.embed_forward
Mathematics
Autoregressive loop, Whole model, forward, Embedding sum, LayerNorm, Matrix multiplication, Linear layer

Forward, loss and full backward for one sequence

Reached from
train.main
Used by, in the pipeline
train.batch_loss_and_grads
Used by, in tests
tests.test_gradcheck.test_every_tensor_passes, tests.test_gradcheck.test_key_bias_gradient_is_zero, tests.test_transformer.test_grads_have_the_shape_of_params, tests.test_transformer.test_initial_loss_is_close_to_ln_vocab, tests.test_transformer.test_overfit_one_window
Calls
ops.cross_entropy_backward, ops.cross_entropy_forward, ops.layer_norm_backward, ops.linear_backward, transformer.block_backward, transformer.embed_backward, transformer.forward
Mathematics
Cross-entropy, Cross-entropy, backward, Linear layer, backward, Whole model, backward, LayerNorm, backward, Embedding backward

Source

transformer.py190 linesGitHub: transformer.py
from dataclasses import dataclassimport numpy as npfrom ops import (cross_entropy_forward, cross_entropy_backward, layer_norm_backward,                 layer_norm_forward, linear_backward, relu_backward, relu_forward,                 softmax_forward, softmax_backward, xavier_uniform)@dataclass(frozen=True)class ModelConfig:    vocab_size: int    context_len: int    emb_dim: int    num_heads: int    ff_dim: int    num_layers: int    ln_eps: float = 1e-5def init_params(cfg: ModelConfig, rng):    d, F, V = cfg.emb_dim, cfg.ff_dim, cfg.vocab_size    p = {        "tok_emb": xavier_uniform(V, d, rng), # lookup table with one row per vocabulary token        "pos_emb": xavier_uniform(cfg.context_len, d, rng) # second table with one row per position, weighted average of values        # input to first block is tok_emb + pos_emb        # this makes cat at first position and fifth position different in a sentence    }    for l in range(cfg.num_layers):        b = f"b{l}." # key        p[b + "ln1_g"], p[b + "ln1_b"] = np.ones(d), np.zeros(d) # layer norm        for n in "qkvo":            p[b + "w" + n] = xavier_uniform(d, d, rng)            p[b + "b" + n] = np.zeros(d)        p[b + "ln2_g"], p[b + "ln2_b"] = np.ones(d), np.zeros(d)        p[b + "ff1_w"], p[b + "ff1_b"] = xavier_uniform(d, F, rng), np.zeros(F)        p[b + "ff2_w"], p[b + "ff2_b"] = xavier_uniform(F, d, rng), np.zeros(d)    p["lnf_g"], p["lnf_b"] = np.ones(d), np.zeros(d)    p["head_w"], p["head_b"] = xavier_uniform(d, V, rng), np.zeros(V)    return pdef count_params(params):    return int(sum(v.size for v in params.values()))# X0[t] = E[i_t] + P[t]# pos_emb has one row for every position the model could ever see (context_len)# training window has 16 tokens, suppose 3 tokens are given for generation# tok_emb[ids]  ->  (T, d)        e.g. (3, 32)# pos_emb       ->  (Tmax, d)     (32, 32)    # shapes don't matchdef embed_forward(params, ids):    return params["tok_emb"][ids] + params["pos_emb"][: len(ids)]def embed_backward(dX0, ids, grads):    np.add.at(grads["tok_emb"], ids, dX0) # grads["tok_emb"][ids[t]] += dX0[t]    grads["pos_emb"][: len(ids)] += dX0# (T,d)->(H,T,dh)def split_heads(M, H):    T, d = M.shape    return M.reshape(T, H, d // H).transpose(1, 0, 2)# (H,T,dh)->(T,d)def merge_heads(Mh):    H, T, dh = Mh.shape    return Mh.transpose(1, 0, 2).reshape(T, H * dh)# takes one sequence of normalized token vectors and returns a new vector built from itself and earlier positionsdef attention_forward(Xn, params, p, cfg: ModelConfig):    T, d = Xn.shape    H = cfg.num_heads    dh = d // H # width of one head    Q = Xn @ params[p+"wq"] + params[p+"bq"]    # (T,d), QUERY    K = Xn @ params[p+"wk"] + params[p+"bk"]    # KEY    V = Xn @ params[p+"wv"] + params[p+"bv"]    # VALUE    Qh, Kh, Vh = split_heads(Q, H), split_heads(K, H), split_heads(V, H)    S = (Qh @ Kh.transpose(0, 2, 1)) / np.sqrt(dh)    S = np.where(np.triu(np.ones((T, T), dtype=bool), 1), -np.inf, S)    A = softmax_forward(S)    O = merge_heads(A @ Vh)    out = O @ params[p + "wo"] + params[p + "bo"]    return out, dict(Xn=Xn, Qh=Qh, Kh=Kh, Vh=Vh, A=A, O=O)# receives the gradient on attention output and returns gradient on its input# given   dOut = ∂L/∂out         (T,d)   what arrives from the residual branch# fills   grads[p+"wo","bo","wq","bq","wk","bk","wv","bv"]# returns dXn  = ∂L/∂Xn          (T,d)   goes on to layer-norm 1's backwarddef attention_backward(dOut, c, params, p, cfg: ModelConfig, grads):    H = cfg.num_heads    dh = cfg.emb_dim // H    grads[p + "wo"] += c["O"].T @ dOut    grads[p + "bo"] += dOut.sum(axis=0)    dOh = split_heads(dOut @ params[p + "wo"].T, H)    dVh = c["A"].transpose(0, 2, 1) @ dOh     dA = dOh @ c["Vh"].transpose(0, 2, 1)    dS = softmax_backward(c["A"], dA)    dQh = dS @ c["Kh"] / np.sqrt(dh)    dKh = dS.transpose(0, 2, 1) @ c["Qh"] / np.sqrt(dh)    dQ, dK, dV = merge_heads(dQh), merge_heads(dKh), merge_heads(dVh)    dXn = np.zeros_like(c["Xn"])    for n, dM in (("q", dQ), ("k", dK), ("v", dV)):        grads[p + "w" + n] += c["Xn"].T @ dM        grads[p + "b" + n] += dM.sum(axis=0)        dXn += dM @ params[p + "w" + n].T    return dXn# attention moves information between positionsdef ffn_forward(Xn, params, p):    Z = Xn @ params[p + "ff1_w"] + params[p + "ff1_b"]    A = relu_forward(Z)    return A @ params[p + "ff2_w"] + params[p + "ff2_b"], dict(Xn=Xn, Z=Z, A=A)def ffn_backward(dOut, c, params, p, grads):    dA, dW2, db2 = linear_backward(c["A"], params[p + "ff2_w"], dOut)    grads[p + "ff2_w"] += dW2    grads[p + "ff2_b"] += db2    dZ = relu_backward(c["Z"], dA)    dXn, dW1, db1 = linear_backward(c["Xn"], params[p + "ff1_w"], dZ)    grads[p + "ff1_w"] += dW1    grads[p + "ff1_b"] += db1    return dXn# one transformer block in pre-LN formdef block_forward(X, params, l: int, cfg: ModelConfig):    p = f"b{l}."    Xn, c_ln1 = layer_norm_forward(X, params[p + "ln1_g"], params[p + "ln1_b"], cfg.ln_eps) # make a normalized copy for attention to read    att, c_att = attention_forward(Xn, params, p, cfg) # each position gathers info from earlier positions    X1 = X + att    Xn2, c_ln2 = layer_norm_forward(X1, params[p + "ln2_g"], params[p + "ln2_b"], cfg.ln_eps) # normalized copy again, for the feed-forward    ff, c_ff = ffn_forward(Xn2, params, p) # ach position thinks on its own    return X1 + ff, dict(ln1=c_ln1, att=c_att, ln2=c_ln2, ff=c_ff)def block_backward(dY, c, params, l, cfg: ModelConfig, grads):    p = f"b{l}."    dXn2 = ffn_backward(dY, c["ff"], params, p, grads)    dX1_branch, dg, db = layer_norm_backward(dXn2, c["ln2"], params[p + "ln2_g"])    grads[p + "ln2_g"] += dg    grads[p + "ln2_b"] += db    dX1 = dY + dX1_branch    dXn = attention_backward(dX1, c["att"], params, p, cfg, grads)    dX_branch, dg, db = layer_norm_backward(dXn, c["ln1"], params[p + "ln1_g"])    grads[p + "ln1_g"] += dg    grads[p + "ln1_b"] += db    return dX1 + dX_branch# whole model in the forward directiondef forward(params, ids, cfg: ModelConfig):    assert len(ids) <= cfg.context_len, "sequence longer than the position table"    X = embed_forward(params, ids)    block_caches = []    for l in range(cfg.num_layers):        X, c = block_forward(X, params, l, cfg)        block_caches.append(c)    Xf, c_lnf = layer_norm_forward(X, params["lnf_g"], params["lnf_b"], cfg.ln_eps)    logits = Xf @ params["head_w"] + params["head_b"]    return logits, dict(blocks=block_caches, lnf=c_lnf, Xf=Xf)# runs forward, computes the loss, then the whole backward passdef loss_and_grads(params, ids, targets, cfg: ModelConfig):    logits, cache = forward(params, ids, cfg)    loss, probs = cross_entropy_forward(logits, targets)    grads = {k: np.zeros_like(v) for k, v in params.items()}    dZ = cross_entropy_backward(probs, targets)    dXf, dW, db = linear_backward(cache["Xf"], params["head_w"], dZ)    grads["head_w"] += dW    grads["head_b"] += db    dX, dg, db = layer_norm_backward(dXf, cache["lnf"], params["lnf_g"])    grads["lnf_g"] += dg    grads["lnf_b"] += db    for l in reversed(range(cfg.num_layers)):        dX = block_backward(dX, cache["blocks"][l], params, l, cfg, grads)    embed_backward(dX, ids, grads)    return loss, grads