ES-MoE Toolkit
Docs: English · 中文 · Quick start in Colab: notebooks/quickstart.ipynb · Ask questions about the code: DeepWiki.
Install
pip install esmoe
The distribution, the import and the CLI are all esmoe; the project is written ES-MoE in prose.
Requires ultralytics.
Use
One call covers register, graft, build and wire:
import esmoe
model = esmoe.equip("yolo11n.yaml", weight=0.01)
model.train(data="coco8.yaml", epochs=10)
Or take the steps apart when you need control over each one:
from ultralytics import YOLO
import esmoe
esmoe.inject_esmoe() # make `ESMoE` resolvable in model.yaml
esmoe.graft("yolov8n.yaml", out="v8-esmoe.yaml", at=[4, 6]) # insert blocks, renumber the head
model = YOLO("v8-esmoe.yaml")
esmoe.attach_aux_loss(model, weight=0.01) # router loss joins the training loss
From the shell:
esmoe graft yolo11n.yaml -o yolo11n-esmoe.yaml -e 4 -k 2 --at backbone_end
attach_aux_loss adds an esmoe_aux entry to the trainer's loss table, so a non-zero,
back-propagated auxiliary term shows up in results.csv rather than merely in a config.
Written by hand, a grafted config layer is just:
[-1, 1, ESMoE, [4, 2]] # num_experts, top_k
The block is channel preserving and infers its width on the first forward, which is what lets stock
parse_model size it without a patch.
Extend
Experts and the balancing objective are plain callables, so a variant is a few lines:
esmoe.ESMoE(num_experts=4, top_k=2, expert=MyExpert, balance=my_balance_fn)
MyExpert(c1, c2, k) -> Module, my_balance_fn(probs, gate) -> scalar. esmoe.blocks(model) walks
every block in a model, and esmoe.collect_aux_loss(model) returns the current step's router loss
for custom training loops.
Compatibility
| backbone | build + forward | grafted config | aux loss in training |
|---|---|---|---|
| YOLOv8 | yes | yes | yes |
| YOLO11 | yes | yes | yes |
| YOLO12 | yes | yes | yes |
| YOLO26 | yes | yes | yes |
Verified by tests/test_ultralytics.py on ultralytics 8.4.101 and 8.4.132, which report loss items
in two different shapes; both are handled. The training column is backed by real 1-epoch VisDrone
runs on all four generations (results/*-compat-*.json), each logging a non-zero train/esmoe_aux.
DDP works: attach_aux_loss routes model.train() through a trainer class that lives in esmoe.trainer, so the
worker processes ultralytics spawns register the block and the auxiliary loss before they build. Verified by
scripts/verify.py (the real worker file in a fresh interpreter; two gloo ranks with agreeing router gradients).
Not supported together with compile=True, which turns off find_unused_parameters.
Selected default
ESMoE(num_experts=4, top_k=2) with attach_aux_loss(weight=0.01), chosen under one budget over
2/4/8-expert and top-1 variants. Under the repository protocol (VisDrone, imgsz 800, 120 epochs, three seeds)
the paired result on YOLOv8n is +0.0025 mAP50 (2/3 seeds) and +0.0004 mAP50-95, at +10.4% parameters and
about 9% more wall-clock per epoch. By COCO-style area bucket, large objects get consistently worse (APl −0.010,
0/3) and small-object recall consistently better (ARs +0.003, 3/3). On YOLO11n the same comparison is a
wash: the mechanics transfer across backbones, the accuracy effect does not.
The default graft leaves YOLOv8's P5 lateral reading SPPF rather than the block; graft(..., rewire=True) changes
that and is the next comparison to run. Reasoning and full tables: docs/SELECTION.md, results/buckets.md,
results/routing.md.
Develop and reproduce
uv sync --group dev
uv run pytest -q
uv run python scripts/capture_env.py # freeze environment into env/
EPOCHS=20 FRACTION=0.25 SEEDS="0 1 2" bash scripts/sweep.sh
uv run python scripts/report.py # results/summary.md
Every run writes one machine-readable record to results/ (config, dataset, hardware, budget, seed,
metrics, artifact, status, limitation). Read limitations.md before quoting any number.
Linked projects
- ultralytics - the official YOLO framework this plugs into.
- Tencent/YOLO-Master - where ES-MoE comes from (paper).
License
AGPL-3.0-only, matching the Ultralytics ecosystem it builds on.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file esmoe-0.1.4.tar.gz.
File metadata
- Download URL: esmoe-0.1.4.tar.gz
- Upload date:
- Size: 32.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3d4c564cc345fa0579189740a3b7cb6bb916916e352576f070fd4a2edc19c86f
|
|
| MD5 |
0b11d56606a9c3fac169d7060a4a3a36
|
|
| BLAKE2b-256 |
caa3fe868011209978c4e08b818e4274ba84f5fb7ae382d2f84a749d53a30f27
|
File details
Details for the file esmoe-0.1.4-py3-none-any.whl.
File metadata
- Download URL: esmoe-0.1.4-py3-none-any.whl
- Upload date:
- Size: 26.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
uv/0.9.13 {"installer":{"name":"uv","version":"0.9.13"},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07b940b7768f4fa40c1f6efcbcc599020318504c8ff36fd15ea51d04cfdbc869
|
|
| MD5 |
134fa48e279ffb4f19561ea1b63ff8a8
|
|
| BLAKE2b-256 |
8f0a888957a03e4b2f4e50b3ee2813f9f6e9a84c3877134ab5f2127268e95772
|