A mixture-of-experts block for the model you already have¶
Native implementations on PyTorch, TensorFlow, Jax, Flax and Keras, each written the way that framework is written, and one call to add it to Ultralytics YOLO. The router's balancing term reaches backward(), rather than sitting in a config.
- Into any modelIt sizes itself on the first forward pass, so an
nn.Sequentialtakes it as written, and it grafts into a model that already exists by submodule name, without touching its source. - One call to add it to YOLO
equipgrafts the block into a config, renumbers layer references and wires the loss, on seven official generations from YOLOv5 to YOLO26 and on YOLO-Master's fork. - Five frameworks, one set of arguments
kernelssets the experts and their receptive fields,top_khow many run; the code that reads them is shared, so moving framework changes nothing you write.
Open the quick start in Colab: install, equip, train and watch the esmoe_aux column, all on a free GPU.
Install¶
uv add esmoe # the block, for any torch model
uv add esmoe[yolo] # ... and the Ultralytics integration
esmoe[ultralytics] is that extra under its other name; esmoe[tensorflow], esmoe[jax], esmoe[flax] and esmoe[keras] each bring one backend. The distribution, the import and the command line are all esmoe.
Use¶
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))
In YOLO:
model = esmoe.equip("yolo11n.yaml", weight=0.01) # register + graft + build + wire
model.train(data="coco8.yaml", epochs=10)
The separate steps, the command line and the hand-written config are in In Ultralytics YOLO; the plain torch path is in In a torch model.
Compatibility¶
| backbone | build + forward | grafted config | aux loss in training | protocol runs |
|---|---|---|---|---|
| YOLOv5 | yes | yes | yes | yes |
| YOLOv8 | yes | yes | yes | yes |
| YOLOv9 | yes | yes | yes | yes |
| YOLOv10 | yes | yes | yes | yes |
| YOLO11 | yes | yes | yes | yes |
| YOLO12 | yes | yes | yes | yes |
| YOLO26 | yes | yes | yes | yes |
| YOLO-Master (fork) | yes | yes | yes | no |
Verified by tests/test_ultralytics.py on ultralytics 8.4.101 and the latest release (the CI matrix), which report loss items in two different shapes; both are handled. The training column is backed by real 1-epoch VisDrone runs on four generations and by the 120-epoch protocol runs on all seven, each logging a non-zero train/esmoe_aux; graft and forward run on every row in CI. The YOLO-Master row runs against the fork's vendored ultralytics: scripts/fork_smoke.py grafts their yolo-master-n.yaml, trains one epoch with a non-zero esmoe_aux, and builds their own ES_MOE config alongside ours. The same-configuration comparison trains upstream's own blocks on the fork and trains yolo-master-n with this package's blocks on official ultralytics, hence the last column.
Default configuration¶
ESMoE(num_experts=4, top_k=2) with attach_aux_loss(weight=0.01). Under one budget of a 25% train split, 640 px and 20 epochs, it beat the 2-expert, 8-expert, top-1 and aux-off variants; on the full VisDrone training set, at the same 20 epochs and 640 px, three seeds confirmed it: 3/3 paired wins, +0.0021 mAP50, and 10.4% more parameters on YOLOv8n. Under the protocol budget (800 px, 120 epochs) the default wiring on YOLOv8n is +0.0025, 2/3. The argument is on Selection.