We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Flax NNX step 63 of 100
NNX vmap over Ensemble
Many models, one input
An ensemble has several parameter sets. Here each member is a linear layer with the same input and output widths, initialized from its own random-key branch. Apply all members to one shared input vector and keep their outputs separate.
This is a forward-pass exercise. It neither trains the models nor averages predictions, and the outputs do not establish calibrated uncertainty. Vectorization can help execute the computation, but parameter storage and arithmetic still grow with the number of models.
Put the model index on axis zero
For N models mapping D input features to F output features, the stacked
kernel has shape (N, D, F) and the stacked bias has shape (N, F).
NNX represents these arrays inside one module-shaped object.
A forward function with in_axes=(0, None) maps the model state along
axis zero and leaves the input vector unmapped. Inside the function,
each model sees a (D, F) kernel, an (F,) bias and the same (D,) x.
The output is (N, F), with one row per model.
Compare these patterns for a function fwd(model, x):
| in_axes | Meaning |
|---|---|
| (None, 0) | One shared model applied to each input row |
| (0, None) | Stacked models applied to the same input |
| (0, 0) | Each model paired with one input; mapped sizes must match |
Pairing models and inputs does not by itself implement mixture-of-experts routing. Also, do not simply call the stacked object as an ordinary Linear layer: use the mapped forward to give each parameter slice its intended model meaning.
Initialize the stacked parameters
Use two decorators on a single-model constructor. The outer
nnx.split_rngs(splits=N) prepares a mapped RNG axis. The inner
nnx.vmap(in_axes=(0,), out_axes=0) constructs a model in each lane
and stacks its parameter state on axis zero.
The order matters: split the RNG state before mapping the constructor. Split keys provide separate random draws; repeatedly using the same key would reproduce the same initialization. Not every parameter becomes different: the default linear biases are all zero. Distinct random draws also do not guarantee that every output differs, for example on zero input.
Use the exact seeded construction below. A loop with sequential RNG draws or seeds seed+n can produce valid models, but it need not reproduce these particular initialized weights.
A complete example
This creates two models and then sets their parameters to known values so the shared-input mapping is easy to check:
import jax.numpy as jnp
from flax import nnx
@nnx.split_rngs(splits=2)
@nnx.vmap(in_axes=(0,), out_axes=0)
def make_model(rngs):
return nnx.Linear(2, 1, rngs=rngs)
ensemble = make_model(nnx.Rngs(0))
ensemble.kernel[...] = jnp.array([[[1.0], [2.0]], [[3.0], [4.0]]])
ensemble.bias[...] = jnp.array([[0.5], [-0.5]])
@nnx.vmap(in_axes=(0, None))
def fwd(model, x):
return model(x)
x = jnp.array([1.0, 2.0])
assert jnp.allclose(fwd(ensemble, x), jnp.array([[5.5], [10.5]]))
The first member computes 1*1 + 2*2 + 0.5; the second computes
1*3 + 2*4 - 0.5. The result contains both values, not their mean.
Altering one member's parameter slice changes only that member's output.
The diagram expands the module into kernel and bias arrays. Both map
their leading model axis, while x is shared. Five models with output
width four produce (5, 4), flattened to 20 values for this task.
Loading visualization…
Problem
Implement vmap_over_ensemble(seed, x, num_models, features):
- Cast N=int(num_models), F=int(features) and seed to Python integers.
- Define make_model(rngs) to return
nnx.Linear(x.shape[-1], F, rngs=rngs)with default initialization and bias. Apply@nnx.split_rngs(splits=N)above@nnx.vmap(in_axes=(0,), out_axes=0)and call the decorated constructor withnnx.Rngs(int(seed)). - Define fwd(model, x) to return model(x), decorated with
@nnx.vmap(in_axes=(0, None)). - Apply fwd to the ensemble and shared x, then flatten
(N, F)to(N * F,)in model order. Do not reduce over models.
x is a finite float32 vector of shape (D,). D, N and F are positive.
Scalar inputs arrive as floats representing integers. Keep seed, N and F
static under JIT. Each entrypoint call creates a fresh ensemble; a
retained ensemble's forward pass should not mutate its parameters.
When composing transformations around module arguments, use NNX-aware
transforms and pass the module explicitly.
Understand the ideas behind this exercise. The Problem tab contains the exact implementation requirements.
Many models, one input
An ensemble has several parameter sets. Here each member is a linear layer with the same input and output widths, initialized from its own random-key branch. Apply all members to one shared input vector and keep their outputs separate.
This is a forward-pass exercise. It neither trains the models nor averages predictions, and the outputs do not establish calibrated uncertainty. Vectorization can help execute the computation, but parameter storage and arithmetic still grow with the number of models.
Put the model index on axis zero
For N models mapping D input features to F output features, the stacked
kernel has shape (N, D, F) and the stacked bias has shape (N, F).
NNX represents these arrays inside one module-shaped object.
A forward function with in_axes=(0, None) maps the model state along
axis zero and leaves the input vector unmapped. Inside the function,
each model sees a (D, F) kernel, an (F,) bias and the same (D,) x.
The output is (N, F), with one row per model.
Compare these patterns for a function fwd(model, x):
| in_axes | Meaning |
|---|---|
| (None, 0) | One shared model applied to each input row |
| (0, None) | Stacked models applied to the same input |
| (0, 0) | Each model paired with one input; mapped sizes must match |
Pairing models and inputs does not by itself implement mixture-of-experts routing. Also, do not simply call the stacked object as an ordinary Linear layer: use the mapped forward to give each parameter slice its intended model meaning.
Initialize the stacked parameters
Use two decorators on a single-model constructor. The outer
nnx.split_rngs(splits=N) prepares a mapped RNG axis. The inner
nnx.vmap(in_axes=(0,), out_axes=0) constructs a model in each lane
and stacks its parameter state on axis zero.
The order matters: split the RNG state before mapping the constructor. Split keys provide separate random draws; repeatedly using the same key would reproduce the same initialization. Not every parameter becomes different: the default linear biases are all zero. Distinct random draws also do not guarantee that every output differs, for example on zero input.
Use the exact seeded construction below. A loop with sequential RNG draws or seeds seed+n can produce valid models, but it need not reproduce these particular initialized weights.
A complete example
This creates two models and then sets their parameters to known values so the shared-input mapping is easy to check:
import jax.numpy as jnp
from flax import nnx
@nnx.split_rngs(splits=2)
@nnx.vmap(in_axes=(0,), out_axes=0)
def make_model(rngs):
return nnx.Linear(2, 1, rngs=rngs)
ensemble = make_model(nnx.Rngs(0))
ensemble.kernel[...] = jnp.array([[[1.0], [2.0]], [[3.0], [4.0]]])
ensemble.bias[...] = jnp.array([[0.5], [-0.5]])
@nnx.vmap(in_axes=(0, None))
def fwd(model, x):
return model(x)
x = jnp.array([1.0, 2.0])
assert jnp.allclose(fwd(ensemble, x), jnp.array([[5.5], [10.5]]))
The first member computes 1*1 + 2*2 + 0.5; the second computes
1*3 + 2*4 - 0.5. The result contains both values, not their mean.
Altering one member's parameter slice changes only that member's output.
The diagram expands the module into kernel and bias arrays. Both map
their leading model axis, while x is shared. Five models with output
width four produce (5, 4), flattened to 20 values for this task.
Loading visualization…
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.