Skip to content

Flax integration

uv add esmoe[flax]

Flax ships two APIs side by side and both are supported: esmoe.flax is nnx, the one Flax recommends now, and esmoe.flax.linen is Linen. Inputs are NHWC either way.

nnx

State lives on the object, and nnx.split hands it to a pure function when one is wanted.

from flax import nnx
from esmoe.flax import ESMoE

net = nnx.Sequential(
    nnx.Conv(3, 32, (3, 3), rngs=nnx.Rngs(0)),
    ESMoE(kernels=[3, 5, 7], top_k=1, channels=32, rngs=nnx.Rngs(1)),
)
out = net(x)

kernels gives each expert a receptive field, an int or an (h, w) pair, and sets how many there are; top_k says how many run per sample. channels is required here: nnx builds its parameters at construction, so there is no first forward pass to infer a width from.

The auxiliary loss

The balancing term lives in the block's nnx.Intermediate variable; routing reads and clears it:

from esmoe.flax import collect_aux_loss


@nnx.jit
def step(net, x):
    def objective(net):
        return task_loss(net(x)) + 0.01 * collect_aux_loss(net)

    return nnx.grad(objective)(net)

nnx.jit and nnx.grad work as they are, with nnx threading the state through the transform.

linen

Parameters live outside the module: declare once, run with apply, and the balancing term is sown into intermediates.

from flax import linen
from esmoe.flax.linen import ESMoE, collect_aux_loss

block = ESMoE(kernels=[3, 5, 7], top_k=1)
variables = block.init(key, x, training=True)
out, state = block.apply(variables, x, training=True, mutable=["intermediates", "batch_stats"])
aux = collect_aux_loss(state)

Linen takes no **kwargs, so settings arrive in options: ESMoE(kernels=[3, 5], options={"out_norm": True}). Channels come from the input init sees, so they need not be stated.

Against the other two ways onto jax

route shape when
esmoe.flax nnx.Module, state on the object the project uses Flax's current API
esmoe.flax.linen linen.Module, parameters outside the project uses Linen
esmoe.jax pure init / apply no Flax wanted, the parameter tree is yours
esmoe.keras keras.layers.Layer with the jax backend the project uses Keras across backends

The routing and the balancing term are the same on all three, and so are the arguments.

Boundaries

Every expert is evaluated. Skipping an unrouted one depends on the batch, which a jitted graph cannot express.