一、逐个专家结构拆解

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。

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 配置的基础:

["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)

# 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)实例化多个,形成异构专家组。


二、六大设计维度总结

维度SimpleExpertOptimizedSimpleExpertSpatialExpertGhostExpertInvertedResidualExpert
空间感受野无(1×1)无(1×1)有(k×k DW)有(k×k)有(k×k DW)
归一化BNGroupNormBNBNBN
参数量基准基准基准~50%基准
FLOPs最低最低中等~50%中等
设计理念纯通道变换小batch稳定多尺度感受野特征冗余利用移动端高效
适用场景深层特征图超小batch浅/中层多尺度移动端部署移动端部署

三、对目标检测任务创建自定义专家的启发

启发 1:专家的本质是"可插拔的特征变换器"

看所有专家的接口,统一签名:

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 配置的思路:

["simple", "spatial", "spatial5", "spatial7"]

不是 4 个一样的专家,而是功能互补的专家组合。路由器学会按内容分发。

检测任务启发——设计面向检测痛点 的异构专家组:

# 专家 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)):

if etype == 'ghost':
    experts.append(GhostExpert(...))
elif etype == 'spatial':
    experts.append(SpatialExpert(...))
elif etype == 'spatial5':
    experts.append(SpatialExpert(..., kernel_size=5))

检测任务启发:设计专家时统一接口,方便配置文件里组合:

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 有检测框回归、类别分类等子任务,可以设计任务级专家:

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,实现任务级条件计算。


四、自定义专家的实操建议

如果要在你的检测项目里创建自己的专家,从这几个方向入手:

  1. 从现有检测模块改造:把 DCN、ASPP、RFB、PConv(Partial Conv)等已有的高效模块封装成统一接口的专家
  2. 按检测层级设计异构组:浅层(大特征图)用轻量空间专家,深层(小特征图)用通道专家或注意力专家
  3. 先跑通再优化:先用 SimpleExpert(最简单)验证 MoE 框架 work,再逐步替换为复杂专家
  4. 统一用 GroupNorm:检测 batch 小 + MoE 稀疏 = BN 的噩梦
  5. 实现 compute_flops:MoE 的优势是 FLOPs 可分析,没有这个方法就无法量化收益
Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