跳转至

Flax 集成

uv add esmoe[flax]

Flax 现在并行着两套 API,两套都支持:esmoe.flax 是 nnx,Flax 现在推荐的那套;esmoe.flax.linen 是 Linen。输入都按 NHWC。

nnx

状态挂在对象上,要纯函数时用 nnx.split。

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 给每个专家一个感受野,int 或 (h, w),专家数由它定;top_k 决定几选几。channels 在这里是必填——nnx 在构造时就建好参数,没有首次前向推断那一步。

辅助损失

平衡项存在块的 nnx.Intermediate 变量上,读取与清理都走 routing:

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 与 nnx.grad 直接可用,状态由 nnx 自己穿过变换。

linen

参数放在模块外面,声明一次、用 apply 跑;平衡项 sow 进 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 没有 **kwargs,所以设置从 options 进:ESMoE(kernels=[3, 5], options={"out_norm": True})。通道由第一次 init 的输入决定,不必写。

与另外两条 jax 路线的区别

用法 形态 何时用
esmoe.flax nnx.Module,状态在对象上 项目用 Flax 的新 API
esmoe.flax.linen linen.Module,参数在外面 项目用 Flax 的 Linen
esmoe.jax 纯函数 init / apply 不想引入 Flax,自己管参数树
esmoe.keras keras.layers.Layer,后端设为 jax 项目用 Keras 跨后端

三条路的路由与平衡项一致,构造参数也一致。

边界

每个专家都要算。跳过未被路由到的专家取决于当批数据,jit 之后的图是静态的,表达不了。