We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Flax step 42 of 100
Mini GPT: Decoder-Only Language Model
Assemble a small causal language model
This exercise combines token embeddings, learned positions, causal self-attention blocks and a tied vocabulary projection. It computes a randomly initialized forward pass. It does not train the model, calculate a loss or generate tokens.
With T input tokens, vocabulary size V and hidden width D, the flow is:
ids (T,)
token embedding (T, D)
+ learned positions (T, D)
L causal blocks (T, D)
final LayerNorm (T, D)
@ token_embedding.T (T, V) logits
There is no separate encoder or cross-attention. This is one GPT-style architecture with ReLU feed-forward networks, not an exact specification of every GPT model.
Keep the two embedding initializers distinct
Create one unnamed nn.Embed(V, D) with its default initializer. Its
(V, D) parameter supplies both the token lookup and the output weights.
Separately declare a root parameter pos_embed of shape (T, D) using
nn.initializers.normal(stddev=0.02). Add it to the lookup result before
the first block.
The position table in this exercise has exactly T rows. It is not a larger table sliced to the current length. Applying the same variables to a different length requests a different parameter shape and fails. Reinitializing at another length creates a new model; it is not a way to extend a trained checkpoint. A variable-length implementation would need a different position-table contract.
Learned positions supply a vector for each position. Tokens with the same ID can have different outputs because their positions and available context differ. The causal mask also records which positions precede each query. Reordering token rows can therefore change the allowed connections.
Apply each causal block
Let H = num_heads and F = d_ff. Each block has two residual updates:
x1 = x + MHA(LN1(x), mask=lower_triangle)
out = x1 + Dense(D)(relu(Dense(F)(LN2(x1))))
Use separate LayerNorms and nn.MultiHeadDotProductAttention with
num_heads=H and qkv_features=D. The (T, T) lower-triangular mask
allows a query at position t to read positions 0 through t, including
itself. Attention scores have shape (H, T, T).
Instantiate a new GPTBlock for each layer. Reusing one instance would
share block parameters, which this exercise does not request. After all
L blocks, apply a separate final LayerNorm. It changes the vectors sent
to the output head and is part of the required computation.
Project to vocabulary scores
If h is the final (T, D) hidden array and W is the (V, D) token
embedding, compute h @ W.T. The result has shape (T, V). No separate
output weight, bias or softmax is used. Tying saves V*D weights compared
with adding an independent bias-free vocabulary projection.
This example uses supplied hidden vectors to isolate the projection and show how next-token targets would line up in a separate training loop:
import jax.numpy as jnp
ids = jnp.array([0, 2, 1], dtype=jnp.int32)
W = jnp.array([[1., 0.], [0., 1.], [1., 1.]])
h = jnp.array([[1., 2.], [0., -1.], [2., 0.]])
logits = h @ W.T
assert jnp.array_equal(logits, jnp.array([
[1., 2., 3.], [0., -1., -1.], [2., 0., 2.],
]))
training_logits = logits[:-1]
targets = ids[1:]
assert training_logits.shape == (2, 3)
assert jnp.array_equal(targets, jnp.array([2, 1]))
Row t is paired with token t+1, not the token already visible at t.
The last row needs a continuation target that is not present in this
sequence. Return all rows for this exercise; do not shift or trim them.
Random initialization does not guarantee useful predictions.
Problem
Implement mini_gpt_forward(seed, token_ids, vocab_size, d_model, num_heads, d_ff, num_layers):
- Convert configuration values to Python integers and token IDs to
jnp.int32. BuildMiniGPT(nn.Module)with those configuration fields. - Follow the embedding, position, block and final-normalization flow above
inside a compact call method. Keep layer defaults, including projection
biases, LayerNorm epsilon
1e-6and zero attention dropout. - Use unnamed modules: root
Embed_0,GPTBlock_0throughGPTBlock_{L-1}, and rootLayerNorm_0. Inside each block, useLayerNorm_0,MultiHeadDotProductAttention_0,LayerNorm_1,Dense_0andDense_1, in that order. - Reuse
embed.embeddingfor the tied head. Initialize the complete model directly withjax.random.PRNGKey(int(seed))andids, apply those variables toids, and returnlogits.reshape(-1).
token_ids is a one-dimensional float array containing integer-valued
IDs in [0, V). T, V, D, H and F are positive, D is divisible
by H, and L is nonnegative. L=0 skips the blocks but retains both
embeddings, final normalization and tied projection.
Return a one-dimensional array of length T * V, with vocabulary scores
varying fastest. Keep seed and all five model configuration arguments
static if JIT-compiling the whole function. Preserve the module scopes
and initializer choices to reproduce the seeded tests. This forward pass
has no decoding cache.
The causal panel below shows the attention permissions used here. The other panels compare patterns that are not part of this model.
Loading visualization…
Understand the ideas behind this exercise. The Problem tab contains the exact implementation requirements.
Assemble a small causal language model
This exercise combines token embeddings, learned positions, causal self-attention blocks and a tied vocabulary projection. It computes a randomly initialized forward pass. It does not train the model, calculate a loss or generate tokens.
With T input tokens, vocabulary size V and hidden width D, the flow is:
ids (T,)
token embedding (T, D)
+ learned positions (T, D)
L causal blocks (T, D)
final LayerNorm (T, D)
@ token_embedding.T (T, V) logits
There is no separate encoder or cross-attention. This is one GPT-style architecture with ReLU feed-forward networks, not an exact specification of every GPT model.
Keep the two embedding initializers distinct
Create one unnamed nn.Embed(V, D) with its default initializer. Its
(V, D) parameter supplies both the token lookup and the output weights.
Separately declare a root parameter pos_embed of shape (T, D) using
nn.initializers.normal(stddev=0.02). Add it to the lookup result before
the first block.
The position table in this exercise has exactly T rows. It is not a larger table sliced to the current length. Applying the same variables to a different length requests a different parameter shape and fails. Reinitializing at another length creates a new model; it is not a way to extend a trained checkpoint. A variable-length implementation would need a different position-table contract.
Learned positions supply a vector for each position. Tokens with the same ID can have different outputs because their positions and available context differ. The causal mask also records which positions precede each query. Reordering token rows can therefore change the allowed connections.
Apply each causal block
Let H = num_heads and F = d_ff. Each block has two residual updates:
x1 = x + MHA(LN1(x), mask=lower_triangle)
out = x1 + Dense(D)(relu(Dense(F)(LN2(x1))))
Use separate LayerNorms and nn.MultiHeadDotProductAttention with
num_heads=H and qkv_features=D. The (T, T) lower-triangular mask
allows a query at position t to read positions 0 through t, including
itself. Attention scores have shape (H, T, T).
Instantiate a new GPTBlock for each layer. Reusing one instance would
share block parameters, which this exercise does not request. After all
L blocks, apply a separate final LayerNorm. It changes the vectors sent
to the output head and is part of the required computation.
Project to vocabulary scores
If h is the final (T, D) hidden array and W is the (V, D) token
embedding, compute h @ W.T. The result has shape (T, V). No separate
output weight, bias or softmax is used. Tying saves V*D weights compared
with adding an independent bias-free vocabulary projection.
This example uses supplied hidden vectors to isolate the projection and show how next-token targets would line up in a separate training loop:
import jax.numpy as jnp
ids = jnp.array([0, 2, 1], dtype=jnp.int32)
W = jnp.array([[1., 0.], [0., 1.], [1., 1.]])
h = jnp.array([[1., 2.], [0., -1.], [2., 0.]])
logits = h @ W.T
assert jnp.array_equal(logits, jnp.array([
[1., 2., 3.], [0., -1., -1.], [2., 0., 2.],
]))
training_logits = logits[:-1]
targets = ids[1:]
assert training_logits.shape == (2, 3)
assert jnp.array_equal(targets, jnp.array([2, 1]))
Row t is paired with token t+1, not the token already visible at t.
The last row needs a continuation target that is not present in this
sequence. Return all rows for this exercise; do not shift or trim them.
Random initialization does not guarantee useful predictions.
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?
JAX reference solution
Sign in to track your attempts toward unlocking the reference solution.