Skip to content

MegaMoE 在 MoE 系统谱系中的位置:从 DeepSpeed-MoE 到 CCFuser 的七方对比

MoE 的稀疏 FLOPs 不会自动变成推理加速。DeepSpeed-MoE 最早把这件事讲清楚:小 batch 和 decode 阶段,瓶颈是权重带宽和通信,不是算力。此后的系统工作基本都在回答同一个问题的不同侧面——动态路由带来的不规则性,该在哪一层吸收?

本文以 DeepSeek DeepGEMM 里的 fp8_fp4_mega_moe 单 kernel 实现为坐标,把六项相关工作放在一起对照。代码行号对应 2026-09-08 的 DeepGEMM @559d79f。


一、谱系:每一步继承了什么问题

Section titled “一、谱系:每一步继承了什么问题”
GShard / Switch Transformer (2020-2021)
│ 静态 capacity factor 的两难:丢 token(掉点)或补零(浪费算力)
▼
DeepSpeed-MoE (2022)
├─ 解决:推理时参数显存与访存瓶颈
├─ 论点:MoE 推理是访存带宽受限,不是 FLOP 受限
└─ 遗留:仍依赖静态 capacity factor 与稠密 Einsum 布局
▼
FasterMoE (PPoPP 2022)
├─ 解决:动态路由倾斜造成的 straggler、集合通信阻塞
├─ 手段:dynamic shadowing 复制热门专家;smart scheduling 跨 stream 流水
└─ 遗留:影子权重吃显存;仍在传统 batched GEMM + capacity factor 框架内
▼
Tutel (MLSys 2023)
├─ 解决:静态并行策略不灵活;千卡规模下 flat all-to-all 扩展性
├─ 手段:运行时零成本 DP/EP/MP 切换;4 阶段 2DH 集合通信;动态容量不丢 token
└─ 遗留:容量取 max(T_e) 导致所有欠载专家补零,浪费 2–11 倍算力
▼
MegaBlocks (MLSys 2023)
├─ 解决:补零与丢 token 的两难
├─ 手段:MoE 表达成块稀疏矩阵乘(BCSR-BCOO,128×128 块,SDD/DSD)
│ padding 浪费上界收紧到每专家 ≤ 127 token
└─ 遗留:仍需在 HBM 里 materialize 置换后的输入张量和块稀疏中间激活
▼
ScatterMoE (2024)
├─ 解决:MegaBlocks 的 HBM 分配与冗余拷贝
├─ 手段:ParallelLinear(Triton)把 grouped GEMM 与间接寻址融合,
│ HBM↔SRAM 之间直接 gather/scatter,不落中间缓冲
└─ 遗留:单机;无多机通信原语;无单 token 解码专用 kernel
▼
├──────────────────────────────┐
▼ ▼
CCFuser (PPoPP 2025) DeepGEMM Mega MoE (2026)
分阶段融合 + NVSHMEM 单 persistent kernel + symmetric memory

最后一步值得强调:ScatterMoE 自己承认的两条局限,正是 Mega MoE 的起点。

ScatterMoE is currently single-node focused and does not provide multi-node communication primitives… It also lacks a specialized decoding kernel for single-token autoregressive generation.

Mega MoE 对这两条的回应分别是:把跨卡 dispatch/combine 直接写进 kernel;以及把 tile 大小做成 serving regime 的函数,最小档明确标注给 decode 和 RL rollout 用。


2.1 一个 kernel 里的六种 warp 角色

Section titled “2.1 一个 kernel 里的六种 warp 角色”

deep_gemm/include/deep_gemm/impls/sm100_fp8_fp4_mega_moe.cuh:329 起,CTA 内的 warp 按 index 分成互不相同的角色:

warp 范围角色行号
[0, kNumDispatchWarps)跨卡 dispatch / combine 搬运:329
kNumDispatchWarps + 0激活 + SFA 的 TMA load:669
kNumDispatchWarps + 1权重 + SFB 的 TMA load:735
kNumDispatchWarps + 2MMA 发射(tcgen05,仅 leader CTA):794
kNumDispatchWarps + 3调度器 mainloop(仅 leader CTA):920
>= kNumDispatchWarps + kNumMMANonEpilogueWarpsepilogue:TMEM → SwiGLU + amax + 量化 → 存回 / combine 写回:928

