一、11 个 MoE 模块的全景图
整个文件经历了从"概念验证"到"极致优化"的完整演进,可以按架构模式分成 5 代:
第 1 代:ES_MOE --- 基础框架验证
modules.py#L287-L486(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L287-L486)
输入 x
│
├─ Router (DynamicRoutingLayer) ──► 全局池化 → 1×1 Conv → Softmax/Top-K
│ 返回 [B, E, H, W] 的逐像素权重
│
├─ Dense Forward(训练时):
│ for i in range(N):
│ output += expert_i(x) * weights[:, i]
│ (所有专家全算,保证梯度)
│
├─ Sparse Forward(推理时):
│ for expert_idx in range(N):
│ mask = (topk_indices == expert_idx)
│ expert_out = experts[expert_idx](x[mask])
│ output.index_add_(0, mask_indices, expert_out * weight)
│ (只算被选中的,跳过无效计算)
│
└─ Norm (BN + SiLU)
关键设计:
- 异构核专家:默认用 3/5/7 不同卷积核
- Dense vs Sparse 双模式:训练时 dense(保梯度),推理时 sparse(省算力)
- 动态阈值剪枝(L406-414):推理时权重 < threshold 的专家直接跳过
- ONNX 兼容 :forward 固定用 dense,因为
if self.training的条件分支会 break ONNX trace
缺失:无共享专家、无 Z-loss、BN 在小 batch 下不稳
第 2 代:OptimizedMOE --- 引入共享专家
modules.py#L489-L637(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L489-L637)
输入 x
│
├─ Router (EfficientSpatialRouter) ──► 降采样4x → Conv → 全局平均 → Top-K
│ 返回 (weights[B,k], indices[B,k], loss_dict)
│
├─ Shared Expert (1×1 Conv + BN + SiLU) ──► 所有样本都走
│
├─ Sparse Experts (dispatch 调度):
│ for i in range(N):
│ mask = (indices == i)
│ if mask.any():
│ batch_idx, k_idx = where(mask)
│ out = experts[i](x[batch_idx]) ← 只算选中样本
│ output.index_add_(0, batch_idx, out * w) ← 散射累加
│
└─ output = shared_out + expert_output
关键突破:
- Shared Expert(L518-524):1×1 卷积,始终激活,保证梯度流
- Dispatch 调度模式(L572-598):不是"每样本循环",而是"每专家循环 + 批量索引",一次 kernel 启动处理所有选中该专家的样本
- MoELoss 辅助损失(L527-532):balance loss + z-loss
- 路由器小方差初始化 (L545-546):
std=0.05,初期路由接近均匀
第 3 代:OptimizedMOEImproved --- 模块化推荐版
modules.py#L640-L854(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L640-L854)
在 OptimizedMOE 基础上增加:
1. 可插拔 Router/Expert(L674-706)
python
# Router: 'efficient' / 'local' / 'adaptive'
# Expert: 'simple' / 'ghost' / 'inverted' / 'spatial' / 'spatial5' / 'spatial7'
# 异构专家:传 list 而非 str
expert_type = ["simple", "spatial", "spatial5", "spatial7"]
2. Progressive Sparsity(渐进稀疏)(L779-786)
python
def _update_sparsity(self):
if self.training_step < self.warmup_steps: # 5000 步
progress = self.training_step / self.warmup_steps
# top_k 从 N 线性退火到目标 k
current_k = N - progress * (N - target_k)
训练初期用全部专家(每个都收到梯度),逐步过渡到稀疏,解决冷启动死专家问题。
3. 残差连接(L838-839)
python
if self.in_channels == self.out_channels:
final_output = final_output + x
4. 异构专家构建器(L726-757):支持字符串(同构)和列表(异构)两种输入
第 4 代:UltraOptimizedMoE --- 极致推理优化
modules.py#L66-L244(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L66-L244)
输入 x
│
├─ UltraEfficientRouter: 路由 FLOPs ↓95%
│ AvgPool 8x → DW Conv 3×3 → PW Conv 1×1 → 深度可分离
│ → Expert Projection → Softmax → Top-K
│ + Z-loss + 噪声注入 + 温度系数
│
├─ Shared Expert (1×1 + GroupNorm + SiLU) 小batch稳定
│
├─ BatchedExpertComputation: 消除Python循环
│ 权重 < 0.01 的直接跳过
│ 按专家分组 → 批量 forward → index_add_
│
└─ output = shared + expert_output
关键优化:
- UltraEfficientRouter(routers.py):深度可分离卷积 + 8× 降采样,路由 FLOPs 减少 95%
- BatchedExpertComputation(utils.py):封装了 dispatch 逻辑 + 权重阈值跳过
- GroupNorm 全替换:OptimizedSimpleExpert / FusedGhostExpert 都用 GroupNorm
- 条件计算 :
weight_threshold=0.01,权重极小的专家直接不算
第 5 代:HyperSplit → HyperFused → HyperUltimate → Ultimate --- 极致工程
这四代引入了全新的架构模式:
HyperSplitMoE --- 通道分裂(L952-L1112(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L952-L1112))
输入 x [B, C, H, W]
│
├─ 通道分裂: x_static[C/2], x_dynamic[C/2]
│
├─ Static Path (DW Conv 3×3 + PW Conv 1×1) ← 轻量固定路径
│
├─ Dynamic Path:
│ Router (GAP → 1×1 → Top-K)
│ Experts (InvertedResidual × N, dispatch)
│
├─ Concat [static, dynamic]
├─ 1×1 Conv Fusion + BN
└─ + x (残差)
核心思想 :借鉴 ShuffleNet 的通道分裂------只对一半通道做 MoE 稀疏计算,另一半走固定轻量路径。参数和 FLOPs 直接减半,且静态路径保证基础特征提取不受路由影响。
HyperFusedMoE --- 零成本路由 + 融合专家(L1115-L1256(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L1115-L1256))
1. ZeroCostRouter(L1259-L1335(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L1259-L1335)):
python
mean = x.mean(dim=[2,3]) # [B, C] ← 这些 BN 已经算过了,近似"免费"
std = x.std(dim=[2,3]) # [B, C]
stats = cat([mean, std]) # [B, 2C]
logits = Linear(2C, num_experts)(stats) # 只有一个线性层!
2. FusedExpertGroup(L1338-L1430(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L1338-L1430)):
python
# 不是 N 个独立卷积,而是一个大卷积输出 N×out_channels
self.fused_conv = Conv2d(in_ch, num_experts * out_ch, 3, groups=num_groups)
# forward:
fused_out = self.fused_conv(x) # [B, N×out, H, W] 一次 kernel 启动
fused_out = fused_out.view(B, N, out, H, W)
# 按 Top-K 索引提取对应专家输出
expert_out = fused_out[arange(B), expert_ids] # gather
核心思想 :把 N 个专家的卷积核融合成一个分组卷积 ,一次 CUDA kernel 完成所有专家计算,然后用 gather 提取 Top-K。虽然理论 FLOPs 是 dense 的,但kernel 启动开销从 N 次降为 1 次,在 GPU 上实际更快。
3. AdaptiveBalanceController(L1434-L1481(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L1434-L1481)):
python
# 早期训练: balance_coeff=0.1 强制均衡
# 后期训练: balance_coeff=0.001 允许分化
# + 可学习的专家重要性权重
# + 熵正则化(最大化专家使用熵 = 鼓励多样性)
HyperUltimateMoE / UltimateOptimizedMoE --- 集大成者
modules.py#L1521-L1912(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L1521-L1912)
整合了之前所有技术:
输入 x
│
├─ 通道分裂 → static + dynamic
│
├─ Complexity Estimator (GAP → 1×1 → Sigmoid)
│ → complexity_score 控制自适应 top_k
│ → 低复杂度时推理跳过 static path
│
├─ Progressive Sparsity + Dynamic Temperature
│ warmup 期: top_k=N, temperature=2.0 (软路由)
│ 后期: top_k=k, temperature=0.5 (硬路由)
│
├─ ZeroCostRouter (mean+std → Linear → Top-K)
│ + autocast 混合精度
│
├─ FusedExpertGroup (一次大卷积 + gather)
│
├─ AdaptiveBalanceController (衰减系数 + 可学习重要性 + 熵正则)
│
├─ Concat + 1×1 Proj + GroupNorm
└─ + x (残差)
新增:
- 动态温度退火(L1825-1834):高温→低温,初期软路由探索,后期硬路由收敛
- 复杂度跳过(L1848-1849):推理时如果复杂度 < 0.1,直接跳过 static path
- 正交初始化(L1604-1610):1×1 卷积用正交初始化,防止通道间冗余
附加:ABlockMoE / A2C2fMoE --- 注意力块内嵌 MoE
modules.py#L857-L945(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L857-L945)
python
class ABlockMoE(ABlock):
def __init__(self, dim, num_heads, mlp_ratio, area, num_experts, top_k, expert_type):
super().__init__(dim, num_heads, mlp_ratio, area)
# 把 ABlock 的 MLP 层替换成 MoE
self.mlp = OptimizedMOEImproved(
in_channels=dim, out_channels=dim,
num_experts=num_experts, top_k=top_k, ...)
def forward(self, x):
x = x + self.attn(x) # 注意力(不变)
return self.mlp(x) # MoE 替代原 MLP(内部含残差)
核心思想 :MoE 不一定要替换整个卷积层,可以只替换 Transformer/Attention 块里的 FFN/MLP 子模块。这是和 NLP MoE(如 Switch Transformer)最接近的做法。
二、七大架构模式提取
从这些实现中可以提取出 7 个可迁移的架构模式:
模式 1:Shared Expert 保底
output = shared_expert(x) + Σ(expert_i(x) × w_i)
迁移启发:任何网络加 MoE 时,务必保留一条无需路由的并行通路。它解决两个问题:
- 训练初期路由随机时保证梯度回流
- 某些专家"死掉"时模型仍有基础能力
模式 2:Dispatch 调度(非循环遍历)
python
# ❌ 慢:每个样本循环
for b in range(B):
for k in range(top_k):
out = experts[indices[b,k]](x[b:b+1])
# ✅ 快:按专家分组批量算
for i in range(N):
mask = (indices == i)
if mask.any():
batch_idx, k_idx = where(mask)
out = experts[i](x[batch_idx]) # 批量
output.index_add_(0, batch_idx, out*w) # 散射
迁移启发 :稀疏计算的关键不是"跳过专家",而是"把分散的样本重新聚合成 batch"。index_add_ 是 MoE 的核心算子。
模式 3:通道分裂 + 半动态 MoE
x → [static_half, dynamic_half]
│ │
轻量固定路径 MoE 稀疏路由
│ │
└──── concat ────┘ → 1×1 fusion → +x
迁移启发:如果整层做 MoE 太重,可以只对部分通道做条件计算。静态路径处理基础特征(低频),动态路径处理判别特征(高频)。
模式 4:融合专家(FusedExpertGroup)
python
# 一个大分组卷积代替 N 个小卷积
fused_conv = Conv2d(C, N*out, groups=g) # 一次 kernel
output = fused_conv(x).view(B, N, out, H, W)
expert_out = output[arange(B), expert_ids] # gather 提取
迁移启发:当专家结构相同(同构)时,不要实例化 N 个独立模块,融合成一个卷积。GPU kernel 启动开销从 N 次降为 1 次。
模式 5:零成本路由
python
# 路由信号来自 BN/GN 已经算过的统计量
mean = x.mean(dim=[2,3]) # GAP
std = x.std(dim=[2,3]) # 标准差
logits = Linear(2C, N)(cat([mean, std]))
迁移启发:路由器不需要和主干一样重。用特征的统计量(mean/std)做路由决策,只需一个线性层,FLOPs 可忽略不计。
模式 6:渐进稀疏 + 动态温度
python
# warmup 期:top_k=N(全专家),temperature=2.0(软)
# 后期: top_k=k(稀疏), temperature=0.5(硬)
迁移启发:MoE 训练的冷启动问题------初期路由器没学会路由,很多专家收不到梯度。解法是"先 dense 后 sparse"的退火策略,让每个专家都被训练到。
模式 7:辅助损失注册表 + 健壮深拷贝
python
MOE_LOSS_REGISTRY = weakref.WeakKeyDictionary() # 全局注册表
# forward 时注册
MOE_LOSS_REGISTRY[self] = aux_loss
# 训练循环收集
for m in model.modules():
if hasattr(m, 'aux_loss'):
total_loss += m.aux_loss
# deepcopy 时清理非叶张量
def _robust_deepcopy(obj, memo):
# 把 grad_fn is not None 的张量替换成标量 0
迁移启发:MoE 的辅助损失不能存在模块实例里(会 break DDP deepcopy / EMA / checkpoint),用 WeakKeyDictionary 全局注册 + property 访问是最干净的方案。
三、迁移到其他深度学习网络的启发
1. Transformer / NLP
这个工程已经演示了:ABlockMoE(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L857-L886) 把注意力块的 MLP 换成 MoE。
Standard Transformer Block:
x → Attention → +x → LayerNorm → MLP → +x → LayerNorm
MoE Transformer Block:
x → Attention → +x → LayerNorm → MoE-MLP → +x → LayerNorm
↑ Shared Expert + Sparse Experts
直接可用 :把 OptimizedMOEImproved(in=dim, out=dim) 替换掉 nn.Linear(dim, 4*dim) + GELU + nn.Linear(4*dim, dim) 即可。专家用 SimpleExpert(1×1 卷积 = 等价于 Linear)。
2. U-Net / 分割网络
在 U-Net 的每个 skip-connection 后插入 MoE:
Encoder ──┬── MoE Block ──► Decoder
│
└── MoE Block ──► Decoder
启发:
- 浅层用 SpatialExpert(多尺度感受野),深层用 SimpleExpert(纯通道变换)
- 分割任务天然有多尺度需求,异构专家(spatial3/5/7)尤其合适
- 用 GroupNorm(分割常用小 batch)
3. GAN(生成对抗网络)
在 Generator 的 ResBlock 里用 MoE:
Generator Block:
x → Conv → MoE-Conv → +x
↑ 路由器根据噪声类别选择不同专家
启发:不同专家可以学习生成不同类别/风格的特征。路由器的 mean/std 统计量天然适合类别感知。用 ZeroCostRouter 几乎不增加开销。
4. 多任务学习
Shared Backbone → MoE 分叉
├─ Expert 0: 检测头
├─ Expert 1: 分割头
└─ Expert 2: 深度估计头
启发 :路由器学会按任务/场景分配专家,实现任务级条件计算。AdaptiveBalanceController 的可学习专家重要性权重可以让网络自己学习哪个任务需要更多容量。
5. 时序网络 / 视频
帧 t → MoE Block → 输出
↑ 路由器可以看多帧统计量
启发:不同帧的内容复杂度不同(静止 vs 运动),复杂度估计器 + 自适应 top_k 可以在静止帧用更少专家、运动帧用更多专家。
6. 通用替换策略
这个工程给出了一个非常清晰的"渐进式 MoE 化"路径:
| 步骤 | 做什么 | 用哪个模块 |
|---|---|---|
| 1. 验证框架 | 替换一个最简单的 MLP/Conv 层 | ES_MOE + SimpleExpert |
| 2. 加稳定性 | 引入 Shared Expert + 辅助损失 | OptimizedMOE |
| 3. 增灵活性 | 可插拔专家 + 渐进稀疏 | OptimizedMOEImproved |
| 4. 推理优化 | 轻量路由 + 批量计算 | UltraOptimizedMoE |
| 5. 极致优化 | 通道分裂 + 融合专家 + 零成本路由 | HyperUltimateMoE |
四、迁移时的关键注意事项
1. 归一化层选择
- 大 batch(>32):BN 可以
- 小 batch(<8)或稀疏调度:必须 GroupNorm/LayerNorm
- MoE 下每个专家实际 batch ≈ B × k/N,远小于 B
2. ONNX 部署兼容
- 不要在 forward 里用
if self.training分支(ES_MOE 的教训(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L353-L357)) - 用
opset_version=13+ - TopK / Gather / Scatter / index_add 都是可导出的标准算子
3. 损失系数调参
- balance_loss_coeff: 0.01(默认,太大会压制路由器学习)
- z_loss_coeff: 1e-3(防数值爆炸)
- 需要在训练循环里收集
aux_loss并加权到总 loss
4. 死专家检测
- 用
ExpertUsageTracker/RoutingCollapseDetector(analysis.py(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/analysis.py))监控 - 如果某专家使用率 < 1%,说明已经死了,需要调大 noise_std 或 warmup_steps
5. DDP 训练
- 负载均衡损失必须做
all_reduce(MoELoss._get_global_mean(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/loss.py#L40-L53)) - 否则各卡各均衡,合起来可能严重倾斜
一句话总结:这个文件的 11 个模块完整展示了 MoE 从"能用"到"极致优化"的工程路径------Shared Expert 保底、Dispatch 批量调度、通道分裂减半、融合专家省 kernel、零成本路由、渐进稀疏退火、自适应负载均衡。这些模式可以1:1迁移到任何含卷积层或 FFN 层的深度学习网络中。