Skip to content

Torch integration

uv add esmoe

Torch is all it needs; ultralytics never has to be installed.

Drop it in

import esmoe
from torch import nn

net = nn.Sequential(
    nn.Conv2d(3, 64, 3, 2, 1),
    esmoe.ESMoE(kernels=[3, 5, 7], top_k=1),
)

The block takes its width from the first forward pass, so nothing has to declare it. Pass channels= to build it before an optimiser is created.

Three knobs

knob takes decides
kernels a sequence of int or (h, w) each expert's receptive field, and how many experts there are
expert a factory, a name, or one per branch what a branch is
top_k an integer or None how many experts run per sample
esmoe.ESMoE(kernels=[3, 5, 7], top_k=1)  # three experts, one per sample
esmoe.ESMoE(kernels=[3, 3, 5, 5, 7, 7, 9, 9, 1, 1])  # ten experts, kernels may repeat
esmoe.ESMoE(kernels=[(1, 9), (9, 1)], top_k=2)  # strip convolutions
esmoe.ESMoE(kernels=[3, 5], expert=[MyExpert, esmoe.DWExpert])

An even kernel steps down to odd so the padding stays centred, and anything over max_kernel is capped. An expert factory is (c1, c2, k) -> Module.

The auxiliary loss

The balancing term has to join the total loss, or it is a number computed and thrown away:

with esmoe.routing(net) as route:
    loss = task_loss(net(x), y) + 0.01 * route.loss()
loss.backward()

routing clears the registry on the way in, so a block that did not run this step cannot report the step before. Without the context manager, pair esmoe.clear_aux_loss() with esmoe.collect_aux_loss(model) yourself.

Into a model that already exists

esmoe.graft(resnet, at="layer3", mode="after", kernels=[3, 5, 7], top_k=1)

at takes a submodule name, several names, or a (name, module) -> bool test. mode is after, before or replace. The model is changed in place and handed back.

However the arguments are spelled

Positional arguments are num_experts, top_k, channels, options, and a mapping among them is read as options whatever its position. c/c1/ch, c2, e and k are understood, and experts lands on the expert count, the kernels or the branch structure according to what it holds. An unrecognised key warns once and is ignored rather than costing a run; a bad value still raises.