每个角色进入分支后先调整寄存器配额(warpgroup_reg_dealloc / warpgroup_reg_alloc),静态断言总量 ≤ 64512(:319)。

对比意义:FasterMoE 的 fine-grained scheduling 和 Tutel 的 adaptive pipelining 都是在 kernel 之上编排多个 kernel 与 stream;Mega MoE 把重叠下沉到单 kernel 内的 warp 特化,靠硬件 warp 调度而非 CUDA stream。

dispatch warp 遍历的不是本地 token,而是本地专家池的目标槽位,反推每个槽位该从哪个 rank 拉哪个 token:

// Pull token data and SF from remote ranks into local L1 buffer (:414)
// Round-robin rank selection via iterative min-peeling (:461)

两点含义:

  1. 没有集合通信。不做 all-to-all,消费端直接去对端符号内存取数。DeepSpeed-MoE 的分层 all-to-all、Tutel 的 2DH 优化的都是集合通信本身,这里整个绕开了。
  2. rank 轮转交错。不是 rank0 的 token 排完再排 rank1,而是让每个 BLOCK_M tile 内的 token 尽量来自不同 rank,把 NVLink 读流量摊平。这是对负载不均的回应,作用在数据布局层,而非 FasterMoE 的专家复制层。

这是与 MegaBlocks / ScatterMoE / CCFuser 差别最大的一点。csrc/apis/mega.hpp:57-65:

int num_ring_tokens = 0;
for (const auto& block_m: layout::kCandidateBlockM) {
const auto num_pool_blocks = ceil_div(num_max_routed_tokens, block_m) + num_experts_per_rank;
const auto num_live_pool_blocks = sched::get_num_max_live_pool_blocks(
num_pool_blocks, num_sms, hidden, intermediate_hidden);
num_ring_tokens = std::max(num_ring_tokens, num_live_pool_blocks * block_m);
}
num_ring_tokens = math::align(num_ring_tokens, layout::kLCMCandidateBlockM);

l1_acts 的第一维是 num_ring_tokens——按「同时在飞的 block 数」定尺寸,不是按路由后的总 token 数。

dispatch → L1 → L2 → combine 四段用四组计数器在环上做生产者–消费者同步:

get_l1_full_count_ptr / get_l1_empty_count_ptr
get_l2_full_count_ptr / get_l2_empty_count_ptr

消费端自旋等待(:527-530、:990),环块索引一律取模 kNumRingBlocks。

2.4 routing index 直接驱动跨卡写地址

Section titled “2.4 routing index 直接驱动跨卡写地址”
struct TokenSrcMetadata {
uint32_t rank_idx;
uint32_t token_idx;
uint32_t topk_idx;
};

dispatch 时为每个池内 token 写下来源(:587),combine 时 epilogue warp 读它算出对端地址,经 NVLink 直接写入目标 rank 的缓冲(:1280、:1294)。

这正是 ScatterMoE「routing index 驱动 GEMM 寻址」的思路,但 scatter 的目标是另一块 GPU 的显存。ScatterMoE 的 gather/scatter 在单卡 HBM↔SRAM 之间;Mega MoE 把同一个机制拉到了 NVLink 域。

候选集(deep_gemm/include/deep_gemm/layout/mega_moe.cuh:11):

static constexpr int kCandidateBlockM[7] = {8, 16, 32, 64, 96, 128, 192};

选择逻辑在 csrc/jit_kernels/heuristics/mega_moe.hpp:80 起,判据是 num_expected_tokens_per_expert=num_tokens×num_ranks×num_topknum_experts\text{num\_expected\_tokens\_per\_expert} = \frac{\text{num\_tokens} \times \text{num\_ranks} \times \text{num\_topk}}{\text{num\_experts}}

