ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

飞桨异构参数服务器:动态切分与混合同步提升65%训练速度

飞桨异构参数服务器:动态切分与混合同步提升65%训练速度 1. 异构参数服务器到底在解决什么问题如果你最近在折腾大规模分布式训练大概率会遇到一个很拧巴的局面集群里的机器不是同一批买的A卡和B卡混着用CPU型号也参差不齐甚至有些节点还插着不同代的加速卡。这时候你跑一个标准的参数服务器训练任务会发现整体吞吐被最慢的那台机器死死拖住快的卡在那儿空转慢的卡成了瓶颈。异构参数服务器架构要解决的就是这个木桶效应。飞桨这次推出的异构参数服务器核心思路是让不同规格的硬件各司其职而不是强行让所有节点干一样的活。传统参数服务器里每个worker节点负责一部分样本的前向和反向计算然后把梯度推给server节点做聚合。问题在于当worker之间的算力差距达到两三倍时同步等待的时间会急剧膨胀。异构参数服务器做的事情是把计算任务按照硬件的实际能力做动态切分让强节点多承担一些计算量弱节点少承担一些同时server端的聚合策略也做了针对性优化。这个架构适合谁用我总结下来是三类人第一类是在公司内部混用多代GPU做训练的团队第二类是预算有限、只能分批采购硬件的中小团队第三类是做推荐系统或搜索排序这类本身就依赖参数服务器范式的场景。如果你只是单机多卡跑个视觉模型这个架构对你帮助不大但一旦你的训练规模跨过几十个worker节点异构带来的收益就会非常明显。官方给出的数据是训练速度提升65%以上这个数字不是凭空来的。它背后是一整套针对异构硬件的调度、通信和聚合优化。接下来我会从架构设计、通信策略、实操配置和踩坑经验几个角度把这个东西拆开讲清楚。2. 异构参数服务器的架构拆解与设计取舍2.1 为什么不能简单地把慢节点踢掉很多人第一反应是既然异构拖慢了整体速度那把慢的机器从集群里移除不就行了这个想法在理论上成立但实际中往往行不通。原因有几个一是集群资源是固定的你不可能因为某台机器慢就不用它闲置就是浪费二是有些场景下慢节点反而是存储或网络更强的节点它的价值不在算力上三是大规模集群里硬件故障是常态今天快的节点明天可能因为散热问题降频静态地划分快慢根本不现实。异构参数服务器的设计哲学是接纳差异动态适配而不是消除差异。它通过一套运行时探测机制持续收集每个worker节点的实际计算吞吐、通信延迟和内存带宽然后把这些指标喂给调度器。调度器据此决定每个节点在当前batch里应该处理多少样本。这个决策是动态的每个epoch甚至每个batch都可能调整。2.2 计算任务的动态切分逻辑具体怎么切分假设你有4个worker节点算力比大概是3:2:1:1。传统做法是每个节点处理相同数量的样本结果就是算力为1的节点拖后腿。异构参数服务器的做法是按算力比例分配样本量算力为3的节点处理3份算力为1的处理1份。这样理论上所有节点能同时完成计算同步等待时间趋近于零。但这里有个细节样本分配不是简单的按比例切分因为不同样本的计算量本身也有差异。比如推荐场景里长序列样本的计算量可能是短序列的好几倍。所以调度器还需要考虑样本的复杂度分布尽量让每个节点分到的总计算量均衡而不是样本数量均衡。这个逻辑在飞桨的实现里是通过一个轻量级的profiling模块完成的它会在训练开始前跑几个step做预热收集每个节点的实际耗时然后建立算力画像。2.3 server端的聚合策略调整worker端的切分只是第一步server端的聚合同样关键。传统参数服务器的server节点通常是对等的每个server负责一部分参数分片接收所有worker推过来的梯度。在异构场景下如果worker的计算速度差异很大server端会面临梯度到达时间参差不齐的问题。有些server可能早早收齐了所有梯度有些还在等慢节点。飞桨的异构参数服务器在server端引入了异步聚合窗口机制。简单说server不会死等所有worker的梯度都到齐才开始聚合而是设置一个时间窗口窗口内到达的梯度先做一次部分聚合窗口结束后再和剩余梯度做合并。这个机制需要配合参数更新策略的调整否则容易影响模型收敛。飞桨的做法是在优化器层面做了适配对延迟到达的梯度做衰减处理保证更新的稳定性。注意异步聚合窗口的大小需要根据集群的实际网络状况调整。窗口太小会导致聚合次数过多通信开销上升窗口太大则退化成同步等待失去异构适配的意义。建议从默认值开始观察server端的梯度队列长度再做微调。3. 通信层的优化异构硬件之间怎么高效对话3.1 通信瓶颈往往比计算瓶颈更致命在异构集群里计算速度的差异可以通过任务切分来弥补但通信速度的差异很难绕过。不同代的GPU其PCIe带宽、NVLink支持情况、甚至网卡型号都可能不同。如果通信层不做优化worker和server之间的梯度传输会成为新的瓶颈。飞桨异构参数服务器在通信层做了几件事。第一是梯度压缩的自适应选择。对于带宽充足的节点使用FP16甚至FP32传输梯度对于带宽紧张的节点自动切换到FP16加稀疏化传输。这个切换是运行时动态判断的依据是当前节点的历史通信速率和梯度稀疏度。第二是通信与计算的重叠。在worker端当一个batch的前向计算还在进行时上一个batch的梯度传输可以并行启动。这个重叠在异构场景下尤其重要因为慢节点的计算时间长正好可以用来掩盖通信时间。3.2 参数分片与路由策略参数服务器的server节点通常会有多个每个server负责一部分参数。worker在推送梯度时需要知道哪个参数该推给哪个server。传统做法是静态哈希参数ID模server数量。但在异构场景下不同server所在节点的硬件配置也可能不同静态哈希可能导致某些server成为热点。飞桨的做法是动态路由。它会监控每个server节点的负载情况包括CPU利用率、内存占用和网络吞吐然后根据负载动态调整参数到server的映射关系。这个调整不是每个batch都做而是每隔一定步数做一次避免频繁迁移带来的开销。路由信息会缓存在worker端减少查询开销。3.3 梯度同步与异步的混合模式纯同步训练在异构集群里会被慢节点拖死纯异步训练则容易导致模型收敛变差。飞桨异构参数服务器采用的是一种混合模式在同一个同步组内worker之间是同步的不同同步组之间是异步的。同步组的划分依据是节点的算力相近程度算力接近的节点分到同一组组内同步等待时间短组间异步则避免了跨组等待。这个设计的好处是兼顾了收敛性和效率。组内同步保证了梯度的一致性组间异步则让快组不必等慢组。实际使用中同步组的数量需要根据集群的硬件分布来定。如果集群里硬件型号很杂同步组可以多分几个如果只有两三种配置两三个组就够了。4. 从零跑通一个异构参数服务器训练任务4.1 环境准备中最容易忽略的细节假设你手里有一个混合了不同型号GPU的集群想跑通飞桨的异构参数服务器。第一步是环境检查这里有几个容易踩坑的地方。首先是CUDA版本的一致性。不同代的GPU可能对CUDA版本有不同的最低要求但飞桨的异构参数服务器要求所有节点使用相同版本的CUDA和cuDNN否则编译出的算子可能不兼容。我的建议是统一到一个较新的稳定版本比如CUDA 11.8它对老卡和新卡的支持都比较均衡。其次是网络配置。参数服务器对网络延迟很敏感建议所有节点在同一个局域网内最好是用万兆以上的网络。如果条件允许RDMA网络会带来明显的通信收益。另外要确认防火墙没有拦截节点之间的通信端口飞桨参数服务器默认使用的端口范围需要在防火墙里放行。第三是Python环境和依赖包版本。飞桨的异构参数服务器对某些依赖包有版本要求比如protobuf的版本不能太高也不能太低。建议直接用飞桨官方提供的Docker镜像作为基础环境省去依赖冲突的麻烦。4.2 启动脚本的关键参数解读飞桨参数服务器的启动通常通过paddle.distributed.launch或者fleetrun来完成。异构场景下有几个参数需要特别关注。第一个是--worker_num和--server_num。worker_num是所有worker节点的总数server_num是server节点的数量。在异构集群里server节点建议选择那些CPU性能好、内存大的机器因为server端主要做梯度聚合对CPU和内存的要求高于GPU。如果集群里有纯CPU的机器拿来做server是很好的选择。第二个是--heterogeneous标志。这个标志告诉飞桨启用异构调度模式。启用后框架会自动做算力探测和任务切分。如果不加这个标志框架会按同构模式运行异构优化的效果就体现不出来。第三个是--sync_mode。这个参数控制同步策略可选值有sync、async和hybrid。异构场景下建议用hybrid配合--sync_group_size参数指定同步组的大小。sync_group_size的设置需要根据硬件分布来定一般建议让算力相近的节点数量作为组大小。4.3 一个可复现的配置示例下面是一个简化的启动配置示例假设你有6个worker节点其中3个是A1003个是V1002个server节点。fleetrun --worker_num 6 \ --server_num 2 \ --heterogeneous \ --sync_mode hybrid \ --sync_group_size 3 \ --worker_endpoints ip1:port1,ip2:port2,ip3:port3,ip4:port4,ip5:port5,ip6:port6 \ --server_endpoints sip1:sport1,sip2:sport2 \ train.py在train.py里你需要用Fleet API来定义分布式训练的策略。关键代码是设置distributed_strategy为ParameterServerStrategy并开启异构相关选项。import paddle.distributed.fleet as fleet strategy fleet.DistributedStrategy() strategy.heterogeneous True strategy.sync_mode hybrid strategy.sync_group_size 3 fleet.init(is_collectiveFalse) optimizer fleet.distributed_optimizer(optimizer, strategy) optimizer.minimize(loss)这段代码的核心是strategy.heterogeneous True它触发了框架的异构调度逻辑。sync_group_size 3表示每3个算力相近的节点组成一个同步组这里正好把A100和V100各分一组。4.4 跑通之后怎么验证异构优化生效了任务跑起来之后怎么确认异构优化真的在起作用我通常看三个指标。第一是每个worker节点的step耗时。如果异构优化生效快节点和慢节点的step耗时应该比较接近而不是差出好几倍。第二是server端的梯度队列长度。如果队列长度经常堆积说明聚合速度跟不上worker的推送速度可能需要增加server节点或调整聚合窗口。第三是整体的吞吐量也就是每秒处理的样本数。对比开启和关闭heterogeneous标志的吞吐量如果提升明显说明优化生效了。飞桨的日志里会输出每个节点的算力画像和任务分配情况可以通过日志确认调度器是否正确地识别了节点的算力差异。如果发现某个节点的算力被低估或高估可以手动调整算力画像的初始值让调度器更快收敛到合理的分配方案。5. 实际部署中遇到的坑与应对方案5.1 算力探测阶段的抖动问题异构参数服务器在启动时会做算力探测这个过程通常跑几十个step。我遇到的一个问题是探测阶段的耗时波动很大导致算力画像不准。后来发现原因是探测阶段正好赶上了其他任务在抢资源或者网络出现了短时拥塞。解决办法是在探测阶段增加采样次数取多次采样的中位数而不是平均值这样能过滤掉偶发的抖动。飞桨的较新版本已经支持配置探测的采样次数建议设置成至少20次。另一个相关的问题是训练过程中节点的算力可能发生变化比如GPU因为温度过高而降频。如果算力画像不更新调度器会继续按旧画像分配任务导致新的瓶颈。飞桨的异构参数服务器支持在线更新算力画像但默认的更新频率比较低。如果你的集群散热条件一般建议把更新频率调高一些比如每100个step更新一次。5.2 同步组划分不合理导致的次优解同步组的划分直接影响训练效率。我见过一个案例集群里有4种不同型号的GPU但运维人员只分了2个同步组结果组内算力差异还是很大同步等待时间没有明显改善。后来改成4个同步组每个组内硬件型号一致效率才上来。但同步组也不是越多越好。组太多会导致组间异步的程度过高模型收敛变慢。我的经验是同步组的数量不要超过硬件型号的数量同时每个组内至少有2个节点否则组内同步的意义就不大了。如果某个型号只有一个节点可以考虑把它和算力最接近的型号分到一组。5.3 梯度压缩带来的精度损失前面提到异构参数服务器会根据带宽情况自动选择梯度压缩策略。梯度压缩虽然能减少通信量但会带来精度损失。我在一个推荐模型上测试过开启FP16压缩后AUC掉了大概0.1个百分点。这个损失在有些场景下可以接受但在精度敏感的场景下就需要谨慎。应对方案有两个一是对压缩后的梯度做误差补偿把压缩产生的误差累积起来在后续的梯度中补回去。飞桨的梯度压缩模块支持误差补偿但需要手动开启。二是对不同的参数使用不同的压缩策略比如embedding层的梯度用FP16压缩全连接层的梯度用FP32传输。这个可以通过参数分组来实现。5.4 server节点成为新瓶颈的情况异构优化把worker端的负载均衡做好了但有时候server端反而成了瓶颈。我遇到过一次worker端的step耗时很稳定但整体吞吐上不去排查后发现是server节点的CPU利用率打满了。原因是server端做梯度聚合时如果参数分片不合理某些server会承担过多的聚合任务。解决办法是调整参数分片策略让每个server的聚合负载更均衡。飞桨支持自定义参数分片函数可以根据参数的大小和更新频率来分片。另外如果server节点是CPU机器可以考虑用多线程来加速聚合飞桨的参数服务器默认是多线程的但线程数需要根据CPU核数来配置默认值往往偏保守。6. 异构训练的未来演进与个人实践体会从我这边的实践来看异构参数服务器解决的是一个非常现实的问题不是每个团队都能一次性采购齐整的硬件混搭是常态。飞桨这套架构的价值在于它没有要求你先统一硬件再谈优化而是直接在异构的基础上做效率提升。65%的速度提升在不同场景下可能有出入但方向是对的。我个人的体会是异构优化的效果高度依赖于算力画像的准确性。如果画像不准调度器的决策就会跑偏甚至可能比同构模式还慢。所以建议在正式训练前花点时间把探测阶段跑充分确保画像稳定。另外同步组的划分需要根据实际硬件分布来调不要照搬默认值。还有一个值得关注的方向是异构参数服务器和混合精度的结合。不同代的GPU对FP16和BF16的支持程度不同如果能在异构调度的基础上再根据硬件的精度支持能力做差异化配置理论上还能再挤出一部分性能。飞桨目前对这方面的支持还在完善中但已经可以看到一些接口预留。最后分享一个小技巧如果你的集群里有CPU节点不要只把它们当server用也可以让它们承担一部分embedding的查表操作。推荐模型里embedding查表是内存密集型操作CPU节点在这方面反而有优势。飞桨的异构参数服务器支持把embedding层放在CPU上GPU只负责稠密部分的计算这样能进一步发挥异构硬件的各自长处。
返回列表