We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Flax NNX step 71 of 100
NNX Bridge: Call Linen From NNX
Reuse a Linen layer inside an NNX model
You can keep an existing Linen layer while building its surrounding
model in NNX. bridge.ToNNX wraps the Linen module so an NNX parent
can store it as a submodule and call it directly.
This exercise wraps a single biased Linen Dense layer. After
initialization it computes x @ kernel + bias, returning F features
from an input vector of width I. The wrapper exposes its trainable
arrays as NNX parameters; you do not manually copy a variables dictionary.
Initialize before the ordinary forward call
Constructing nn.Dense(features=F) does not create its kernel. Linen
needs an input shape to infer I and initialize a kernel of shape
(I, F) and a bias of shape (F,).
bridge.lazy_init(model, x) runs the model in initialization mode,
including the wrapped Dense, and installs the resulting state. In
this recipe an ordinary call before lazy_init fails because the Linen
parameters are missing. It is not an implicit initialization path.
The input to lazy_init supplies the shape and dtype needed by Dense; this layer's parameter values do not depend on the sample's numerical contents. Other Linen modules may have different initialization behavior. This bridge operation runs the model; do not assume it is a shape-only calculation with no work or state changes.
A complete example
The example initializes the bridge, then assigns known parameters so the forward result is easy to check. The exercise keeps the original seeded parameters instead.
import jax.numpy as jnp
import flax.linen as nn
from flax import nnx
from flax.nnx import bridge
class HybridModel(nnx.Module):
def __init__(self, features, rngs):
self.linen_dense = bridge.ToNNX(nn.Dense(features), rngs=rngs)
def __call__(self, x):
return self.linen_dense(x)
model = HybridModel(2, nnx.Rngs(0))
x = jnp.array([1., 2.])
bridge.lazy_init(model, x)
model.linen_dense.kernel[...] = jnp.array([[2., 0.], [0., 3.]])
model.linen_dense.bias[...] = jnp.array([1., -1.])
assert jnp.allclose(model(x), jnp.array([3., 5.]))
assert jnp.allclose(nnx.jit(lambda m, x: m(x))(model, x), model(x))
Once initialized, call the model normally to reuse its parameters. Calling lazy_init again is another initialization pass and can replace those parameters. Do not put it inside each prediction or training step. A different input feature width requires a compatible layer, not merely a new input to the existing initialized Dense.
Parameters and RNG state have different roles
Inspect trainable arrays after initialization with
nnx.state(model, nnx.Param). Before initialization, the wrapped Dense
has no kernel or bias yet, but the wrapper may already hold RNG state.
An empty parameter selection is not the same as an entirely empty model.
The bridge handles RNG keys on behalf of Linen. Use the specified
nnx.Rngs(int(seed)) construction and one initialization pass so the
exercise reproduces the expected seeded weights. A standalone Linen
init using the same integer seed need not produce those same weights.
An ordinary call can advance the wrapper's RNG counters even though this Dense forward uses no random operation and its parameters stay unchanged. Mutable Linen collections and stochastic layers need their own deliberate RNG and mutable-state settings; this Dense example does not establish that every wrapped module is stateless.
For differentiation or JIT, pass the initialized model explicitly to the NNX transformation. The wrapper's parameters remain available to parameter filters and gradients. No optimizer or training step is required here.
Problem
Implement linen_inside_nnx(seed, x, features):
- Define an NNX parent whose constructor stores
bridge.ToNNX(nn.Dense(features=F), rngs=rngs)as linen_dense. Its call method returns linen_dense(x). - Convert features and seed to Python integers. Construct the parent
with F=int(features) and
nnx.Rngs(int(seed)). - Call
bridge.lazy_init(model, x)once, then returnmodel(x). Keep Dense's default kernel initialization and bias enabled.
x is a one-dimensional float32 array with positive input width.
features is a positive integer passed as a float; seed is also passed
as a float representing an integer. Keep seed and features static if
JIT-compiling the entrypoint, which creates a fresh model on each call.
Return an array shaped (int(features),) and leave x unchanged.
The numerical output checks this forward calculation. It cannot by itself prove use of the bridge; constructing the requested wrapped submodule is part of the exercise.
Understand the ideas behind this exercise. The Problem tab contains the exact implementation requirements.
Reuse a Linen layer inside an NNX model
You can keep an existing Linen layer while building its surrounding
model in NNX. bridge.ToNNX wraps the Linen module so an NNX parent
can store it as a submodule and call it directly.
This exercise wraps a single biased Linen Dense layer. After
initialization it computes x @ kernel + bias, returning F features
from an input vector of width I. The wrapper exposes its trainable
arrays as NNX parameters; you do not manually copy a variables dictionary.
Initialize before the ordinary forward call
Constructing nn.Dense(features=F) does not create its kernel. Linen
needs an input shape to infer I and initialize a kernel of shape
(I, F) and a bias of shape (F,).
bridge.lazy_init(model, x) runs the model in initialization mode,
including the wrapped Dense, and installs the resulting state. In
this recipe an ordinary call before lazy_init fails because the Linen
parameters are missing. It is not an implicit initialization path.
The input to lazy_init supplies the shape and dtype needed by Dense; this layer's parameter values do not depend on the sample's numerical contents. Other Linen modules may have different initialization behavior. This bridge operation runs the model; do not assume it is a shape-only calculation with no work or state changes.
A complete example
The example initializes the bridge, then assigns known parameters so the forward result is easy to check. The exercise keeps the original seeded parameters instead.
import jax.numpy as jnp
import flax.linen as nn
from flax import nnx
from flax.nnx import bridge
class HybridModel(nnx.Module):
def __init__(self, features, rngs):
self.linen_dense = bridge.ToNNX(nn.Dense(features), rngs=rngs)
def __call__(self, x):
return self.linen_dense(x)
model = HybridModel(2, nnx.Rngs(0))
x = jnp.array([1., 2.])
bridge.lazy_init(model, x)
model.linen_dense.kernel[...] = jnp.array([[2., 0.], [0., 3.]])
model.linen_dense.bias[...] = jnp.array([1., -1.])
assert jnp.allclose(model(x), jnp.array([3., 5.]))
assert jnp.allclose(nnx.jit(lambda m, x: m(x))(model, x), model(x))
Once initialized, call the model normally to reuse its parameters. Calling lazy_init again is another initialization pass and can replace those parameters. Do not put it inside each prediction or training step. A different input feature width requires a compatible layer, not merely a new input to the existing initialized Dense.
Parameters and RNG state have different roles
Inspect trainable arrays after initialization with
nnx.state(model, nnx.Param). Before initialization, the wrapped Dense
has no kernel or bias yet, but the wrapper may already hold RNG state.
An empty parameter selection is not the same as an entirely empty model.
The bridge handles RNG keys on behalf of Linen. Use the specified
nnx.Rngs(int(seed)) construction and one initialization pass so the
exercise reproduces the expected seeded weights. A standalone Linen
init using the same integer seed need not produce those same weights.
An ordinary call can advance the wrapper's RNG counters even though this Dense forward uses no random operation and its parameters stay unchanged. Mutable Linen collections and stochastic layers need their own deliberate RNG and mutable-state settings; this Dense example does not establish that every wrapped module is stateless.
For differentiation or JIT, pass the initialized model explicitly to the NNX transformation. The wrapper's parameters remain available to parameter filters and gradients. No optimizer or training step is required here.
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.