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 则把相邻通信和矩阵乘融合进一个算子。只有先分清这些层次,才能正确讨论它们的优劣和组合关系。
HybridEP 论文进一步提出了一个更上游的问题:跨数据中心带宽太低、通信已经无法完全隐藏时,是否还应坚持“专家不动、所有 token 都跨域搬运”?它通过比例 \(p\) 在 token All-to-All 与压缩 expert AllGather 之间选择,改变的是通信对象与专家放置,而不是给 DeepEP 或 MC2 换一个名字。
2026 年 8 月的增量观察再补两层:MoonEP 与 UltraEP 都根据当前 micro-batch 的真实路由结果复制热点 expert,但 MoonEP 自己拥有 dispatch/combine 并追求每 rank 固定 S×K 行,UltraEP 则把配额规划、跨层副本 buffer 和权重/梯度同步做成独立运行时,继续复用外部 dispatcher 与 Grouped GEMM。HyperParallel-MoE 则在 Ascend A3 上把 Dispatch、两次 GMM、SwiGLU 和 Combine 编译成 AIC/AIV 瓦片任务流。这些路线分别回答“热点怎么摊平”和“单个 MoE-FFN 怎么细粒度交错”,都不是 XTuner 当前已有开关。
结论¶
截至 XTuner commit 9989fb7,结合 HybridEP 论文后,相关技术可以按下面五层理解:1
加入 MoonEP、UltraEP 与 HyperParallel-MoE 后,还应额外区分“冗余 expert 负载均衡运行时”和“完整 MoE-FFN 瓦片调度”两层;下面保留原有五层,并在表尾做增量扩展。
| 层次 | 技术 | 解决的问题 | 是否能与 Domino EP 组合 |
|---|---|---|---|
| 并行语义 | 普通 EP | 把不同 experts 放到不同 rank,路由 token 后再合并 | Domino EP 的基础 |
| 通信对象与放置 | HybridEP 论文 | 跨域时在搬 token 与搬压缩 expert 之间选择 | 思想上可组合,但 XTuner 当前没有该实现 |
| 通信后端 | All-to-All / DeepEP / AGRS | 具体怎样 dispatch 和 combine token | 可以,三选一作为底层 dispatcher |
| 调度方法 | Domino EP | 怎样用多个 micro-batch 隐藏 EP 通信 | 本身不替代 dispatcher |
| 融合算子 | MC2 | 在一个 micro-batch 内融合通信与 GMM/MM | 思想上正交,但 XTuner 当前未接入该 dispatcher |
| 负载均衡与冗余 expert | MoonEP | 怎样复制热点 expert、预取权重,并把每个 rank 的 routed token 行数固定 | 概念上可置于 Domino 下方,但 XTuner 当前没有该后端 |
| 负载均衡与冗余 expert | UltraEP | 怎样按本批 exact load 联合求副本与 quota,并复用外部 dispatcher/GMM | 概念上可置于 Domino 下方,但 XTuner 当前没有该运行时 |
| 完整 MoE 瓦片调度 | HyperParallel-MoE | 怎样把 Dispatch、GMM、SwiGLU、GMM、Combine 编译成 AIC/AIV 任务流 | 与 Domino 粒度不同,但硬件与接口都需重新接入 |
因此,最准确的比较不是“Domino EP 和 DeepEP 谁替代谁”,而是:
- All-to-All、DeepEP、AGRS 比通信实现。
- Domino EP 比跨微批调度。
- MC2 比单微批内的算子融合。
- HybridEP 比跨域场景下的数据/专家混合传输与放置。
- MoonEP 比路由偏斜下的冗余 expert 规划与固定计算量。
- UltraEP 比 exact-load 驱动的 quota 复制、跨层 buffer 与副本状态同步。
- HyperParallel-MoE 比单微批完整 MoE-FFN 的瓦片级异构调度。
一句话判断
如果 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 负责尽量把这次通信藏起来。 二者通常是互补关系。
这里还要纠正一个容易由 API 名称引起的误读:XTuner 确实以 low_latency_mode=True 创建 DeepEP buffer,但固定版本的调用路径是 get_dispatch_layout -> Buffer.dispatch -> 外部 expert GMM -> Buffer.combine,并没有直接调用公开 API low_latency_dispatch。8 因而本文讨论的是 XTuner 固定适配器的真实算子边界,而不是 DeepEP 主分支所有模式的统称。
为什么不是 MC2¶
DeepEP 并没有把通信语义变成另一种集体通信:它完成的仍是 MoE 的 dispatch All-to-All 与 combine All-to-All。区别在于,通用 All-to-All 把“已经按 expert 展开的 token 行”当作待发送对象;DeepEP 先理解 topk_idx,再把发送对象压缩为“这个 token 是否需要到达某个目标 rank”。
设 token \(i\) 选择的远端 expert 集合为 \(\mathcal{E}_i\),expert \(e\) 所属 rank 为 \(r(e)\)。普通 XTuner All-to-All 路径需要发送的 hidden 行数近似为:
DeepEP 的 layout kernel 明确维护 is_token_in_rank [T,R]:同一个 token 即使选择了同一远端 rank 上的多个 experts,也只把 hidden state 发到该 rank 一次,到达后再按 expert 本地展开。9 例如一个 token 的 Top-8 分别命中 4 个远端 rank、每个 rank 2 个 experts,普通路径发送 8 行 hidden,DeepEP 只发送 4 行;这里只能说 hidden payload 约减半,不能据此宣称端到端必然 2× 加速。若每个 expert 都位于不同 rank,则这一项没有字节收益。
第二层收益来自专用通信内核。跨节点 kernel 把 RDMA sender、RDMA/NVLink forwarder 和 NVLink receiver 分配给不同 warp,让数据按 chunk 在节点内外转发,减少通用 collective 周围的 pack、动态 split、同步和 kernel launch 开销。10 这是一种通信子步骤融合,但 expert GMM、SwiGLU 和 down projection 仍在框架侧执行。因此它与 MC2 的边界不同:MC2 把 expert 权重交给 fused operator,在算子内部安排通信—GMM 流水;XTuner 的 DeepEP 路径只交付 recv_x,随后才调用外部 Grouped GEMM。
三个独立加速层次
- 减少重复激活:Top-k 多个 experts 落在同一目标 rank 时,hidden state 按 rank 去重发送。
- 缩短一次通信:用 NVLink/RDMA 感知的 dispatch/combine kernel 代替通用变长 All-to-All 的控制路径。
- 隐藏剩余通信:
async_finish=True、event 和限定的通信 SM 让 Domino 有机会用另一微批的 expert 计算覆盖通信。
前两项属于 DeepEP,第三项是 DeepEP 为 Domino 提供的异步边界;三者不能混成一个“融合算子加速比”。
完整运行路径¶
| 对象 | DeepEP 产生或消费 | 后续所有者 | 生命周期 |
|---|---|---|---|
x [T,H]、topk_idx [T,K] |
计算 layout 并发起 dispatch | DeepEP buffer | dispatch 前后 |
recv_x [T_recv,H] |
dispatch 输出的已路由 token | 框架侧 expert GMM | combine 前 |
handle、counts、event |
路由元数据与异步完成信号 | combine、反向和计算 stream | 一次 MoE 通信周期 |
expert_out [T_recv,H] |
combine 的输入 | 由框架侧 GMM 产生 | combine 前 |
combined_x [T,H] |
combine 后返回原 token 所属 rank | 下一层与残差路径 | 当前层结束 |
把源码归一化为不省略关键对象的伪代码,前向路径如下;反向则按相反顺序消费保存的 handle、counts 与路由排列:
layout = get_dispatch_layout(topk_idx, num_experts, ep_group)
recv_x, recv_topk_idx, recv_topk_weight, counts, handle, dispatch_event = \
deep_ep_buffer.dispatch(
x=x,
topk_idx=topk_idx,
topk_weight=topk_weight,
layout=layout,
async_finish=True,
)
dispatch_event.wait(compute_stream)
permuted_x, tokens_per_expert, inverse_map = permute(
recv_x, recv_topk_idx, counts
)
gate = grouped_gemm(permuted_x, expert_w1)
expert_hidden = silu(gate[:, 0::2]) * gate[:, 1::2]
expert_out = grouped_gemm(expert_hidden, expert_w2)
recv_out = unpermute(expert_out, inverse_map, recv_topk_weight)
combined_x, combine_event = deep_ep_buffer.combine(
recv_out, handle=handle, async_finish=True
)
save_for_backward(handle, counts, inverse_map, dispatch_event, combine_event)
效果与边界¶
从效果与资源占用看,DeepEP 优化的是 token 通信时间和可重叠性,并不自动减少 expert 激活。峰值显存仍要计入持久通信 buffer、recv_x、路由 handle,以及 Domino 同时保留的多个微批状态;async_finish=True 只表示结果可以通过 event 延后等待,不表示这些对象已经不存在。
DeepEP 官方固定版本的 standalone benchmark 表明其 dispatch/combine 能接近测试平台的 NVLink 或 RDMA 链路上限,但它没有给出与 XTuner 普通 All-to-All 在同一模型、同一 shape、同一调度下的 A/B,因此不能从该表推导“比 All-to-All 快多少”。真正需要测量的是:rank 去重后的实际行数、dispatch/combine 裸时延、通信 SM 占用、Domino 覆盖比例、峰值显存和端到端 step time。
MoonEP¶
MoonEP 把 DeepEP 路径仍可能暴露的另一个瓶颈拉到台前:即使 token 搬得足够快,只要 router 把大量 token 压到少数 expert,最忙 rank 的 Grouped GEMM 仍决定整层尾延迟。MoonEP 最新公开仓库 commit 0f385f0 的做法不是丢 token 或修改 top-k 语义,而是在线规划热点 expert 的冗余副本,把副本权重预取到空槽,再把 routed token 直接写入最终 virtual-expert 分组位置。23
设当前 rank 的输入 token 数为 \(S\)、每个 token 选择 \(K\) 个 experts。MoonEP 的目标是让每个 rank 承担固定的有效 routed 行数:
通信 shard 还会为 virtual-expert 分组加入 padding,因此物理容量是 NvS = S*K + padding。在线 planner 根据 tokens_per_expert 选择复制哪些热点 expert、每个 token 的目的 rank 与目的槽位;权重预取则把 home expert 的行搬入对端预留区。这样,动态的是副本放置和 token→副本映射,进入 GMM 的总行数与 shape 则保持静态。
MoonEP 不是只有一次 dispatch;训练闭环至少包含下列对象:
| 对象 | 形状或内容 | 生产者 | 消费者与生命周期 |
|---|---|---|---|
hidden_shard |
[S,H] BF16 |
框架与 router | dispatch;一次 MoE 调用 |
topk_idx / tpe |
[S,K] / [E] |
router 与直方图 | planner、dispatch、combine;plan 需跨反向保存 |
plan.dst / experts_to_copy |
token 目的位置、[R,B] 副本表 |
GPU planner | dispatch、prefetch、combine 与反向 |
| NVL token shard | [NvS,H] BF16 |
dispatch 直接远端写 | virtual-expert GMM;zero-copy view 会被下一次 dispatch/combine 覆盖 |
| expert weights | [E+B,H,H′] BF16 对称内存 |
框架与 prefetch | [0,E) 是 home 行,[E,E+B) 是副本槽 |
cu_seqlens |
[E+B] 分组边界 |
plan/dispatch epilogue | virtual-expert GMM |
| grad / reduce pool | [E+B,H,H′] 与 [R,B,H,H′] FP32 |
backward GMM 与远端读 | 把副本梯度累加回 home expert;进程级 pool 可跨层复用 |
下面是归一化伪代码。它把“先削平热点、再固定目的 shard、最后归并副本梯度”三件中心工作展开;变量名对齐公开 API,但不是逐行翻译 Triton/CUDA 内核:
def build_balanced_plan(topk_idx, tokens_per_expert, rank_count, rows_per_rank):
assignments = initial_home_assignments(topk_idx, rank_count)
rank_load = count_rows_per_rank(assignments, rank_count)
experts_to_copy = empty_copy_table(rank_count)
while max(rank_load) > rows_per_rank:
source_rank = argmax(rank_load)
destination_rank = argmin(rank_load)
expert_id = largest_movable_expert_on_rank(
assignments,
source_rank,
tokens_per_expert,
)
movable_rows = min(
count_expert_rows(assignments, expert_id, source_rank),
rows_per_rank - rank_load[destination_rank],
)
copy_slot = reserve_redundant_expert_slot(experts_to_copy, destination_rank)
move_token_slice(
assignments=assignments,
expert_id=expert_id,
source_rank=source_rank,
destination_rank=destination_rank,
row_count=movable_rows,
copy_slot=copy_slot,
)
rank_load[source_rank] -= movable_rows
rank_load[destination_rank] += movable_rows
assignments = add_static_padding(assignments, rows_per_rank)
assert all(load == rows_per_rank for load in rank_load)
return materialize_plan(assignments, experts_to_copy)
def moonep_forward(hidden_shard, topk_idx, topk_weight, expert_weights, config):
tokens_per_expert = histogram(topk_idx, minlength=config.num_experts)
plan = build_balanced_plan(
topk_idx=topk_idx,
tokens_per_expert=tokens_per_expert,
rank_count=config.ep_size,
rows_per_rank=config.tokens_per_rank * config.topk,
)
routed_view, cu_seqlens = moonep_dispatch(
hidden_shard=hidden_shard,
topk_idx=topk_idx,
plan=plan,
zero_copy=True,
)
moonep_prefetch_weight(expert_weights, plan.experts_to_copy)
expert_output = virtual_expert_grouped_gemm(
routed_view,
expert_weights,
cu_seqlens,
)
output_shard = moonep_combine(expert_output, topk_weight, plan)
saved_state = save_for_backward(plan, cu_seqlens, topk_idx, topk_weight)
return output_shard, saved_state
def moonep_backward(output_grad, saved_state, expert_weights, grad_pool):
routed_grad, cu_seqlens = moonep_dispatch(
hidden_shard=output_grad,
topk_idx=saved_state.topk_idx,
plan=saved_state.plan,
zero_copy=True,
)
input_grad_routed, expert_grad = virtual_expert_grouped_gemm_backward(
routed_grad,
expert_weights,
cu_seqlens,
grad_pool,
)
input_grad = moonep_combine(
input_grad_routed,
saved_state.topk_weight,
saved_state.plan,
)
moonep_reduce_duplicate_gradients(
expert_grad,
saved_state.plan.experts_to_copy,
grad_pool,
)
return input_grad, expert_grad.home_rows
训练时公开说明要求 B=E/R 个预取槽;推理可降低 B,仓库建议从 3–4 起测,超出预取容量的 expert 会回读 home 权重。zero-copy 也有严格生命周期:返回 tensor 是通信 buffer 的 alias,下一次 dispatch/combine 会覆盖它,不能把该 view 跨调用长期保存或交给 autograd 任意留存。24
新增显存不能只数 token shard。按单个线性权重矩阵的量级,可把增量上界拆成:
其中第一项是 BF16 副本槽,两个系数 4 的项是 FP32 grad/reduce buffer;实际实现用进程级 pool 跨层共享,不能把它机械地乘层数,也不能忽略其峰值存在。
因此 MoonEP 与 Domino 的关系是:MoonEP 尝试缩短并固定每个微批的通信/专家尾部,Domino 尝试拿另一个微批的计算遮住剩余尾部。 两者概念上能分层,但 XTuner 当前 dispatcher 不接受 MoonEP 的 symmetric-memory 权重布局、plan 生命周期和梯度 pool;同时启用后还会共同争用 NVLink、SM 与显存,收益不能直接相加。25
UltraEP¶
UltraEP 与 MoonEP 处理的是同一个一阶瓶颈:router 已经决定 top-k 后,最热 expert 所在 rank 的 token 行数远高于平均值,整层只能等待最慢 rank。它的核心差异不是换一种 token collective,而是在 router 与外部 dispatcher 之间插入一个独立的 exact-load balancing runtime:当前 layer、当前 micro-batch 的 gate 结果一产生,就联合决定热点 expert 的副本位置和每个物理实例应承担的 quota,再把 logical routing 改写为 physical routing。26
可以把 UltraEP 分成三层,避免把论文思想、通用机制和仓库补丁混为一谈:
- 概念:不再用历史热度预测下一批,而是用本批 post-gate load 直接压低最忙 rank 的执行上限。
- 可复用机制:固定 master experts,只在每 rank 的少量冗余槽中物化热点副本;quota 同时约束副本放置与 token 分流;前向复制权重,反向把副本梯度归并回 master。
- 框架补丁:本文固定到 UltraEP
v1.0.0、commit94cab09。Manager注册框架持有的 master weight/grad 指针并暴露跨层复用 buffer;external dispatcher 仍搬 token,external Grouped GEMM 仍完成 expert 计算。它不是 XTunerdispatcher="ultraep"这样的现成配置。27
exact load 不等于无条件完美均衡
Exact 描述的是输入给 planner 的 load 来自当前 routing,而不是历史估计;near-optimal 描述的是在 N_slot、同一 logical expert 不得在同 rank 重复、最小有效 quota、NVLink domain 和关键路径时限等约束下,寻找尽可能低的最大 rank load。槽数不足或 NVLink domain 小于 EP group 时,最终 imbalance 可以高于 1.0。
配额规划¶
设 \(\lambda_{r,e}\) 是源 rank \(r\) 在当前 micro-batch 中路由给 logical expert \(e\) 的 token 数,\(\lambda_e=\sum_r\lambda_{r,e}\)。master expert 的 home rank 为 \(h(e)\),复制前 rank \(r\) 的 expert 计算量是:
UltraEP 不先拍脑袋选副本再 round-robin 分 token,而是搜索一个最小可行上限 \(\tau\)。对候选 \(\tau\),每个 rank 的超额和余量分别为:
feasibility oracle 从最过载 rank 的最热 expert 开始,把最多 \(\delta=\min(\operatorname{exc},\operatorname{slk},\text{remaining expert load})\) 的 quota 转给仍有冗余槽且尚未承载该 expert 的目标 rank;若 \(\delta<u_{\min}\) 就不创建这个低收益副本。可行则继续向更低 \(\tau\) 搜索,不可行则提高下界。最终 quota \(u_{e,t}\) 和 source split \(q_{r,e,t}\) 满足:28
前一式保证每个源 rank 的原始路由数量不变,后一式保证物理实例真正收到 solver 为它预留的 quota。locality-aware decomposition 会先让 source rank 消耗本地实例的 quota,再按剩余容量比例分配远端需求;它只改变“哪个来源使用 quota”,不改变已求得的实例负载上限。
用归一化的小例子看,四个 rank 初始负载为 [40,160,32,24],平均值是 64。若每个空闲 rank 都有一个可用槽,planner 可以把 R1 热点 expert 的 95 个负载单位分成三个有效副本 quota,使结果接近 [65,64,64,63]。总路由量仍是 256,master 仍在 R1;改变的是一对多的 logical-to-physical 映射。 论文默认的最小有效 quota 是 1024 tokens,因此这个四 rank 数字只用于解释上限搜索,不是实际配置。28
下面的归一化伪代码完整展开 threshold、slot、quota、locality 和 deterministic rounding;它映射论文 Algorithm 1 与 placement.cu,但省去了 warp/shared-memory 的性能实现细节:
import math
def solve_exact_load_plan(load, home_rank, slots_per_rank, beta, min_quota):
# load[r][e] = current-microbatch token count from source rank r to expert e
rank_count = len(load)
expert_count = len(load[0])
expert_load = [sum(load[r][e] for r in range(rank_count))
for e in range(expert_count)]
initial_rank_load = [
sum(expert_load[e] for e in range(expert_count)
if home_rank[e] == r)
for r in range(rank_count)
]
total_load = sum(initial_rank_load)
low = max(math.ceil(beta * total_load / rank_count), 0)
high = max(initial_rank_load)
best_quota = None
best_slots = None
while low < high:
threshold = (low + high) // 2
excess = [max(value - threshold, 0) for value in initial_rank_load]
slack = [max(threshold - value, 0) for value in initial_rank_load]
quota = [[0 for _ in range(rank_count)] for _ in range(expert_count)]
occupied = [[False for _ in range(rank_count)] for _ in range(expert_count)]
slots_used = [0 for _ in range(rank_count)]
for expert_id in range(expert_count):
source = home_rank[expert_id]
quota[expert_id][source] = expert_load[expert_id]
occupied[expert_id][source] = True
feasible = True
source_order = sorted(range(rank_count), key=lambda r: (-excess[r], r))
for source in source_order:
experts = [e for e in range(expert_count) if home_rank[e] == source]
experts.sort(key=lambda e: (-expert_load[e], e))
for expert_id in experts:
transferable = quota[expert_id][source]
while excess[source] > 0 and transferable > 0:
targets = [
target for target in range(rank_count)
if slack[target] > 0
and slots_used[target] < slots_per_rank
and not occupied[expert_id][target]
]
if not targets:
break
target = max(targets, key=lambda r: (slack[r], -r))
moved = min(excess[source], slack[target], transferable)
if moved < min_quota:
break
quota[expert_id][source] -= moved
quota[expert_id][target] += moved
occupied[expert_id][target] = True
slots_used[target] += 1
excess[source] -= moved
slack[target] -= moved
transferable -= moved
if excess[source] > 0:
feasible = False
break
if feasible:
best_quota = quota
best_slots = occupied
high = threshold
else:
low = threshold + 1
if best_quota is None:
best_quota = [[0 for _ in range(rank_count)] for _ in range(expert_count)]
best_slots = [[False for _ in range(rank_count)] for _ in range(expert_count)]
for expert_id in range(expert_count):
source = home_rank[expert_id]
best_quota[expert_id][source] = expert_load[expert_id]
best_slots[expert_id][source] = True
split = [[[0 for _ in range(rank_count)] for _ in range(expert_count)]
for _ in range(rank_count)]
for expert_id in range(expert_count):
residual_demand = [load[source][expert_id]
for source in range(rank_count)]
residual_quota = [best_quota[expert_id][target]
for target in range(rank_count)]
for target in range(rank_count):
local_count = min(residual_demand[target], residual_quota[target])
split[target][expert_id][target] += local_count
residual_demand[target] -= local_count
residual_quota[target] -= local_count
for source in range(rank_count):
demand = residual_demand[source]
capacity = sum(residual_quota)
if demand == 0:
continue
assert capacity >= demand
raw = [demand * residual_quota[target] for target in range(rank_count)]
allocated = [value // capacity for value in raw]
remaining = demand - sum(allocated)
remainder_order = sorted(
range(rank_count),
key=lambda target: (-(raw[target] % capacity), target),
)
for target in remainder_order[:remaining]:
allocated[target] += 1
for target in range(rank_count):
split[source][expert_id][target] += allocated[target]
residual_quota[target] -= allocated[target]
return low, best_slots, best_quota, split
运行闭环¶
UltraEP 的关键所有权边界是:它计算 physical expert map,并负责副本 weight/grad 的物化与回收;它不接管 token dispatcher 或 Grouped GEMM。 公开接口先为一个 EP group 建一个 Manager,每层注册 master 指针;每个 rank 只预留相同数量的冗余槽。max_microbatches 把 (real_layer_id, microbatch_slot) 映射为 virtual layer ID,避免 PP/VPP 或 Domino 一类多在途微批覆盖彼此的 plan。27
| 对象 | Shape / 内容 | 生产者 | 消费者与生命周期 |
|---|---|---|---|
routing_map / probs |
[T,L] bool / float |
router | update_placement、reroute;当前 MoE 前向到对应反向 |
| global exact load | [R,L] int32 |
notify/all-gather + histogram | quota solver;只在规划窗口使用 |
physical_to_logical / logical_to_physical |
physical slot 与 logical expert 的一对多映射 | placement kernel | weight sync、reroute、GMM;按 virtual layer slot 保存 |
| quota 与 prefix | [L,max_replicas] int32 |
placement 与 locality decomposition | token-to-instance lookup;按 virtual layer slot 保存 |
| master pointer pools | 每层本地 expert 的 weight/grad 指针 | framework 注册 | weight sync / grad reduce;参数本体归 framework |
| replica weight buffer | [N_slot,F_1/F_2],BF16 或配置精度 |
初始化分配、逐层 weight sync | 外部 GMM;buffer 常驻、内容逐层重写 |
| replica grad buffer | [N_slot,F_1/F_2],当前实现 FP32 |
backward GMM | grad_reduce;归并并清理后跨层复用 |
| virtual layer ID | (layer,mb_slot) 的环形槽 |
allocate_microbatch_slot |
前向 plan、反向重物化与 join;对应反向结束后复用 |
| expanded physical routes | [T,P] float / bool |
reroute |
外部 dispatcher;autograd 反向聚回 logical expert |
| token / expert output | [T_recv,H] / [T,H] |
外部 dispatcher、GMM、combine | 当前 MoE activation;不由 UltraEP buffer 持有 |
公开 README 的最小集成路径把四个核心调用放在 gate 与 token dispatch 之间;下面保留关键调用,不把示例仓库代码说成 XTuner 已接入的补丁:26
vid = manager.allocate_microbatch_slot(layer_id)
probs, routing_map = router(hidden)
manager.update_placement(vid, routing_map)
ws_event = manager.weight_sync(vid, async_finish=True)
probs, routing_map = manager.reroute(vid, probs, routing_map)
ws_event.current_stream_wait()
recv_tokens = dispatch(hidden, probs, routing_map)
recv_tokens = experts(recv_tokens, probs)
out = combine(recv_tokens)
完整教学路径还必须把外部 GMM、反向 restore、梯度归并和 slot 最后消费者展开:
def ultraep_forward(hidden, layer_id, manager, router, dispatcher,
master_w1, master_w2, replica_w1, replica_w2):
virtual_id = manager.allocate_microbatch_slot(layer_id)
residual = hidden
logical_probs, logical_routes = router(hidden)
manager.update_placement(virtual_id, logical_routes)
weight_event = manager.weight_sync(virtual_id, async_finish=True)
physical_probs, physical_routes = manager.reroute(
virtual_id, logical_probs, logical_routes
)
weight_event.current_stream_wait()
recv_x, dispatch_handle = dispatcher.dispatch(
hidden, physical_probs, physical_routes
)
grouped_x, group_offsets, inverse_permutation = permute_by_physical_expert(
recv_x, physical_routes
)
physical_w1 = concatenate_expert_rows(master_w1, replica_w1)
physical_w2 = concatenate_expert_rows(master_w2, replica_w2)
gate_up = grouped_gemm(grouped_x, physical_w1, group_offsets)
expert_hidden = silu(gate_up[:, 0::2]) * gate_up[:, 1::2]
expert_y = grouped_gemm(expert_hidden, physical_w2, group_offsets)
recv_y = inverse_permute_and_weight(
expert_y, inverse_permutation, physical_probs
)
output = dispatcher.combine(recv_y, dispatch_handle)
saved = save_for_backward(
virtual_id, logical_routes, physical_routes, physical_probs,
dispatch_handle, grouped_x, group_offsets, inverse_permutation,
)
return residual + output, saved
def ultraep_backward(output_grad, saved, manager, dispatcher,
master_w1, master_w2, replica_w1, replica_w2):
recv_output_grad = dispatcher.combine_backward(
output_grad, saved.dispatch_handle
)
restore_event = manager.weight_sync(saved.virtual_id, async_finish=True)
grouped_output_grad = permute_with_saved_inverse(
recv_output_grad, saved.inverse_permutation
)
physical_w1 = concatenate_expert_rows(master_w1, replica_w1)
physical_w2 = concatenate_expert_rows(master_w2, replica_w2)
w2_grad, expert_hidden_grad = grouped_gemm_wgrad(
grouped_output_grad, saved.group_offsets
)
restore_event.current_stream_wait()
grouped_x_grad, w1_grad = grouped_gemm_dgrad_and_wgrad(
expert_hidden_grad, saved.grouped_x, physical_w1, physical_w2,
saved.group_offsets,
)
recv_x_grad = inverse_permute(grouped_x_grad, saved.inverse_permutation)
input_grad = dispatcher.dispatch_backward(
recv_x_grad, saved.dispatch_handle
)
write_master_and_replica_grad_buffers(w1_grad, w2_grad)
grad_event = manager.grad_reduce(saved.virtual_id, async_finish=True)
router_grad = reroute_backward_to_logical_experts(
saved.virtual_id, saved.logical_routes, saved.physical_routes,
saved.physical_probs,
)
attention_grad = run_attention_backward(input_grad)
grad_event.current_stream_wait()
release_virtual_layer_slot_after_last_consumer(saved.virtual_id)
return input_grad + attention_grad, router_grad
这里的 release_virtual_layer_slot_after_last_consumer 是对生命周期约束的规范化表达,不是 UltraEP 当前公开 API;实现必须等该 virtual layer 对应的反向和异步归并都不再消费 plan/buffer 后,才能复用槽位。
效果边界¶
论文 Figure 8 把前向关键路径画得很清楚:gate 后先收集 exact load,replication planning 与 reroute 在 compute stream 上推进;weight distribution 在 communication stream 上与 reroute 重叠,但 token dispatch 必须等待 replica 权重就绪。因此 UltraEP 缩短最忙 rank 的 GMM/A2A 尾部,同时把 plan + max(reroute, weight_sync) 加到前向关键路径;它不是“均衡免费”。29
公开论文的主要训练证据固定在 BF16、64 GPU/rack 的 RSN:GLM4.5-106B 用 128 GPUs,Qwen3-235B 与 DeepSeek-V3-671B 用 256 GPUs;三个训练配置均为 EP64,并分别结合 DP/PP。各 baseline 从同一第 3500 个 global batch checkpoint 继续 20 个 batch。Figure 11 报告 UltraEP 的平均 rank imbalance 为 1.01–1.03,三模型平均达到 force-balanced ideal 的 94.6%,相对 Megatron-LM 平均提升 42%。30
同一论文还报告 prefill 平均达到 ideal 的 93.9%,相对 SGLang/EPLB 为 1.56×/1.29×,相对使用 exact load 但采用 EPLB placement + round-robin reroute 的 EPLB+ 为 5%–24%。这些是 Qwen3-235B/GLM4.7-358B 的 prefill RPS–TTFT 结果,不能写成训练吞吐提升。论文的生产 RefMoE-288B 结果是达到 ideal 的 92% 以上、比关闭 balancing 平均高 9.6%,但只说明多 rack、EP32,没有披露可复现的内部训练栈和精确 GPU 总数。30
仓库 test_e2e.py 还给了一个 EP64、256 experts、每 rank 4 master + 2 redundant slots、Top-8、8K tokens/rank 的 runtime 样本:31
| 初始 imbalance | 1.51 | 2.01 | 3.03 |
|---|---|---|---|
| 最终 imbalance | 1.01 | 1.01 | 1.01 |
update_placement |
0.067 ms | 0.072 ms | 0.078 ms |
reroute |
0.039 ms | 0.037 ms | 0.036 ms |
adaptive weight_sync |
0.194 ms | 0.195 ms | 0.188 ms |
overlapped grad_reduce |
1.935 ms | 2.181 ms | 2.425 ms |
这张表的 grad_reduce 已标为 overlapped,不能把各行直接相加成 step time。Figure 16 的 3.1–5.5× 也只比较同一 placement plan 下的 expert weight distribution backend,不是 token dispatch、MoonEP 或端到端训练加速;adaptive relay 的 1.3–1.8× 只说明高 fan-out 权重分发比 no-relay 更快。31
显存方面,冗余槽没有 optimizer state,且 weight/FP32 grad buffer 跨层复用。若单 expert 的两组权重元素数为 \(F_1+F_2\),每 rank 的主要增量上界近似为:
其中 \(b_w\) 是 weight element bytes,系数 4 来自当前 FP32 replica gradients。论文以 94 个 MoE 层的 Qwen3-235B 为例:跨层复用把每 rank 的一个冗余槽从所有层合计 3.3 GB weights + 6.6 GB gradients 降到 36 MB + 72 MB。另一方面,负载拉平还可降低最热接收 rank 的 MoE activation 峰值;这两件事分别作用于 新增副本状态与原有热点 activation,不能合并成固定显存节省比例。32
当前公开发行还有明确适用条件:SM90/SM100,CUDA 12.3+/12.9+,PyTorch 2.10+,NVSHMEM 3.4.5,并要求 NVLink 承担域内 expert replication;训练 replica gradients 只支持 FP32。NVLink domain <=8 时实现强制 direct weight sync,更大域才根据 fan-out 自适应 relay。SGLang 集成仍标为 experimental/unreleased;公开 Megatron fork与 8-GPU demo 能证明接入路径,不能证明任意 XTuner recipe 可直接复用。26
与 MoonEP 比较¶
两者都比 history-based EPLB 更“晚”地做决定:等当前 gate 结果出现后再复制热点 expert,因此都支付在线规划和权重预取/分发成本。但它们不是同一实现的两个名字:
| 维度 | MoonEP 0f385f0 |
UltraEP 94cab09 |
|---|---|---|
| 规划输入 | 当前 topk_idx/tokens_per_expert |
当前 [R,L] post-gate exact load |
| 目标 | 每 rank 固定 S×K 有效 routed 行,外加 NvS padding |
在 slot/quota/locality 约束下最小化最大 rank load,通常接近而非强制完美相等 |
| 决策变量 | virtual-expert plan、token 目的槽、experts_to_copy |
threshold、slot assignment X、instance quota U、source split Q |
| master/replica 布局 | contiguous symmetric [E+B,H,H′],训练建议 B=E/R |
master 不重排;每 rank 配置少量 N_slot,pointer-register + 跨层 weight/grad buffer |
| token 路径所有权 | 自己实现 dispatch/prefetch/combine、zero-copy shard 与 fused permute | 与 token dispatcher/GMM 解耦,只改 physical routes 和 replica state |
| 权重 fan-out | 直接预取到副本槽 | persistent tile streaming;大 NVLink 域可 adaptive relay |
| 反向 | plan 复用、virtual expert backward、FP32 pool、duplicate grad reduce | virtual layer ID 恢复前向 plan;重物化 weight;async replica-to-master grad reduce |
| 公开规模证据 | 单机 H20、EP8 通信图;仓库无同名论文 | 128/256 GPU 研究实验、EP40/64 prefill/training、生产多 rack;论文 + repo runtime |
| 关键限制 | H20/EP8 公共路径、zero-copy 生命周期、静态 NvS、完整 symmetric layout |
SM90/100、NVSHMEM/NVLink、关键路径 weight sync、slot/domain floor、框架物理 expert 接口改造 |
因此更准确的判断是:MoonEP 用更强的 dispatcher/GMM 内存契约换取固定计算 shape;UltraEP 用更模块化的 quota runtime 与较少冗余槽换取大 EP/RSN 下的近理想负载。 MoonEP 的公共仓库图与 UltraEP 论文不共享硬件、模型、dispatcher、计时窗口或端到端边界,目前没有足够证据判定谁“绝对更快”。
把 UltraEP 放回本文分层后,组合关系如下:
- 与 All-to-All / DeepEP / AGRS:UltraEP 先把 logical routes 展开为 physical routes,后者再搬 token;论文集成使用 DeepEP
hybrid-epbranch,但该 branch 名称不是本文跨数据中心 HybridEP 论文。 - 与 Domino:UltraEP 缩短一个 micro-batch 的最忙 rank 尾部,Domino 用另一 micro-batch 的计算隐藏剩余通信;virtual layer ID 已考虑多个在途 micro-batch,但两者尚无 XTuner 联合实现或 benchmark,且会争用 NVLink、通信 SM、activation 与 replica buffer。
- 与 MC2 / HyperParallel:这些 fused runtime 需要接管 expert weights 和 tile 依赖;动态 physical expert map 与逐层 replica weight deadline 必须进入编译/算子接口,因此不是可直接叠加的开关。
- 与跨数据中心 HybridEP:HybridEP 决定跨域搬 token 还是压缩 expert,UltraEP 在一个 NVLink scale-up domain 内复制热点实例;拓扑尺度不同,理论上可分层,当前没有共同实现证据。
UltraEP 的验收不能只看 planner 输出。至少要在同一 routing trace 下同时记录:pre/post rank imbalance、物理实例数、remote token ratio、plan/reroute/weight sync 裸时延、grad reduce 的未覆盖尾部、最热 rank activation、replica/dispatcher buffer 峰值、端到端 step time 与收敛;只有这些量保持模型、batch、精度、EP、拓扑和测量窗口一致时,才能给出数值收益。
AGRS¶
AGRS 在 XTuner 中可按 AllGather + ReduceScatter 理解:dispatch 时 AllGather hidden states,同时用小规模 All-to-All 交换 top-k IDs/weights;本地 experts 计算后,combine 用 ReduceScatter(sum) 把结果送回 token 所属 rank。11
等价性前提¶
这里的关键不是 AllGather 天生比 All-to-All 搬得少,而是 grouped router 把路由问题改写成了一个规则的代数分解。当前 XTuner 路径固定 ep_size = router_n_groups = top_k = 8:experts 被划成 8 个连续 group,每个 token 在每个 group 中只选 1 个 expert;一个 group 对应一个 EP rank。13 因而每个 rank 都必然需要看到每个 token,但只需计算这个 token 属于本 rank 的一项 expert 贡献。
设 \(R\) 个 rank 各有输入 \(X_r\in\mathbb{R}^{S\times H}\)。AllGather 后,每个 rank \(d\) 都持有:
小规模 metadata All-to-All 把 rank \(d\) 对应的 expert ID 与路由权重送到该 rank。其本地 experts 计算加权贡献 \(Z_d\in\mathbb{R}^{RS\times H}\),最后:
ReduceScatter(sum) 恰好同时完成“跨 rank 求和”和“把第 \(r\) 段送回源 rank \(r\)”这两件事。以 4 个 rank 的缩小示例看:token \(x_0\) 被 AllGather 到 4 个 rank,每个 rank 只算自己的 group contribution \(z_{0,d}\),ReduceScatter 再返回 \(y_0=z_{0,0}+z_{0,1}+z_{0,2}+z_{0,3}\)。这与普通 All-to-All 把 \(x_0\) 的 4 份 expert 路由结果送回源 rank 后求和,在数学上等价。
为什么可能更快¶
它用更规则的 collective 替换可变长 token All-to-All,在某些拓扑和 collective 实现上更容易获得稳定吞吐。不过当前实现有严格前提:必须使用 grouped router,并要求 ep_size == router_n_groups == 8。12
在当前 \(K=R=8\)、每个 rank 一个 group 的约束下,理想化的大 hidden 通信量并没有数量级差异。忽略 metadata 后,每个 rank 单向发送或接收的主 payload 都约为:
其中 \(b_x\) 是单个 hidden 元素的字节数。AGRS 更快时,主要省在内存重排与控制路径,而不是凭空少传一份 activation:
- 省掉发送前的大张量复制:普通路径先把
[S,H]按 Top-k 展开为接近[SK,H],再按目标 rank 排列;AGRS 直接 AllGather 连续的[S,H]。 - 省掉动态 split 同步:普通路径统计 token count、交换 count,并把 split size 搬到 CPU 形成 Python list;AGRS 的主 collective 是等长 shape。
- 减少一次源端重排:普通 combine 先做反向 All-to-All,再在源 rank 按 token 恢复排列并加权求和;AGRS 在目标 rank 先
unpermute(..., probs),随后由ReduceScatter(sum)合并“回传 + expert 求和”。 - 利用成熟规则 collective:固定大小的 AllGather/ReduceScatter 更容易使用 NCCL 的 ring/tree 算法和连续访存,但实际优劣仍取决于拓扑、消息大小和 collective 实现。
因此,AGRS 更准确的表述是:用可预测的全量复制换掉不规则 token 搬运与多次重排。它不是无条件优于 All-to-All,更不是通信—GMM 融合;Grouped GEMM 仍然位于两个 collective 之间,由框架独立调用。
完整运行路径¶
| 阶段 | 主对象与形状 | 所有者 | 与普通 All-to-All 的差异 |
|---|---|---|---|
| 路由 | x [S,H]、topk_idx/topk_weight [S,8] |
router | 每个 group 恰选一个 expert |
| 主 dispatch | all_x [8S,H] |
AllGather | 不先复制成 [8S,H] 再做变长 A2A |
| metadata dispatch | local_idx/local_weight [8S,1] |
小规模 All-to-All | 只交换 ID/weight,不交换大 hidden |
| expert 计算 | permuted_x -> expert_out [8S,H] |
框架侧 GMM | 与 collective 没有算子融合 |
| 本地恢复 | local_out [8S,H] |
unpermute(..., probs) |
在目标 rank 提前乘路由权重 |
| 返回与求和 | y [S,H] |
ReduceScatter(sum) | 一次 collective 同时回传并求和 |
all_x = all_gather_autograd(x, ep_group) # [R*S, H]
local_idx = all_to_all_small(transpose(topk_idx)) # [R*S, 1]
local_weight = all_to_all_small(transpose(topk_weight)) # [R*S, 1]
permuted_x, tokens_per_expert, inverse_map = permute(all_x, local_idx)
gate = grouped_gemm(permuted_x, local_expert_w1)
expert_hidden = silu(gate[:, 0::2]) * gate[:, 1::2]
expert_out = grouped_gemm(expert_hidden, local_expert_w2)
local_out = unpermute(expert_out, inverse_map, local_weight)
y = reduce_scatter_autograd(local_out, op="sum", group=ep_group)
效果与边界¶
此外,AGRS 会 AllGather activation,通信量和在途 buffer 不一定更小;ReduceScatter 的求和顺序也与普通路径不同。XTuner 的对照测试使用 atol=rtol=1e-2,对应 PR 也明确提示了浮点归约顺序造成的小误差。14
公开 PR 没有提供可复用的 AGRS 性能表,所以不能给出通用加速倍数。若 \(K<R\)、路由不是“一 group 一 rank”,AllGather 会让不需要某个 token 的 rank 也接收 activation;如果 [RS,H] buffer 造成显存压力,或目标拓扑的 AllGather/ReduceScatter 实现不占优,AGRS 甚至可能更慢。测试时至少应分开记录 collective 时间、permute/unpermute 时间、host sync、峰值显存与数值误差。
三种通信后端怎么选¶
| 维度 | 普通 All-to-All | DeepEP | AGRS |
|---|---|---|---|
| 数学语义 | 按 expert 路由 token | 同一 dispatch/combine 语义 | 在 grouped router 下重写为 AG + RS |
| GMM 所在位置 | 框架侧 | 框架侧,不是 MC2 | 框架侧,不是 MC2 |
| 主要收益 | 语义直接、适用面广 | 按 rank 去重、专用拓扑 kernel、异步 event | 规则 collective、少大张量重排、RS 合并回传与求和 |
| 关键前提 | 只需 EP 可用 | DeepEP/CUDA/拓扑与 buffer 匹配 | 当前实现要求 EP=groups=top_k=8 |
| 主要代价 | 变长 split、重排与 host 控制 | 持久 buffer、handle、通信 SM、版本敏感 | [RS,H] activation、固定路由约束、归约顺序变化 |
| 最稳妥用途 | 正确性与性能基线 | NVIDIA MoE 通信优化候选 | 满足 grouped-router 约束时的 A/B 候选 |
判断顺序应是:先用普通 All-to-All 建立正确性基线;若瓶颈来自不规则 dispatch/combine,再测试 DeepEP;只有模型本来就满足 AGRS 的 grouped-router 语义时,才比较 AG/RS 是否在目标拓扑更快。Domino 是三者之上的跨微批调度层,不能替代这组后端 A/B。
HybridEP 论文的启发¶
Domino、DeepEP 和 MC2 都建立在一个默认前提上:专家参数留在原 rank,路由后的 token 去找专家。 在 NVLink、InfiniBand 或同数据中心高速互联中,这通常合理;但跨数据中心链路只有 10 Gbps 一类的低带宽时,token All-to-All 的长尾可能远大于 pre-expert compute,继续增加 overlap 也藏不住它。
HybridEP 论文的核心反问是:既然 token 数据量 \(D\) 与 expert 参数量 \(P_E\) 的相对大小会随模型、序列和网络变化,为什么通信对象必须固定?论文引入比例 \(p\),让一部分工作继续走 token All-to-All,另一部分改为提前迁移 expert,再让 token 留在本域计算:18
其中,\(\mathcal{P}\) 是离散候选比例集合,\(D\) 是一次 MoE 路由涉及的 token 激活数据量,\(P_E\) 是待迁移 expert 的参数负载,\(B\) 表示分层网络带宽。\(p=1\) 退化为标准 EP:只搬 token;\(p=0\) 则只通过 expert AllGather 搬参数。中间值对应两条路径并存,并用论文的线性延迟模型选择预计 step time 最小的拓扑。
两个 HybridEP 不是同一实现
本节讨论的是论文 HybridEP: Scaling Expert Parallelism to Cross-Datacenter Scenario via Hybrid Expert/Data Transmission。NVIDIA Megatron Core 也有一个名为 HybridEP 的 token-dispatch backend,但它是另一套工程实现;不能把 Megatron 的 API、约束或 benchmark 直接当作这篇跨数据中心论文的证据。19
混合传输¶
直接跨域复制完整 expert 往往比搬 token 更贵,因此 HybridEP 又引入两层结构:
- Expert Domain:域内使用 expert AllGather,域间继续使用 token All-to-All;比例 \(p\) 决定 domain 的划分和两类流量的份额。
- SR 压缩:先构造共享 expert \(W_{\text{shared}}\),再只发送每个 expert 相对共享值的稀疏残差:
这里 \(E\) 是参与构造共享基底的 expert 数,\(W_e\) 是第 \(e\) 个 expert 权重,\(\Delta W_e\) 是残差,\(\widehat{W}_e\) 是接收端恢复出的近似 expert。通信包必须同时携带 Top-k 数值和索引;稀疏化减少的是链路 payload,而不是免费得到完整权重。
| 关键对象 | 产生位置 | 消费位置 | 资源含义 |
|---|---|---|---|
| \(p\) 与 Expert Domain | 延迟模型、拓扑构造器 | 通信组与路由拆分 | 控制量,不是 activation |
| token \(x_i[T,H]\) | router 前后 | 跨域 A2A、expert GMM | 在途 activation |
| \(W_{\text{shared}}\) | expert 均值与异步同步 | SR 编解码 | 持久或域内复制参数 |
| \(C(\Delta W_e)\) | Top-k 残差编码 | expert AG、接收端解码 | 稀疏 value/index buffer |
| \(\widehat{W}_e\) | SRDecode | 迁移 expert GMM | 预取的近似参数 |
| Send/Recv Queue | 异步 communicator | 网络与 decoder | 有界通信状态 |
下面的伪代码只表达论文机制,不对应 XTuner 当前 API:
def hybrid_ep_forward(tokens, router_result, experts, candidate_p, link_profile):
token_bytes = tokens.numel() * tokens.element_size()
expert_bytes = sum(weight.numel() * weight.element_size() for weight in experts)
p = min(
candidate_p,
key=lambda ratio: modeled_step_time(
ratio=ratio,
token_bytes=token_bytes,
expert_bytes=expert_bytes,
link_profile=link_profile,
),
)
domains = build_expert_domains(p, router_result, link_profile)
shared_weight = mean_expert_weight(experts, domains)
sparse_packets = {
expert_id: topk_encode(experts[expert_id] - shared_weight[domains[expert_id]])
for expert_id in range(len(experts))
}
expert_ag_handle = async_expert_allgather(sparse_packets, domains)
local_tokens, cross_domain_tokens = split_tokens(router_result, domains, p)
token_a2a_handle = async_token_alltoall(cross_domain_tokens, domains)
local_output = run_local_experts(local_tokens, experts, router_result)
migrated_packets = expert_ag_handle.wait()
migrated_experts = {
expert_id: shared_weight[domains[expert_id]] + topk_decode(packet)
for expert_id, packet in migrated_packets.items()
}
remote_tokens = token_a2a_handle.wait()
remote_output = run_migrated_experts(remote_tokens, migrated_experts, router_result)
return combine_routed_outputs(local_output, remote_output, router_result)
效果与边界¶
HybridEP 的直接收益目标是降低受限跨域链路上的关键路径时间,并用 expert 预取与 pre-expert compute 重叠;它不保证降低 GPU 峰值显存。更完整的峰值预算应包含:
SR 压缩可以减小 \(M_{\text{sparse-packet}}\) 和网络 payload,但共享权重、恢复后的 \(\widehat{W}_e\)、收发队列与预取窗口都可能抬高峰值;CPU offload 只是把其中一部分压力转移到主存和 PCIe。
论文模型还依赖 balanced routing、分段线性延迟与特定网络层级,并把 backward AllReduce 干扰近似为常量。SR 是有损 expert 恢复,必须另外验证前向误差、梯度、收敛和共享基底更新频率。因此,HybridEP 更像跨数据中心极低带宽下的系统设计空间,而不是在普通单集群训练中默认替换 DeepEP 的现成开关。
与 MC2 对比¶
DeepEP 与 MC2¶
DeepEP 与 EP MC2 的相同点,是都不改变 top-k 路由语义,而是在 router 与 unpermute 之间优化 MoE 数据面;两者都需要 token counts、rank/expert 映射、专用通信 workspace,并在反向中调用与前向相反的通信方向。
真正的差异是算子边界。DeepEP 接收 token 与路由元数据,输出路由后的 token;expert 权重不进入 DeepEP 调用。MindSpeed-MM commit 64eeb735 的 EP MC2 则把 fc1_weight、fc2_weight 与 HCCL communicator 一起传入两个 fused op:npu_alltoallv_gmm 融合 A2Av 与第一层 GMM,npu_gmm_alltoallv 融合第二层 GMM 与 A2Av,二者之间的 SwiGLU 仍是独立操作。16
def deepep_moe_path(x, topk_idx, topk_weight, expert_weights, num_experts, group):
recv_x, recv_idx, recv_weight, counts, handle, dispatch_event = dispatch_forward(
x=x,
topk_idx=topk_idx,
topk_weights=topk_weight,
num_experts=num_experts,
group=group,
)
dispatch_event.current_stream_wait()
expert_out = grouped_expert_gemm(recv_x, counts, expert_weights)
combined_x, combine_event = combine_forward(
x=expert_out,
num_experts=num_experts,
handle=handle,
group=group,
)
combine_event.current_stream_wait()
return combined_x
def mc2_moe_path(x, selected_experts, routing_weights, fc1_weight, fc2_weight, hcomm):
send_counts, recv_counts = allgather_expert_counts(selected_experts)
permuted_x, reverse_mapping = permute_by_expert(x, selected_experts)
fc1_output, permute_output = npu_alltoallv_gmm(
permuted_x, fc1_weight, send_counts, recv_counts, hcomm
)
activated = swiglu(fc1_output)
routed_output = npu_gmm_alltoallv(
activated, fc2_weight, send_counts, recv_counts, hcomm
)
return unpermute(routed_output, reverse_mapping, routing_weights), permute_output
这段代码是边界等价伪代码:DeepEP 的 GMM 明确位于通信调用外,MC2 的两层 GMM 则分别进入 fused call。实际 shape、packed layout 与 workspace 由固定版本后端决定,不能从 Python wrapper 擅自补齐。
| 维度 | DeepEP(XTuner 固定 V1 路径) | EP MC2(MindSpeed-MM 固定提交) |
|---|---|---|
| 输入 | token、top-k 元数据、layout/handle | token、counts、expert 权重、HCCL communicator |
| 输出 | routed/combined token、counts、handle、event | 已完成一层 GMM 或回程 GMM+A2Av 的 activation |
| 算子所有权 | 通信库拥有 dispatch/combine;框架拥有 GMM | fused op 同时拥有通信 tile 与 GMM tile |
| 重叠位置 | event 暴露给框架,由外部 stream 调度 | fused op 内部切 tile 流水 |
| 中间对象 | recv_x 明确暴露给 expert GMM |
packed layout/workspace 由后端内部持有 |
| 反向 | dispatch 的反向用 combine,combine 的反向用 dispatch | 使用相反 fused op;权重梯度另做 npu_grouped_matmul |
| 硬件栈 | NVIDIA CUDA、NVLink/RDMA | Ascend NPU、CANN、HCCL |
| 接入 Domino | 现有 dispatcher 接口可直接调度 | 必须改写 dispatcher/expert 的可分离边界 |
| 主要风险 | buffer/拓扑/版本敏感,额外在途 token | shape/CANN 约束、融合调试与潜在精度差异 |
因此二者不是“同一个算子在不同硬件上的名字”。DeepEP 保留通信与 expert compute 的软件边界,换来调度自由度;MC2 牺牲这条边界,把通信和 GMM 的 tile 调度交给后端,换取更深的单算子融合。 前者的显存要显式看到 recv_x + handle + buffer,后者可能减少暴露的中间张量,但仍有 backend-owned packed layout、workspace 与为反向保存的 permute_output,不能把“Python 看不见”误当成“显存不存在”。
MindSpeed-MM PR #2480 的公开材料只能证明功能合入与本地自测,没有公开多 rank 单测、统一 benchmark 或收敛曲线;DeepEP 公布的带宽数据又来自不同硬件和软件栈。因此本文不做跨 NVIDIA/Ascend 的数值排名,只比较固定源码中的对象与边界。
Domino EP 与 MC2¶
此前 MindSpeed-MM 接入的 EP MC2 面向 Ascend NPU,把 AllToAllv -> GroupedMatmul 和 GroupedMatmul -> AllToAllv 分别融合为 NPU fused operator。15 它让算子内部把大通信和大计算切块、流水,减少中间张量、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 文档也明确提示部分模型可能出现精度问题,并列出了模型与硬件限制。17
HyperParallel-MoE¶
HyperParallel-MoE 把 MC2 的问题再扩一层。MC2 融合两对相邻边界,即 A2Av→GMM 与 GMM→A2Av;HyperParallel 则把一次 MoE-FFN 的 Dispatch → GMM → SwiGLU → GMM → Combine 全路径建成 Operator Dependency Graph(ODG),再按 rank、expert 和 token tile 拆成异构任务,静态排入 Ascend 的 Cube Task Queue(CTQ/AIC)与 Vector Task Queue(VTQ/AIV)。33
| 机制 | 调度范围 | 最小并发单位 | 谁拥有通信—计算边界 | 当前硬件与接入状态 |
|---|---|---|---|---|
| Domino EP | 多个 micro-batch | 一个微批的一段通信或专家计算 | XTuner framework + dispatcher event | CUDA 路径已存在 |
| EP MC2 | 相邻 A2Av/GMM | fused op 内部 tile | CANN/HCCL fused op | Ascend;XTuner 未接入 |
| HyperParallel-MoE | 单微批完整 MoE-FFN | rank × expert × token tile | Host compiler + AIC/AIV workers | Ascend A3;XTuner 未接入 |
| MoonEP | dispatch、权重副本与 virtual-expert GMM 边界 | token row / redundant expert | MoonEP symmetric-memory buffer | NVIDIA;XTuner 未接入 |
从整体 collective 到目的 tile 事件¶
HyperParallel 的关键不是“再开一条 stream”,而是改变下游工作的就绪条件。普通算子级路径通常等整个 All-to-All 返回后才启动 GMM;HyperParallel 的 AIV task 使用一侧 put_mem_signal,先把 tile 写进目的 rank 的可读位置,再更新目的端 event counter。只依赖该 tile 的 GMM task 在计数器达到阈值后即可执行,不必等其它 rank、其它 expert 的所有 tile 都完成。固定源码的 forward graph 也明确给出五算子依赖与 CTQ/VTQ 归属。34
Host compiler 与 Device runtime 之间需要完整对象契约:
| 对象 | 产生者 | 消费者 | 生命周期与资源含义 |
|---|---|---|---|
| ODG | MoE graph builder | splitter、dependency builder | 编译期五算子逻辑图 |
SplitSpec |
每个 operator 的合法切分定义 | task generator | 指定 rank/expert/token tile 维度 |
TaskDescriptor |
task generator | AIC/AIV worker | 含 op、tile、queue、wait 与 trigger |
| Event Table | dependency compiler/runtime init | worker wait/trigger loop | Device 端计数器状态 |
| SSC | legal reorder + serializer | 统一 device kernel | CTQ/VTQ 的静态有序任务数组 |
| remote dispatch tile | AIV put_mem_signal |
目的端 GMM | 写入后以 event 表示可读,而不是整个 collective 完成 |
| AIC/AIV intermediate | GMM 或 SwiGLU/Add handler | 下一个 tile task | tile 流水中的显式或 backend-owned 中间状态 |
| combine tile | 第二个 GMM | AIV return task 与 token owner | 回程写与目的端事件完成后结束 |
下面的伪代码把编译期和运行期都展开。reorder_ready_tasks 只能在依赖允许的候选集中做 RATR 目的 rank 均衡或 backward GMM 的 L2 复用重排,不能跨越真实数据依赖:
def compile_hyperparallel_moe(split_specs):
operators = ["dispatch", "gmm_up", "swiglu", "gmm_down", "combine"]
queue_of = {
"dispatch": "VTQ",
"gmm_up": "CTQ",
"swiglu": "VTQ",
"gmm_down": "CTQ",
"combine": "VTQ",
}
graph = build_linear_dependency_graph(operators)
tasks = []
for operator in operators:
tiles = split_operator(graph[operator], split_specs[operator])
for tile in tiles:
tasks.append(
TaskDescriptor(
operator=operator,
tile=tile,
queue=queue_of[operator],
wait_events=[],
trigger_events=[],
)
)
task_index = index_tasks_by_operator_and_tile(tasks)
event_table = EventTable()
for producer_name, consumer_name in graph.edges:
for producer_task, consumer_task in match_dependent_tiles(
task_index[producer_name],
task_index[consumer_name],
):
event_id = event_table.allocate_counter()
producer_task.trigger_events.append(event_id)
consumer_task.wait_events.append((event_id, 1))
ctq_tasks = reorder_ready_tasks(
select_queue(tasks, "CTQ"),
policy="backward_gmm_l2_reuse",
)
vtq_tasks = reorder_ready_tasks(
select_queue(tasks, "VTQ"),
policy="rank_aware_task_reordering",
)
return StaticScheduleCode(ctq_tasks, vtq_tasks, event_table)
def execute_hyperparallel_moe(ssc, tensors, communicators):
def worker(task_queue):
for task in task_queue:
for event_id, threshold in task.wait_events:
wait_until(ssc.event_table[event_id] >= threshold)
if task.operator in {"dispatch", "combine"}:
put_mem_signal_kernel(task.tile, tensors, communicators)
elif task.operator in {"gmm_up", "gmm_down"}:
grouped_matmul_kernel(task.tile, tensors)
elif task.operator == "swiglu":
swiglu_add_kernel(task.tile, tensors)
for event_id in task.trigger_events:
atomic_increment(ssc.event_table[event_id])
launch_one_ascendc_kernel(
aic_worker=lambda: worker(ssc.ctq_tasks),
aiv_worker=lambda: worker(ssc.vtq_tasks),
)
return tensors["combined_output"]
这套静态化的收益前提很强:shape 和 tile 集合要能在 Host 侧确定,单 task 开销必须足够小,而且 CTQ/VTQ 重排不能制造缓存抖动。论文报告静态任务开销约 0.1 μs,对照的动态调度为 2.36 μs;在 \(M=32K\) 的 SwiGLU+Add 微基准中,交错把 723.29 μs 降到 588.38 μs、L2 hit 从 5.20% 提到 25.44%,但在 8K 小尺寸上出现轻微退化。这说明“切得更细”本身不是单调收益。35
结果与组合边界¶
论文的 end-to-end 提升只有 1.08–1.09×,明显小于模块的 1.49–1.58×;完整模型覆盖仍被列为后续工作。这个差距非常重要:模块外还有 attention、其它通信、pipeline 气泡和框架开销,单层局部加速不能直接当训练吞吐加速。36
把它纳入本文后的组合判断是:
- 与 Domino:一个在单微批内部按 tile 推进,一个跨微批调度;理论上可层叠,但 unified kernel 会改变 Domino 能看到的 event 和可重叠边界。
- 与 MC2:HyperParallel 覆盖完整五算子路径,MC2 覆盖两对 fused 边界;在 Ascend 上更像两种算子所有权方案,不是默认叠加的独立开关。
- 与 MoonEP:MoonEP 依赖 NVIDIA symmetric memory、动态冗余 expert 和权重预取;HyperParallel 依赖 Ascend AIC/AIV 与静态 tile 编译。两者解决的问题可同时存在,但当前硬件、内存与 runtime 契约并不兼容。
- 与 HybridEP:HybridEP 决定跨数据中心搬 token 还是压缩 expert;HyperParallel 优化 Ascend 集群内单个 MoE-FFN 路径的细粒度推进,尺度与通信对象都不同。
所以,HyperParallel 为 EP 方案增加的是一条平台专用的完整 MoE 编译路线,不是“把 XTuner dispatcher 名称改成 hyperparallel”。真正接入必须重画框架与 fused runtime 的所有权边界,并用 Ascend timeline、峰值内存、精度和端到端吞吐共同验收。
论文证据¶
原始 Domino 论文处理的是 tensor parallel:沿 batch 维切输入、沿输出维切权重,让一个切片的 AllReduce 与另一个切片的矩阵乘重叠。论文报告在 DGX-H100 集群上相对 Megatron-LM 最高约 1.3× 加速。20 这为“切分独立工作并用异步通信跨切片重叠”提供了机制证据,但通信对象是 TP AllReduce,不是 MoE EP 的 token dispatch/combine。
与 XTuner 实现更接近的论文先例来自 DeepSeek-V3:其预填充阶段同时处理两个 micro-batch,使一个微批的 attention/MoE 计算与另一个微批的 dispatch/combine 重叠。21 不过 DeepSeek-V3 的 DualPipe 还包含 pipeline parallel 调度,不能与 XTuner 的单个 MoE 层实现画等号。
截至上述 XTuner commit,公开代码和 PR 能证明实现、测试与持续修复过程,但没有提供可复用的 Domino EP 专项性能表。PR #1078 是功能接入,后续 PR #1824 修复了 H2D 同步延迟第二个微批 DeepEP dispatch 的问题;这恰好说明收益依赖细致的异步路径,而不能只看开关名称。22
选择方法¶
建议把“后端”和“调度”拆开做 A/B,而不是一次同时更改:
在进入 A/B 前,先判断瓶颈是否真的是跨数据中心低带宽。如果只是同机或同数据中心 EP 通信长,优先从 DeepEP、MC2、AGRS 和 Domino 逐层优化;只有 token A2A 的长尾明显超过可覆盖计算、并且迁移压缩 expert 更便宜时,HybridEP 的通信对象切换才进入候选集。
还要单独看 router skew 与设备执行时间线:如果 step 被最热 expert 所在 rank 拖尾,MoonEP/UltraEP 一类实时冗余 expert 方案才进入候选。偏好固定 S×K 计算 shape、愿意交出 dispatch/combine 与 symmetric-memory 布局时看 MoonEP;希望保留外部 dispatcher/GMM、以少量跨层槽换大 EP 近似均衡时看 UltraEP。如果目标是 Ascend A3,且 AIC/AIV 空闲与 EP 裸露通信同时存在,才评估 HyperParallel 的完整 MoE 瓦片编译。它们分别需要新的后端/运行时接入,不能当成已有 XTuner 配置项。
- 建立基线:
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 验收。
- 跨域再测 HybridEP:扫描候选 \(p\)、SR sparsity 与 expert prefetch window,同时记录跨域字节数、主存/显存峰值和收敛,不能只复用论文的 10 Gbps 结论。
- 有路由偏斜再测 MoonEP:固定模型、路由结果、EP size 与
S×K,记录最忙/最闲 rank 的 token 行数、权重预取字节、plan 时间、grad reduce 尾部和 FP32 pool 峰值。 - 大 EP/NVLink 域再测 UltraEP:重放同一 routing trace,扫描
N_slot、quota floor、direct/relay 与 grad-reduce SM 数,同时记录 post-balance max/mean、physical instances、weight fan-out、未覆盖尾部和跨层 buffer 峰值。 - Ascend 再测 HyperParallel:先做算子级基线,再逐项核对 tile 依赖、CTQ/VTQ 利用率、event wait、L2 hit、模块延迟和端到端 step;模块加速不能代替端到端验收。
一个最小配置思路如下,具体对象名应以所用 XTuner recipe 为准:
常见失败条件
- 显存上涨:多个微批的 residual、router 结果、dispatch handle 和激活同时在途。
- 形状限制:当前
_micro_batch_forward要求同组 hidden states 形状一致。 - 覆盖不足:通信太长而计算太短时仍会露出尾部;通信本来很短时调度成本反而突出。
- 计算退化:为了塞进两个微批而缩小单微批,Grouped GEMM 的效率可能下降。
- 版本错配:DeepEP、CUDA/NCCL、GroupedGEMM 或 NPU CANN/MC2 的版本都会改变结果。
- 通信对象错判:HybridEP 的 SR 压缩会增加共享权重、稀疏包、解码与预取状态,并引入有损近似;低带宽并不自动等于净收益。
- 负载均衡错判:MoonEP 的固定 routed 行数不能消除 plan、权重预取、zero-copy 生命周期与副本梯度归并成本。
- exact-load 错判:UltraEP 的输入 load 是精确的,但 slot 数、NVLink domain、quota floor 与 weight-sync deadline 仍会形成高于
1.0的均衡下限。 - 局部加速外推:HyperParallel 的模块收益来自完整 bundle;平台、shape 或 tile 变化,以及模块外瓶颈,都会改变端到端收益。
总结¶
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 的比较是算子融合对跨微批调度,不是同一层替代关系。
- 跨数据中心链路低到 overlap 无法隐藏长尾时,才进一步评估 HybridEP 的 token/expert 混合传输;论文结果不能直接外推到 XTuner。
- NVIDIA NVLink 域内若瓶颈来自严重 router skew,可研究 MoonEP 的 动态冗余 expert + 固定
S×K工作量;先验条件是接受权重预取、symmetric-memory 布局与梯度 pool 接口改造。 - NVIDIA 大 EP/RSN 若希望保留外部 dispatcher 与 GMM,可研究 UltraEP 的 exact-load quota + 跨层 replica buffer + 自适应权重 relay;先验条件是接入 physical expert map、virtual layer state 与副本梯度回收。
- Ascend A3 若单微批的通信、Cube 与 Vector 空闲可通过 tile 事件互相覆盖,可研究 HyperParallel-MoE;必须同时看其 MoE 模块与端到端 两级收益。
DeepEP 与 MC2 的核心分界也可以压缩成一句话:DeepEP 把 routed token 交还框架再做 GMM,MC2 把 expert 权重交给 fused operator 并在内部完成通信—计算流水。
最终选择应由同一模型、同一 batch 语义下的 profiler、显存和收敛结果决定,而不是由技术名称决定。
加入三条新路线后,这个判断仍不变:MoonEP 用固定工作量、UltraEP 用 quota 上限缩短负载偏斜造成的 rank 尾部,HyperParallel 重排单微批完整 MoE 的瓦片关键路径,Domino 再尝试跨微批遮蔽剩余气泡。 这些层能否共存是待实现、待测量的系统问题,不是名称上的可组合性证明。
-
XTuner commit
9989fb74130d8f848662c5021458480ba161b9fa。本文源码结论均固定到该版本,避免主分支变化造成歧义。 ↩ -
XTuner
deepep_op.py:DeepEP SM 配置;low-latency buffer 与异步 dispatch。 ↩ -
XTuner
deepep_op.py固定版本源码;DeepEP commit9af0e0d的Buffer.dispatch/combine。 ↩ -
DeepEP commit
9af0e0d的 layout kernel:is_token_in_rank对每个 token/rank 只记录一次命中;XTuner DeepEP dispatcher 在收到 rank 级 dispatch 结果后再做 framework-side permute。 ↩ -
DeepEP commit
9af0e0d的跨节点 dispatch kernel:warp 分工覆盖 RDMA sender、RDMA/NVLink forwarder 与 NVLink receiver。它证明的是通信 kernel 的分层转发,不包含 expert GMM。 ↩ -
XTuner
agrs.py:AllGather、All-to-All IDs/weights 与 ReduceScatter。 ↩ -
XTuner grouped router 明确要求
top_k == n_group == 8,并在每个连续 expert group 中选择一个 expert;AGRS dispatch/combine 实现 则分别交换大 hidden 与小型 metadata。 ↩ -
MindSpeed-MM commit
64eeb735:EP dispatcher 调用链;两个 MC2 fused op 与反向实现。 ↩ -
HybridEP: Scaling Expert Parallelism to Cross-Datacenter Scenario via Hybrid Expert/Data Transmission,本文固定到 arXiv v1(2025-10-22),机制主要见 Sections III-IV、Figures 7-9 与 Equations 8-12。 ↩
-
HybridEP 论文;Megatron Core MoE README 中的 HybridEP backend。二者名称相同,但研究目标、接口与实现来源不同。 ↩
-
Domino: Eliminating Communication in LLM Training via Generic Tensor Slicing and Overlapping。 ↩
-
XTuner PR #1078:Domino EP;XTuner PR #1824:DeepEP dispatch overlap 修复。 ↩
-
MoonEP 官方仓库,固定 commit
0f385f038fc33bec22e3bcf5a07a8a22693e754c;GPU planner 对象与约束。截至该提交,仓库没有关联的 MoonEP 同名论文,本文把源码和 README 当作实现证据,不把仓库说明提升为同行评审结论。 ↩ -
MoonEP
api.py:dispatch、prefetch、combine、plan 复用与 gradient reduction;README:训练/推理B约束;README:zero-copy 生命周期。 ↩ -
MoonEP
bench_vs_deepep.py;官方通信图。脚本明确列出计时边界,并说明grad_reduce因假设可与后续计算重叠而不计入该 benchmark。 ↩ -
UltraEP 官方仓库 tag
v1.0.0,固定 commit94cab099b44fffa99a82fea99e7c12d89cf65e4f;README 的依赖、Manager 接口与前后向集成骨架。公开发行支持 SM90/SM100;SGLang 集成在该版本仍标为 experimental/unreleased。 ↩↩↩ -
UltraEP
Manager:跨层 replica buffer、FP32 gradient、virtual layer ID 与核心 API;allocate_microbatch_slot;grad_reduce、weight_sync、update_placement与reroute。这些代码证明独立 runtime 的接口,不证明 XTuner 已集成。 ↩↩ -
UltraEP 论文 arXiv v3,§5 Quota-Driven Planning 与 Algorithm 1;固定源码
placement.cu的 quota solver。论文工作中u_min=1024、目标 balancing coefficient 为1.01;仓库 tuning env 可调整相应阈值。 ↩↩ -
UltraEP: Unleash MoE Training and Inference on Rack-Scale Nodes with Near-Optimal Load Balancing,本文固定到 arXiv v3;前向/反向 pipeline 见 Figures 8–9 与 §4.2,RSN tile streaming/relay 见 §6。 ↩
-
UltraEP arXiv v3 §8 与 Figures 11–12:训练平均
94.6%ideal、相对 Megatron-LM 平均+42%;prefill 平均93.9%ideal、相对 SGLang/EPLB 为1.56×/1.29×。模型、GPU 数、EP、precision 与 continuation window 见 Table 2 和 §8.1–8.2;production 结果见 Figure 17/§8.6。 ↩↩ -
UltraEP README 的 EP64 runtime 表与 tuning 开关;论文 Figure 16 与 §8.5 比较的是 expert-weight distribution backend,不是 token dispatch 或端到端训练。 ↩↩
-
UltraEP arXiv v3 Figure 7/§4.1 的跨层 buffer 复用;Figure 14/§8.4 的 activation memory。前者减少 replica state 的层数倍增,后者是负载拉平后的实测峰值变化,二者不能相加为固定节省比例。 ↩
-
HyperParallel-MoE: Multi-Core Interleaved Scheduling for Fast MoE Training on Ascend NPUs,本文固定到 arXiv v2(2026-06-01)。 ↩
-
MindSpore HyperParallel forward graph,固定 commit
b5e80ae;put_mem_signaltask 构造。worker kernel 与 forward AIV worker 也固定到同一提交。 ↩ -
HyperParallel-MoE arXiv v2,任务调度开销与 SwiGLU/Add 微基准。这些是论文在 Ascend A3 上的局部测量,不代表所有 shape 都加速。 ↩
-
HyperParallel-MoE arXiv v2,MoE 模块与端到端评估。论文设置为 64 张 Ascend A3、25 AIC + 50 AIV/设备、192 MB L2;模块与端到端数字必须分开引用。 ↩



