每专家期望 tokenBLOCK_MBLOCK_K代码原注释
≤ 8.516256Really small token-per-expert (e.g. RL long-tail rollout)
≤ 16.532128Small batch size, small EP, decoding, e.g. 6/384 experts, EP8, bsz 128
≤ 32.564128Medium batch size, small EP, decoding, e.g. 6/384 experts, EP8, bsz 256
≤ 64.596128Large batch size, small EP, decoding, e.g. 6/384 experts, EP8, bsz 512
≤ 96.5128128Medium batch size, Medium EP, decoding, EP16 bsz 256, or EP32 bsz 128
其余192128Prefill, or large EP decoding

MegaBlocks 用固定 128×128 块加块稀疏消除 padding,并承认小 batch 或 Te<128T_e < 128 时 fringe 严重;Mega MoE 保留稠密 tile,但把块大小做成 serving regime 的函数,最小档到 16。注释直接点名 RL rollout 与 decode——它是从推理场景倒推的设计。

池容量按最坏情况开(layout/mega_moe.cuh:18):

pool=num_ranks×num_max_tokens_per_rank×min⁡(topk,experts_per_rank)+experts_per_rank×191\text{pool} = \text{num\_ranks} \times \text{num\_max\_tokens\_per\_rank} \times \min(\text{topk}, \text{experts\_per\_rank}) + \text{experts\_per\_rank} \times 191

对齐到 384。没有 capacity factor、不丢 token——代价是显存按最坏情况预留,而不是像 MegaBlocks 那样用稀疏表示避开。

2.7 共享专家是同一队列里的两个 phase

Section titled “2.7 共享专家是同一队列里的两个 phase”
enum class BlockPhase { None, Linear1, Linear2, SharedLinear1, SharedLinear2 };

调度器是全局原子计数器驱动的工作队列(get_next_task_idx 用 atomic_add),L2 任务会自旋等待对应的 L1 任务先被取走:

const auto num_required_l1_tasks = (task_info.pool_block_idx + 1) * kNumL1Clusters;
while (ptx::ld_volatile(workspace.get_l1_task_count_ptr()) < num_required_l1_tasks) {}

依赖关系用 kernel 内的计数器表达,而不是靠 kernel 边界。 共享专家的 GEMM 与路由专家的 GEMM 在同一个任务流里被调度。


Hulin Wang, Yaqi Xia, Donglin Yang, Xiaobo Zhou, Dazhao Cheng. Harnessing Inter-GPU Shared Memory for Seamless MoE Communication-Computation Fusion. PPoPP ‘25. DOI 10.1145/3710848.3710868 · 代码 WongHuLin/CCFuser(无 arXiv 预印本)

CCFuser 和 Mega MoE 的目标几乎相同——把 MoE 通信融进计算——但四个关键选择都不一样。

  • 都用「跨 GPU 共享内存」而非集合通信:CCFuser 走 NVSHMEM PGAS 对称堆(nvshmem_malloc),Mega MoE 走 PyTorch symmetric memory 的对等映射指针
  • dispatch 都是 pull:CCFuser 用 nvshmemx_get16_nbi_block,Mega MoE 由消费端反推源 rank
  • 都在设备侧发起通信,不经主机
维度CCFuserMega MoE
kernel 结构分阶段融合,多次 launch:local scatter → barrier → 融合 dispatch+GEMM1 → 激活(PyTorch)→ 融合 GEMM2+combine → barrier → gather单 persistent kernel,整层一次 launch
内存抽象NVSHMEM PGAS,可走 UCX/IB 出节点symmetric memory 对等指针,纯 NVLink,无 RDMA,kNumMaxRanks = 72
通信方向混合:dispatch pull,combine push(nvshmemx_put128_nbi_block)全 pull
中间激活完整落 HBM:merged_input、ffn1_out 用 torch::zeros 分配,供 autograd 保存有界环形缓冲,容量与总 token 数解耦
CTA 内模型同构协作 warp,全部 warp 一起发 async 传输再一起算 WMMAwarp 特化,六种角色
融合粒度token block,固定 BM=64BM=64MMA tile / warp tile,BM∈{16,32,64,96,128,192}BM \in \{16,32,64,96,128,192\} 动态选
硬件A100,Ampere WMMA,FP16SM100,tcgen05 + TMA,FP8×FP4
目标负载分布式训练(含反向与权重梯度 kernel)推理(Mega MoE 无反向)
对比基线 / 加速比FastMoE 2.96×、FasterMoE 2.48×,峰值 4.34×见 DeepGEMM PR #316

