We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Tokenization & Embeddings step 15 of 15
Train Tiny GPT End-to-End
Implement train_tiny_gpt: train a small causal language model over a supplied sequence of batches. Each step predicts next tokens, calculates gradients by hand and updates the model with SGD. Carry those updated weights into the next step.
The order matters. This is a sequence of updates, not one gradient computed over the whole corpus and not a collection of independent updates from the initial weights. There is no shuffling, random sampling or generation loop.
Inputs and output
- tokens_corpus: (n_steps, N, T+1), integer-valued IDs in [0, V), delivered as floats. Batch k is tokens_corpus[k].
- w_emb: (V, d), token embeddings.
- pos_embed: (T, d), position embeddings shared across examples.
- blocks_weights: (L, 6, d, d), each block holding [w_q, w_k, w_v, w_o, w_mlp1, w_mlp2].
- w_head: (d, V), a separate vocabulary projection.
- num_heads: a positive integer dividing d.
- lr: a finite, nonnegative learning rate, constant across steps.
- n_steps: an integer from 0 through 5, matching the corpus's first dimension.
N, T, V and d are positive; L may be zero. Parameters are finite and share a floating-point dtype and device; the corpus is on that device. Shapes agree. Leave all inputs unchanged and preserve parameter dtype and device.
After all steps, flatten the final weights in row-major order and concatenate [w_emb, pos_embed, blocks_weights, w_head]. Return this one-dimensional tensor of length Vd + Td + L6dd + dV. Do not return intermediate weights or losses. With n_steps=0, the corpus has shape (0, N, T+1) and no batch is read. With lr=0, all updates are zero. In both cases, return the original parameters packed in the specified order.
Sequential updates
current = supplied parameter tensors
for k = 0, ..., n_steps-1:
tokens = tokens_corpus[k]
gradients = next_token_loss_gradients(tokens, current)
current = current - lr * gradients
return flatten_and_concatenate(current)
Recompute the forward pass and gradients using the current weights at every step. Within a step, compute all gradients before updating any parameter. If you reuse a helper that returns packed weights, unpack them into their original shapes before the next step; a flat tensor is not a tuple of four tensors. The helper from another exercise is not supplied automatically.
For a batch row [2,4,1,3], model inputs are [2,4,1] and targets are [4,1,3]. There are N*T next-token predictions in each step. The following sections define that step completely.
1. Forward pass
Set input_ids = tokens[:, :-1] and targets = tokens[:, 1:]. Start with x = w_emb[input_ids] + pos_embed, broadcasting positions across the batch. Apply the L blocks in order using post-normalization:
q, k, v = x @ w_q, x @ w_k, x @ w_v
y = LN(x + causal_attention(q, k, v) @ w_o)
x = LN(y + GELU(y @ w_mlp1) @ w_mlp2)
For attention, split projected features into consecutive heads of width d/num_heads. Divide q @ k.T by sqrt(d/num_heads), mask future keys with negative infinity, then apply stable softmax over key positions. Query t may read keys 0 through t, including itself. Concatenate heads in their original order. Different batch examples never attend to each other.
LN uses each token's feature mean and population variance: (token - mean) / sqrt(variance + 1e-5). There is no learned scale or bias. Use GELU(u) = 0.5u(1 + tanh(sqrt(2/pi)(u + 0.044715u**3))). The feed-forward width is d. There are no biases, dropout, extra embedding scaling or additional final normalization. No token ID is treated as padding.
Compute logits = x @ w_head. With L=0, project token-plus-position embeddings directly. Keep the intermediate values needed to differentiate each block.
2. Loss and backward pass
Use cross-entropy against targets at all NT model positions, averaging once over NT. For one vocabulary row, its logit gradient is (softmax(logits) - one_hot(target)) / (N*T). There is no additional averaging when accumulating parameter gradients. Use stable log-softmax if calculating the loss value; the return does not include it.
Differentiate the output head, then visit the blocks in reverse order. For post-normalization, first pass the gradient through the layer normalization, then send it through both the residual skip and the sublayer. Each block's normalization derivative must use that block's saved normalized output and standard deviation. The final block's output cannot stand in for earlier ones.
Flatten the batch and position axes when forming gradients for shared matrices. For example, dw_head = x_flat.T @ dlogits_flat. Future attention positions have zero probability and zero score gradient.
Sum position-embedding gradients across the batch. Accumulate token-embedding gradients for every occurrence of each input ID; repeated IDs add to the same row. Only tokens[:, :-1] are embedding lookups. A token occurring solely in the final target position does not directly update its input embedding row, although its target class affects gradients elsewhere through cross-entropy.
3. Simultaneous SGD update
Compute every gradient from the weights at the start of this step, then apply new_parameter = parameter - lr * gradient to all four parameter groups. There is no clipping, weight decay, momentum or optimizer state.
Implement the forward operations and derivatives from basic tensor operations in either framework. Do not use autograd, backward(), jax.grad, packaged transformer layers or optimizer helpers. The same manual-gradient requirement applies to both variants.
Understand the ideas behind this exercise. The Problem tab contains the exact implementation requirements.
Match each prediction to the token it should predict
A causal language model predicts the next token from the tokens before it. For
sequence [A, B, C], logits produced after reading A are trained against B,
and logits after reading B are trained against C. This is why input and target
sequences are usually shifted by one position. A causal attention mask prevents
access to later tokens that would reveal the answer.
A masked language model instead predicts selected tokens from a corrupted sequence and can use context on both sides. The supervised positions are not necessarily just those showing a mask token. A corruption rule may leave some selected tokens unchanged or replace them randomly while still training on them. Compute the loss over the specified supervised positions only.
Teacher forcing supplies the correct previous tokens
In encoder-decoder training, teacher forcing feeds the decoder the correct previous target tokens. During generation, it usually receives its own previous choices instead. A mistake can therefore change later inputs, which helps explain why training loss alone does not describe generation quality.
Convert mean token loss to perplexity
For M valid target tokens, mean negative log likelihood is
$$L=-\frac1M\sum_t\log p(y_t).$$
With natural logarithms, perplexity is exp(L). A model that gives the correct
token probability 1/4 at every step has perplexity 4. A uniform distribution
over a vocabulary of size V has perplexity V.
When batches contain different numbers of valid tokens, add their token losses and valid-token counts before dividing. Averaging batch means would give small batches too much weight. Exclude padding and other unsupervised positions as specified, and define what happens when no tokens are supervised.
Perplexity also depends on tokenization. Scores based on different token units are not directly comparable just because both are called perplexity.
Your code, saved here
Sign in to access your accepted solutions and saved draft history. You can keep reading the lesson without an account.
Sign in to your accountStuck?
PyTorch reference solution
Sign in to track your attempts toward unlocking the reference solution.