笔记日期: 2026-08-06 作者: Zhongzhu Zhou 评阅论文: Fine-grained Computation-Communication Overlap via Tile-level Signaling and Scheduling for Mixture-of-Experts 论文作者: Minyu Cui, Anna Wingkvist, Morgan Ericsson(林奈大学) arXiv: 2607.19539 会议/状态: 将发表于 ICPP 2026(第 55 届国际并行处理会议)
1. 为什么读这篇,它到底在解决什么问题
先说一个任何在多 GPU 上跑过 Mixture-of-Experts(MoE)前向的人都心里有数的事实:一个分布式 MoE 层的 wall-clock 时间里,大约一半可能根本没在算东西,而是在等 token 在 GPU 之间搬运。稀疏 MoE 架构本来是要解决一个扩展性问题——让模型容量(更多专家、更多参数)增长而不让每个 token 实际要碰的计算量成比例增长。从 FLOPs 的角度看,它确实做到了:一个被路由到 E 个专家里 top-k 个的 token,只需要付 k/E 份稠密层的计算代价。但在分布式部署里,“只算一点点”悄悄地把一个瓶颈换成了另一个。要把 token 路由到它被分配的专家,你首先得把它”运”过去;要把结果拿回来,你还得再”运”回来。这两次搬运都是集合通信操作(all-to-all),而在现代硬件上,all-to-all 之所以昂贵,恰恰是因为 GPU 算力的增长速度早就把互连带宽甩在了后面。这篇论文用的平台上,A100 级 GPU 能提供数百 TFLOPs 的张量核心吞吐,但 GPU 对之间的 NVLink 单向带宽封顶大约是 100 GB/s——算力是指数级增长,线缆没跟上。
“通信慢、计算有余量”的教科书解法,就是把两者重叠起来——让通信和独立的计算并发执行,这样决定 wall-clock 时间的是两者中较慢的那个,而不是两者之和。这个想法在这篇论文里不算新——基于分解(把 GEMM 和集合通信切块、在不同 stream 里流水线化)和基于融合(把计算和通信焊接进同一个内核)的重叠技术已经存在多年,论文自己在相关工作里也认真梳理了这两个流派。真正值得完整读一遍而不是扫一眼的原因,是introduction 里一句具体的技术论断:现有的重叠方法要么(a) 因为 NCCL 的集合通信 API 要求”连续缓冲区”,只能沿单一张量维度做分解,导致通信粒度和 GEMM 本身的 tile 结构错位——所以”仅靠分解很难实现 tile 级重叠”;要么(b) 靠把计算和通信融合进一个整体内核来获得细粒度,代价是”侵入式的工程实现,包括自定义屏障、跨 rank 原子协议、以及针对每个目标的内核特化”。两个流派都没能在不产生错位或不做侵入式内核手术的前提下,拿到 tile 粒度的重叠。
这篇论文给出的答案是第三条路:让 GEMM 和通信保持成两个完全独立、看起来很”普通”的内核——不融合,不在 GEMM 主循环里穿插自定义屏障协议——但通过轻量级的、per-tile 的、设备驻留的”就绪”标志来协调它们,并让它们并发运行在互不重叠的一组流多处理器(SM)上,这样谁都不会抢占对方的算力。真正花了工程力气的地方,是一个数据布局技巧——重新排列 GEMM 的输入行,让每一个输出 tile 都对应一个静态已知、唯一确定的目标 rank——这正是让 tile 粒度、per-tile 触发的传输在不需要在热路径上做逐行路由的情况下变得可行的关键。在一个 4-GPU A100 节点上,针对四个业界代表性 MoE 系统(FasterMoE、Tutel、用 CUTLASS GroupGEMM 实现的 Megatron、用 Transformer Engine 实现的 Megatron)做对比测量,结果是端到端最高 2.64 倍、MoE 层最高 2.74 倍的加速,且逐 token 的输出与串行 baseline 的差异都在 FP16 舍入噪声范围内。如果你从事分布式 LLM 训练或推理系统、MoE 路由,或者 GPU 内核级重叠工程,这是一个机制上非常清晰、值得完整理解的设计——不是因为”重叠计算和通信”这个想法本身是新的,而是因为让 tile 粒度重叠在不做内核融合的前提下变得可实操的那个具体的数据布局与调度纪律,才是这里真正有意思的工程内容。
2. 前置知识:读懂方法之前需要知道什么
2.1 Mixture-of-Experts,简要回顾
标准(稠密)transformer block 里,每个 token 都要经过同一个前馈网络(FFN),所以总的 FFN 计算量随模型规模线性增长——模型越大,每个 token 的 FLOPs 就成比例地越多。Mixture-of-Experts 层打破了这种耦合。它不再只有一个 FFN,而是有 E 个”专家”子网络(通常本身也是 FFN)和一个小的门控/路由网络。对每个 token,路由器计算一个在 E 个专家上的概率分布,选出 top-k个(常见取 k=2),层的输出就是这 k 个专家输出的门控加权和。因为每个 token 只会被 k 个(而不是全部 E 个)专家碰到,每个 token 的实际计算量基本上不随 E(以及由此带来的总参数量)增长而增长——这正是 Switch Transformer、GShard,以及现代万亿参数 LLM 能够在不成比例增加每 token FLOPs 的前提下扩展模型容量的原理。
2.2 分布式 MoE 执行:五步,其中两步是通信
当 E 个专家放不进一张 GPU(或者你单纯想要专家并行带来的其他好处)时,你会把不同的专家放到不同的 rank 上(这篇论文里”rank”和”GPU”可以互换使用——评测里是一个 rank 对应一张 GPU,总共四个 rank)。一次分布式 MoE 层的前向传播,依次执行五个步骤:
- 路由(Routing)——每个 rank 在自己本地的 token 上跑门控,产生逐 token 的专家分配和门控权重。
- 第一次 all-to-all(dispatch,分发)——token 从持有它的 rank 被送到承载其被分配专家的那个/那些 rank。
- 专家计算(Expert computation)——每个 rank 对刚收到的 token 跑本地专家的 FFN 计算。
- 第二次 all-to-all(combine,合并/返回)——专家的输出被送回每个 token 原来所属的 rank。
- 加权归约(scale)——所属 rank 把收到的多个专家输出组合成该 token 最终的门控加权输出。
两次 all-to-all 都是集合通信,按论文的说法(引用了此前的测量结果),两者合计可以占到 MoE 总执行时间的近一半。这篇论文的范围刻意收窄:它只针对第二次 all-to-all(专家计算之后的”返回”/“合并”路径),明确把第一次 all-to-all 和(训练用的)反向传播都留给未来工作。这个收窄很重要——它意味着这是一个针对推理时、只针对前向传播、只针对某一个具体通信步骤的优化,而不是一个万能的 MoE 训练加速器。
2.3 GEMM 分块与 epilogue 信号
上面”专家计算”里做的事,归根结底是一次通用矩阵乘法(GEMM):。现代 GPU 上的 GEMM 内核(比如 NVIDIA CUTLASS 库里实现的那些)并不是一次性算完整个输出矩阵 。它们把 分解成一个矩形输出 tile 的网格,每个 tile 交给一个 thread block(“协作线程数组”,CTA)来算。每个 CTA 沿归约维度 迭代,用张量核心指令在寄存器里累加部分乘积,一旦这个 tile 的累加完成,CTA 就会执行一个epilogue——一小段代码,做最终的输出变换(数据类型转换、逐元素操作),然后把算完的 tile 写到全局内存里。这次写入正是这个 tile 对系统其余部分可见的那一刻。因为不同 tile 的 CTA 是独立执行、在不同时间完成的,单个 tile “算完”的时刻,往往远早于整个 GEMM 内核结束——而 epilogue 正是一个自然、廉价的位置,可以在这里设置一个标志说”这个 tile 完成了”,供另一个内核轮询。这种”epilogue 信号”的思路(论文致谢了此前的工作,比如 T3 和 COMET,推广了 tile 级信号用于重叠)是这篇论文一切设计的承重原语。
2.4 设备发起的通信(NVSHMEM)
通常,一次集合通信调用(比如 NCCL 的 all-to-all)是从主机(CPU)发起的,由 CPU 协调各个 rank,再把控制权交给一个 GPU 通信内核。更新的库比如 NVSHMEM 则暴露了一套单边 put/get 原语,让 GPU 内核可以直接调用——rank A 上跑的一个线程,可以直接写入 rank B GPU 显存里的一块对称堆(symmetric heap)缓冲区,完全不需要回到主机 CPU 去安排这次传输。这让把一次 all-to-all 实现为一个普通 CUDA 内核、由内核内部发起许多小的远程写入变得可行——这也正是 NVSHMEM 在这篇论文里扮演的角色:“consumer”内核就是一个持久化 CUDA 内核,随着数据就绪就发起 NVSHMEM 的 put,热路径上完全没有主机端同步。
2.5 并发内核、stream,以及 SM 分区
CUDA 允许你在不同stream上并发启动多个内核,但默认情况下,这些并发内核共享同一个 SM 资源池——如果内核 A 和内核 B 同时在跑,硬件调度器会把它们的线程块交替调度到任何空闲的 SM 上,每个内核的性能都可能被对方的存在所干扰。持久化内核(persistent kernel) 是指用固定数量的 CTA(通常一个 SM 一个)启动的内核,每个 CTA 在内核的整个生命周期里都常驻在自己的 SM 上,从一个工作队列里不断取任务,而不是像 GPU 常规的网格调度机制那样被当作全新的 CTA 来调度。如果你把两个持久化内核启动在互不重叠的一组目标 SM 上(比如内核 A 把 CTA 钉在 SM 0-89,内核 B 钉在 108-SM A100 上的 SM 90-107),它们就能真正并行运行,不会争抢同一批物理执行单元——这正是论文用来让 GEMM 和通信内核互不干扰的”SM 分区”手段。
有了这五块拼图——MoE 的两次 all-to-all、带 epilogue 信号的 GEMM 分块、NVSHMEM 的设备发起写入,以及 SM 分区的持久化内核——这篇论文实际的设计其实是一个相当简短的故事:用 epilogue 信号知道一个 tile 何时就绪,用 NVSHMEM 从内核内部把它写出去,用 SM 分区让 GEMM 和写入者互不干扰。真正难的部分,占了论文第 3 节的大部分篇幅,是要确保每一个就绪的 tile 都有一个唯一、无歧义的目标 rank,这样写入者才永远不需要在发出去之前逐行检查 tile 里的内容。
3. 架构总览
这个设计把传统的”先算完整个专家输出 GEMM,再之后发起一次批量 all-to-all”模式,替换成了一个持续重叠的流水线。从高层看:
flowchart LR
A["路由(Gate)"] --> B["第一次 All-to-All(dispatch)"]
B --> C["专家问题构造\n+ 合并计划\n(远端归属对齐布局,\ntile 调度, 传输元数据)"]
C --> D["重叠执行\nGEMM(producer) || 第二次 All-to-All(consumer)"]
D --> E["Scale\n(门控加权归约)"]
style C fill:#ffe0cc,stroke:#cc4400
style D fill:#ffe0cc,stroke:#cc4400
论文自己的总览图(下面复现的 Figure 1)把范围说得很明确:论文的贡献(图中红色标注的”Our work”)覆盖的是”专家问题构造+合并计划”和”重叠 GEMM/all-to-all 执行”这两个方框。路由、第一次 all-to-all,以及最后的 scale 步骤都没有改动——这篇论文只触碰”token 到达其专家”和”专家输出返回原处”之间发生的事情。

