DeepEP专家并行通信库:MoE训练与推理的all-to-all优化实践 1. 从一次深夜调优说起DeepEP到底解决了什么问题第一次注意到DeepEP是在一个多机多卡的训练任务里。当时模型规模刚过千亿参数专家数量拉到64个每张卡上挂8个专家跨节点通信量一下子涨到让人头疼的程度。训练日志里最扎眼的就是all-to-all那一段的耗时几乎占了单步时间的四成。我们试过调batch size、换并行策略、甚至把专家重新分布效果都不理想。后来有同行提到DeepSeek开源周第二天放出来的DeepEP说是一个专门为专家并行Expert Parallelism简称EP设计的通信库抱着试试看的心态拉下来跑了一轮单步时间直接降了接近三成。这件事让我意识到MoEMixture of Experts架构的瓶颈早就不在计算本身了而在专家之间那堆看不见的“搬运工”上。DeepEP要解决的正是这个搬运效率的问题。它不是一个训练框架也不是一个模型结构而是一个通信中间件——专门负责在专家并行场景下把token高效地送到该去的专家那里再把结果收回来。用一句话概括DeepEP是面向MoE训练和推理的专家并行通信库核心目标是让all-to-all通信尽可能跑满带宽、尽可能少占SM资源。这篇文章适合谁看如果你正在跑MoE架构的模型不管是训练还是推理只要涉及多卡多机、专家分散在不同设备上DeepEP就值得你花时间研究。如果你只是听说过MoE但还没实际部署过这篇文章也能帮你理解专家并行里那些“看不见的成本”到底在哪。我会从设计思路、核心机制、实操配置、常见坑几个角度把DeepEP拆开讲清楚尽量让不同基础的读者都能拿到能用的东西。2. 专家并行的通信困局与DeepEP的设计取舍2.1 为什么传统all-to-all在MoE场景下不够用要理解DeepEP的价值得先搞清楚MoE训练里通信到底难在哪。普通数据并行或者张量并行通信模式相对规整要么是梯度all-reduce要么是激活值all-gather拓扑结构固定优化空间明确。但专家并行不一样它的通信是动态的、稀疏的、不规则的。具体来说每个token经过gate网络后会被路由到top-k个专家。这些专家分布在不同GPU上有的卡可能被分到很多token有的卡可能很少。这就导致all-to-all的通信量在设备之间严重不均衡。更麻烦的是token到专家的映射是训练过程中动态变化的你没法提前静态规划通信模式。传统all-to-all实现通常是基于NCCL的NCCL在规整通信上很强但面对这种细粒度、动态变化的模式它的调度开销和SM占用就成了问题。我实测过一个场景用NCCL做专家并行的dispatch和combine通信本身只花了不到10毫秒但等待和调度占了将近30毫秒。这中间的差距就是DeepEP想填的坑。2.2 DeepEP的核心设计思路把通信和计算解耦DeepEP的设计哲学可以用一句话概括让通信在后台跑计算在前台跑两者尽量不抢资源。这个思路听起来简单但实现起来涉及几个关键决策。第一个决策是使用NVLink和RDMA的分层通信。节点内走NVLink节点间走RDMA这是硬件层面的最优路径。DeepEP没有重新发明轮子而是把这两层通信做了更细粒度的调度。节点内通信延迟低、带宽高适合做细粒度的token交换节点间通信延迟高适合做批量聚合后的传输。DeepEP会根据专家分布自动选择路径不需要用户手动干预。第二个决策是SM资源的精细管理。在GPU上通信操作通常需要占用SMStreaming Multiprocessor来执行。如果通信占用的SM太多计算就会饿死如果太少通信又跑不满带宽。DeepEP通过一个叫“通信SM配额”的机制把通信占用的SM数量控制在一个可配置的范围内剩下的SM全部留给计算。这个配额不是固定的而是根据当前通信量和计算量动态调整。第三个决策是低延迟模式与高吞吐模式的分离。训练场景下吞吐优先DeepEP会把多个小包聚合成大包再发减少协议开销推理场景下延迟优先DeepEP会尽量让每个token尽早到达目标专家哪怕牺牲一点带宽利用率。这两种模式在API层面是分开的用户可以根据场景选择。2.3 和同类方案相比DeepEP的差异化在哪市面上做专家并行通信的方案不止DeepEP一个但大多数要么是框架内置的比如某些训练框架自带的EP通信要么是通用通信库的扩展。DeepEP的差异化主要体现在三个地方。第一是它对MoE场景的针对性。通用通信库做all-to-all通常假设通信模式是静态的或者半静态的但MoE的通信模式是每个batch都在变的。DeepEP在API设计上就考虑了这种动态性支持运行时动态调整通信计划不需要重新编译或者重启。第二是它对SM占用的控制。这一点在实际训练中特别重要。我见过不少团队为了跑通MoE把通信SM配额调到很高结果计算效率掉得厉害。DeepEP把这个配额做成了可配置项而且提供了监控接口你可以实时看到通信占了多少SM、计算占了多少方便调优。第三是它对推理场景的优化。很多EP通信方案只考虑训练推理场景下batch size小、延迟敏感那些方案就不太适用。DeepEP专门做了低延迟模式在推理场景下能把端到端延迟压到比较理想的水平。3. 核心机制拆解DeepEP内部到底怎么跑3.1 Dispatch与Combine两个阶段的通信逻辑DeepEP的通信流程可以拆成两个阶段Dispatch和Combine。Dispatch阶段负责把token从原来的位置发送到目标专家所在的GPUCombine阶段负责把专家计算完的结果收回来还原成原来的顺序。这两个阶段的通信模式是不一样的。Dispatch是“多对多”的散射每个GPU都可能向多个GPU发送不同数量的tokenCombine是“多对一”的聚集每个GPU从多个GPU收集结果。DeepEP对这两个阶段用了不同的优化策略。Dispatch阶段的关键是分组打包。如果每个token单独发一个消息协议开销会大到无法接受。DeepEP会把发往同一个目标GPU的token聚合成一个包一次性发过去。聚合的粒度是可配置的太小了协议开销大太大了延迟高。我一般建议从默认值开始根据实际通信量微调。Combine阶段的关键是顺序还原。token在Dispatch阶段被打乱了顺序Combine阶段需要把它们放回原来的位置。DeepEP用一个索引映射表来记录每个token的原始位置Combine时根据这个表做重排。这个表是在Dispatch阶段生成的存在显存里开销很小。3.2 低延迟模式与高吞吐模式的切换逻辑DeepEP提供了两种通信模式分别对应推理和训练场景。这两种模式的底层实现差异挺大的理解它们的切换逻辑对调优很有帮助。高吞吐模式的核心是“攒批”。它会等待一段时间把多个小包攒成一个大包再发。等待时间是可配置的默认值大概在几十微秒量级。这个等待时间换来的收益是协议开销大幅降低带宽利用率能跑到90%以上。代价是延迟增加但对于训练场景来说这点延迟完全可以接受。低延迟模式的核心是“直发”。每个token生成后立即发送不做攒批。这样延迟最低但协议开销大带宽利用率可能只有高吞吐模式的一半左右。在推理场景下端到端延迟比带宽更重要所以这个取舍是合理的。切换逻辑不是非此即彼的。DeepEP允许在同一个任务里混合使用两种模式比如Dispatch用高吞吐模式Combine用低延迟模式。这种混合模式在某些场景下能取得不错的平衡。具体怎么配后面实操部分会详细说。3.3 SM配额机制通信和计算如何不打架SM配额是DeepEP里最值得细说的机制之一。在GPU上SM是执行所有计算和通信指令的基本单元。如果通信占用的SM太多计算kernel就没法充分并行如果通信占用的SM太少通信带宽又跑不满。DeepEP的做法是给通信分配一个固定的SM配额剩下的全部留给计算。这个配额是通过一个叫num_sms的参数控制的。默认值通常是GPU总SM数的一小部分具体多少取决于GPU型号和通信量。我实测下来在A100上把num_sms设在8到16之间比较合适在H100上可以稍微高一点因为H100的SM数量更多。配额不是设完就不管了。DeepEP提供了一个监控接口可以实时查看通信SM的利用率。如果发现通信SM经常跑满说明配额不够需要调高如果通信SM利用率很低说明配额过剩可以调低把更多SM留给计算。这个调优过程需要结合具体任务来没有万能值。注意调SM配额的时候不要一次调太多每次调整幅度建议控制在总SM数的5%以内。调完跑几百个step看稳定性再决定下一步怎么调。4. 实操配置从零跑通一个DeepEP示例4.1 环境准备与依赖检查在跑DeepEP之前有几项环境依赖需要确认。这些依赖不是DeepEP独有的但版本不匹配会导致各种奇怪的问题。首先是CUDA版本。DeepEP对CUDA版本比较敏感建议用11.8或12.x。太老的版本可能缺少某些通信原语太新的版本可能还没适配。我一般用12.1实测比较稳。其次是NCCL版本。DeepEP底层依赖NCCL做节点内通信NCCL版本建议不低于2.18。可以用nccl --version查看当前版本。如果版本太低建议升级升级方法取决于你的环境这里不展开。然后是RDMA驱动。节点间通信依赖RDMA需要确认RDMA驱动已经正确安装并且ibstat命令能正常输出。如果ibstat报错说明RDMA环境有问题需要先解决这个。最后是Python环境。DeepEP提供了Python绑定需要Python 3.8以上。建议用conda创建一个独立环境避免和系统Python冲突。conda create -n deepep python3.10 conda activate deepep pip install deepep安装完成后可以用一个简单的脚本验证是否安装成功import deepep print(deepep.__version__) print(deepep.get_device_count())如果输出了版本号和GPU数量说明安装没问题。4.2 关键参数配置与计算过程DeepEP的配置参数不多但每个都很关键。下面这张表列出了最常用的几个参数以及它们的含义和推荐值。参数名含义推荐值调整建议num_sms通信占用的SM数量8-16根据通信量动态调整buffer_size通信缓冲区大小256MB通信量大时调高mode通信模式auto训练用high_throughput推理用low_latencygroup_size聚合粒度32太小协议开销大太大延迟高timeout_ms通信超时时间5000网络差时调高这些参数里num_sms和buffer_size是最需要关注的。num_sms的调整逻辑前面说过了这里重点说buffer_size。buffer_size决定了通信缓冲区能容纳多少token。如果缓冲区太小token会排队等待增加延迟如果太大会浪费显存。计算buffer_size的一个经验公式是buffer_size max_tokens_per_batch * hidden_size * dtype_size * safety_factor其中safety_factor一般取1.5到2.0留出余量。举个例子如果单batch最多有4096个tokenhidden_size是4096用fp162字节那么buffer_size 4096 * 4096 * 2 * 1.5 50MB这是单个方向的大小Dispatch和Combine各需要一个所以总共需要100MB左右。实际配置时可以稍微留大一点比如128MB。4.3 一个完整的训练场景配置示例下面是一个训练场景的完整配置示例基于我实际跑过的一个MoE任务。这个任务有64个专家分布在8台机器上每台机器8张GPU。import deepep # 初始化DeepEP deepep.init( num_experts64, num_gpus64, hidden_size4096, num_sms12, buffer_size256 * 1024 * 1024, # 256MB modehigh_throughput, group_size32, timeout_ms5000 ) # 配置专家分布 expert_layout deepep.ExpertLayout( num_experts_per_gpu8, expert_parallel_size8, data_parallel_size8 ) # 注册通信计划 deepep.register_plan(expert_layout) # 在训练循环中调用 for batch in dataloader: # Dispatch阶段 dispatched_tokens, indices deepep.dispatch( tokensbatch[input_ids], gate_logitsbatch[gate_logits], top_k2 ) # 专家计算这里省略具体计算逻辑 expert_outputs expert_compute(dispatched_tokens) # Combine阶段 combined_outputs deepep.combine( expert_outputsexpert_outputs, indicesindices ) # 后续处理 loss compute_loss(combined_outputs, batch[labels]) loss.backward()这个配置里num_sms12是经过几轮调优后确定的。一开始用默认值8发现通信SM经常跑满训练速度上不去调到16之后计算效率又掉了。最后定在12通信和计算比较平衡。4.4 推理场景的低延迟配置推理场景的配置和训练场景差别挺大。推理时batch size小延迟敏感所以要用低延迟模式而且group_size要调小避免攒批带来的延迟。deepep.init( num_experts64, num_gpus8, hidden_size4096, num_sms4, # 推理时通信量小SM配额可以调低 buffer_size64 * 1024 * 1024, # 64MB modelow_latency, group_size1, # 不攒批每个token直发 timeout_ms1000 )推理场景下num_sms可以调得比较低因为通信量本身不大。group_size1意味着每个token单独发送延迟最低。代价是协议开销大但在推理场景下这个代价可以接受。提示推理场景下建议开启DeepEP的延迟监控功能可以实时看到每个token的端到端延迟。如果发现某些token延迟特别高可能是专家分布不均衡导致的需要调整专家布局。5. 常见问题与排查技巧实录5.1 通信超时与死锁的排查思路通信超时是DeepEP使用中最常见的问题之一。表现是训练卡住不动日志里出现timeout相关的报错。这个问题通常有几个原因排查时按顺序来。第一个要检查的是网络连通性。用ping和ibstat确认节点之间能正常通信。如果RDMA链路有问题DeepEP会回退到TCP延迟会高很多严重时直接超时。第二个要检查的是专家分布是否均衡。如果某些GPU上的专家特别多通信量会严重不均衡导致部分GPU等待时间过长。可以用DeepEP提供的get_expert_load()接口查看每个GPU的负载情况。第三个要检查的是timeout_ms设置是否合理。默认5000毫秒在大多数场景下够用但如果网络质量差或者通信量特别大可能需要调高。我遇到过一个大batch场景默认超时不够调到10000毫秒后问题解决。第四个要检查的是是否有死锁。DeepEP的Dispatch和Combine必须成对调用如果某个GPU上Dispatch了但没有Combine其他GPU会一直等。这种情况通常是因为代码逻辑有分支某些条件下跳过了Combine。排查方法是加日志确认每个GPU都执行了完整的Dispatch-Combine流程。5.2 带宽利用率上不去的调优方法带宽利用率低是另一个常见问题。表现是通信时间比预期长但延迟并不高说明带宽没跑满。这个问题通常和配置有关。首先检查group_size是否太小。如果group_size1每个token单独发协议开销会吃掉大量带宽。训练场景下建议至少设到16最好32。我实测过group_size从1调到32带宽利用率能从40%提升到85%以上。其次检查buffer_size是否够大。如果缓冲区太小token会排队带宽利用率也会受影响。可以用deepep.get_buffer_usage()查看缓冲区使用率如果经常超过80%说明需要调大。然后检查SM配额是否足够。如果num_sms太小通信kernel没法充分并行带宽也跑不满。可以逐步调大num_sms观察带宽利用率的变化。但注意不要调太大否则会影响计算。最后检查网络拓扑。如果节点间通信走了非最优路径带宽会受限。可以用deepep.get_topology()查看当前的通信拓扑确认节点内走NVLink、节点间走RDMA。5.3 显存占用异常的定位与解决DeepEP本身占用的显存不多主要是通信缓冲区和索引表。但如果配置不当显存占用会异常增长。最常见的原因是buffer_size设得太大。256MB的缓冲区在大多数场景下够用但如果设到1GB以上会明显挤占模型显存。建议从128MB开始根据实际通信量调整。第二个原因是索引表没有及时释放。Dispatch阶段生成的索引表在Combine之后应该被释放但如果代码里有引用没断开这块显存会一直占着。排查方法是看显存占用是否随着step增加而持续增长如果是大概率是索引表泄漏。第三个原因是通信缓冲区没有复用。DeepEP默认会复用缓冲区但如果配置了多个通信计划每个计划可能会分配独立的缓冲区。建议尽量用一个通信计划避免不必要的显存开销。下面这张表总结了常见问题、可能原因和解决方法方便快速查阅。问题表现可能原因解决方法通信超时网络问题、专家不均衡、超时设置太小检查网络、调整专家分布、调大timeout_ms带宽利用率低group_size太小、buffer_size不够、SM配额不足调大group_size、调大buffer_size、调大num_sms显存占用异常buffer_size太大、索引表泄漏、缓冲区未复用调小buffer_size、检查引用、合并通信计划训练速度慢SM配额不合理、通信模式选错调优num_sms、切换high_throughput模式推理延迟高用了高吞吐模式、group_size太大切换low_latency模式、调小group_size5.4 几个我踩过的坑和对应的经验第一个坑是在训练初期就调优SM配额。训练初期通信量小调出来的配额在训练后期可能不够用。建议等训练稳定后再调或者用动态配额。第二个坑是忽略了专家分布的均衡性。专家分布不均衡会导致部分GPU通信量特别大成为瓶颈。建议在初始化时就做好专家布局尽量让每个GPU的负载接近。第三个坑是在推理场景用了训练配置。推理和训练的通信模式差别很大直接套用训练配置会导致延迟高得离谱。一定要根据场景选择对应的模式。第四个坑是没有监控通信SM利用率。调优SM配额时如果不看监控数据就是盲调。建议开启监控根据数据来调。提示DeepEP的监控接口默认是关闭的需要在init时设置enable_monitorTrue。开启后会有少量性能开销但调优阶段建议开着。6. 从训练到推理DeepEP的适用边界与扩展思路6.1 什么场景下DeepEP最值得用DeepEP不是万能的它在某些场景下收益很大在另一些场景下可能还不如默认方案。根据我的经验以下几种场景最值得用DeepEP。第一种是专家数量多、跨节点通信量大的训练任务。如果专家数量少于16个且都在同一节点内NVLink本身就能跑得不错DeepEP的收益有限。但如果专家数量超过32个跨节点通信成为瓶颈DeepEP的优势就体现出来了。第二种是延迟敏感的推理任务。推理场景下端到端延迟比吞吐更重要。DeepEP的低延迟模式能把延迟压到比较理想的水平比默认的all-to-all实现好不少。第三种是通信和计算需要精细平衡的任务。如果任务对计算效率要求很高不能容忍通信占用太多SMDeepEP的SM配额机制就很有用。你可以精确控制通信占多少资源剩下的全部留给计算。反过来如果专家数量很少、都在单节点内或者任务对延迟完全不敏感用DeepEP的收益就不大直接用框架自带的通信方案可能更省事。6.2 后续可以扩展的方向DeepEP目前还在快速迭代中有几个方向值得关注。第一个是自适应SM配额。目前SM配额是静态配置的需要手动调优。未来如果能根据实时通信量和计算量自动调整会省去很多调优工作。第二个是更细粒度的通信调度。目前DeepEP的调度粒度是GPU级别的未来如果能做到SM级别甚至warp级别通信效率还能进一步提升。第三个是和推理引擎的深度集成。目前DeepEP在推理场景下需要手动配置如果能和主流推理引擎深度集成用起来会更方便。第四个是更多硬件平台的支持。目前DeepEP主要针对NVIDIA GPU优化未来如果能支持更多硬件平台适用范围会更广。6.3 一个实际调优案例的完整记录最后分享一个我实际调优的案例。任务是一个64专家的MoE模型8台机器每台8张A100。初始配置用默认值单步时间大约420毫秒其中通信占了180毫秒。第一轮调优把group_size从默认的16调到32通信时间降到150毫秒单步时间降到390毫秒。第二轮调优把num_sms从8调到12通信时间降到120毫秒单步时间降到360毫秒。第三轮调优把buffer_size从128MB调到256MB通信时间降到100毫秒单步时间降到340毫秒。第四轮调优检查专家分布发现有几个GPU负载偏高重新调整专家布局后通信时间降到85毫秒单步时间降到325毫秒。最终单步时间从420毫秒降到325毫秒降幅约23%。这个收益在MoE训练里算是相当可观了。整个调优过程花了大约两天主要是反复跑实验和看监控数据。这个案例说明DeepEP的调优不是一蹴而就的需要结合监控数据逐步调整。每个参数的影响不是独立的调完一个参数后可能需要重新调另一个。建议每次只调一个参数观察效果后再调下一个。注意调优过程中一定要固定其他变量比如batch size、学习率等否则无法判断性能变化是调优带来的还是其他因素导致的。7. 一些个人体会DeepEP给我的最大感受是它把专家并行通信这件事从“黑盒”变成了“白盒”。以前用框架自带的EP通信出了问题只能猜现在有了DeepEP的监控接口和可配置参数能看清楚通信到底卡在哪、资源到底怎么分配的。这种透明性对于调优来说太重要了。另一个感受是DeepEP的设计思路值得借鉴。它没有试图重新发明通信原语而是在现有硬件和通信库的基础上做更细粒度的调度。这种“在约束下找最优”的思路比追求理论最优更实用。如果你正在跑MoE任务我建议至少花半天时间把DeepEP跑通看看在你的场景下能带来多少收益。即使最终不用理解它的设计思路对优化自己的通信方案也有帮助。调优的时候别急一次调一个参数多看监控数据慢慢就能找到适合自己任务的配置。