Enwei Zhu (朱恩伟), NVIDIA 加速计算专家团队 高级工程师
Jinyang Yuan (袁劲飏), NVIDIA 加速计算专家团队 高级工程师
大规模专家并行(EP)可以在以下方面提供帮助:
下图展示了在GB200上的预期帕累托曲线,比较了EP规模为4和8时的性能差异。
注:此图仅供技术讨论和参考,实际性能可能因产品组合不同而异。
在不同并行策略组合下,通信开销是关键挑战。
小规模EP:
AllReduce通信。AllGather后接ReduceScatter的通信模式。大规模EP:
AllGather和ReduceScatter的组合是否仍然高效存疑。AllGather + ReduceScatter 模式这种模式下,每个令牌(token)的隐藏状态(hidden states)会被发送到所有的计算排名(rank),但实际上只有拥有被选中专家(topK experts)的rank才需要这些数据,造成了通信冗余。
AlltoAllv 模式AlltoAllv是一种更高效的通信原语。在这种模式下,每个令牌的隐藏状态只会被发送到那些拥有其所需专家的rank,从而减少了不必要的数据传输。
专家负载不均衡是大规模EP面临的另一个核心挑战。
下图展示了在翻译任务(模型:DeepSeek-R1,EP规模:32)中,不同rank和层的令牌分配数量热力图,以及特定rank上专家负载的柱状图,清晰地揭示了负载不均衡现象。
为了解决专家负载不均衡问题,我们设计了专家并行负载均衡器(EPLB)。
生成配置文件: 基于收集到的统计数据,生成EPLB配置文件。
GPU数量解耦: 在线服务和离线统计收集所用的GPU数量可以不相同。
下图对比了禁用和启用EPLB后的专家负载情况。启用后(右图),负载分布明显更加均匀。
离线均衡器依赖于数据分布的稳定性。然而,实际场景中数据分布可能动态变化,这促使我们研究在线负载均衡。
基于以上观察,我们设计了在线负载均衡器,以适应动态变化的负载模式。
动态更新:
后台执行: 映射信息和专家权重的更新在后台执行,与其它层的推理计算重叠,以减少对性能的影响。
在线负载均衡器通过在运行时动态调整专家(Expert)的放置和权重,以解决负载不均衡问题。其工作流程涉及CPU和GPU的协同,具体如下:
CPU端 在时间步 n 的工作包括:
Wait End Signal)。Statistics Data),计算专家副本和放置策略 (Replication & Placement Compute)。Update Weights & Placement)。Placement Info) 和 MoE 权重 (MoE Weights) 发送给GPU,并为下一个时间步 n+1 发送更新开始信号 (Update Start Signals)。GPU端 在时间步 n 的工作流程如下:
GPU Start Signal)。Wait -> Statistics (统计负载信息) -> Expert-Slot Mapping (根据Placement Info进行映射) -> Prepare -> Dispatch -> MoE (使用MoE Weights进行计算) -> Combine -> End。Statistics Data 发送回CPU,用于下一步的决策。一个关键约束是,在GPU执行从接收启动信号到发送结束信号之间的核函数时,主机(CPU)不应更改放置信息和MoE权重,以确保数据一致性。
在线负载均衡器能有效解决负载不均衡问题。实验结果表明:
- 在权重更新后,负载在不同的槽(slots)/等级(ranks)之间变得均衡,不再出现热点槽/等级。
- 下图展示了在翻译数据集上的效果,其中红色横线表示权重更新发生的层。更新前,存在明显的负载不均衡(黄色条带);更新后,负载分布变得非常均匀(紫色/蓝色区域)。
- 实验设置:翻译数据集,ep_size=32, local_bs=256, num_slots=288 (32个冗余专家)。
下表对比了离线和在线两种负载均衡器的优缺点及适用场景:
在解决了负载均衡问题后,下一个优化重点是通信开销。
在优化前,基线采用 AllGather + ReduceScatter 的通信模式。性能分析显示:
- 计算部分:MoE的分组GEMM(2 moe_gemm)和Attention(attn)核函数的执行时间表现良好。随着专家并行(EP)规模的增大,GEMM的耗时甚至会减少,而Attention的耗时保持不变。
- 通信部分:AllGather和ReduceScatter操作的耗时随着EP规模的增大而显著增加。然而,在每个GPU批处理大小固定的情况下,所需的通信消息大小应为常数,这表明当前的通信方式存在优化空间。
性能剖析图显示,AllGather(152us + 149us)和 ReduceScatter(390us)占用了大量的执行时间,成为性能瓶颈。
为了优化通信,引入了 AlltoAll 操作,其特点包括:
- 通过NVLink进行点对点(P2P)访问。
- 支持设备内存中的发送/接收计数,实现 AlltoAll 风格的通信。
- 支持CUDA Graph。
新的流程变为 Prepare -> A2A -> MoE -> A2A -> Local Reduce。性能剖析显示,All2All 的通信时间(fp4: 18us+12us, bf16: 174us)远低于之前的 AllGather 和 ReduceScatter。但该实现可能会因 recv_m ~selectedExpert * m 而引入新的负载不均衡问题。
在初步实现的基础上,进行了三项进一步的优化:
1. 使用 AlltoAll 重构 prepare AllGather:将用于准备专家ID和令牌尺度的AllGather操作也替换为更高效的AlltoAll。
2. 低精度合并:在发送端进行量化(Quant),在接收端进行反量化(DeQuant),以减少数据传输量。
3. 融合 AlltoAll 核函数:将多个小的AlltoAll核函数融合成一个,减少核函数启动开销。
优化1的效果显著,如下图左侧所示,“current”(当前)版本的耗时远低于“previous”(之前)版本。优化2和3的实现细节如右侧图所示,通过共享内存(SM)和P2P工作空间进行高效的数据交换。
Permute/Activation/Unpermute等辅助核函数的执行时间会随着EP规模的增大而增加。理论上,在每个GPU批处理大小固定的情况下,这些核函数的工作负载应该是恒定的。根本原因:在MoE GEMM之前,M个令牌被扩展为M*topK个令牌。平均而言,只有1/EP的扩展令牌是有效的(即被路由到当前rank上的专家)。当EP规模很大时,大多数线程块被启动用于处理无效令牌,造成了严重的资源浪费。例如,对于DS-R1在EP32的设置下,96个扩展令牌中只有3个在rank 0上是有效的。
解决方案:
expandInputRows、dyActivation和finalizeMoeRouting等辅助核函数的执行时间不再随EP规模增加而增加,而是保持为一个常数,显著提升了性能。AllGather,将每个rank的本地批处理大小从32增加到32*4=128,使GEMM操作更接近计算瓶颈。AllGather和切片操作合并logits。argmax,然后仅对argmax的结果进行AllGather和全局argmax。更多低精度计算
更多融合与重叠
主机侧优化