We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Flax NNX step 66 of 100
NNX Checkpoint / Remat
A forward function can accept an NNX model and call it directly.
nnx.remat wraps that function with gradient checkpointing: during
differentiation, some values needed for derivatives can be recomputed
instead of retained from the forward pass.
Build the model first
class MLP(nnx.Module):
def __init__(self, num_layers, features, *, rngs):
self.layers = nnx.List([
nnx.Linear(features, features, rngs=rngs)
for _ in range(num_layers)
])
def __call__(self, x):
for layer in self.layers:
x = jax.nn.relu(layer(x))
return x
Use one nnx.Rngs(seed) object for the constructors, in order. Each
Linear consumes fresh keys from that stream. The layers have separate
parameters; their default zero biases can still have equal values.
Creating a new RNG object with the same seed for every layer would repeat
initialization instead of advancing the stream.
nnx.List is a state-aware container for these submodules. Ordinary lists
are valid JAX pytrees, so the reason for this container is NNX attribute
and state handling, not a general ban on lists in pytrees. Explicitly
marking a list with nnx.data is another supported approach in current
NNX; use nnx.List for this exercise.
Wrap the forward function
@nnx.remat
def fwd(model, x):
return model(x)
model = MLP(N, F, rngs=nnx.Rngs(seed))
output = fwd(model, x)
The NNX transformation handles module state so the wrapped function can accept the model object. Initialization happens before this call. The model here has no dropout, running statistics or optimizer updates.
Checkpointing preserves the mathematical output and derivatives. Compare floating-point results with a tolerance rather than expecting byte-for-byte equality across compilation settings. A forward-only call does not measure backward-pass memory savings.
Values saved for differentiation are called residuals. Checkpointing can reduce their storage by recomputing internal values from retained inputs. It does not remove all activation storage or guarantee constant memory in network depth. Cost depends on the boundary, model, compiler and hardware; there is no fixed extra-compute percentage. Profile a differentiated workload when choosing boundaries or nested checkpoint regions.
Problem
Implement checkpoint_forward(seed, x, features, num_layers):
- Convert the scalar arguments to integers: seed, width
Fand depthN. - Construct the MLP above with
Nseparate defaultnnx.Linear(F, F)layers, using a singlennx.Rngs(seed)in constructor order. - Apply ReLU after every layer, including the last. Keep default biases and initializers.
- Call the model through a forward function decorated with
@nnx.remat. - Return the final activation flattened to a JAX vector of shape
(F,).
x is a finite float32 JAX vector of shape (F,). Width and depth are
positive integer-valued scalars; the seed is a nonnegative integer-valued
scalar. These scalars may arrive as floats. Leave x unchanged.
Tests check the seeded forward values. They cannot prove that remat was used or demonstrate memory savings; an ordinary model call can return the same values.
Understand the ideas behind this exercise. The Problem tab contains the exact implementation requirements.
A forward function can accept an NNX model and call it directly.
nnx.remat wraps that function with gradient checkpointing: during
differentiation, some values needed for derivatives can be recomputed
instead of retained from the forward pass.
Build the model first
class MLP(nnx.Module):
def __init__(self, num_layers, features, *, rngs):
self.layers = nnx.List([
nnx.Linear(features, features, rngs=rngs)
for _ in range(num_layers)
])
def __call__(self, x):
for layer in self.layers:
x = jax.nn.relu(layer(x))
return x
Use one nnx.Rngs(seed) object for the constructors, in order. Each
Linear consumes fresh keys from that stream. The layers have separate
parameters; their default zero biases can still have equal values.
Creating a new RNG object with the same seed for every layer would repeat
initialization instead of advancing the stream.
nnx.List is a state-aware container for these submodules. Ordinary lists
are valid JAX pytrees, so the reason for this container is NNX attribute
and state handling, not a general ban on lists in pytrees. Explicitly
marking a list with nnx.data is another supported approach in current
NNX; use nnx.List for this exercise.
Wrap the forward function
@nnx.remat
def fwd(model, x):
return model(x)
model = MLP(N, F, rngs=nnx.Rngs(seed))
output = fwd(model, x)
The NNX transformation handles module state so the wrapped function can accept the model object. Initialization happens before this call. The model here has no dropout, running statistics or optimizer updates.
Checkpointing preserves the mathematical output and derivatives. Compare floating-point results with a tolerance rather than expecting byte-for-byte equality across compilation settings. A forward-only call does not measure backward-pass memory savings.
Values saved for differentiation are called residuals. Checkpointing can reduce their storage by recomputing internal values from retained inputs. It does not remove all activation storage or guarantee constant memory in network depth. Cost depends on the boundary, model, compiler and hardware; there is no fixed extra-compute percentage. Profile a differentiated workload when choosing boundaries or nested checkpoint regions.
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.