一、逐个专家结构拆解
1. SimpleExpert --- 基线专家(FFN 式)
experts.py#L72-L86(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/experts.py#L72-L86)
输入 x [B, C_in, H, W]
│
├─ Conv 1×1 (C_in → C_in×2) ── 升维,增加非线性容量
├─ BN + SiLU ── 归一化 + 激活
├─ Conv 1×1 (C_in×2 → C_out) ── 降维回投影
└─ BN
结构本质 :就是 Transformer 里的 FFN(Feed-Forward Network)的卷积版------先用 1×1 升维扩容量,再 1×1 降维投影。全程 1×1 卷积,没有任何空间感受野,纯粹在通道维度做变换。
设计意图:最轻量的专家,和路由器配合时开销最小,便于验证 MoE 框架是否 work。BN 适合大 batch。
2. OptimizedSimpleExpert --- 小 batch 稳定版
experts.py#L12-L34(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/experts.py#L12-L34)
和 SimpleExpert 结构完全一样,唯一区别:BN → GroupNorm。
python
nn.GroupNorm(get_safe_groups(hidden_dim, num_groups), hidden_dim)
get_safe_groups 会自动找能整除通道数的最大分组数(最多 8 组)。
为什么换? MoE 稀疏调度下,每个专家实际拿到的样本数远小于 batch_size。比如 batch=8、4 专家选 2,每个专家平均只收到 4 个样本。BN 在 batch<4 时统计量极不稳定,GroupNorm 按通道分组归一化,和 batch 大小无关。
3. SpatialExpert --- 多感受野专家(核心设计)
experts.py#L111-L133(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/experts.py#L111-L133)
输入 x [B, C_in, H, W]
│
├─ Conv 1×1 (C_in → C_in×2) ── 升维
├─ BN + SiLU
├─ DW Conv k×k (groups=C_in×2) ── 深度空间卷积,感受野=k×k
├─ BN + SiLU
├─ Conv 1×1 (C_in×2 → C_out) ── 降维投影
└─ BN
注意中间的 Depthwise Conv :groups=hid 意味着每个通道独立做空间卷积,FLOPs 只有标准卷积的 1/C。kernel_size 可配(3/5/7),不同核大小 → 不同感受野。
这就是异构 MoE 配置的基础:
yaml
["simple", "spatial", "spatial5", "spatial7"]
# 无空间 3×3感受野 5×5感受野 7×7感受野
路由器根据图像内容决定:小目标多的区域选 3×3 专家,大目标区域选 7×7 专家。
4. GhostExpert --- 参数减半专家
experts.py#L137-L166(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/experts.py#L137-L166)
输入 x [B, C_in, H, W]
│
├─ primary_conv: Conv k×k (C_in → C_out/r) ── 只生成一半通道
│ BN + SiLU
│ │
│ ├── x1 (直接输出)
│ └─ cheap_operation: DW Conv 3×3 (C_out/r → C_out-C_out/r) ── 廉价线性变换
│ BN + SiLU
│ └─ x2
│
└─ output = cat(x1, x2)[:, :C_out] ── 拼接后截断
核心思想 (来自 GhostNet):卷积输出的特征图存在大量冗余(通道间高度相似)。与其用完整卷积生成全部通道,不如只算一半(primary),另一半用极廉价的深度卷积"变造"出来。参数量和 FLOPs 近似减半,但精度损失很小。
5. FusedGhostExpert --- Ghost 的 GroupNorm 版
experts.py#L37-L69(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/experts.py#L37-L69)
和 GhostExpert 逻辑完全相同,区别:
- BN → GroupNorm(同样为小 batch 稳定性)
kernel_size可配(GhostExpert 也可配,但 Fused 版默认 3)
6. InvertedResidualExpert --- MobileNetV2 倒残差
experts.py#L169-L196(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/experts.py#L169-L196)
输入 x [B, C_in, H, W]
│
├─ Conv 1×1 (C_in → C_in×2) ── 升维(expand)
├─ BN + SiLU
├─ DW Conv k×k (groups=C_in×2) ── 空间卷积(depthwise)
├─ BN + SiLU
├─ Conv 1×1 (C_in×2 → C_out) ── 降维(project)
└─ BN
和 SpatialExpert 几乎一模一样! 区别仅在注释风格。真正的倒残差应该有残差连接 (当 C_in==C_out 时 x + conv(x)),但这里的实现没加残差。所以实际效果等同于 SpatialExpert。
真正的倒残差精髓:中间宽(升维)、两头窄(降维),中间用 DW 做空间,配合残差连接,在低维空间做线性变换(最后一层不带激活)。这是移动端最高效的结构之一。
7. DepthwiseSeparableConv + EfficientExpertGroup --- ES_MOE 的异构核
experts.py#L199-L226(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/experts.py#L199-L226)
python
# DepthwiseSeparableConv
DW Conv k×k (C_in → C_in, groups=C_in) ── 空间
PW Conv 1×1 (C_in → C_out) ── 通道
BN + SiLU
EfficientExpertGroup 就是包了一层,在 ES_MOE 里用不同 kernel_size(3/5/7)实例化多个,形成异构专家组。
二、六大设计维度总结
| 维度 | SimpleExpert | OptimizedSimpleExpert | SpatialExpert | GhostExpert | InvertedResidualExpert |
|---|---|---|---|---|---|
| 空间感受野 | 无(1×1) | 无(1×1) | 有(k×k DW) | 有(k×k) | 有(k×k DW) |
| 归一化 | BN | GroupNorm | BN | BN | BN |
| 参数量 | 基准 | 基准 | 基准 | ~50% | 基准 |
| FLOPs | 最低 | 最低 | 中等 | ~50% | 中等 |
| 设计理念 | 纯通道变换 | 小batch稳定 | 多尺度感受野 | 特征冗余利用 | 移动端高效 |
| 适用场景 | 深层特征图 | 超小batch | 浅/中层多尺度 | 移动端部署 | 移动端部署 |
三、对目标检测任务创建自定义专家的启发
启发 1:专家的本质是"可插拔的特征变换器"
看所有专家的接口,统一签名:
python
def __init__(self, in_channels, out_channels, ...):
def forward(self, x) -> torch.Tensor:
def compute_flops(self, input_shape) -> float:
检测任务启发:你可以把任何现有的检测模块改造成专家。比如:
- 把 ASPP(空洞空间金字塔池化)做成专家,专门处理多尺度上下文
- 把 DCN(可变形卷积)做成专家,专门处理几何形变目标
- 把 CBAM/CAN(通道+空间注意力)做成专家,做自适应特征选择
启发 2:异构专家组合比同构更有效
看 yaml 配置的思路:
yaml
["simple", "spatial", "spatial5", "spatial7"]
不是 4 个一样的专家,而是功能互补的专家组合。路由器学会按内容分发。
检测任务启发------设计面向检测痛点 的异构专家组:
python
# 专家 0: 小目标专家 --- 用膨胀卷积扩大感受野,不增加下采样
expert_0 = DilatedExpert(in_ch, out_ch, dilation=2) # 看更远但不丢分辨率
# 专家 1: 大目标专家 --- 大核 + 池化,获取全局上下文
expert_1 = LargeKernelExpert(in_ch, out_ch, kernel=7) # 类似 RepLKNet
# 专家 2: 边界专家 --- 用 Sobel/Laplacian 梯度算子初始化,强化边缘
expert_2 = EdgeAwareExpert(in_ch, out_ch) # 检测框边界回归
# 专家 3: 上下文专家 --- 全局池化 + 1×1,类似 SE 模块
expert_3 = ContextExpert(in_ch, out_ch) # 全局语义辅助分类
启发 3:归一化选择影响 MoE 训练成败
SimpleExpert 用 BN → OptimizedSimpleExpert 换 GroupNorm,是因为 MoE 稀疏调度下每个专家的实际 batch 很小。
检测任务启发 :目标检测经常用 batch=2~8(大分辨率、大模型),MoE 下每个专家实际 batch 可能只有 1~2。专家内部统一用 GroupNorm 或 LN,不要用 BN。除非你确认 batch 足够大。
启发 4:专家要轻,因为 N 个专家的参数是叠加的
GhostExpert 把参数砍半,InvertedResidual 用 DW+PW 降低 FLOPs。因为 MoE 的参数量 = N × 单专家参数,4 个专家就是 4 倍。
检测任务启发:检测模型本身就很大(YOLOv8-L 有 ~50M 参数),如果每个专家再堆重卷积,参数量和显存爆炸。专家设计原则:
- 升维比控制在 2×,不要 4×(Transformer FFN 常 4×,但检测不需要这么大容量)
- 空间卷积用 Depthwise,不用标准卷积
- 考虑 Ghost 思路:专家不一定要"全新"的卷积,可以在共享专家基础上做轻量变换
启发 5:专家的"可组合性"是工程关键
看 _build_heterogeneous_experts 的实现(modules.py#L726-L757(file:///mnt/data0/ysn/YOLO-Master-main/ultralytics/nn/modules/moe/modules.py#L726-L757)):
python
if etype == 'ghost':
experts.append(GhostExpert(...))
elif etype == 'spatial':
experts.append(SpatialExpert(...))
elif etype == 'spatial5':
experts.append(SpatialExpert(..., kernel_size=5))
检测任务启发:设计专家时统一接口,方便配置文件里组合:
python
class DetectionExpert(nn.Module):
"""统一接口:in_channels, out_channels, 然后是专家特有参数"""
def __init__(self, in_channels, out_channels, **kwargs):
...
def forward(self, x): ...
def compute_flops(self, input_shape): ...
启发 6:从"卷积专家"到"检测专家"的思路延伸
这个工程的专家都是纯卷积块,处理的是 backbone 里的通用特征。但检测任务的 head 有检测框回归、类别分类等子任务,可以设计任务级专家:
python
class BoxRegExpert(nn.Module):
"""专门处理边界框回归的专家 --- 关注梯度/边缘特征"""
def __init__(self, in_ch, out_ch):
super().__init__()
# 用 Sobel 初始化,天生对边缘敏感
self.edge_conv = nn.Conv2d(in_ch, in_ch, 3, padding=1, groups=in_ch)
self.init_sobel_weights()
self.proj = nn.Conv2d(in_ch, out_ch, 1)
...
class ClsExpert(nn.Module):
"""专门处理分类的专家 --- 关注全局语义"""
def __init__(self, in_ch, out_ch):
super().__init__()
self.global_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Linear(in_ch, in_ch // 4)
self.proj = nn.Conv2d(in_ch, out_ch, 1)
...
这样路由器会学会:前景区域选 BoxRegExpert,背景区域选 ClsExpert,实现任务级条件计算。
四、自定义专家的实操建议
如果要在你的检测项目里创建自己的专家,从这几个方向入手:
- 从现有检测模块改造:把 DCN、ASPP、RFB、PConv(Partial Conv)等已有的高效模块封装成统一接口的专家
- 按检测层级设计异构组:浅层(大特征图)用轻量空间专家,深层(小特征图)用通道专家或注意力专家
- 先跑通再优化 :先用
SimpleExpert(最简单)验证 MoE 框架 work,再逐步替换为复杂专家 - 统一用 GroupNorm:检测 batch 小 + MoE 稀疏 = BN 的噩梦
- 实现
compute_flops:MoE 的优势是 FLOPs 可分析,没有这个方法就无法量化收益