We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Flax NNX step 76 of 100
NNX Port: Linen Transformer Block → NNX
Transfer a complete encoder block
This exercise copies an initialized Linen encoder block into a matching NNX block. It includes two LayerNorms, four attention projections and two feed-forward layers. The transfer must preserve both the arrays and the computation that uses them.
Use this pre-normalization layout, where the second branch receives the result of the first residual addition:
a = x + attention(ln1(x))
output = a + ff2(relu(ff1(ln2(a))))
Both LayerNorms normalize the last feature axis independently for each token, use epsilon=1e-6, and have trainable scale and bias. Keep their default variance calculation. ff1 maps D features to F, and ff2 maps F back to D. All linear layers include bias. There is no final ReLU or normalization after the second residual addition.
Specify the same attention on both sides
Input x has shape (T, D). With H heads, each head has d=D/H features.
Build attention from four separate biased Dense/Linear layers, named
q_proj, k_proj, v_proj and out_proj, each mapping D features to D.
- Project the normalized input to Q, K and V. Reshape each
(T, D)result to(T, H, d), then transpose to(H, T, d). - For each head, compute
Q @ K.T / sqrt(d)and apply softmax over the final key-position axis. Multiply those weights by V. - Transpose the
(H, T, d)result back to(T, H, d), reshape to(T, D), and apply out_proj.
Attention is unmasked: every token can attend to every input position. There is no dropout, positional encoding, padding mask or cache in this block. These omissions are part of the specified model, not features you should add during the port.
Both Linen and NNX have built-in attention layers, including projections with separate head dimensions. This exercise uses explicit projections to make the parameter mapping easy to inspect. A different implementation may need reshaping and matching configuration; matching names alone is insufficient to establish equivalence.
Map all sixteen parameter arrays
Give Linen submodules explicit names. Under the variables["params"] dictionary, use the following paths, mirrored by NNX attributes:
| Linen path | NNX attribute | Arrays and shapes |
|---|---|---|
| ln1, ln2 | block.ln1, block.ln2 | scale (D,), bias (D,) |
| attn / q_proj, k_proj, v_proj, out_proj | block.attn.<projection> | kernel (D, D), bias (D,) |
| ff1 | block.ff1 | kernel (D, F), bias (F,) |
| ff2 | block.ff2 | kernel (F, D), bias (D,) |
LayerNorm has scale and bias, not a kernel. Attention parameters live
inside the attn subtree. Copy through parameter[...] to preserve
nnx.Param wrappers. No transpose is needed for these matching layouts.
A small normalization transfer example
Here both implementations normalize [1, 3] to approximately [-1, 1]
before applying scale [2, 3] and bias [1, -1]. The resulting vector
is approximately [-1, 2].
import jax.numpy as jnp
import flax.linen as nn
from flax import nnx
variables = {"params": {
"scale": jnp.array([2., 3.]), "bias": jnp.array([1., -1.])
}}
source = nn.LayerNorm(epsilon=1e-6)
target = nnx.LayerNorm(2, epsilon=1e-6, rngs=nnx.Rngs(0))
target.scale[...] = variables["params"]["scale"]
target.bias[...] = variables["params"]["bias"]
x = jnp.array([[1., 3.]])
assert jnp.allclose(target(x), source.apply(variables, x))
assert jnp.allclose(target(x), jnp.array([[-1., 2.]]), atol=1e-5)
For the complete block, transfer every listed array, even a scale or
bias that happens to equal its destination's initial value. Validate
the full outputs with a numerical tolerance. Equal sums alone cannot
prove equivalence: [1, 3] and [2, 2] both sum to four. Do not replace
the requested two forwards with one result reported twice.
Problem
Implement port_transformer_block(seed, x, num_heads, d_model, d_ff):
- Define LinenMHA and LinenEncoderBlock using the architecture above.
Initialize the block with
jax.random.PRNGKey(int(seed))and x. Compute linen_out using the returned variables. - Define matching NNXMHA and NNXEncoderBlock modules. Construct them
with
nnx.Rngs(int(seed) + 1)and matching dimensions. Their initial parameters will be overwritten. - Copy all sixteen arrays according to the table, then compute nnx_out.
- Return
jnp.stack([nnx_out.sum(), linen_out.sum()]), shaped(2,), in that order. Keep the sums as JAX scalars so JIT can trace them.
x is a float32 array of shape (T, D). T, D, F and H are positive,
and D is divisible by H. Dimension and seed inputs arrive as floats
representing integers; convert them to Python integers and keep them
static under JIT. Each call builds fresh models and leaves x unchanged.
This is an in-memory forward-equivalence exercise, not checkpoint
loading or a training step.
Understand the ideas behind this exercise. The Problem tab contains the exact implementation requirements.
Transfer a complete encoder block
This exercise copies an initialized Linen encoder block into a matching NNX block. It includes two LayerNorms, four attention projections and two feed-forward layers. The transfer must preserve both the arrays and the computation that uses them.
Use this pre-normalization layout, where the second branch receives the result of the first residual addition:
a = x + attention(ln1(x))
output = a + ff2(relu(ff1(ln2(a))))
Both LayerNorms normalize the last feature axis independently for each token, use epsilon=1e-6, and have trainable scale and bias. Keep their default variance calculation. ff1 maps D features to F, and ff2 maps F back to D. All linear layers include bias. There is no final ReLU or normalization after the second residual addition.
Specify the same attention on both sides
Input x has shape (T, D). With H heads, each head has d=D/H features.
Build attention from four separate biased Dense/Linear layers, named
q_proj, k_proj, v_proj and out_proj, each mapping D features to D.
- Project the normalized input to Q, K and V. Reshape each
(T, D)result to(T, H, d), then transpose to(H, T, d). - For each head, compute
Q @ K.T / sqrt(d)and apply softmax over the final key-position axis. Multiply those weights by V. - Transpose the
(H, T, d)result back to(T, H, d), reshape to(T, D), and apply out_proj.
Attention is unmasked: every token can attend to every input position. There is no dropout, positional encoding, padding mask or cache in this block. These omissions are part of the specified model, not features you should add during the port.
Both Linen and NNX have built-in attention layers, including projections with separate head dimensions. This exercise uses explicit projections to make the parameter mapping easy to inspect. A different implementation may need reshaping and matching configuration; matching names alone is insufficient to establish equivalence.
Map all sixteen parameter arrays
Give Linen submodules explicit names. Under the variables["params"] dictionary, use the following paths, mirrored by NNX attributes:
| Linen path | NNX attribute | Arrays and shapes |
|---|---|---|
| ln1, ln2 | block.ln1, block.ln2 | scale (D,), bias (D,) |
| attn / q_proj, k_proj, v_proj, out_proj | block.attn.<projection> | kernel (D, D), bias (D,) |
| ff1 | block.ff1 | kernel (D, F), bias (F,) |
| ff2 | block.ff2 | kernel (F, D), bias (D,) |
LayerNorm has scale and bias, not a kernel. Attention parameters live
inside the attn subtree. Copy through parameter[...] to preserve
nnx.Param wrappers. No transpose is needed for these matching layouts.
A small normalization transfer example
Here both implementations normalize [1, 3] to approximately [-1, 1]
before applying scale [2, 3] and bias [1, -1]. The resulting vector
is approximately [-1, 2].
import jax.numpy as jnp
import flax.linen as nn
from flax import nnx
variables = {"params": {
"scale": jnp.array([2., 3.]), "bias": jnp.array([1., -1.])
}}
source = nn.LayerNorm(epsilon=1e-6)
target = nnx.LayerNorm(2, epsilon=1e-6, rngs=nnx.Rngs(0))
target.scale[...] = variables["params"]["scale"]
target.bias[...] = variables["params"]["bias"]
x = jnp.array([[1., 3.]])
assert jnp.allclose(target(x), source.apply(variables, x))
assert jnp.allclose(target(x), jnp.array([[-1., 2.]]), atol=1e-5)
For the complete block, transfer every listed array, even a scale or
bias that happens to equal its destination's initial value. Validate
the full outputs with a numerical tolerance. Equal sums alone cannot
prove equivalence: [1, 3] and [2, 2] both sum to four. Do not replace
the requested two forwards with one result reported twice.
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.