尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

DeepSpeed ZeRO-3与MoE结合:大模型显存优化与训练调优实战

发布时间:2026/9/29 17:30:42

资讯中心
01
ARTICLE

DeepSpeed ZeRO-3与MoE结合:大模型显存优化与训练调优实战

DeepSpeed ZeRO-3与MoE结合:大模型显存优化与训练调优实战
1. 为什么ZeRO-3和MoE总是被放在一起聊先聊个我经常被问到的组合DeepSpeed、ZeRO-3、MoE。很多人一看到这三个词就晕了觉得这是三套完全不同的东西。其实它们的关系很简单——DeepSpeed是微软开源的一套大模型训练框架ZeRO-3是DeepSpeed里最核心的显存优化方案而MoEMixture of Experts混合专家是一种模型架构。你现在打开各大AI实验室的技术报告几乎都能看到这三者的名字同时出现。原因也很直接MoE模型参数动辄几千亿甚至上万亿不用ZeRO-3这类分片策略根本塞不进显存而ZeRO-3如果只用来跑普通稠密模型又发挥不出它真正的潜力。两者一结合反而各取所长。这篇内容我默认你是遇到过模型装不下显存或者单卡训练慢到怀疑人生的人。如果你正在做大模型预训练、微调或者前期做可行性评估这篇文章能帮你理顺原理层面最关键的几条线并且给出可以直接用在DeepSpeed里的配置思路和调优方向。我不打算给你备份一堆API文档那些你在官方文档里都能查到。我更想讲清楚的是为什么ZeRO-3能把几百B的模型装进显存为什么MoE的结构天生适合分片以及这两者配合时真正要注意的坑在哪里。2. 从数据流看ZeRO-3它到底在哪儿省了显存2.1 传统数据并行为什么先撑不住要理解ZeRO-3得先知道不用它的时候显存死在哪儿。假设你在8张80GB的A100上训练一个70B稠密模型用最普通的数据并行DDP。每个节点都复制一份完整的模型参数、梯度和优化器状态然后在反向传播结束时做一次梯度同步。算一下你就会发现70B模型仅参数用FP16存储就是140GB加上梯度、Adam优化器里的FP32状态一份完整的副本就奔着400GB去了。单卡80GB连参数都放不下更别谈训练。很多人第一反应是那我用模型并行Tensor Parallelism加流水线并行Pipeline Parallelism。这确实是一条路但它要求你手动切分注意力头、切分MLP层并且要精心设计通信顺序工程复杂度很高。ZeRO的思路不一样它不做手动切分而是把训练过程中三种关键状态——优化器状态、梯度、参数——分别切分到所有设备上谁需要谁取回来。显存压力瞬间从每卡存全量变成每卡存1/N份。2.2 三阶段演进从只切优化器到全部切分ZeRO是有阶段的很多人只记得名字分不清边界。简单梳理一下ZeRO-1只切优化器状态。模型参数和梯度仍然每卡各存一份但优化器状态如Adam里的momentum和variance被切成N份分散到N张卡上。单卡显存占用大约降25%~30%。ZeRO-2在ZeRO-1基础上把梯度也切分了。反向传播过程中梯度不再每卡冗余保存而是各自只留属于自己那部分的梯度片段。显存占用进一步降低通信开销略有增加。ZeRO-3最狠的一刀——模型参数本身也被分片了。每张卡只持有全部参数的1/N。进行前向或反向需要完整权重时通过全集合通信All-Gather把缺失的部分临时拉齐算完立即释放。这里有个关键点很多人忽略ZeRO-3省的不是显存总量而是单卡峰值显存。如果只看整个集群的总显存其实参数、梯度、优化器状态都还在只是被拆散了。这也回答了那个经常有人问的问题——moe架构要全部参数进显存吗——不需要甚至稠密模型在ZeRO-3下也不需要。显存里只存分片用的时候才聚合用玩就丢。2.3 All-Gather聚合与释放的完整循环我手动推演一遍ZeRO-3跑了forward时发生的事情你就有感觉了。假设现在输入一个batch的数据模型有一个Transformer层参数W已经被切成8份分布在8张卡上每张卡触发一次All-Gather操作把W的8份分片从所有卡收集过来在当前卡上临时拼成一份完整的W。当前卡用完整W计算出本卡的激活值。前向传播结束后立即释放临时拼出来的完整W只保留自己原来的那1/8分片。反向传播同理梯度算到某一层时再All-Gather一次完整W用来计算该层的梯度算完再释放。这个机制层数越多、参数越大节省的显存越可观。但代价也明显——因为每层的计算都依赖一次全量通信通信量会显著上升。所以ZeRO-3不是万灵药它把显存压力转移成了带宽压力。如果你的机器网络带宽很差比如只有千兆网卡那这种频繁All-Gather会导致通信时间占整个训练时间的比例飙升甚至出现卡在通信上的假死现象。3. MoE架构为什么让训练又爱又恨3.1 稀疏激活的算账逻辑MoE的全称是Mixture of Experts。听名字很玄拆开看就是一个门控网络Router/Gating加一堆专家子网络Experts。以Switch Transformer为例每个Token进来先经过门控门控会计算这个Token与哪个专家最匹配然后只激活得分最高的Top-1或Top-2专家。没有匹配到的专家完全不参与计算这就是稀疏激活。模型是个纯稠密结构的70B你看一眼就知道它前向要走整个模型的计算路径。但如果是MoE假设它总参数700B包含64个专家每个Token只走其中1个专家那这个Token实际参与计算的参数量大概是10B左右。也就是说参数量上去了10倍计算量只上去了一点点。这账可以说是划算得很。但这里有个容易误会的点——MoE算力省了不等于显存省了。所有专家参数还是得有个地方放着内存占用依然按照总参数量计算。所以MoE模型要能训练起来显存优化反而比稠密模型更依赖ZeRO-3。但好消息是MoE的专家部分天然适合分片每个专家是一个独立的FFN块Token并不依赖所有专家的权重同时出现在同一张卡上只需要在门控决定的那个瞬间把那个专家的权重取出来就算完了。这个特性与ZeRO-3按需聚合、用完即放的哲学是完全一致的。3.2 训练MoE时最头疼的事负载失衡MoE理论很美训起来一堆坑。第一个坑就是不均衡。门控网络如果真的只看语义相似度很容易出现赢家通吃的情况——比如训练语料里通用拼写、标点占比很大大部分Token都被同一两个专家抢走了剩下几十个专家闲着看热闹。这样不但浪费了算力而且会形成一个恶性循环被压中的专家拿到更多梯度训练更快下一次被压中的概率更高。业界标准解法是加辅助负载均衡lossaux load-balancing loss。Switch Transformer的版本是给每个Token计算期望路由概率分布与真实分布之间的差异把这个差异加一个系数乘以交叉熵或KL散度作为惩罚项叠到总loss里。这个系数非常敏感我见过0.001到0.01之间能派生出完全两种收敛曲线。系数太大会导致路由过于均匀完全丧失稀疏性优势太小又起不到均衡作用只能眼巴巴看几个专家累成狗。如果是DeepSpeed原生实现MoE除了aux loss还要关注capacity factor。这个概念可以理解为每个专家最多能容纳多少Token。实际训练中经常出现一个专家收到的Token数超过预设容量超出的Token会被丢到残差连接里绕过专家。Capacity factor调大了能降低token drop比例但每个专家的batch会变大计算变慢调小了又可能出现大量drop导致这个专家根本没有学到东西。我建议在前期把capacity factor设为2.0左右跑一个小数据集验证逻辑没问题再逐步往下压寻找性能和loss的平衡点。3.3 门控本身的权重更新陷阱还有一个容易被忽略的地方——门控网络是不是太容易过拟合。门控是整个MoE的决策中枢但它自己只有很小的一组参数如果训练初期数据分布不均门控很快会偏向高频专家。一个实用的做法是给门控增加温度参数temperature scaling即在路由打分前除以一个温度值增大分布的平滑度让低分专家也有概率被选到。温度太低路由很决绝后续load balancing loss也救不回来模型容易早期就崩掉温度太高路由近乎随机MoE退化成均匀混合ensemble稀疏性意义全无。我从实操经验来看0.8到1.2这个区间是你应该先试的范围。4. DeepSpeed里ZeRO-3与MoE的协同配置实战4.1 为什么DeepSpeed的MoE实现选的是分片路DeepSpeed里支持MoE训练不是新故事但它在设计上很聪明地走了参数分片专家分组的路子。它把专家按组expert groups切分到不同的GPU上每个GPU只负责一小部分专家。当一个Token经过门控后如果目标专家在别的设备上就通过All-to-All通信把Token送到目标设备上计算结果再传回来。这个过程叫做Token级路由是沿着数据维度切而不是沿着模型维度切。为什么不用前面说的All-Gather因为Token去往哪个专家不定。如果All-Gather把专家参数取到本地那你本卡既存的专家权重还是留着所有参数依然冗余存一遍反而破坏分片意义。Token级路由的好处是只在数据层面搬运Token权重依然保持分片状态。所以在DeepSpeed训练MoE时你看到的画面常常是一部分通信是ZeRO-3的参数All-Gather另一部分是MoE的Token All-to-All。两者交叉进行通信结构比纯稠密更复杂。4.2 ZeRO-3核心配置片段解读直接上配置我用的是DeepSpeed的JSON方式这也是最常用的方式{ zero_optimization: { stage: 3, offload_optimizer: { device: cpu, pin_memory: true, ratio: 0.9 }, offload_param: { device: cpu, pin_memory: true }, overlap_comm: true, contiguous_gradients: true, stage3_max_live_parameters: 1000000000, stage3_prefetch_bucket_size: 50000000, reduce_bucket_size: 50000000, reduce_scatter: true }, train_batch_size: 64, gradient_accumulation_steps: 4 }我逐项说下我的理解。stage: 3表示启用ZeRO-3。offload_optimizer和offload_param是把优化器状态和参数进一步卸载到CPU内存。这张卡显存不够的时候把参数放CPU是常见做法但代价是CPU与GPU之间的PCIe带宽会成为瓶颈。overlap_comm: true是为了让通信与计算重叠思想很简单——前向传播中某层在All-Gather的时候后面几层的计算已经开始做编译器调度帮你隐藏一部分通信延迟这非常重要。reduce_scatter是反向传播时梯度归约采用Reduce-Scatter方式而不是All-Reduce前者边归约边分散最后每卡保存的梯度片段刚好是对应分片参数的那部分效率更高。配置文件里那几个bucket size值很多人在官方默认值基础上完全不动。我实际调参的经验是如果你的模型层级非常多比如几百层适当调大stage3_prefetch_bucket_size能有效提高预取效率——因为ZeRO-3有参数预取prefetch机制可能会在未来几层之前提前拿参数权重bucket size越大一次预取的范围就越远命中率会变高。但也要克制太大会引入无效预取。4.3 MoE训练时加上的独有配置项DeepSpeed训练MoE时,配置可能需要加一段moe相关字段大致的示意是这样{ moe: { enable: true, num_experts: 8, top_k: 1, expert_group: [epoch], capacity_factor: 2.0, aux_loss_coef: 0.01, ep_size: 1, save_pretrained_model: false } }num_experts是好理解8个专家。top_k是每个Token选几个专家我见很多实验用1少数为提升精度用2。用1的原因很简单——训练速度最大化用2则意味着所有Token必须等到2个专家都返回结果才能继续通信和计算延迟更高。ep_size是专家并行度表示一组专家在几张卡上切分。一般建议ep_size不要大于节点数因为跨节点通信延迟远高于节点内部。expert_group: [epoch]这个字段的意思稍微绕一点它表示专家分组交互的频率。若设置epoch级别每个epoch才重新排队专家分组还有步级别选项但会显著增加通信频率。新手我的建议是先选epoch省事因为在阶段初期交互频率对性能影响很小后面再深入调。aux_loss_coef就是负载均衡loss的系数前文说过的敏感度。训练时我建议打印一下每个专家的token数量分布如果某专家占比超过40%你就该考虑把aux_loss_coef往大调几位数。这步骤不算复杂却经常被忽略等发现时模型已经训练好几轮了回退成本很高。4.4 关于负载均衡代码的常见误解这里多说一句很多人搜MoE负载均衡代码是想找现成的可以copy的代码片段。但我直接给你结论DeepSpeed在配置层面已经帮你做了负载均衡用的是前面说的aux loss capacity factor控制。你真正要写代码的是应该在每次validation后统计一下专家分配直方图然后判断训练是否处在健康状态。网上那些手动实现Switch负载均衡的代码如果你不是自己手搓MoE架构完全用不上不用被热搜词带偏了。5. 实操调优我从踩坑里总结的四条经验5.1 先把通信压下去再谈加代码ZeRO-3训练MoE会同时出现All-Gather和All-to-All两类通信。如果集群的NVLink带宽充足节点内通信问题不大真正紧要的是跨节点的网络。我试过一个场景单机8卡A100MoE训练时的吞吐率尚可但是一旦桥接到两台机器loss curve立刻变得抖动明显有个任务直接训练了36个小时没有读出来有效进度后来排查发现是Ethernet网卡的MTU没调大数据包碎片化严重通信效率极低。优先把NCCL的socket网络调优比如设置NCCL_SOCKET_IFNAME、NCCL_IB_DISABLE等比研究一堆训练技巧来得实在。5.2 显存不够时怎么调整合理很多人在ZeRO-3 MoE训练时仍然碰到OOM我通常会按这个顺序排查先把CPU Offload的参数开关打开让优化器状态和参数都往CPU卸。代价是训练变慢但能跑通。检查激活值显存占用激活值不在ZeRO-3的优化范围内。DeepSpeed提供了activation checkpointing机制重新计算前向激活值而非保存所有中间结果可以极大降低内存峰值但对计算时间有损耗。最后才是缩小batch size。因为MoE本身有Token级切分如果你batch size太小每张卡上的Token分布很稀疏通信占比上升训练效率会非常难看。常有同事一上来就调小batch size我是不太建议的。对于大模型训练batch size本身还有梯度噪声控制和稳定性意义。顺序很重要。5.3 Loss突然变得起伏很大先想专家均衡有个很典型的case某个数据分布极其不均匀的训练任务前几百步看起来loss下降正常后来开始周期性飙升。检查log时发现一个特定专家几乎占据了80%的Token辅助loss已经压制不住。最后定位到问题在capacity factor太高drop tokens很少导致头号热门专家承担了过多压力降到1.25后token drop比例小幅上升但整体loss稳定多了。这个教训是不要迷信降低token drop比例很多时候token drop是模型的自我保护。5.4 不要低估checkpoint的体积和读写时间ZeRO-3默认会把模型切碎的state存成多个文件只保存分片的那部分。MoE模型参数大checkpoint写满磁盘是常有的事。我踩过最深的一个坑是训练中途宕机重启加载checkpoint时才发现save的目录是节点本地目录其他节点根本访问不到。用ZeRO-3时务必做好checkpoint统一存储且定期验证能够在完整集群上加载。这个验证成本低但很多人就是不做直到需要恢复数据时才崩溃。6. 常见问题速查表我遇到过很多人在同一批问题上反复打转整理一个速查表对应排查会更省时间。现象可能原因排查顺序与建议模型虽然能加载但训练极慢通信开销过大 / 网络带宽不足先检查节点内与跨节点通信耗时调整NCCL相关环境变量再用overlap_comm隐藏部分延迟显存依然OOM激活值峰值太高 / CPU offload没开开activation checkpointing检查batch size与sequence length乘积loss抖动剧烈专家负载不均衡 / aux_loss_coef太小打印各专家token占比调整aux_loss_coef与capacity factor一个batch训练时间突然翻倍出现大量跨节点All-to-All把专家分组尽量放在节点内增大ep_size从而减少跨节点tensor的搬运模型推理时结果不对checkpoint加载顺序错误检查所有卡上的分片文件是否齐全统一从集中式存储loadtoken被drop太多capacity factor过小增大capacity factor但观察loss曲线是否变差这张表背后实际上就是两句话显存问题优先看分片与offload训练质量问题优先看专家路由的均衡性与通信布局。思路有了参数调起来才有方向感。DeepSpeed ZeRO-3和MoE这套组合逻辑上看起来很配但真正训起来每一层都涉及通信、显存、计算三者的微妙平衡。我个人实际用了很久后的体会是配置本身只是万里长征第一步读数据曲线、盯专家分配比例、反复比较通信时耗这些日常工作才是决定你训练能否收敛的关键。祝你们都能少走点弯路多省点电费。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。