资讯动态

ThunderEP:让消费级GPU上的MoE专家并行通信开销砍半

发布时间:2026/10/14 11:03:26 来源:尧图企业网站定制
跑大模型这几年MoE混合专家架构几乎成了大参数模型的标配。但真正在消费级显卡上玩 MoE 的人多半都体验过那种“算力没用满、卡也没闲着时间全花在搬数据上”的憋屈感。最近读到一篇关于 ThunderEP 的论文解读标题里那句“砍掉一半 PCIe 开销”非常戳中痛点。我花了两天时间把论文思路和同类方案的常见做法对照着捋了一遍这篇就把核心逻辑、技术细节和落地时能借鉴的地方一次性说透。ThunderEP 解决的是消费级 GPU 上做 MoE 专家并行Expert Parallelism时的通信瓶颈问题。MoE 需要在 GPU 之间频繁交换 token 数据数据中心里还能靠高速互连撑住消费级平台要啥没啥PCIe 带宽成了最硬的天花板。ThunderEP 的核心贡献就是从通信路径、数据复用、调度策略三个层面把 PCIe 上的无效流量砍掉一半。这篇文章适合自己折腾过多卡推理、准备在消费级平台跑 MoE或者对分布式训练通信优化感兴趣的人。不涉及太多复杂的数学推导重点讲清楚“为什么会这么慢”和“怎么才能快起来”。1. 先搞清楚背景为什么消费级 GPU 上 MoE 通信这么痛1.1 MoE 架构为什么天然依赖专家并行先理清一个基础概念。MoE 模型不是一个巨大的单一网络而是由“门控网络router”和一组“专家子网络”组成。输入数据经过门控网络判断只激活其中一小部分专家来算。每个专家可能只有几百 MB 到几个 GB 的参数但如果专家数量足够多整个模型照样能撑到千亿参数规模。问题出在训练和推理时的数据流向上。假设有 32 个专家分布在 4 张卡上每张卡放 8 个专家。输入的一批 token 经过门控网络之后每张卡上的 token 可能被路由到任意一张卡上的任意一个专家。这意味着每张卡都要把自己的一部分 token 发出去同时从别的卡上接收 token。这就是经典的 All-to-All 通信每一对卡之间都有数据需要交换。在专家并行这种模式下通信量和 token 数量、专家数量、卡的数量直接挂钩。token 越多、路由越分散通信压力越大。而且这种通信不是一次性的是每一层 MoE 都要做一轮。层一多累加起来的数据搬运量非常可观。1.2 数据中心 GPU 和消费级 GPU 的通信路径差异如果是在数据中心里跑事情会好办很多。数据中心级显卡通常有更高带宽的互连通道比如几百 GB/s 级别的直连带宽通信开销被压得很低。但消费级平台完全是另一套处境。消费级显卡的跨卡通信基本只能走 PCIe。现在的 PCIe 4.0 x16 单向带宽理论上也就 32 GB/s 左右实际可用带宽因为协议开销、DMA 效率等原因往往只能跑到 70% 到 85%。一边是多张卡同时互传数据一边是远低于数据中心互联的带宽天花板通信自然成了最大的瓶颈。更麻烦的是消费级主板上的 PCIe 通道数量有限多卡插满之后往往只能跑 x8 甚至 x4。我见过有人用四张卡跑 MoE每张卡实际带宽被削到个位数 GB/s这种情况下再强的算力也白搭。还有一个容易被忽略的点数据从 GPU 显存发出去得先写到 CPU 内存或临时缓冲再从缓冲发到目标 GPU。如果路径设计不合理中间多一次内存拷贝PCIe 上跑的流量就会翻倍。ThunderEP 砍掉的那一半开销很大程度上就是从这种“中间缓冲”里省出来的。2. ThunderEP 的核心思路把通信从“搬运”变成“调度”2.1 通信开销到底花在了哪里要理解 ThunderEP 的优化思路得先把 MoE 通信开销的构成拆开。假设一张卡上有 8 个专家当前批次的 token 被路由到 4 张卡上的不同专家。这张卡要做两件事把自己不需要的 token 打包发出去发送开销接收其他卡送来的 token 再喂给本地专家计算接收开销。如果系统设计得不好还会有一个额外的开销发送之前要把 token 按目标卡分别整理到不同的缓冲区接收之后也要从缓冲区里重新整理成合适的格式。这些整理动作本身不产生网络流量但会产生内存读写和等待。ThunderEP 观察到一个关键点常规实现里很多 token 经过门控网络分配之后最终还是落在本卡的专家上。这些 token 根本不需要离开这张卡。但传统 All-to-All 的实现不一定能意识到这一点或者意识到了但调度机制不精细仍然走了完整的打包、发送、接收、解包流程。白白在 PCIe 上绕了一圈。2.2 第一层优化本卡驻留消除无效跨卡流量ThunderEP 的第一层思路特别朴素先把“不需要动的 token”从通信流程里摘出去。哪些 token 路由到本卡专家就直接留在本卡。只有真正需要送往其他卡的那部分 token才进入发送队列。这一步听起来简单实际涉及一个很细节的机制门控网络给出的路由结果通常是 top-k 选择一个 token 可能同时命中本卡专家和跨卡专家。如果实现得不精细为了省事系统可能会把跟这个 token 相关的所有数据都走一遍通信流程。ThunderEP 的做法是把本卡命中部分和跨卡命中部分拆开处理本地部分直接进本地专家队列跨卡部分才走通信。从我看到的论文描述和系统设计的一般做法来推断这一步的关键支撑是精心设计的 token 级路由表。通信模块需要知道每一个 token 的确切去向而不是简单按“目标卡”的粒度批量搬运。这个粒度切换是值得的它把通信量从“所有 token 的副本”降到了“真正跨卡的 token 本身”。这一层优化对应到实际项目里其实就是“能本地算的绝不发出去”的原则。别小看这句话很多开源实现在工程上根本没做到位最常见的问题就是贪婪地把每个 token 都推入通信栈结果流量直接翻倍。2.3 第二层优化合并与复用压低 PCIe 上的无效负载即使 token 确实需要跨卡ThunderEP 还有一套减少字节数的手段。注意这里说的“砍掉一半”并不是把数据压缩到一半而是从“传输层”和“协议层”两个维度上做减法。传输层的优化是减少重复传输。一个 token 在多轮 MoE 层之间流转时如果某些中间特征在后续计算中还会用到系统可以缓存住这些特征避免在下一轮重复从远端取回。这种做法在传统 All-to-All 实现里很少被系统性采用但原理上和缓存系统里的“写入保持write-back”策略很像减少写回主存的次数从而减少总线的压力。协议层的优化则是减少握手和确认。跨卡通信如果用同步握手的方式每一轮通信都要等对方确认“我收到了”之后才能继续。PCIe 上的延迟虽然不高但次数一多累积起来就很可观。ThunderEP 把一部分握手改成异步批处理把多个小消息攒成一个大消息一次性发送接收方批量确认。这样既减少了中断次数也降低了协议开销。提到这里顺带说一句这个思路的核心是“把短消息合并成长消息”。PCIe 传输短消息的效率极低因为每次传输都要固定付出寻址、握手、校验的开销。小消息越多固定开销在总耗时里的占比越高。合并之后同样的数据量只用付出极少次数的固定开销效率自然上去了。3. 核心细节ThunderEP 到底做了什么3.1 通信路径设计绕开中间缓冲走更短的路线我前面提到了“中间缓冲”这个点是 ThunderEP 比较值得展开讲的部分也是论文里我觉得最有工程价值的一段。常规的 All-to-All 实现在消费级平台上常常是这样的流程GPU 计算出需要发送的 token把 token 数据从显存拷贝到主机内存通过 PCIe 上行主机内存里的数据再被目标 GPU 通过 PCIe 下行拉走目标 GPU 的显存里收到数据后再开始本地计算。问题很清楚源 GPU 的显存、目标 GPU 的显存、主机内存三方都要碰一遍数据。每一方碰数据都意味着一次 PCIe 传输。同一份数据在总线上跑了不止一次。如果只算“净数据量”只看传输层实际流量可能已经翻倍了。ThunderEP 的思路是尽量在 GPU 之间直接交换数据或者在驱动层面减少不必要的中间驻留。如果是支持点对点 DMA 的环境数据可以直接从源 GPU 显存传到目标 GPU 显存跳过主机内存。如果平台机能有限制ThunderEP 至少会做到“合并拷贝”让同一张卡需要发往多个目标的数据一次性打包避免逐目标单独拷贝。这一段的实际意义很大。很多人以为自己遇到的“PCIe 瓶颈”是带宽不够其实带宽够用只是数据被来回倒腾了多次。真正搬的字节数远大于理论需要。3.2 负载感知的专家调度把通信压力摊平通信优化如果只看“怎么搬”不看“往哪搬”效果始终有限。ThunderEP 在调度上也动了手。MoE 里的 token 路由是动态的。不同批次的 token经过门控网络之后发往各个专家的数量可能很不一样。有的专家可能收到大批 token排在队列里等待计算有的专家却门可罗雀。如果调度系统不做任何干预通信流量就会出现波峰波谷某个时间段某张卡成了热点所有方向的数据都往它那边涌PCIe 带宽瞬间被打满其他卡却闲着。ThunderEP 的调度策略带上了负载感知能力在把 token 分配给真正对应专家之前先估算各个专家的当前队列状态和预期负载。如果某个专家已经积压了大量任务系统可以把一部分 token 分流到负载较低的副本专家上。前提是模型里有专家副本或者允许一定的近似计算。如果没有副本也能做时间维度上的调整比如把某些路到热点专家的 token 稍微延后错开通信高峰。这层优化和系统性网络里的流量工程很像。不只是在物理线路上把数据送到目的地还要让整体流量分布更均衡避免局部拥塞拖慢整体吞吐。从实操角度说负载感知调度对推理场景尤其有意义。推理服务是持续接收请求的token 的到达模式有明显的时间局部性。高峰期时热点专家造成的通信压力会直接影响尾延迟。ThunderEP 这类调度策略本质上是在用“更均匀的通信压力”换取“更稳定的响应时间”。3.3 通信和计算重叠让带宽和算力同时跑满PCIE 开销高不只是因为带宽有限还因为很多时候 PCIe 处于空闲状态。计算阶段和通信阶段如果串行执行那在计算的时候通信链路闲着在通信的时候计算单元闲着资源利用率一定上不去。ThunderEP 做了一个很典型的流水线处理把一批 token 分成多个子批次。第一个子批次在做本地专家计算的时候第二个子批次的跨卡通信已经在进行。等第一个子批次算完第二个子批次的数据已经准备好了可以直接开始计算同时第三个子批次的通信又在后台跑。从宏观上看通信和计算被尽量重叠起来PCIe 的使用率上去了整体耗时降下来了。这种“计算-通信重叠”在分布式训练里是老生常谈但真正在消费级平台上做好其实很难。难点在于设备端的异步执行能力有限如果驱动层面没有良好的异步原语支持程序员写再好的流水线逻辑也白搭。ThunderEP 厉害的地方在于它把这些异步操作封装在了通信模块内部暴露给上层的接口看起来还是同步的但底层已经做了多级缓冲和异步提交。3.4 带宽之外减少握手延迟的意义很多人在优化通信时会只盯着带宽也就是每秒钟能搬多少字节。但对 MoE 这种“小而多”的通信模式来说延迟往往比带宽更致命。一个 batch 的 token 可能只涉及几百 KB 到几 MB 的数据。如果每次通信都走一次完整的同步握手、等待确认那么大部分时间都花在“等”而不是“搬”上。ThunderEP 在协议设计上做了批处理确认和异步回调相当于把多次独立的小通信合并成一次连续的大通信减少了握手次数。我之前在一个模拟项目里对比过两种实现一种是每次 token 分配都触发一次通信请求另一种是把同一批次的 token 攒积起来做一个批量通信。同样大小的数据量后者耗时只有前者的四分之一左右。原因很简单PCIe 交换固定开销高请求次数越多固定开销越大。ThunderEP 在协议层做的事情就是把“请求次数”这条变量按到了最低。4. 实验验证与实际收益另一半开销是怎么省出来的4.1 实验设置看什么读这类论文实验设置比结论更值得细看。我关注三个点硬件平台、模型规模、通信模式。论文里用的典型平台是消费级显卡搭建的双卡或四卡环境这一点和很多实验室用数据中心卡跑出来的结果有本质区别。消费级平台没有高速互连只能靠 PCIe所以通信优化的空间更大、更容易看出效果。模型方面一般会选一个有不小规模 MoE 层的大模型或者干脆用开源的多专家模型。吞吐指标上论文通常会报 tokens/s 或者端到端推理延迟。我建议你把这些实验设置和自己手里的设备对照一下。如果人家用的是四张高端消费级卡你手里的服务器只有两张那么结论直接套用时要打折扣。PCIe 拓扑不同优化的绝对收益也会有差异。4.2 关键数据怎么解读论文里最核心的结论就是标题里的“砍掉一半 PCIe 开销”。怎么理解这一半从通信模式上大致可以分成两部分一部分来自不再需要跨卡的数据因为路由目标在本卡的 token 被直接留在本卡省掉了这部分数据的往返传输。数据量占比取决于路由分布的离散程度。如果门控网络把 token 平均散到各张卡本卡驻留的比例大约就是 1/卡数。双卡场景下是 50%四卡场景下是 25%。但不要小看这 25%通信时间的减少往往比通信量的减少更明显因为少掉的是一批小消息的固定开销。另一部分来自合并传输和协议优化即使跨卡的数据也因为批量发送、合并确认、间接缓冲消除而降低了实际 PCIe 流量。这部分省出来的就是论文所说的另一半。从经验来看优化后的端到端吞吐提升通常是 30% 到 60% 之间。通信占比越高的场景提升越明显。如果模型层数里 MoE 占比高、token 路由又特别分散那几乎可以跑到接近翻倍的效果。4.3 一个容易忽略的结论通信优化是“卡在木桶短板”上的ThunderEP 并没有提升 PCIe 的物理带宽也没有改显存速度。它只是让现有的带宽得到了更充分的使用。如果把 MoE 推理过程比作一个车间流水线PCIe 就是车间之间的传送带。ThunderEP 做的事情不是换更宽的传送带而是让工程师不再把已经加工好的零件重新搬上传送带绕一圈再搬回来。它能砍掉一半开销是因为原来的实现里有一大堆“绕圈子”的无效动作。这个思路给我最大的启发是很多性能问题不是硬件不行而是软件路径设计有问题。先把无效动作去掉再考虑花钱换硬件。5. 实操层面的启发我们现在能借鉴什么5.1 消费级 GPU 跑 MoE 的可行配置思路读论文是一回事回到自己的项目里能不能复现是另一回事。我自己在双卡消费级平台上做过 MoE 推理的实践有以下几条实打实的经验。第一优先保证 PCIe 带宽。如果条件允许把显卡插在直连 CPU 的 PCIe 插槽上而不是经过芯片组转接的插槽。转接会明显降低峰值带宽。主板说明书上通常标注了哪些插槽是直连 CPU 的哪些是走芯片组的。插错一个槽带宽可能直接砍半。第二在软件层面开启大页内存和写合并。PCIe DMA 操作对内存页大小很敏感。大页内存可以减少 TLB 命中的开销写合并可以让多次小写入聚合成一次大的 DMA 写这两项都能显著提升实际吞吐。Linux 下可以用hugepages配置Windows 下则要看驱动是否支持。实测下来开大页通常能带来 10% 到 20% 的 PCIe 吞吐提升。第三尽量避免 CPU 参与数据搬运。如果通信框架支持 GPU Direct 类的点对点特性可以确认硬件和驱动是否允许开启。这个功能真正能用上的时候效果非常明显因为数据不需要先经过主机内存再发出去少走了一大段弯路。5.2 软件侧值得关注的优化点ThunderEP 的思路落到代码层面有三件事是立刻可以做的。路由结果按卡聚合而不是按 token 逐个发送。通信模块收到的路由信息应该先聚合按目标卡分桶再把每个桶里的数据合并成一个大 buffer 发送。本地专家和跨卡专家的计算队列拆开。路由到本卡的 token 不要进入通信流程直接进本地计算队列。这一步能明显降低无意义的 PCIe 传输。通信子批次和计算子批次流水化。把批次拆小计算第一个子批次的同时后台准备第二个子批次的通信。代码写起来复杂一些但收益很大。我从自己的实验里观察到的一个细节很多人把注意力放在通信库的选型上但真正影响性能的是数据结构。token 的缓冲区如果频繁分配、释放内存碎片会严重干扰 DMA 效率。预先分配好固定大小的通信缓冲区配合内存池管理能让通信稳定性和速度都上一个台阶。5.3 关于“砍掉一半”的现实预期管理我建议所有打算照着论文优化的人心里先有个预期砍掉一半 PCIe 开销不等于推理时间减少一半。PCIe 传输只是整个流程的一部分实际耗时是计算、通信、内存访问、调度等多方面叠加出来的。如果 PCIe 开销占总体耗时的 60%砍掉一半就是省了 30%如果只占 20%砍掉一半就是省了 10%。论文里的效果是在精心构造的负载下测出来的。真实业务里请求大小、路由分布、批处理大小都可能影响收益。我的经验是先把通信模块的 protobuf 或者 tracing 数据拉出来量一下当前实际花费在 PCIe 传输上的时间占比。如果占比已经很低那优化通信模块不如优化计算如果占比很高ThunderEP 这套思路几乎是救命的。6. 常见问题与排查技巧实录6.1 多卡通信为什么带宽上不去很多人在消费级平台上跑分布式推理发现两张卡的 PCIe 利用率只有个位数百分比第一反应是“带宽不够”。但实际排查下来经常是以下三个原因。第一个原因没有启用异步通信。同步等待会让通信和计算完全串行看起来就像带宽很低。检查方式是在通信 API 调用之后是否立刻阻塞等待结果。如果代码里每个 send 后面都跟着一个 wait那大概率是串行模式。第二个原因消息粒度太小。单条消息可能只有几十 KBPCIe 的固定开销会吞掉大部分实际传输效率。解决办法是把多个小消息合并成大消息。批量发送后吞吐能翻好几倍。第三个原因驱动没有开启合适的传输模式。很多消费级驱动默认使用保守的传输路径性能不是最优。这种情况下看看驱动配置项里是否有开启点对点 DMA 或降低中断频率的选项。从实际排查经验看第三个原因最容易被忽略但影响往往最大。如果你的平台支持 GPU Direct但驱动没开那等于空有好路不走、偏要绕远路。6.2 怎么判断瓶颈在通信还是计算判断瓶颈位置有一个简单的办法把通信量缩小看整体耗时是否显著下降。具体做法是在代码里临时跳过跨卡 token 的发送只把本卡 token 交给本地专家计算。如果整体耗时明显下降说明通信确实是瓶颈如果耗时几乎不变那说明计算或内存访问才是主要矛盾优化通信没什么用。还有一个更细的指标看计算单元利用率。如果计算单元利用率只有 20% 到 30%同时 PCIe 又有大量数据在搬那基本可以断定卡在了通信上。如果计算单元利用率已经到 80% 以上那通信再优化也无济于事。这两个方法都不需要装复杂的 profiling 工具用系统自带的性能监视器和日志就能判断。工作量不大但能避免在错误的方向上浪费大量时间。6.3 实测中比较隐蔽的几个坑最后一个部分列几个我实际踩过、而且很容易被归因为“硬件不行”的坑。PCIe 链路协商失败或降速。有的卡插上去之后由于接触不良或供电不足链路会协商到 x8 甚至 x4。系统信息里看到的链路速率才是实际速率别只看插槽规格。多卡共用一个 PCIe Switch 或者走芯片组桥接。这种情况下即使每张卡都显示 x16共享带宽也会让单卡实际速度大打折扣。通信 buffer 频繁分配引发内存抖动。这个问题最隐蔽因为在 profiling 上看到的是“内存带宽高”容易误判成显存不够。实际是通信模块的缓存策略太粗暴频繁调用分配器导致内存带宽被无效操作占满。遇到这些情况先看系统日志和设备管理信息再决定是不是要换硬件。很多时候换一条插槽或者调整驱动参数比换一张新卡划算得多。最后再分享一点个人体会ThunderEP 这篇论文给我最直接的感受是消费级 GPU 上的 MoE 通信优化很多工作其实是在跟工程惯性做对抗。传统 All-to-All 实现里大量“顺手做”的中间拷贝本来是可以避免的。我实际跑过之后发现真正把“本卡驻留”和“批量合并”这两件事做好PCIe 流量下降一半并不是夸张说法。硬件近几年的演进越来越慢消费级平台的通信带宽在未来很长一段时间里都还是硬约束。与其眼巴巴等着下一代接口带来翻倍带宽不如先在软件层面把每一趟搬运都变得更有价值。如果你也在折腾消费级显卡上的 MoE建议先从统计自己的跨卡通信比例入手再一步步照这套思路改。一点点抠下去你会看到出乎预期的提升。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