真正干活的是两个阶段,分别对应论文的 3.2 节和 3.3 节(下面会逐一展开):
- 阶段 A——专家问题构造与合并计划(一个预处理/元数据构建步骤,还没有通信发生)。给定门控的路由决策,这个阶段会:(1) 重新排列 GEMM 的输入行,让每一个输出 tile 恰好属于一个目标 rank(“远端归属对齐的行布局”),(2) 构造一个调度,告诉 GEMM 应该按什么顺序去算哪些 tile(“远端优先的 tile 调度”),以及(3) 为每个 tile 预先算好 consumer 一旦它完成就能立即把它发出去所需要的元数据(“合并计划”:目标 rank、远程写偏移量、有效行数)。
- 阶段 B——重叠执行(真正的运行时重叠)。两个持久化内核并发运行:一个 producer(rank 范围内的 GEMM,按阶段 A 给出的调度逐 tile 计算专家输出,并在其 epilogue 里,每完成一个 tile 就发出一个信号)和一个 consumer(一个占用一小部分互不重叠 SM 的通信内核,轮询信号,并在每一段完成的 tile “segment”就绪的瞬间发起一次 NVSHMEM
put)。
因为 producer 会(由调度的构造方式保证)优先处理远端目标的 tile,而 consumer 会在第一段 tile 刚就绪、远早于整个 GEMM 结束时就开始传输,两个阶段是在 wall-clock 时间上真正重叠的,而不只是一种粗粒度的批级流水线。
4. 远端归属对齐的行布局与 tile 调度:逐步推导
这是论文的机制核心,值得仔细走一遍,而不是只信一段话的总结。
4.1 这个布局要解决的问题
第一次 all-to-all 之后,rank r 上的某个本地专家 e 收到的行(token),原本可能来自系统里任意一个甚至每一个 rank——rank 0 的 token、rank 1 的 token、rank 2 的 token 等等,以 dispatch 恰好把它们送到的顺序混在一起。如果你就这么用收到的原始行顺序去跑专家 e 的 GEMM,一个输出 tile(覆盖 M 维方向上一段连续的行块,因为 GEMM 输出 tile 是 tb_M × tb_N 的矩形)通常会包含目标是多个不同所有者 rank的行。这就破坏了 tile 粒度重叠的整个前提:如果一个 tile 混杂了 rank 0、2、3 的行,consumer 就没法直接说”把这个 tile 发给 X 号 rank”——它得检查 tile 里的每一行,在某个路由元数据表里查这一行原来的 owner,然后发起逐行(而不是逐 tile)的传输。这个逐行查找的开销,正是论文一开始选择在 tile 粒度上工作想要消除的东西。
4.2 解法:重新排列行,让 tile 的所有者保持一致
解法在概念上很简单,虽然真正实现起来需要一些簿记工作:在 GEMM 运行之前先重新排列行,让每个本地专家的输入里,行按目标(所属)rank 连续分组,远端目标的分组排在前面(按属于哪个远端 rank 分组),本地目标的行排在最后。具体来说,按 3.2.1 节(论文里的 Algorithm 1)的描述,对每个本地专家 e:
- 对每一个远端 rank
r'(除本地 rank 之外的每个 rank),收集分配给专家e、原始 owner 是r'的行,组成分组G_{r'}。 - 如果
|G_{r'}|不是 tile 高度tb_M的整数倍,就用零填充行把G_{r'}补到下一个tb_M的倍数。这些填充行会参与 GEMM 的实际运算(不需要在计算中途特殊跳过——一个零填充的行经过一个线性层,只会产生一个零填充的输出行,代价是一点被浪费的计算,但不影响正确性),但在返回传输时会被排除——consumer 从合并计划里就知道一个 segment 里到底有多少行是”有效”的,只发送那些。 - 把所有远端分组 (跳过本地 rank)拼接起来,再拼上本地分组 ,组成专家
e最终的行排列X_e。
因为填充只发生在分组边界(把一个分组凑整到 tile 高度的倍数),而且这种排列保证远端 rank 的分组之间、以及和本地行之间都不会交错,每一个完整高度的输出 tile 现在都只对应一个目标 rank——从”这个 tile”到”发给哪个 rank”的查找,变成了一次简单的数组读取,而不是逐行的路由判断。下面复现的 Figure 2b,针对一个 4-rank、每 rank 2 专家的配置,具体展示了这一点:专家 2 的输入被排布成 [来自 R0 的行][来自 R2 的行][来自 R3 的行][填充][本地行][本地行],填充恰好落在 R3 到本地行的边界处,保证这个边界是 tile 对齐的。

4.3 为什么这样设计,显而易见的替代方案是什么,在哪里会失效
为什么这样设计: 它把”这一行属于哪个 rank”的全部簿记工作,一次性挪到了 GEMM 运行之前的元数据构建阶段(上面的阶段 A),这样在真正的重叠执行(阶段 B)期间,producer 和 consumer 都可以把”tile → 目标 rank”当成一次没有任何逐行分支的扁平数组查找。这让热路径上的内核——GEMM 主循环和 NVSHMEM 传输循环——完全没有条件性的路由逻辑,这一点很重要,因为条件分支和分散的内存访问模式,恰恰是扼杀 GPU 内核吞吐量的元凶。
显而易见的替代方案: 完全不重排行;而是让 consumer 内核在传输时检查每个输出行的原始 owner 元数据,在 tile 内部所有者边界落在哪里就在哪里发起逐行(或逐小组)的远程写入。这在架构上更简单(不需要提前做一遍行重排),但要付出真实、反复发生的代价:每次传输现在都需要查一次逐行的路由元数据,而一个行跨越两三个所有者 rank 的 tile,要被拆成多次小的、可能不连续的远程写入,而不是一次干净的连续写入。论文自己的测量(3.3.2 节 / Figure 3a)显示,即使是单 tile 的传输(32-64 KiB)在实测的 NVLink 带宽曲线上也”远未饱和”——把一个 tile 拆成更小的按所有者划分的碎片,只会把传输大小进一步推向低带宽区间,让论文本来想解决的问题变得更严重。
选定设计在哪里会付出代价: 填充不是免费的。4.5 节(开销分析)算出每个 rank 最坏情况下的填充量是 行,其中 是 rank 数量——对论文里 tile 高度最高到 256 的 4-rank 配置,这最多可能是每个 rank 765 行被浪费,在评测的问题规模下是一个真正不小的比例(论文的说法是”百分之几”)。这个开销在均衡路由下最小(此时分组大小往往已经接近 tile 高度的倍数),在 stress_skew(极端偏斜)路由下最大(此时某个所有者 rank 的行分组可能很小,填充占该分组的相对比例就会被放大)。论文诚实的结论是”重叠带来的好处超过了这个代价”——第 4 节的吞吐量数字确实支持这一点——但值得指出的是,这是一个真实的效率税,不是免费午餐,而且它随着 rank 数量的增长而增长,意味着在比论文评测的 4-GPU 规模更大的场景下,这个填充开销是一个真正悬而未决的问题(见下文第 8 节局限性讨论)。
4.4 远端优先的 tile 调度
在上面的行布局基础上,论文进一步对 tile 施加了一个执行顺序(仍属于 3.2.2 节,Algorithm 1 的后半部分),由两条规则支配:
- 规则(i): 所有远端目标的 tile 都排在任何仅本地的 tile 之前调度,因为只有远端 tile 需要传输——本地 tile 永远不会离开这个 rank,提前算它们没有任何好处。
- 规则(ii): 在一个 rank 的多个本地专家之间,远端行更多的专家排在前面调度——这样最大的传输能最早进入流水线,让 consumer 有尽可能长的时间在整个 GEMM 结束之前把它们排空。
Figure 2c 展示了某个示例 rank 上产生的调度结果:专家 3 的远端 tile 最先跑(它的远端负载更重),然后是专家 2 的远端 tile,最后才是两个专家所有的本地 tile。3.2.2 节里给出的直觉是要”最大化第一个可传输数据段与 GEMM 结束之间的时间窗口”——一个远端 tile 越早就绪,consumer 在 producer 耗尽工作之前保持跟上的余地就越大。
Algorithm 1(论文伪代码,略作排版整理),完整的行布局 + 调度构造:
输入: routing(路由结果); 本地 rank r_self; rank 数量 R;
每 rank 专家数 E; tile 的 M 维尺寸 tb_M
输出: 每个专家的行布局 {X_e, e in E}; tile 调度 S
# --- 远端归属对齐的行布局 ---
for 每个本地专家 e:
for 每个远端 rank r' in {0, ..., R-1}, r' != r_self:
G_{r'} <- 分配给专家 e、来自 rank r' 的行
if |G_{r'}| mod tb_M != 0:
用零填充行把 G_{r'} 补到下一个 tb_M 的倍数
把 G_0, G_1, ..., G_{R-1}(远端优先, G_{r_self} 排最后)拼接进 X_e
# --- 远端优先的 tile 调度 ---
按远端 row-band 数量降序排列本地专家 # 规则(ii)
S <- []
for 按排序后顺序的每个专家 e:
for X_e 中目标 != r_self 的每个 row band b:
把 b 的 tile 追加进 S # 规则(i): 所有远端 tile 优先
for 每个本地专家 e:
for X_e 中目标 == r_self 的每个 row band b:
把 b 的 tile 追加进 S
返回 {X_e, e in E}, S
4.5 合并计划
要真正把一个已完成的 row band 发出去,consumer 需要知道关于它的三件事:发给哪个 rank、在那个 rank 的接收缓冲区里的写入位置在哪,以及里面有多少行是真实(非填充)数据。如果在传输时才通过检查各行来解出这些信息,会给每一次传输都加上逐行开销——这正是整个设计想要避免的代价。取而代之的是,“合并计划”(3.2.3 节)是一次性提前算好的,通过遍历前面第 4.2 步构建的行布局,构造出三个扁平的、per-tile、设备驻留的数组:目标 rank、远程缓冲区写偏移量、有效行数,全部按 tile 编号索引。在传输时,解析一个 tile 的目的地就只是三次数组读取——没有逐行检查,没有路由表查找,没有分支。每一个可远程发布的 row band 都会在其目标 rank 的接收缓冲区里获得一个唯一的写偏移量,这些偏移量在所有本地专家之间做了全局协调,保证来自不同专家、目标是同一个对端 rank 的数据落在不重叠的位置上。
5. 重叠执行:producer、consumer,以及通信粒度,逐步推导
5.1 producer:一个 rank 范围内的持久化 GEMM 内核
传统模式是每个专家启动一个独立的 GEMM 内核(这会给 rank 上每个专家都带来一次内核启动开销),而这里的 producer 是每个 rank 一个持久化 GEMM 内核,覆盖该 rank 上所有本地专家。它以每个 SM 一个持久化 CTA 启动;每个 CTA 以跨步的方式处理调度 S 里的 tile——CTA i 处理 tile i, i+g, i+2g, ...,其中 g 是总的 CTA(网格)数量。对每个被调度的 tile,一个 CTA 会取出一个轻量级的”设备视图”(指向该专家 、、 矩阵的指针加上问题规模元数据),拷贝一份共享的基础参数块,再覆写其中随专家变化的字段,然后才调用实际的 GEMM tile 计算例程。论文特别指出,这是一次寄存器级的拷贝,而不是重新启动内核,所以在内核运行中途切换专家基本上是免费的——正是这一点,让”覆盖 rank 上所有专家”这件事能够发生在一个持久化内核内部,区别于把多个问题的矩阵拼接成一个组合问题的经典”分组 GEMM”。tile 完成信号被直接编织进 CUTLASS 的 epilogue 里(遵循论文引用的”Epilogue Visitor Tree”,EVT 模式):在一个 CTA 做完线程栅栏(thread-fence)以保证其完成的 tile 数据在内存中全局可见之后,它会翻转一个该 tile 对应的设备驻留就绪标志——每个 tile 只增加一小笔固定的指令开销,不会打扰 GEMM 真正的乘加主循环。
5.2 通信粒度:为什么不干脆一 tile 完成就立即传输?
最朴素的下一步——一个 tile 的就绪标志一翻转就立即传输它——其实是个坏主意,论文用一次真实的带宽测量(下面复现的 Figure 3a)证实了这一点。在两个示例 consumer SM 预算下(4 个和 8 个 SM),实测的 NVLink 带宽随传输大小从小尺寸开始陡峭上升,直到大约 1 MiB 附近趋于平稳(8 个 SM 时 87 GB/s,4 个 SM 时 67 GB/s)。单个 tile 的负载(取决于 tile 配置,32-64 KiB)落在这条曲线的低带宽区间——以单 tile 粒度传输,相对于负载大小会把大部分可用链路带宽浪费在协议/建立开销上。

除了带宽之外,单个 tile 作为传输单元还有一个独立于带宽的结构性问题:一个 GEMM 输出 tile 只跨越完整输出宽度 N 中的 tb_N 列。虽然(由行布局的构造保证)一个 tile 里的所有行都属于同一个所有者 rank,但这个 tile 在内存里的实际负载只是一整行的一段部分宽度切片——如果你想重建出真正的输出行,这在内存里是一个跨步的、不连续的区域,需要多次小拷贝,而不是一次干净的写入。Row band——横跨全部 N 个输出列、高度为 tb_M 的一整条——则既是所有者一致的(按构造,全程属于同一个目标 rank),又是内存连续的,可以作为一次单独的远程写入来移动。
论文的解法(Figure 3b)是一套可调的 segment 划分方案,有三种情形:
- 首个 segment 固定为单个 row band,这样 consumer 可以尽快发起它的第一次传输(最小化首字节延迟)。
- 末个 segment 同样固定为单个 row band,以避免一条较长的”通信尾巴”——更大的最后一个 segment,既要等更久让其构成 tile 全部完成,完成后传输本身也要花更久,两者叠加会让通信更容易溢出到 GEMM 结束之后。
- 中间的 segment 会合并
x个连续的 row band,其中x(论文记号里的mgb,“multiple of gap bands”)是一个可调参数,选取时要让每次中间传输的负载落在带宽曲线的饱和区间。
论文明确指出最优的 x 是经验性的、与工作负载相关——他们自己的评测(第 4 节)发现,x=1(约 1-2 MiB)和 x=2(约 2-4 MiB)都不是普遍最优的,因为把更多 row band 合并进一个更大的内部 segment,只有在由此带来的带宽收益超过它给 consumer 带来的额外等待时才划算。这是一个真实存在、尚未彻底解决的权衡旋钮,而不是论文声称有闭式解的东西——为什么把这一点当作一项设计局限来看待,见下文的批判性分析部分。
5.3 Producer-Consumer 协同调度:stream、优先级、以及 SM 分区
最后一块拼图,是确保两个内核——GEMM producer 和 NVSHMEM consumer——真正并发执行,而不是一个饿死另一个的 GPU 资源。三个具体技术(3.3.3 节):
- 独立的 CUDA stream,不对称的优先级。 两个内核都跑在独立的非阻塞 stream 上,但 consumer 被赋予了比 producer 更高的调度优先级。这个理由是基于不对称代价的:延迟一个 producer 的 tile 只会在 GEMM 的最末尾造成一点时间损失(GEMM 整体还有很多其他 tile 可以继续算,即使一个 tile 的信号被短暂延迟);而延迟一次 consumer 的传输会直接压到通信关键路径上——而且因为各个 segment 是有序的,这个延迟会叠加影响到后续的每一个 segment。
- SM 级分区。 因为 consumer 优先级更高,而且每个内核都是持久化的(在其整个生命周期内占用固定的 CTA/SM 数量),consumer 占用的那些 SM 在整个重叠执行窗口内实际上被为通信预留了,剩下的 SM 留给 GEMM。这消除了两个内核之间的 SM 级调度干扰——GEMM 全程获得一个稳定的 SM 预算,consumer 也有能维持前述带宽平台所需的保证 SM 数量,而不是动态争抢共享资源。
- 只靠设备驻留状态协调。 一旦两个持久化内核都启动并驻留后,它们之间之后的一切协调(tile 就绪标志、segment 派发)都通过设备内存进行——关键路径上没有主机(CPU)参与,也没有阻塞式的屏障同步。
consumer 的 SM 预算本身也是一个可调参数(论文记号为 cCTA,评测里从 2 扫到 24),存在真实的权衡:consumer 的 SM 太少,传输流水线会跟不上 tile 产出的速度,导致通信溢出到 GEMM 结束之后(这一点在 4.3.4 节里有明确测量,下文第 6 节会讨论);而从 producer 那里拿走太多 SM,GEMM 本身会慢到足以抵消重叠带来的好处。论文的默认值 cCTA=14(A100 总共 108 个 SM 里的 14 个)是从他们自己的扫描实验里经验选出的,而不是分析推导出来的。
6. 值得深究的设计选择:为什么、替代方案是什么、边界在哪
除了第 4.3 节已经讨论过的行布局选择之外,还有三个设计决定值得用同样的 why/替代方案/边界方式来审视。
用分离的内核加设备驻留信号,而不是一个融合内核。 为什么: 这让 GEMM 那套 CUTLASS 模板化的主循环基本保持不变(信号只加在 epilogue 这个自然的、低开销的插入点),同时把通信逻辑放进它自己独立的、更简单的内核里——不需要自定义的跨内核屏障协议,不需要把针对特定目标的特化逻辑焊进 GEMM 本身。替代方案: 基于融合的方案(论文引用了 FlashMoE、CCFuser、COMET)把计算和通信焊接进一个内核,彻底消除内核启动和 stream 切换开销,理论上可能拿到更紧密的重叠。在哪里会失效: 论文自己在相关工作部分承认,融合方案”实现了细粒度重叠,但代价是侵入式的软件复杂度和大量针对特定目标的优化”——也就是说,分离内核的设计是用理论上可达到的重叠紧密度,去换取显著更简单、更可移植、更易维护的代码。这个取舍值不值,完全取决于你是在为单一固定的硬件/软件目标优化峰值吞吐(倾向融合),还是想要一个更容易跨 GEMM 形状、路由模式、硬件世代移植的设计(倾向这篇论文的方案)——论文自己的数字(4.2.1 节)显示,在隐藏维度较小的模型上,这篇论文的方法有时会落后于 Megatron-TE 这类偏融合路线的框架,而这恰恰是融合的紧密集成回报最大的场景。
给 consumer 比 producer 更高的优先级。 为什么: 正如 5.3 节所论证的,一次延迟的传输会叠加影响之后每一个 segment,而一个延迟的 GEMM tile 只在末尾损失一点时间。替代方案: 相同优先级,甚至给 producer 更高的优先级(理由是”GEMM 是昂贵的部分,应该保护它”)。在哪里会失效: 论文自己的争用研究(4.3.4 节,下文讨论)显示,实际会咬人的其实是相反的失效模式——饿死 consumer 的 SM(不是优先级本身,而是原始的 SM 分配量)才是在路由偏斜下拖垮重叠的原因,这说明单靠优先级还不够;SM 分配大小至少同样重要,而论文并没有完全厘清收益里有多少来自优先级、多少来自互不重叠的 SM 分区本身。
固定的、可调的 segment 大小(mgb),而不是自适应的。 为什么: 让 consumer 发起传输的逻辑保持简单——一个部署时选定一次的静态参数,没有运行时决策开销。替代方案: 一个根据实时观测到的 producer 吞吐和 consumer 积压情况来动态增减 segment 大小的自适应方案(论文明确把这一点点名为未来工作:“生产系统可以受益于运行时自适应的 SM 分区选择器,我们把这个留给未来工作”——同样的逻辑也适用于 segment 大小)。在哪里会失效: 在路由偏斜(stress_skew)下,固定的 segment 大小和固定的 SM 预算共同造出了 4.3.4 节里测到的争用病理——一个为均衡路由调好的静态配置,一旦路由分布在运行时发生变化,就可能变得次优甚至反而有害,而这恰恰是生产环境 LLM 服务里会发生的情况,因为 token 到专家的路由模式本身依赖于数据,可能随请求变化。
在所有者 rank 分组边界处填充,而不是接受混合所有者的 tile 加逐行派发。 已经在上面 4.3 节里覆盖过,但为了完整性在这里重复一遍:这个取舍是用提前的、有界的、可量化的计算浪费(填充),去换取消除传输时无界的、逐行分支的开销。论文量化了最坏情形( 行每 rank),但没有报告随着 (rank 数量)远超评测用的 4 个 rank 增长时,这个开销在定性上会如何变化——下文局限性部分会再次提到这个空白。
7. 实验结果,附评述
7.1 实验设置
所有实验都跑在一个单节点、四张 NVIDIA A100 GPU(每张 108 个 SM、40 GB HBM)、通过节点内 NVLink 互连(每 lane 25 GB/s,每对 GPU 4 条 lane,单向带宽上限约 100 GB/s)的机器上。软件栈:CUDA 12.1、NCCL 2.29.3、PyTorch 2.6.0、CUTLASS 3.9、NVSHMEM 3.6.5。四个 baseline:FasterMoE(定制的 all-to-all 原语,与专家通信流水线化)、用 CUTLASS GroupGEMM 实现的 Megatron、用 NVIDIA Transformer Engine 实现的 Megatron、以及 Tutel(自适应 MoE 运行时,支持可调的流水线度和分层 2D all-to-all)。三个基于真实 transformer 架构的评测模型配置——M-GPT(只在 12 个 block 里的第 11 个用 MoE,一种稀疏替换场景)、M-BERT(在第 2/5/8/11 个 block 用 MoE,均匀间隔)、M-Trans-xl(全部12 个 block 都用 MoE,密集替换的极端情况)——都用 top-k=2 路由,共 64 个专家(每 rank 16 个)。
7.2 真实模型上的端到端与 MoE 层延迟

在 M-GPT 和 M-BERT 上,这篇论文的方法稳定地超过了全部四个 baseline:端到端相对 FasterMoE 快 1.57 倍/1.66 倍,相对 Tutel 快 1.35 倍/1.15 倍,相对 Megatron-CUTLASS 快 1.15 倍/1.04 倍,相对 Megatron-TE 快 1.25 倍/1.09 倍;MoE 层级别的收益更大(相对 FasterMoE 快 2.65 倍/1.78 倍,相对 Tutel 快 1.77 倍/1.15 倍)。但在 M-Trans-xl 上,论文坦率地指出了一个真实的弱点:端到端上它比 FasterMoE 快 2.64 倍,但略微落后于 Tutel、Megatron-CUTLASS 和 Megatron-TE。论文自己的解释是,M-Trans-xl 是三个模型里隐藏维度最小的(512)——每 token 的 all-to-all 传输量随隐藏维度线性增长,所以更小的维度意味着可供隐藏的通信更少,而细粒度信号本身固定的开销,占已经更短的层时间的比例反而更大。这是一个有用的、有机制依据的提醒:这项技术的优势应该预期在隐藏维度较小的模型/层上收窄(甚至反转),想要采用这项技术的读者,在假设标题里的加速数字能直接照搬之前,应该先检查自己工作负载的隐藏维度和专家数之比落在哪个区间。
论文还正确地指出,端到端加速一直略小于 MoE 层加速——按照阿姆达尔定律,因为只有模型里 MoE 部分被加速了,而非 MoE 的层(注意力、embedding 等)代价不变,所以端到端收益的上限由 MoE 层占模型总时间的比例决定。
7.3 随专家数量的扩展性

在单层微基准上把每 rank 专家数 从 4 扫到 64,这篇论文的方法在整个范围内都拿到了最低的层延迟,而且加速比随 增长而增长(相对 FasterMoE 是 1.30 到 5.33 倍,相对 Tutel 是 1.77 到 2.16 倍)。这个趋势在机制上是说得通的:随着 增长,专家计算和返回通信一起变重,给重叠机制留出的、可以吸收进(同样在增长的)计算时间里的通信量也就成比例增多。重叠比例那个面板可以说是论文里信息量最大的单个结果:整个扫描范围内,第二次 all-to-all 被成功隐藏在计算之后的比例保持在 71.9% 到 99.9% 之间,有几个配置已经接近完全隐藏——这是对”tile 级信号足够早地暴露出通信工作,让 consumer 能跟上 producer 的步伐”这一说法的直接、定量证实,而不只是把通信的暴露成本按某个固定常数因子降低。
7.4 算子级加速与路由偏斜

在纯算子级别(只单独看专家-GEMM-加-第二次-all-to-all,排除模型的其余部分),相对串行 baseline 的加速峰值分别是 2.97 倍(均衡路由)、2.94 倍(中度偏斜)、3.01 倍(极端偏斜),出于上面同样的原因,普遍随 增长。mgb=1 和 mgb=2 在各个配置里都没有哪一个能一致地占优——这与论文早先(5.2 节)的说法一致,即最优的 segment 合并因子取决于工作负载,不存在唯一的最佳默认值。
7.5 资源争用:SM 分区敏感性研究
论文 4.3.4 节报告了对 consumer SM 分配(cCTA 从 2 扫到 24,剩余 SM 给 GEMM)的敏感性扫描。头条发现是:过度压缩 consumer 会导致性能急剧下降,甚至可能让整个重叠设计比不做重叠的串行 baseline 还要差。 在 cCTA=2 时,这个设计在所有测试的形状和路由模式下都比 baseline 慢,在 stress_skew 路由、最大测试问题规模下达到 baseline 延迟的 1.91 倍——两个 consumer SM 根本没法快速排空已完成的 tile,由此产生的背压会拖垮整个重叠收益。扫描范围内最低延迟通常在 cCTA 落在 [10, 20] 这个区间时取得。关键的是,路由偏斜会放大这种争用的代价:cCTA=2 时的减速在均衡路由下最小,在 stress_skew 下最大,因为偏斜路由会把专家输出的生产集中到承载热门专家的那些 rank 上,而这些 rank 上供给不足的 consumer 根本跟不上随之而来的就绪 tile 爆发。论文自己在这里的结论相当直白:“一个有效的重叠方法必须是资源感知的,要在计算吞吐和通信进度之间取得平衡,而不是单独把任何一边最大化”——而且它明确把运行时自适应的 SM 分区选择器列为尚未解决的未来工作,而不是含糊带过这个空白。
7.6 正确性验证
论文在 1440 个独立配置检查上(3 种路由分布 x 12 种 SM 分区 x 2 个 mgb 值 x 形状网格)验证了正确性,用相同的种子、权重、路由决策,把逐 token 的最终输出和串行 baseline 做对比。除了 stress_skew 下某一个 rank 的某一次迭代之外,所有检查都在 的相对容差内通过(那次异常在重新运行时无法复现,被合理地当作一次瞬态的非确定性异常处理,而不是正确性 bug,因为这个容差水平本身就已经比已知的 FP16 张量核心舍入噪声高出好几倍,又远低于任何结构性 bug 会产生的误差)。所有检查里观测到的最大相对误差是 ,大约比容差小一个数量级。
8. 局限性,作者说明的和未说明的
作者自己说明的: 只支持前向传播(不支持反向传播/训练——明确留给未来工作);只在单个 4-GPU 节点内 NVLink 场景下评测,没有更大规模或跨节点的评测;SM 分区(cCTA)和 segment 合并因子(mgb)都是静态的、经验调出来的参数,而不是自适应的,作者明确把运行时自适应的 SM 分区选择器列为未来工作;只重叠了第二次all-to-all(返回/合并路径)——第一次 all-to-all(dispatch)未被触碰。
作者低估或没有说明的:
-
行填充开销随 rank 数量的扩展性是断言出来的,不是实测出来的。 4.5 节给出了一个最坏情形公式,每 rank 行填充,但只在 时做了评测。这个公式对 是线性的,意味着在,比如说, 或 (拥有数百个专家的生产级 MoE 部署里现实的专家并行度)时,填充开销可能占到总行数中一个明显更大的比例——尤其是叠加路由偏斜之后,单个所有者 rank 分组相对于填充单位可能很小。论文断言”重叠的好处超过了这个代价”,但只在 时证明了这一点;把这个结论外推到更大的集群,正是论文没有真正测试过的那种论断。
-
没有跨节点(跨 NVLink 孤岛)的评测。 所有实验都跑在单节点、节点内 NVLink 上,这提供了比跨节点互连(InfiniBand、RoCE 等)显著更高的带宽(以及更低、更可预测的延迟)——而跨节点互连正是当专家并行跨越多个节点时(对最大规模的生产级 MoE 模型来说,这是现实的部署场景)会用到的东西。论文自己关于为什么 segment 粒度重要的带宽曲线论证(Figure 3a)本身就是 NVLink 特有的;在一条更慢、更高延迟、争用更激烈的跨节点结构上,最优的 segment 大小、SM 分区,甚至重叠 vs. 融合这个基本权衡的可行性,都可能看起来截然不同。这是一个论文没有讨论、甚至没有明确列为未来工作的显著泛化空白(结论里”部署在更大机器上”这句话隐含了它,但没有展开讨论)。
-
cCTA=14这个默认值以及”consumer 应占适中的 SM 份额,大致在 [10,20]“这个建议,只是在两种问题形状上扫描出来的(M×N为 16384×8192 和 32768×8192,K=2048),这是论文自己 Table 2 定义的完整工作负载空间里相当窄的一个切片。同样的 SM 分区最优点,在明显不同的 GEMM 长宽比上(比如实践中确实会出现的、隐藏维度与专家数比例不同导致的很瘦长或很扁宽的专家矩阵)是否依然成立,没有被直接测试过。 -
论文对比的四个 baseline 都是尝试重叠通信或做过通信优化的系统,但没有把相关工作里提到的最新内核融合工作(比如 FlashMoE、COMET、CCFuser)作为直接实验 baseline 拿来对比——这些工作在第 5 节(相关工作)里做了定性讨论,但没有在第 4 节里做正面的加速对比。考虑到论文自己把自己定位为分解式重叠和融合式重叠之间的一条中间路线,在实际的加速对比(Figure 4-7)里缺少一个融合式系统,给”相对更侵入式的融合方案,这里到底牺牲了多少重叠紧密度”这个问题留下了一个开放的经验性空白。
9. 批判性分析
(a) 这篇论文特有的弱点与缺陷。 这份评测虽然方法论上相当仔细(1440 个配置上验证了正确性,覆盖多种路由偏斜情形,对 SM 分区和 segment 大小做了真正的消融实验),但局限在单一硬件世代(A100)和单一节点拓扑(4-GPU 节点内 NVLink)。考虑到论文的核心论证前提——GPU 算力已经跑到了互连带宽前面——本身就是一个关于硬件趋势的论断,而具体的数字性带宽饱和点(按 Figure 3a,8 个 SM 时 87 GB/s,约 1 MiB 传输大小)是这一具体 NVLink 世代实测出来的产物,那么调好的默认值(segment 大小、SM 分区)能否干净地迁移到更新的互连(NVLink 4/5,下一代 NVSHMEM)或 AMD/其他加速器生态上,是一个真实存在的开放问题——那里带宽曲线的形状和饱和点大概率是不同的。论文没有声称跨硬件世代的可移植性,但也没有明确警示这个局限,这是想要采用这项技术的读者应该记在心里的一点。
(b) 作者低估或省略的局限性。 正如上面第 8 节详述的,两个后果最严重的省略是:(i) 完全没有跨节点评测,而大规模生产级 MoE 部署几乎总是跨越多个节点,用的互连比节点内 NVLink 延迟明显更高、性质也不同;(ii) 与 rank 数量成线性关系的填充开销公式,只在论文测试的最小现实 rank 数量()上做了验证,而论文自己引言里提到的万亿参数生产级 MoE 系统(Switch Transformer、Llama-4 级别的模型)实际用到的 rank 数量(几十到几百)完全没有数据。这两个空白之所以更加重要,恰恰是因为论文自己的动机叙事就是关于把 MoE 扩展到越来越大的部署规模。
(c) 具体的改进建议。 第一,如果论文能有至少一次跨节点实验——哪怕只是一个 2 节点、8-GPU、跑在 InfiniBand 上的配置——就能让读者判断调好的 segment 大小和 SM 分区默认值,到底是 NVLink 特有的产物,还是真正更普遍的设计原则,论文的说服力会大大增强。第二,既然作者自己已经指出运行时自适应的 SM 分区选择器是重要的未来工作,一个自然而且相对低成本的下一步实验,是实现一个简单的启发式自适应控制器(比如,用已经就位的设备驻留就绪标志基础设施,根据一个运行中的 consumer 积压估计值来调整 cCTA),拿去和当前的静态分区设计做对比——这能直接量化 4.3.4 节里的资源争用病理,有多少是不需要等一整轮”未来工作”就能挽回的。第三,把行填充开销的测量至少扩展到 或 个 rank(哪怕是通过一个模拟/合成的路由分布实验,如果拿不到真实的多节点集群的话),能让读者直接检验 4.5 节”好处超过代价”这个结论在生产级现实的专家并行度下是否依然成立,而不是要求读者相信一个从 外推出来的线性结论。
10. 可复现性说明
论文的实现被描述为一个暴露给 PyTorch 2.6.0 的 CUDA 运行时,构建在 CUDA 12.1、CUTLASS 3.9、NVSHMEM 3.6.5 之上,GEMM 建立在 CUTLASS 的模板化内核上,用一个从 CUTLASS profiler 生成的查找表为每个目标 GEMM 形状选择最优配置,tile 级信号则遵循 EVT(Epilogue Visitor Tree)模式集成进 CUTLASS 的 epilogue。就本文阅读到的正文内容而言,论文没有说明代码是否会开源发布;想要复现结果的读者,需要上面列出的确切 CUTLASS/NVSHMEM/CUDA 版本组合(NVSHMEM 尤其如此,版本不匹配是设备发起通信库里一个已知的、会带来细微行为差异的来源)、从四个 baseline 系统各自的公开仓库构建出来的对应实现(FasterMoE、Tutel,以及分别用 CUTLASS GroupGEMM 和 Transformer Engine 后端的 Megatron-LM),以及一台配置与评测硬件拓扑相匹配的、节点内 NVLink 互连的多 A100 节点。论文里明确说明的正确性验证协议(在重叠路径和 baseline 路径上使用相同的种子/权重/路由,在给定的、适合 FP16 的容差范围内对比最终逐 token 输出)描述清晰,如果要对任何重新实现做一次基本的正确性检查,这个协议是很容易照搬复现的。
11. 在更广阔的 MoE 系统版图中的定位
这篇论文在更广阔的 MoE 通信重叠技术版图中,占据了一个具体而且范围界定清晰的位置,论文自己的相关工作部分(第 5 节)把这个版图分成三个流派:基于分解的重叠(把 GEMM/集合通信切块,在不同 stream 里流水线化——CoCoNet、Centauri、Domino,以及针对 MoE 的流水线方法如 FasterMoE、PipeMoE、ScheMoE、MPipeMoE)、基于融合的重叠(把计算和通信焊接进一个内核——FlashMoE、COMET、CCFuser),以及这篇论文自己基于信号、内核分离的方法——作者把自己定位为采纳了此前工作(引用了 T3 以及一篇关于”信号与重排序”的论文,大概率是他们参考文献里 COMET 相邻或类似的工作)的”tile 级信号”原则,但刻意让计算和通信内核在架构上保持分离而不是融合。相对于本博客此前覆盖过的 MoE 和分布式训练系统工作——Libra 对注意力工作负载偏斜的处理、Tangram 对 GPU 异构性的抽象、以及仍在本博客队列中待发布的 DisagMoE 的解聚计算-通信流水线——这篇论文具体位于 MoE 系统栈里内核与调度这一层,恰好在那些系统所做的解聚/放置决策下面的一层,和它们是互补而非竞争关系:像 DisagMoE 这样的系统决定专家计算和通信发生在哪里(哪些物理资源上),而这篇论文的技术原则上可以用来让无论最终选定共址在同一个 rank 上的 GEMM/通信步骤,在 tile 级别上重叠得更紧密。想了解这篇论文刻意放弃的融合式替代方案的读者,可以直接去看论文自己相关工作里引用的 COMET,以对上文第 6 节讨论的”重叠更紧密但工程更侵入”这个权衡有一个正面的直观感受。
11.5 与本博客已发布 MoE/分布式训练系列的对照
为了方便读者定位这篇论文在本博客已覆盖内容中的坐标,下面是一张简单的对照表(时间顺序,只列与 MoE/分布式通信直接相关的条目):
| 博客文章 | 关注层次 | 与本文的关系 |
|---|---|---|
| PipeDream(2026-04-16) | 流水线并行调度(层间) | 更粗粒度——调度的是整层/整个 stage,不是 tile |
| MegaScale(2026-06-11) | 万卡集群训练全栈优化 | 更宏观——本文的 tile 级重叠是 MegaScale 描述的众多子问题之一的一种可能实现 |
| ForeMoE(2026-06-13) | MoE 路由前瞻式负载均衡 | 互补——ForeMoE 解决的是“专家该放在哪、负载怎么分”的放置/均衡问题,本文解决的是“专家输出该怎么运回来”的重叠问题,两者可以叠加使用 |
| Tangram(2026-07-02) | GPU 异构性抽象 | 更高层——Tangram 隐藏的是硬件异构性,本文假设的是同构 A100 集群 |
| DisagMoE(排队待发布) | 解聚计算-通信流水线 | 最接近——两者都在处理 MoE 的计算/通信重叠,但 DisagMoE 是在系统/资源放置层面做解聚,本文是在单个 rank 内部的内核调度层面做重叠,层次不同、可以组合 |
| Libra(2026-07-30) | 长上下文训练里的注意力负载偏斜 | 主题相邻但机制不同——两者都关心“偏斜”带来的负载不均问题,Libra 针对的是序列长度偏斜,本文针对的是 MoE 路由偏斜(stress_skew) |
11.6 部署前自检清单
如果你正在评估是否要把这篇论文的技术引入自己的 MoE 训练/推理栈,下面是一份基于本文分析整理出的自检清单:
- 你的隐藏维度够大吗? 按第 7.2 节的发现,隐藏维度越小,可供隐藏的通信量相对固定的 tile 信号开销就越小——先确认自己模型的隐藏维度不是三个评测模型里最小的 M-Trans-xl(512)那个量级。
- 你的部署是单节点还是跨节点? 本文所有实验都在单节点 NVLink 上完成,跨节点(InfiniBand/RoCE)场景下的 segment 大小、SM 分区默认值都需要重新调优,不能直接照搬。
- 你的专家并行度(rank 数量)是多少? 行填充开销随 rank 数量线性增长,论文只在 W=4 验证过;如果你的部署是几十上百个 rank,先做一次小规模的填充开销估算。
- 你的路由分布是否已知会偏斜? 如果你的路由器已知会产生 stress_skew 级别的偏斜(某些领域自适应任务确实会),要格外注意 SM 分区调优,过窄的 consumer SM 预算在偏斜路由下会比不重叠还慢。
- 你是否需要训练(反向传播)支持? 本文明确只支持前向传播,如果你需要训练场景下的重叠加速,这篇论文本身不直接适用,需要自己扩展或寻找其他方案。
- 你能承受多少工程投入? 如果你的团队愿意承担更多侵入式工程复杂度以换取理论上更紧密的重叠,内核融合方案(FlashMoE/COMET/CCFuser)可能是更合适的起点;如果你更看重代码可维护性和跨硬件世代的可移植性,本文的分离内核+设备驻留信号方案更合适。
11.7 常见误读澄清
在讨论这篇论文时,以下几个误解很容易产生,值得提前澄清:
误读 1:“这是一个通用的 MoE 通信优化,对任何集合通信都有效。” 不对。这篇论文明确只针对第二次 all-to-all(从专家返回到原 rank 的那次),第一次 all-to-all(dispatch)以及反向传播都明确不在范围内。把这项技术当成“一个任意集合通信都能套用的通用方法”是对它范围的高估。
误读 2:“只要把 cCTA 调到 14,就能在任何集群上构到论文报告的加速比。” 不对。cCTA=14 是仅在 4 张 A100、两种问题形状上扰出来的经验值。不同的 SM 数量、不同的互连带宽、不同的 GEMM 长宽比,最优值都可能完全不同。照搬这个数字而不重新扫描,是一种常见但危险的捷径。
误读 3:“重叠比接近 100% 就意味着通信完全不影响延迟了。” 部分正确。重叠比接近 100% 确实意味着第二次 all-to-all 本身几乎完全被藏到了计算背后,但论文自己在 7.2 节就指出,端到端加速会因为非 ateMoE 部分(注意力、embedding 等)的固定开销而受限于阿姆达尔定律——重叠比高不代表端到端加速也同样高。
误读 4:“填充开销只是一个必须接受的小细节,不值得单独关注。” 不完全对。在本文评测的 4-rank 规模下确实如此,但因为填充开销公式对 rank 数量 是线性的,在专家并行度达到几十至几百的生产环境下,这个小细节可能变成一个真正需要单独评估的开销项,而不是一个可以忽略不计的常数。
11.8 术语快查表
| 术语 | 含义 |
|---|---|
| Rank | 这篇论文中与 GPU 一一对应的分布式计算单元 |
| Tile | GEMM 输出矩阵被分解成的矩形子块,一个 CTA 负责计算一个 tile |
| CTA | 协作线程数组(cooperative thread array),即 CUDA 的 thread block |
| Epilogue | GEMM 主循环结束后、将 tile 写回全局内存之前执行的一段代码,这篇论文在这里插入了 tile 就绪信号 |
| Persistent kernel | 用固定数量 CTA 启动、每个 CTA 在内核整个生命周期内都常驻在自己 SM 上的内核 |
| NVSHMEM | 一套支持设备发起的单边 put/get 通信的库,让 GPU 内核可以直接写对端 GPU 显存 |
| Row band | 高度为 tb_M、宽度覆盖完整输出列 N 的一条,是本文实际传输的基本单位 |
| Segment | 一次实际传输包含的 row band 数量(首尾固定为 1,中间可调) |
cCTA | 分配给 consumer 的 CTA(即 SM)数量,可调参数 |
mgb | 中间 segment 合并的 row band 数量,可调参数 |
| Overlap ratio | 第二次 all-to-all 中被成功藏到计算背后的比例 |
11.9 四种重叠方案的工程取舍对照
把这篇论文自己在相关工作里归纳的三大流派加上它自己的方案,整理成一个工程取舍的对照表,方便读者快速判断自己的场景适合哪一条路线:
| 方法类型 | 代表 | 重叠紧密度 | 工程侵入性 | 可移植性 |
|---|---|---|---|---|
| 基于分解(粗粒度分块流水线) | FasterMoE, PipeMoE, ScheMoE | 中 | 低 | 高 |
| 基于信号、内核分离(本文) | 本文 | 高(tile 级) | 中 | 中高 |
| 基于融合(单内核) | FlashMoE, COMET, CCFuser | 最高 | 高 | 低 |
这张表本身就是一个精简版的决策指南:如果你只需要快速集成且对重叠紧密度要求不高,选分解流派;如果你愿意投入一定工程成本换取真正的 tile 级重叠、但不想把自己锁死在一套完全自定义的融合内核里,选本文这条路线;如果你有专门的内核工程团队、只针对固定硬件目标追求极致性能,才值得走到融合这条路上。
11.10 一个手算例子:看看填充开销具体有多大
为了让第 4.3 节讨论的填充开销公式更具体,不妨代入一组数字自己算一遍。假设 个 rank,tile 高度 ,按论文的公式 ,每个 rank 最坏情况下的填充行数是 行。如果每个 rank 上每个专家处理的 token 数量大约是 (表 2 中的一个具体取值),那么 381 行占总行数的比例大约是 ——这个数字与论文 4.5 节自己说的“百分之几”大致对应上。现在把 换成 32(一个常见的专家并行度): 行。如果每 rank 每专家的 token 数量保持同样数量级(假设仍约为几千到一万多),3937 行就不再是一个微不足道的小数目,而可能接近或超过总行数的一定比例,尤其是在偏斜路由下、某些小专家的每 rank 有效 token 数量本身就不多时。这个简单的手算例子,直观地说明了为什么第 8 节把“填充开销随 rank 数量线性增长但只在 验证过”列为一个真实、不能忽略的局限性,而不仅仅是一个理论上的担忧。
11.11 再看一个手算例子:重叠比与专家数 E 的直观关系
第 7.3 节的重叠比(overlap ratio)随 增长而提高,这背后的直观可以用一个粗略的时间预算模型来感受。设一个 MoE 层的 GEMM 总计算时间为 ,第二次 all-to-all 的总通信时间为 。在串行(不重叠)执行下,总延迟大致是 ;在完全重叠(overlap ratio = 100%)下,总延迟变成 。因为 和 都跟每 rank 的专家数 大致成正比(更多专家意味着更多 token 要计算、也更多要返回),两者的绝对差距 往往也跟着线性放大,而 producer 优先处理远端 tile 的调度策略,又让消费者能尽早开始排空这些日益增长的传输量。这就是为什么论文自己解释“重叠的好处随着专家并行度增加而增长”时,背后的机制是记得住的:不是因为重叠机制本身变得“更厉害”了,而是因为可以被隐藏的绝对通信量本身变大了,而计算的相对充裕度也跟着变大。
11.12 为什么不能直接把这套设计搭在第一次 all-to-all 上
读到这里,一个自然的问题是:既然第一次 all-to-all(dispatch)同样是集合通信,为什么论文不把同一套 tile 信号+行重排机制直接搬过去用在它上面?答案在于两次 all-to-all 在数据依赖链条上的位置完全不对称。第二次 all-to-all 的输入是本 rank 自己刚刚算出来的 GEMM 输出——正因为这个输出是本地计算的产物,论文才能在 GEMM 运行之前就提前重排行序,让每个输出 tile 预先就具备单一目标 rank 这个性质。而第一次 all-to-all 的输入,是路由器在其他 rank 上算出的路由决策结果——本 rank 在收到这些 token 之前,根本无从提前知道它们会以什么顺序到达、来自哪些 rank,也就无从提前对它们做类似的重排。要对第一次 all-to-all 做同样的 tile 级重叠,需要解决的是一个方向相反的问题——让路由决策本身就产生对接收方友好的数据布局,这已经超出了这篇论文的范围,也是为什么作者在引言里将其明确列为未来工作。
这篇论文的贡献范围不大,但确实有用:一种在不借助内核融合的前提下,以单个 GEMM 输出 tile 为粒度,把分布式 MoE 层的第二次(返回)all-to-all 和专家计算重叠起来的方法,做法是解决那个实际上真正困难的问题——确保每一个完成的输出 tile 都有一个干净、无歧义的目标 rank——通过一次预先的行重排步骤(远端归属对齐的布局),而不是在热路径上塞进侵入式的逐 tile 路由逻辑。工程实现是克制的:tile 完成信号被折进已有的 GEMM epilogue,而不是改动主循环;通信内核是一个普通的、独立的持久化内核,用 NVSHMEM 单边写入;两个内核之间靠 SM 分区和 stream 优先级,而不是任何自定义同步协议,来避免互相干扰。测得的结果——最高 2.74 倍的 MoE 层加速,在一个宽泛的专家数量扫描范围内重叠比例接近完全隐藏第二次 all-to-all,以及在 1440 个配置上验证到 FP16 舍入噪声容差的正确性——支持了核心论断:tile 粒度、基于信号的重叠,是介于粗粒度分块流水线和完全内核融合之间一个实用的、非侵入式的替代方案。同样值得记住的是那些诚实的但书:这项技术的收益在隐藏维度较小的模型上会收窄(甚至反转),因为可供隐藏的通信本来就少,而逐 tile 信号的固定开销占比反而更大;这个设计对 SM 分区调优足够敏感,一个没调好的配置甚至可能比完全不做重叠还要糟,尤其是在路由偏斜的情况下;而且整套评测都活在单一节点的 NVLink 结构里,跨节点的场景——恰恰是这篇论文引言里用来当作动机的、最大规模生产级 MoE 部署最要紧的场景——完全没有被测试过。对任何正在构建或调优分布式 MoE 推理系统的人来说,这篇论文是一个清晰的、文档齐全的存在性证明,证明细粒度重叠在不做融合的前提下是可行的;它调好的那些具体参数,能否迁移到你自己集群的互连、rank 数量、专家数量区间,则是这篇评测留给你自己去验证的开放问题。