We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Flax step 37 of 100
Tied Input/Output Embedding
Reuse one vocabulary matrix
An input embedding turns a token ID into a vector. An output projection turns a hidden vector into one score per vocabulary entry. Weight tying uses the same learned matrix for both operations.
Let W have shape (V, D), where V is vocabulary size and D is
embedding width:
x = W[ids] # (T, D): look up one row for each token ID
logits = x @ W.T # (T, V): score every vocabulary row
This exercise feeds the lookup vectors directly into the output projection. There is no Transformer, context mixing or training step in between. It demonstrates parameter sharing, not a complete next-token predictor.
What each score means
logits[t, v] is the dot product of W[ids[t]] and W[v]. These are
raw scores, not probabilities. Do not apply softmax.
The score for the input token itself is its squared vector norm:
logits[t, ids[t]] = sum(W[ids[t]] ** 2). That score is nonnegative,
but it is not necessarily the largest score. Another vector with a larger
norm can have a larger dot product. Randomly initialized vectors also do
not come with learned semantic meaning.
Work through a small example
Use known weights here so every score is easy to check:
import jax.numpy as jnp
W = jnp.array([[1., 0.], [2., 0.], [0., 1.]])
ids = jnp.array([0, 2, 0], dtype=jnp.int32)
x = W[ids]
logits = x @ W.T
assert jnp.array_equal(logits, jnp.array([
[1., 2., 0.],
[0., 0., 1.],
[1., 2., 0.],
]))
assert logits[0, 1] > logits[0, 0] # The input token need not score highest.
assert jnp.array_equal(logits[0], logits[2]) # Repeated IDs give repeated rows.
For this direct lookup-and-project computation, changing the token order only changes the output row order. A full language model normally transforms embeddings using their context before projecting to vocabulary scores.
What tying saves
Two independent vocabulary weight matrices contain 2 * V * D
parameters. Sharing one leaves V * D, saving V * D parameters.
For V=50000 and D=4096, that saves 204,800,000 parameters. This halves
the weights in these two matrices, not necessarily the whole model.
The exercise has no output bias or other trainable parameters.
Sharing also means gradients from lookup and output scoring accumulate
into the same parameter. Even a vocabulary row not selected by ids can
receive a gradient through its output score. Whether tying improves a
trained model's quality depends on the model and training setup.
Use an explicit shared parameter here
Declare a single parameter named embedding with
self.param("embedding", nn.initializers.normal(stddev=0.02), (V, D))
inside a compact Linen module. Both lookup and projection must use that
array. Two independently trainable arrays would not be tied, even if
initialized to equal values.
Linen also supports tying through an nn.Embed instance and its
attend method, which projects using that instance's embedding matrix.
This exercise uses self.param to make the shared array explicit and
specifies its initializer. The default nn.Embed initializer is different.
Problem
Implement tied_io_embed(seed, token_ids, vocab_size, d_model):
- Set
V = int(vocab_size)andD = int(d_model). Converttoken_idstojnp.int32. - Build a compact Linen module with one root parameter
embedding, shape(V, D), initialized withnn.initializers.normal(stddev=0.02). - Look up
embedding[ids], then multiply byembedding.Tto obtain(T, V)logits. Do not add a bias, normalization or hidden layer. - Initialize the module directly with
jax.random.PRNGKey(int(seed))andids. Apply those variables toidsand return.reshape(-1).
V and D are positive. token_ids is a one-dimensional array of length
T containing integer-valued entries in [0, V), supplied as floats by
the exercise harness. Casting is for the array dtype, not for rounding
arbitrary fractional values into valid IDs. An empty input returns an
empty output.
Return a one-dimensional array of length T * V, with vocabulary scores
varying fastest. Keep seed, vocab_size and d_model static if
JIT-compiling the whole function. Preserve the root parameter name and
initialization path to reproduce the seeded test values.
Understand the ideas behind this exercise. The Problem tab contains the exact implementation requirements.
Reuse one vocabulary matrix
An input embedding turns a token ID into a vector. An output projection turns a hidden vector into one score per vocabulary entry. Weight tying uses the same learned matrix for both operations.
Let W have shape (V, D), where V is vocabulary size and D is
embedding width:
x = W[ids] # (T, D): look up one row for each token ID
logits = x @ W.T # (T, V): score every vocabulary row
This exercise feeds the lookup vectors directly into the output projection. There is no Transformer, context mixing or training step in between. It demonstrates parameter sharing, not a complete next-token predictor.
What each score means
logits[t, v] is the dot product of W[ids[t]] and W[v]. These are
raw scores, not probabilities. Do not apply softmax.
The score for the input token itself is its squared vector norm:
logits[t, ids[t]] = sum(W[ids[t]] ** 2). That score is nonnegative,
but it is not necessarily the largest score. Another vector with a larger
norm can have a larger dot product. Randomly initialized vectors also do
not come with learned semantic meaning.
Work through a small example
Use known weights here so every score is easy to check:
import jax.numpy as jnp
W = jnp.array([[1., 0.], [2., 0.], [0., 1.]])
ids = jnp.array([0, 2, 0], dtype=jnp.int32)
x = W[ids]
logits = x @ W.T
assert jnp.array_equal(logits, jnp.array([
[1., 2., 0.],
[0., 0., 1.],
[1., 2., 0.],
]))
assert logits[0, 1] > logits[0, 0] # The input token need not score highest.
assert jnp.array_equal(logits[0], logits[2]) # Repeated IDs give repeated rows.
For this direct lookup-and-project computation, changing the token order only changes the output row order. A full language model normally transforms embeddings using their context before projecting to vocabulary scores.
What tying saves
Two independent vocabulary weight matrices contain 2 * V * D
parameters. Sharing one leaves V * D, saving V * D parameters.
For V=50000 and D=4096, that saves 204,800,000 parameters. This halves
the weights in these two matrices, not necessarily the whole model.
The exercise has no output bias or other trainable parameters.
Sharing also means gradients from lookup and output scoring accumulate
into the same parameter. Even a vocabulary row not selected by ids can
receive a gradient through its output score. Whether tying improves a
trained model's quality depends on the model and training setup.
Use an explicit shared parameter here
Declare a single parameter named embedding with
self.param("embedding", nn.initializers.normal(stddev=0.02), (V, D))
inside a compact Linen module. Both lookup and projection must use that
array. Two independently trainable arrays would not be tied, even if
initialized to equal values.
Linen also supports tying through an nn.Embed instance and its
attend method, which projects using that instance's embedding matrix.
This exercise uses self.param to make the shared array explicit and
specifies its initializer. The default nn.Embed initializer is different.
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.