CCFuser 自己列的局限里,有两条正好被 Mega MoE 的设计绕开:

  1. 固定 BM=64BM=64 离散化:token 数不是 64 的倍数就要补齐,负载倾斜时碎片明显 → Mega MoE 用六档动态块大小
  2. 本地/远程块配对依赖:靠「一个本地块配一个远程块」在 CTA 内互相掩藏延迟,若某 GPU 本地块过少就无法掩藏,需要离线的 expert reassignment 图变换 → Mega MoE 的 warp 特化让 dispatch warp 与 MMA warp 天然独立,不依赖配对

另外两条是它承认的代价:中间激活全落 HBM 吃带宽;多次 kernel launch 与 stream barrier 把 CPU 留在关键路径上。


维度DeepSpeed-MoEFasterMoETutelMegaBlocksScatterMoECCFuserMega MoE
年份/会议2022PPoPP’22MLSys’23MLSys’232024PPoPP’252026
主要目标推理服务扩展训练负载不均统一自适应并行dropless 训练消除 HBM 拷贝通信计算融合推理单核融合
丢 token是(静态容量)是可配(补零式 dropless)否否否否(最坏情况预留)
GEMM 原语稠密 batched稠密 batched稠密 batched块稀疏 SDD/DSD融合 grouped GEMMWMMA 分块tcgen05 分块
应对倾斜PR-MoE 架构复制热门专家调 rr + 补零每专家动态块数指针偏移动态 tile离线专家重分配rank 轮转交错
通信分层 all-to-all点对点流水2DH 集合标准 EP无(单机)NVSHMEM get/putsymmetric memory pull
中间激活落盘落盘落盘(补零膨胀)落盘不 materialize 置换缓冲落盘有界环
融合层次库调用跨 stream 编排跨 stream 编排kernel 内块稀疏kernel 内 gather/scatter分阶段跨 kernel单 kernel 内 warp 特化
规模域scale-outscale-outscale-outscale-out单机两者皆可仅 scale-up(NVLink)
训练/推理两者训练两者训练两者训练仅推理前向

对比之后,代价也清楚了:

  • 没有 scale-out。全仓库 grep 不到任何 RDMA / IBGDA / NVSHMEM,kNumMaxRanks = 72 对着 NVL72。跨节点仍得回到 DeepEP。CCFuser 和 Tutel 在这一维上更通用。
  • 没有反向。csrc/apis/mega.hpp 只导出 fp8_fp4_mega_moe 和 bf16_mega_moe 两个前向入口,impls/ 下没有反向大 kernel。训练路径仍是「DeepEP dispatch → DeepGEMM grouped GEMM → TileKernels SwiGLU → GEMM → combine」的分立流水。
  • 放弃了自适应并行。Tutel 用参数 rr 在激活通信与参数通信之间搜最优点;Mega MoE 把「搬 token」这个选择写死了,不复制专家、不切换 DP/EP/MP。
  • 放弃了稀疏表示。MegaBlocks 用 BCSR-BCOO 从根上消除 padding;Mega MoE 仍是稠密 tile,只是把块大小选细。

换来的是:整层一次 kernel launch,CPU 完全离开关键路径,通信与计算的重叠由硬件 warp 调度器完成,中间激活容量与路由总量解耦。这是一套彻底面向低延迟推理的取舍。


交叉检索 DBLP、ACM DL、IEEE Xplore、arXiv 后确认:不存在名为 UniMoE / Uni-MoE 的系统或 GPU kernel 论文。

该名称下的文献是哈尔滨工业大学(深圳)与腾讯的多模态模型系列:

官方代码库 HITsz-TMG/Uni-MoE 中没有任何 .cu / C++ / Triton kernel,MoE 层是标准 PyTorch 线性层,跨卡通信直接用 DeepSpeed / PyTorch 的 all_to_all_single。它与本文讨论的 kernel 级工作不在同一层次。