XTuner Domino EP
导言
XTuner 的 Domino EP 不是一种新的 EP 通信算子,而是位于 MoE 层内部的跨微批调度方法:把多个原本独立做梯度累积的 micro-batch 一起送入模型,用异步通信流把一个 micro-batch 的 token dispatch/combine 与另一个 micro-batch 的专家计算重叠。
普通 EP 定义专家如何切分;All-to-All、DeepEP、AGRS 决定 token 如何搬运;Domino EP 决定多个 micro-batch 如何交错;MC2 则把相邻通信和矩阵乘融合进一个算子。只有先分清这四层,才能正确讨论它们的优劣和组合关系。
结论¶
截至 XTuner commit 9989fb7,相关技术可以按下面四层理解:1
| 层次 | 技术 | 解决的问题 | 是否能与 Domino EP 组合 |
|---|---|---|---|
| 并行语义 | 普通 EP | 把不同 experts 放到不同 rank,路由 token 后再合并 | Domino EP 的基础 |
| 通信后端 | All-to-All / DeepEP / AGRS | 具体怎样 dispatch 和 combine token | 可以,三选一作为底层 dispatcher |
| 调度方法 | Domino EP | 怎样用多个 micro-batch 隐藏 EP 通信 | 本身不替代 dispatcher |
| 融合算子 | MC2 | 在一个 micro-batch 内融合通信与 GMM/MM | 思想上正交,但 XTuner 当前未接入该 dispatcher |
因此,最准确的比较不是“Domino EP 和 DeepEP 谁替代谁”,而是:
- All-to-All、DeepEP、AGRS 比通信实现。
- Domino EP 比跨微批调度。
- MC2 比单微批内的算子融合。
一句话判断
如果 profiler 中 EP 通信很长、专家 GEMM 足以覆盖通信,并且显存能容纳两个微批的在途状态,Domino EP 才更可能有效;否则异步事件、更多激活和更小 GEMM 的开销可能抵消收益。
执行路径¶
XTuner 的入口参数是 intra_layer_micro_batch。训练引擎不再逐个 micro-batch 调用模型,而是按该参数切成列表;相应的梯度累积次数也会除以这个数。2
for i in range(0, len(data_batches), intra_layer_micro_batch):
data_batch = data_batches[i : i + intra_layer_micro_batch]
seq_ctx_list = [item["seq_ctx"] for item in data_batch]
loss_ctx_list = [item["loss_ctx"] for item in data_batch]
if self.intra_layer_micro_batch == 1:
output = self.model(seq_ctx=seq_ctx_list[0], loss_ctx=loss_ctx_list[0])
else:
output = self.model(seq_ctx=seq_ctx_list, loss_ctx=loss_ctx_list)
进入每个 MoE decoder layer 后,_micro_batch_forward 分成三个阶段:3
- 批量准备:各微批依次完成 attention、router 和异步
dispatch_preprocess。 - 分发与专家计算:为各微批发起 dispatch,只在消费结果前建立必要的事件依赖,然后执行 experts 和
combine_preprocess。 - 批量回收:发起各微批的 combine,最后逐个
combine_postprocess,再做残差和 shared experts 合并。
下面是从实际实现压缩出的关键控制流,省略了上下文保存代码:
for hidden_states, seq_ctx, position_embeddings in micro_batches:
residual, hidden_states, router_results = self._pre_moe_forward(...)
pre_dispatched = self.dispatcher.dispatch_preprocess(..., async_op=True)
for router_results, pre_dispatched in zip(router_results_list, pre_dispatched_list):
dispatched = self.dispatcher.dispatch(..., async_op=True)
post_dispatched = self.dispatcher.dispatch_postprocess(..., async_op=True)
experts_out = self.experts(post_dispatched["hidden_states"], ...)
pre_combined = self.dispatcher.combine_preprocess(..., async_op=True)
for item in pre_combined_list:
combined_list.append(self.dispatcher.combine(..., async_op=True))
关键不在 Python 循环本身,而在 dispatcher 维护的通信 stream、计算 stream 和 CUDA event。CPU 可以继续提交后续微批的工作;GPU 则在真正使用数据的位置等待事件,从而形成“微批 A 算 experts,微批 B 做 dispatch/combine”的流水。
这也解释了它为什么叫 Domino:一个微批的通信被下一个微批的计算接力遮住。但它仍然不是完整的 pipeline parallel 调度,也不是原论文实现的直接复制。
通信后端¶
XTuner 的 build_dispatcher 在 ep_size > 1 时支持 deepep、all2all 和 agrs;未指定时默认选择 all2all。4 Domino EP 对这三种后端调用同一组 preprocess/dispatch/combine 接口。
All-to-All¶
普通 All-to-All 路径先按 expert 排列 token,交换各 rank 的 token 计数,计算可变长 input_splits 和 output_splits,再调用 autograd 版本的 all_to_all_single。5
优点是语义直接、依赖少、最适合作为正确性基线;缺点是负载不均时通信尺寸不规则,而且 token count、重排和两次 All-to-All 的成本更容易暴露在关键路径上。
这里的“普通 EP”通常就是“EP 语义 + 普通 All-to-All dispatcher + intra_layer_micro_batch=1”,而不是另一种与 All-to-All 无关的算法。
DeepEP¶
DeepEP 是为 MoE dispatch/combine 定制的通信库。XTuner 使用 low-latency buffer,先计算 dispatch layout,再以 async_finish=True 发起 dispatch,并通过 event handle 表达依赖;实现还把 DeepEP 通信占用的 SM 数设为 20。6
它的优势是对 NVLink/RDMA 拓扑、跨节点转发和低延迟 kernel 做了专门优化;代价是依赖自定义 CUDA 通信栈,对版本、拓扑和 buffer 配置更敏感。需要特别注意:XTuner 的构建脚本固定的是 DeepEP v1.2.1 commit 9af0e0d,不能把 DeepEP 当前主分支或 V2 的公开 benchmark 直接当成 XTuner 实测结果。7
DeepEP 负责让一次通信更快,Domino EP 负责尽量把这次通信藏起来。 二者通常是互补关系。
AGRS¶
AGRS 在 XTuner 中可按 AllGather + ReduceScatter 理解:dispatch 时 AllGather hidden states,同时用小规模 All-to-All 交换 top-k IDs/weights;本地 experts 计算后,combine 用 ReduceScatter(sum) 把结果送回 token 所属 rank。8
它用更规则的 collective 替换可变长 token All-to-All,在某些拓扑和 collective 实现上更容易获得稳定吞吐。不过当前实现有严格前提:必须使用 grouped router,并要求 ep_size == router_n_groups == 8。9
此外,AGRS 会 AllGather activation,通信量和在途 buffer 不一定更小;ReduceScatter 的求和顺序也与普通路径不同。XTuner 的对照测试使用 atol=rtol=1e-2,对应 PR 也明确提示了浮点归约顺序造成的小误差。10
与 MC2 对比¶
此前 MindSpeed-MM 接入的 EP MC2 面向 Ascend NPU,把 AllToAllv -> GroupedMatmul 和 GroupedMatmul -> AllToAllv 分别融合为 NPU fused operator。11 它让算子内部把大通信和大计算切块、流水,减少中间张量、launch 和单微批内的串行等待。
| 维度 | Domino EP | EP MC2 |
|---|---|---|
| 重叠粒度 | 跨多个 micro-batch | 一个 micro-batch 的 fused operator 内部 |
| 核心机制 | 异步 stream/event 调度 | 通信与 GMM/MM 融合、切块流水 |
| 最低并发来源 | 至少两个可同时在途的微批 | 一个微批也可产生算子内流水 |
| 通信后端 | 可搭配 All-to-All、DeepEP、AGRS | 绑定支持 MC2 的 NPU/HCCL 算子 |
| 可移植性 | 调度思想较通用,但实现仍依赖 dispatcher | 强硬件、CANN 与算子版本约束 |
| 主要代价 | 更高激活峰值、事件调度与小 GEMM | 融合算子约束、调试困难、潜在精度差异 |
两者在理论上可以叠加:用 MC2 优化每个微批的 dispatch/GMM,再用 Domino 在微批之间继续遮蔽剩余气泡。但 XTuner 当前没有 MC2 dispatcher,而且两种机制都会争用通信带宽、计算单元和 stream;真正组合需要新接入与 profiler 证明,不能默认收益相加。MindSpeed-LLM 的 MC2 文档也明确提示部分模型可能出现精度问题,并列出了模型与硬件限制。12
论文证据¶
原始 Domino 论文处理的是 tensor parallel:沿 batch 维切输入、沿输出维切权重,让一个切片的 AllReduce 与另一个切片的矩阵乘重叠。论文报告在 DGX-H100 集群上相对 Megatron-LM 最高约 1.3× 加速。13 这为“切分独立工作并用异步通信跨切片重叠”提供了机制证据,但通信对象是 TP AllReduce,不是 MoE EP 的 token dispatch/combine。
与 XTuner 实现更接近的论文先例来自 DeepSeek-V3:其预填充阶段同时处理两个 micro-batch,使一个微批的 attention/MoE 计算与另一个微批的 dispatch/combine 重叠。14 不过 DeepSeek-V3 的 DualPipe 还包含 pipeline parallel 调度,不能与 XTuner 的单个 MoE 层实现画等号。
截至上述 XTuner commit,公开代码和 PR 能证明实现、测试与持续修复过程,但没有提供可复用的 Domino EP 专项性能表。PR #1078 是功能接入,后续 PR #1824 修复了 H2D 同步延迟第二个微批 DeepEP dispatch 的问题;这恰好说明收益依赖细致的异步路径,而不能只看开关名称。15
选择方法¶
建议把“后端”和“调度”拆开做 A/B,而不是一次同时更改:
- 建立基线:
dispatcher="all2all"、intra_layer_micro_batch=1,确认 loss、吞吐、峰值显存和通信占比。 - 只换后端:分别测 DeepEP 或满足约束时的 AGRS,保持 global batch、micro-batch shape、EP size 和精度配置不变。
- 再开 Domino:先把
intra_layer_micro_batch改为 2,确认数据批次数可整除,并保留相同 global batch 语义。 - 看时间线而非开关:确认 dispatch/combine 确实与另一微批的 GMM 重叠,而不是被 H2D copy、host sync 或 event wait 串行化。
- 同时验精度:比较前向、梯度、loss 曲线和长程收敛;AGRS、低精度 DeepEP 与 MC2 都不能只用 step time 验收。
一个最小配置思路如下,具体对象名应以所用 XTuner recipe 为准:
常见失败条件
- 显存上涨:多个微批的 residual、router 结果、dispatch handle 和激活同时在途。
- 形状限制:当前
_micro_batch_forward要求同组 hidden states 形状一致。 - 覆盖不足:通信太长而计算太短时仍会露出尾部;通信本来很短时调度成本反而突出。
- 计算退化:为了塞进两个微批而缩小单微批,Grouped GEMM 的效率可能下降。
- 版本错配:DeepEP、CUDA/NCCL、GroupedGEMM 或 NPU CANN/MC2 的版本都会改变结果。
总结¶
XTuner Domino EP 的本质是:把原本串行的梯度累积微批提升为 MoE 层内部可共同调度的工作集合,用通信 stream 和 event 建立跨微批流水。 它不改变 EP 的专家切分,也不规定 token 必须通过哪一种 collective 搬运。
- 追求通用基线与易调试,优先 All-to-All。
- NVIDIA 多机 MoE 且拓扑合适,优先评估 DeepEP + Domino EP。
- 满足
EP=router groups=8且 AG/RS collective 更占优,可评估 AGRS + Domino EP。 - Ascend 路径若已有成熟 fused operator,MC2 更像单微批内的深度融合;它与 Domino 的比较是算子融合对跨微批调度,不是同一层替代关系。
最终选择应由同一模型、同一 batch 语义下的 profiler、显存和收敛结果决定,而不是由技术名称决定。
-
XTuner commit
9989fb74130d8f848662c5021458480ba161b9fa。本文源码结论均固定到该版本,避免主分支变化造成歧义。 ↩ -
XTuner
deepep_op.py:DeepEP SM 配置;low-latency buffer 与异步 dispatch。 ↩ -
XTuner
agrs.py:AllGather、All-to-All IDs/weights 与 ReduceScatter。 ↩ -
Domino: Eliminating Communication in LLM Training via Generic Tensor Slicing and Overlapping。 ↩
-
XTuner PR #1078:Domino EP;XTuner PR #1824:DeepEP dispatch overlap 修复。 ↩

