Phase 05 / Neural network training
Model and state with Flax NNX
Revision v0.7.0 · Updated since your last visit. Review the changes; your progress is saved.
Lesson · about 80 minutes · CPU practice
Express a model and inspect the distinction between parameters and other state.
Phase study guide: preparation, milestones and project evidence
Before you start
Before this: Build a tiny multilayer perceptron
Hardware: CPU
Setup help: Python, notebook, or Cloud TPU VM
Use the tested CPU packages (the recorded environment uses Python 3.14.3). Other Python versions and accelerator environments require separate checks. The optional Cloud TPU specialization explains the target-specific execution boundaries. Logical CPU devices practice partitioning; they do not simulate TPU performance.
Download the repository ZIP or use your existing checkout. Extract it and open a terminal in the folder containing requirements-cpu.txt. The beginner ZIP is separate; these lessons use the full repository folder.
python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-cpu.txtpy -3 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements-cpu.txtAfter activation, run the complete worked example from that same folder:
python phases/05-networks/02-model-and-state-with-flax-nnx/code/main.py.\.venv\Scripts\python.exe phases/05-networks/02-model-and-state-with-flax-nnx/code/main.pyFor an incremental build, create main.py in your own practice folder, append each build block in order, and run python main.py with this environment active. Keep that file separate from the downloaded reference solution.
Download this lesson’s notebook. Open it with a Python kernel using the course environment. Run cells from the top after restarting the kernel; experiments depend on earlier definitions.
For local Jupyter, first install the course requirements using the Local Python steps, then use your environment’s Python to start Jupyter:
python -m pip install jupyterlab
python -m jupyter lab.\.venv\Scripts\python.exe -m pip install jupyterlab
.\.venv\Scripts\python.exe -m jupyter labSelect the downloaded networks-02.ipynb file in JupyterLab. The notebook contains reference solutions; make your prediction and attempt each practice task before opening them. Restart the kernel before changing logical device setup.
The worked reference for this lesson is CPU-validated. Some references explicitly configure logical CPU devices; setting JAX_PLATFORMS does not adapt them to a TPU. Follow the execution matrix and reviewed multi-host example before attempting a target run.
For accelerator preparation, study TPU setup, observed runtime receipts, checkpointed jobs and precision and retained traces. These target paths remain unqualified on physical TPU hardware until an actual receipt is recorded. Cloud provisioning is optional for CPU learning.
Check: Trainable scalars: ; other state: ; original and cloned counters are and .
Revisit the guided CPU setup · Open 19: TPU Setup. Package instructions are CPU instructions; Windows and accelerator environments need their own validation.
What you will be able to do
- Move the explicit equations into a module
- Separate parameter state from diagnostic state
- Reconstruct an independent graph from a snapshot
- Check a library forward pass independently
The problem
A model library can organize weights for us, but we still want to see what is being learned. We’ll build a small Flax NNX module, compare its predictions with NumPy, and inspect its state. A diagnostic counter will help us distinguish trainable parameters from other values a model carries.
The idea
A model has a computation structure and state values. Some state is trainable; other state may record statistics or randomness. Flax NNX helps manage these objects, but we still need to understand which identities are shared and which values an update changes.
Distinguish shared structure from changing values
Imagine two attributes referring to the same layer object. They share one parameter set. Drawing two independent weight boxes would misrepresent the computation, even if the printed values initially match.
Separating graph structure and state lets us transform the numerical state while preserving the intended relationships. Compare paths and values before and after splitting, merging or cloning; copying a Python reference does not create independent model state.
The existing merge figure is a map of these roles. Use the actual NNX operations in the lesson to test the map, especially when a model has state beyond parameters. Evaluation should follow its declared state rules rather than silently repeating training updates.
Move the explicit equations into a module
A module groups the same equations from the previous lesson with the arrays that they use. nnx.Linear owns a kernel and bias; hidden( x ) computes . A second Linear consumes tanh of the hidden values. The constructor must know the feature dimensions, so an input feature mismatch is a contract error rather than a request to infer a new model. Our width-three example has trainable scalars. A batch dimension can vary without adding parameters. Class-based organization has not changed the mathematics.
Separate parameter state from diagnostic state
The calls counter is an ordinary nnx.Variable, not nnx.Param. Calling the model with record=True increments it as a deliberate teaching example. It is not a learned weight and should not enter optimizer updates. nnx.state(model, nnx.Param) selects trainable variables; nnx.state(model, nnx.Not(nnx.Param)) selects the remaining state. A real model may have batch-normalization statistics or dropout RNG counts in this second category. Excluding a variable from gradients does not make it immutable. You still need to specify when forward passes may change it.
model
├─ hidden: kernel, bias [Param]
├─ out: kernel, bias [Param]
└─ calls: diagnostic counter [Variable]Reconstruct an independent graph from a snapshot
nnx.split separates the graph definition from dynamic state; nnx.merge reconstructs a module from those pieces. In the tested Flax version, merging the original State can reuse Variable objects; map a copying function over state leaves to reconstruct independent typed state first. Think of graph definition as the wiring and state as the current array contents. This is an in-memory demonstration, not a durable checkpoint format. The copied-state clone starts with identical values and independent mutable variables, which we verify below. Incrementing its counter must not increment the original. Copying an attribute reference, in contrast, may share a module. Explicitly test independence rather than assuming any assignment or shallow copy creates a training snapshot.
Check a library forward pass independently
Reading model.hidden.kernel[...] retrieves its array contents in the tested Flax API. Compute the affine maps and tanh with NumPy using those arrays and compare the logits on several observations. This verifies the wiring, activation placement, and squeeze axis; it does not independently validate the initializer. Then change one output bias and predict the exact added logit shift. Keep this algebraic test separate from classification accuracy. A library refactor can preserve predictions but change state ownership, so both numerical and ownership checks are necessary.
Layer parameter counts across the NNX module tree
Predict before looking: Predict how many trainable parameters live in the kernel versus the bias of the NNX module.
Read the figure
Each bar shows the exact element count of a parameter leaf extracted by nnx.state(model, nnx.Param).
Connect it to the computation
Splitting an NNX module into its graph definition and state PyTree makes every parameter leaf inspectable and transformable by standard JAX functions.
Open full-size figureDownload data and provenance
The notebook contains the data experiment, plotting code, and recorded figure output. Rerun it to test your changes.
Where to put & run this code: Work in phases/05-networks/02-model-and-state-with-flax-nnx/code/main.py (or downloaded exercises/networks-02.py / exercises/networks-02.ipynb) and execute from the repository root with python3 phases/05-networks/02-model-and-state-with-flax-nnx/code/main.py.
1 Define typed state and the model
Create main.py. From the repository root, run python3 -m pip install - requirements-cpu.txt in your activated environment; the course pins Flax 0.12.6. Copy the model before adding any optimizer.
# Step 1 — 1. Define typed state and the model: nnx.Rngs manages initializer keys.
from flax import nnx
import jax
import jax.numpy as jnp
import numpy as np
# Define `TinyMLP` module / container with explicit state and forward pass:
class TinyMLP(nnx.Module):
def __init__(self,seed=0):
rngs=nnx.Rngs(seed)
self.hidden=nnx.Linear(2,3,rngs=rngs)
self.out=nnx.Linear(3,1,rngs=rngs)
# Create device-backed JAX array `self.calls`.
self.calls=nnx.Variable(jnp.array(0,dtype=jnp.int32))
def __call__(self,x,record=False):
if record:self.calls[...] += 1
return self.out(nnx.tanh(self.hidden(x))).squeeze(-1)
model=TinyMLP()
batch=jnp.array([[1.,2.],[-1.,.5],[0.,0.]])Code building blocks in this step
jnp.array(values, dtype=...)— Constructs an immutable device-backed JAX array from Python/NumPy values.nnx.Module / nnx.Linear / nnx.Optimizer— Flax NNX stateful module and optimizer containers with explicit RNG streams (nnx.Rngs) and traced graph updates.
nnx.Rngs manages initializer keys. The optional diagnostic counter changes only when explicitly requested.
2 Verify equations and inspect the filters
Append an independent NumPy computation. Count only nnx.Param leaves, not the counter.
# Step 2 — 2. Verify equations and inspect the filters: The matching forward pass verifies the network wiring.
scores=model(batch,record=True)
# Convert `h` to a host NumPy array for inspection or verification.
h=np.tanh(np.asarray(batch)@np.asarray(model.hidden.kernel[...])+np.asarray(model.hidden.bias[...]))
# Convert `expected` to a host NumPy array for inspection or verification.
expected=(h@np.asarray(model.out.kernel[...])+np.asarray(model.out.bias[...])).squeeze(-1)
np.testing.assert_allclose(scores,expected,rtol=1e-5,atol=1e-6)
param_state=nnx.state(model,nnx.Param)
other_state=nnx.state(model,nnx.Not(nnx.Param))
assert sum(a.size for a in jax.tree.leaves(param_state))==13
assert sum(a.size for a in jax.tree.leaves(other_state))==1
assert int(model.calls[...])==1Code building blocks in this step
jnp.allclose(actual, expected, rtol=..., atol=...)— Checks that two arrays match elementwise within floating-point tolerance.nnx.Module / nnx.Linear / nnx.Optimizer— Flax NNX stateful module and optimizer containers with explicit RNG streams (nnx.Rngs) and traced graph updates.
The matching forward pass verifies the network wiring. The filters identify learned scalars and one unlearned state scalar.
3 Split, merge, and test independence
Append the clone test. Predict which counter changes when the clone executes.
# Step 3 — 3. Split, merge, and test independence: nnx.grad differentiates Param variables by default.
graphdef,state=nnx.split(model)
# Transform every leaf of the parameter PyTree (`clone`).
clone=nnx.merge(graphdef,jax.tree.map(lambda a:jnp.array(a,copy=True),state))
np.testing.assert_allclose(clone(batch),model(batch),rtol=1e-6)
clone(batch,record=True)
assert int(clone.calls[...])==2 and int(model.calls[...])==1
# Differentiate the objective to obtain `grads` via automatic differentiation.
grads=nnx.grad(lambda m:jnp.mean(m(batch)**2))(model)
assert sum(a.size for a in jax.tree.leaves(grads))==13
assert 'calls' not in grads
print('Trainable scalars: 13; other state: 1; clone counters:',int(model.calls[...]),int(clone.calls[...]))Code building blocks in this step
jnp.array(values, dtype=...)— Constructs an immutable device-backed JAX array from Python/NumPy values.x.sum(axis=...) / x.mean(axis=..., keepdims=...)— Reduces values along the namedaxis(the axis you name is collapsed unlesskeepdims=True).jnp.allclose(actual, expected, rtol=..., atol=...)— Checks that two arrays match elementwise within floating-point tolerance.jax.tree.map(lambda p, g: ..., params, grads)— Applies a function leaf-by-leaf across matching PyTrees (such as updating every parameter tensor with its gradient).
nnx.grad differentiates Param variables by default. Counter independence is a state-ownership invariant, not a performance measurement.
View the cumulative build in main.py
These blocks append in order to the same file.
# Step 1 — 1. Define typed state and the model: nnx.Rngs manages initializer keys.
from flax import nnx
import jax
import jax.numpy as jnp
import numpy as np
# Define `TinyMLP` module / container with explicit state and forward pass:
class TinyMLP(nnx.Module):
def __init__(self,seed=0):
rngs=nnx.Rngs(seed)
self.hidden=nnx.Linear(2,3,rngs=rngs)
self.out=nnx.Linear(3,1,rngs=rngs)
# Create device-backed JAX array `self.calls`.
self.calls=nnx.Variable(jnp.array(0,dtype=jnp.int32))
def __call__(self,x,record=False):
if record:self.calls[...] += 1
return self.out(nnx.tanh(self.hidden(x))).squeeze(-1)
model=TinyMLP()
batch=jnp.array([[1.,2.],[-1.,.5],[0.,0.]])
# Step 2 — 2. Verify equations and inspect the filters: The matching forward pass verifies the network wiring.
scores=model(batch,record=True)
# Convert `h` to a host NumPy array for inspection or verification.
h=np.tanh(np.asarray(batch)@np.asarray(model.hidden.kernel[...])+np.asarray(model.hidden.bias[...]))
# Convert `expected` to a host NumPy array for inspection or verification.
expected=(h@np.asarray(model.out.kernel[...])+np.asarray(model.out.bias[...])).squeeze(-1)
np.testing.assert_allclose(scores,expected,rtol=1e-5,atol=1e-6)
param_state=nnx.state(model,nnx.Param)
other_state=nnx.state(model,nnx.Not(nnx.Param))
assert sum(a.size for a in jax.tree.leaves(param_state))==13
assert sum(a.size for a in jax.tree.leaves(other_state))==1
assert int(model.calls[...])==1
# Step 3 — 3. Split, merge, and test independence: nnx.grad differentiates Param variables by default.
graphdef,state=nnx.split(model)
# Transform every leaf of the parameter PyTree (`clone`).
clone=nnx.merge(graphdef,jax.tree.map(lambda a:jnp.array(a,copy=True),state))
np.testing.assert_allclose(clone(batch),model(batch),rtol=1e-6)
clone(batch,record=True)
assert int(clone.calls[...])==2 and int(model.calls[...])==1
# Differentiate the objective to obtain `grads` via automatic differentiation.
grads=nnx.grad(lambda m:jnp.mean(m(batch)**2))(model)
assert sum(a.size for a in jax.tree.leaves(grads))==13
assert 'calls' not in grads
print('Trainable scalars: 13; other state: 1; clone counters:',int(model.calls[...]),int(clone.calls[...]))Run the example
python3 phases/05-networks/02-model-and-state-with-flax-nnx/code/main.pyAbout this command
This shortcut uses an activated macOS/Linux course environment in the repository folder. For Windows, use the explicit interpreter command in “Setup help” above. The script includes worked examples and reference solutions.
Inspect the complete assembled example
# Step 1 — 1. Define typed state and the model: nnx.Rngs manages initializer keys.
from flax import nnx
import jax
import jax.numpy as jnp
import numpy as np
# Define `TinyMLP` module / container with explicit state and forward pass:
class TinyMLP(nnx.Module):
def __init__(self,seed=0):
rngs=nnx.Rngs(seed)
self.hidden=nnx.Linear(2,3,rngs=rngs)
self.out=nnx.Linear(3,1,rngs=rngs)
# Create device-backed JAX array `self.calls`.
self.calls=nnx.Variable(jnp.array(0,dtype=jnp.int32))
def __call__(self,x,record=False):
if record:self.calls[...] += 1
return self.out(nnx.tanh(self.hidden(x))).squeeze(-1)
model=TinyMLP()
batch=jnp.array([[1.,2.],[-1.,.5],[0.,0.]])
# Step 2 — 2. Verify equations and inspect the filters: The matching forward pass verifies the network wiring.
scores=model(batch,record=True)
# Convert `h` to a host NumPy array for inspection or verification.
h=np.tanh(np.asarray(batch)@np.asarray(model.hidden.kernel[...])+np.asarray(model.hidden.bias[...]))
# Convert `expected` to a host NumPy array for inspection or verification.
expected=(h@np.asarray(model.out.kernel[...])+np.asarray(model.out.bias[...])).squeeze(-1)
np.testing.assert_allclose(scores,expected,rtol=1e-5,atol=1e-6)
param_state=nnx.state(model,nnx.Param)
other_state=nnx.state(model,nnx.Not(nnx.Param))
assert sum(a.size for a in jax.tree.leaves(param_state))==13
assert sum(a.size for a in jax.tree.leaves(other_state))==1
assert int(model.calls[...])==1
# Step 3 — 3. Split, merge, and test independence: nnx.grad differentiates Param variables by default.
graphdef,state=nnx.split(model)
# Transform every leaf of the parameter PyTree (`clone`).
clone=nnx.merge(graphdef,jax.tree.map(lambda a:jnp.array(a,copy=True),state))
np.testing.assert_allclose(clone(batch),model(batch),rtol=1e-6)
clone(batch,record=True)
assert int(clone.calls[...])==2 and int(model.calls[...])==1
# Differentiate the objective to obtain `grads` via automatic differentiation.
grads=nnx.grad(lambda m:jnp.mean(m(batch)**2))(model)
assert sum(a.size for a in jax.tree.leaves(grads))==13
assert 'calls' not in grads
print('Trainable scalars: 13; other state: 1; clone counters:',int(model.calls[...]),int(clone.calls[...]))Expected result: Trainable scalars: ; other state: ; original and cloned counters are and .
Recorded reference execution
Reference output captured from executing the complete lesson script (code/main.py) and its assertions in the course CPU environment.
Trainable scalars: 13; other state: 1; clone counters: 1 2
Trainable scalars: 13; other state: 1; clone counters: 1 2
Clone logits shifted by one; original parameters unchanged.
PASS: networks-02
Download execution record · The notebook includes executed cells and figure outputs. Compare your run with these values; timings depend on the machine.
Bias shifts every score equally
# Experiment — Bias shifts every score equally: An affine output bias changes the decision threshold uniformly;...
before=np.asarray(model(batch))
saved=model.out.bias[...]
model.out.bias[...] = saved+.25
np.testing.assert_allclose(model(batch),before+.25,atol=1e-6)
model.out.bias[...] = savedExpected: All logits increase by .
An affine output bias changes the decision threshold uniformly; the hidden representation is unchanged.
Repeated construction reproduces state
# Experiment — Repeated construction reproduces state: Replay is checked in one pinned environment.
replay=TinyMLP(0)
for a,b in zip(jax.tree.leaves(nnx.state(replay,nnx.Param)),jax.tree.leaves(param_state)):
# Execute `np.testing.assert_array_equal(a,b)`
np.testing.assert_array_equal(a,b)
different=TinyMLP(1)
assert not np.array_equal(np.asarray(different.hidden.kernel[...]),np.asarray(model.hidden.kernel[...]))Expected: Equal seeds reproduce the parameter arrays; a changed seed changes the hidden kernel.
Replay is checked in one pinned environment. Seeds alone do not promise identical bytes across package versions or backends.
Make it yours
Where to put your code: Append your solution at the bottom of phases/05-networks/02-model-and-state-with-flax-nnx/code/main.py (or inside the Make it yours cell of exercises/networks-02.ipynb) and run python3 phases/05-networks/02-model-and-state-with-flax-nnx/code/main.py to verify your prediction before opening the reference solution.
Apply the model to one new row and to five rows. Verify the scalar parameter count stays and the batch axis survives.
Show a writing guide and starter scaffold
How to write this exercise code: step-by-step recipe & starter scaffold
1. Key functions & syntax to use:
jnp.array(values, dtype=...)— Constructs an immutable device-backed JAX array from Python/NumPy values.nnx.Module / nnx.Linear / nnx.Optimizer— Flax NNX stateful module and optimizer containers with explicit RNG streams (nnx.Rngs) and traced graph updates.
2. Step-by-step implementation plan:
- Check tensor shape invariant:
model(single).shape==(1,) - Check tensor shape invariant:
model(jnp.repeat(single,5,axis=0)).shape==(5,)
# Exercise solution: Apply the model to one new row and to five rows.
single = jnp.array(...) # TODO: compute single
# Check tensor shape invariant: `model(single).shape==(1,)`
assert model(single).shape # TODO: complete assertion check
# Check tensor shape invariant: `model(jnp.repeat(single,5,axis=0)).shape==(5,)`
assert model(jnp.repeat(single,5,axis=0)).shape # TODO: complete assertion check
assert sum(a.size for a # TODO: complete assertion check
Reveal the reference solution
# Exercise solution: Apply the model to one new row and to five rows.
single=jnp.array([[.3,-.7]])
# Check tensor shape invariant: `model(single).shape==(1,)`
assert model(single).shape==(1,)
# Check tensor shape invariant: `model(jnp.repeat(single,5,axis=0)).shape==(5,)`
assert model(jnp.repeat(single,5,axis=0)).shape==(5,)
assert sum(a.size for a in jax.tree.leaves(nnx.state(model,nnx.Param)))==13Change a clone without changing its source
Transfer
Increase only the cloned model’s output bias by one. Predict the change in every logit and verify the original model’s parameter leaves remain unchanged.
Show a writing guide and starter scaffold
How to write this practice solution: step-by-step recipe & starter scaffold
1. Key functions & syntax to use:
jnp.allclose(actual, expected, rtol=..., atol=...)— Checks that two arrays match elementwise within floating-point tolerance.nnx.Module / nnx.Linear / nnx.Optimizer— Flax NNX stateful module and optimizer containers with explicit RNG streams (nnx.Rngs) and traced graph updates.
2. Step-by-step implementation plan:
- Transform every leaf of the parameter PyTree (
original_leaves). - Convert
old_cloneto a host NumPy array for inspection or verification. - Execute
np.testing.assert_array_equal(before,after)
# Change a clone without changing its source (Transfer): The bias is broadcast over observations, so every logit...
# Transform every leaf of the parameter PyTree (`original_leaves`).
original_leaves = ... # TODO: compute original_leaves
# Convert `old_clone` to a host NumPy array for inspection or verification.
old_clone = np.asarray(...) # TODO: compute old_clone
clone.out.bias[...] = ... # TODO: compute clone.out.bias[...]
np.testing.assert_allclose(clone(batch),old_clone+1.,rtol=1e-5,atol=1e-6)
for before,after in zip(original_leaves,jax.tree.leaves(nnx.state(model,nnx.Param))):
# Execute `np.testing.assert_array_equal(before,after)`
np.testing.assert_array_equal(before,after)
print('Clone logits shifted by one; original parameters unchanged.')
Show a hint
Snapshot the original parameters before editing clone.out.bias.
Reveal reference solution and reasoning
# Change a clone without changing its source (Transfer): The bias is broadcast over observations, so every logit...
# Transform every leaf of the parameter PyTree (`original_leaves`).
original_leaves=[np.array(a,copy=True) for a in jax.tree.leaves(nnx.state(model,nnx.Param))]
# Convert `old_clone` to a host NumPy array for inspection or verification.
old_clone=np.asarray(clone(batch)).copy()
clone.out.bias[...] = clone.out.bias[...] + 1.
np.testing.assert_allclose(clone(batch),old_clone+1.,rtol=1e-5,atol=1e-6)
for before,after in zip(original_leaves,jax.tree.leaves(nnx.state(model,nnx.Param))):
# Execute `np.testing.assert_array_equal(before,after)`
np.testing.assert_array_equal(before,after)
print('Clone logits shifted by one; original parameters unchanged.')The bias is broadcast over observations, so every logit shifts equally. Comparing original leaves detects accidental aliasing; equal parameter counts cannot detect that bug.
Diagnose a shared module reference
Intermediate
Assign shared = model, increment its counter, and demonstrate why this is not a snapshot. Then reconstruct an independent module.
Show a writing guide and starter scaffold
How to write this practice solution: step-by-step recipe & starter scaffold
1. Key functions & syntax to use:
jnp.array(values, dtype=...)— Constructs an immutable device-backed JAX array from Python/NumPy values.jax.tree.map(lambda p, g: ..., params, grads)— Applies a function leaf-by-leaf across matching PyTrees (such as updating every parameter tensor with its gradient).
2. Step-by-step implementation plan:
- Diagnose a shared module reference (Intermediate): Sharing a graph is useful for tied layers but wrong for an...
- Transform every leaf of the parameter PyTree (
independent).
# Diagnose a shared module reference (Intermediate): Sharing a graph is useful for tied layers but wrong for an...
saved_count = int(...) # TODO: compute saved_count
shared = ... # TODO: compute shared
shared(batch,record = ... # TODO: compute shared(batch,record
assert int(model.calls[...]) # TODO: complete assertion check
graph,state = nnx.split(...) # TODO: compute graph,state
# Transform every leaf of the parameter PyTree (`independent`).
independent = nnx.merge(...) # TODO: compute independent
independent(batch,record = ... # TODO: compute independent(batch,record
assert int(model.calls[...]) # TODO: complete assertion check
assert int(independent.calls[...]) # TODO: complete assertion check
Show a hint
Assignment changes which name refers to the same object.
Reveal reference solution and reasoning
# Diagnose a shared module reference (Intermediate): Sharing a graph is useful for tied layers but wrong for an...
saved_count=int(model.calls[...])
shared=model
shared(batch,record=True)
assert int(model.calls[...])==saved_count+1
graph,state=nnx.split(model)
# Transform every leaf of the parameter PyTree (`independent`).
independent=nnx.merge(graph,jax.tree.map(lambda a:jnp.array(a,copy=True),state))
independent(batch,record=True)
assert int(model.calls[...])==saved_count+1
assert int(independent.calls[...])==saved_count+2Sharing a graph is useful for tied layers but wrong for an independent evaluation copy. Split, copy state, and merge supply independent variables in this example.
Diagnose the result
If evaluation changes state, inspect every nonparameter variable and forward flag. If a clone changes the original, inspect shared references. If an NNX module fails under a raw JAX transform, use nnx transformations or split it into graph/state deliberately instead of hiding the error.
Carry forward
- Move the explicit equations into a module
- Separate parameter state from diagnostic state
- Reconstruct an independent graph from a snapshot
- Check a library forward pass independently
Keep your evidence
Keep the NumPy forward comparison, count of 13 trainable scalars and one diagnostic counter, counter independence after copying state, and logits for batch size one and five.
Save your modified code, its output, and an explanation of your prediction. Checkpoint answers and exercise evidence are tracked separately.
Saved in this browser. Export or back up your portfolio anytime in My learning.
Verified on local CPU. Stage to a Cloud TPU VM via Cloud TPU setup to record accelerator execution receipts (TODO: add automated Cloud TPU CI execution receipts).