
做高性能计算的人应该都体会过一种无力感集群账面算力很高跑起真实任务却总差一口气。节点有的忙到排队、有的闲着吃灰网络动不动就拥塞最终整个应用的完成时间被最长的那条路径拖死。这就是典型的负载不均衡。很多人一听到“负载均衡”第一反应是Nginx、L4/L7转发、加权轮询那套Web场景的东西。但放在高性能计算里负载均衡完全是另一套逻辑它面对的往往是成百上千个节点并行协作的海量计算任务任务之间还有通信依赖节点之间通过InfiniBand或RoCE高速网络耦合。简单把一个请求转发给空闲后端这种思路在HPC集群里是行不通的。这篇文章我想围绕“高性能计算负载均衡”这个词说说我自己的理解以及实际集群运维里踩过的坑和沉淀下来的经验。内容涉及作业调度器层面的均衡、并行计算框架里的负载切分、网络链路的均衡以及最近热词“等开销负载均衡”背后的思想。也算是一个实操向的梳理希望能给正在折腾HPC集群或准备入坑的朋友一些参考。1. 为什么HPC的负载均衡和Web场景根本是两回事1.1 从任务模型差异说起Web服务的负载均衡核心是“请求”级别的转发。每个请求通常互相独立后端节点无状态或弱状态请求落在哪一个节点上结果都一样。负载均衡器要做的就是在多个后端里挑一个最合适的目标算法无非是轮询、最少连接、一致性哈希、带权重随机等再配合健康检查基本就能覆盖大部分需求。HPC不是这个玩法。一个典型的科学计算任务比如计算流体力学模拟往往会被分解成几十万个网格子块分配到几十甚至几百个计算节点上并行计算。这些子块之间不是独立的每一轮迭代都要和相邻子块交换边界数据算完一段后还要做全局归约。这就意味着负载均衡不能只看“哪个节点空闲”还必须看任务之间的通信拓扑。1.2 “分配”和“调度”带着约束所以HPC的负载均衡本质上是一个带约束的组合优化问题。你把哪些子块放在哪个节点上不仅影响节点自身的负载水平还影响节点之间的通信量。好的分法是通信量大的子块尽量放同一个节点、同一台机器或者至少放在同一个交换机域内通信量小的子块则可以稍微放飞一下。我经常跟团队里的新人说一句话把HPC负载均衡想象成往不同大小的背包里装石头每个背包除了重量上限每块石头还有其他石头之间的“感情浓度”关系好的要尽量放一个包里。这显然比“哪个人少往哪丢”复杂几个数量级。2. 高性能计算负载均衡的典型分层负载均衡在HPC里不是单点技术而是一整套贯穿上下的体系。我从下往上给大家梳理一层。2.1 作业调度层这是HPC集群最经典的负载均衡层面。典型工具是SLURM、PBS、LSF等作业调度器。它们负责把用户提交的“作业”分配到计算节点的“槽位”上。这里的均衡逻辑主要是队列优先级和公平共享策略节点空闲资源多少节点的专属属性比如有没有GPU、显存多大、是不是胖节点拓扑感知尽量减少跨交换机通信。可以说只要一个集群里的作业五花八门、长短不一、资源需求各异调度器的均衡策略直接决定整个集群的利用率上限。2.2 并行计算框架层这一层更“微观”。对于一个已经在多个节点上跑起来的并行应用负载均衡要解决的是计算任务在各个进程间怎么切分的问题。在MPI编程模型里这是“静态分区”还是“动态负载再平衡”的问题。有些计算场景比如自适应网格加密AMR、粒子模拟如SPH、图计算计算量会在不同区域剧烈变化初始分配很快就不均衡了。这时候需要负载均衡库如Zoltan、ParMETIS介入周期性做任务的重新划分和迁移。到了AI训练场景模型并行策略数据并行、张量并行、流水线并行的选择本质上也是一个负载均衡决策。GPU之间算力差异、显存差异、通信拓扑差异都会被考虑进去。2.3 系统软件与网络层这是很多人容易忽略的一层。HPC的高速网络InfiniBand、RoCE通常有多路径。数据在节点之间传输时到底走哪条物理路径由ECMP或自适应路由决定。如果哈希做得不好多条大流量可能打在同一个链路上形成局部拥塞整体通信性能就会严重下降。这一层的负载均衡做得好不好直接影响上层应用并行效率的上限。再好的任务分配底下一拥塞全都白搭。3. 作业调度器能做哪些实实在在的均衡操作3.1 静态分配与排序策略最简单也最有效的做法是在作业调度时把节点资源想象成“内存碎片”用类似于内存分配的最佳适配算法去挑选节点。以SLURM为例它支持两种节点分配逻辑线性分配linear逐个节点往下填优先把作业堆在同一批节点上。块分配block一次性划出一块连续的空闲节点。如果你的作业是通信敏感的MPI作业块分配通常更好因为它能把作业限定在连续的节点集合内减少和其他作业在节点上互相争抢带宽的概率。但块分配也会带来碎片化问题集群跑一段时间后空闲节点会东一块西一块一个大作业可能因为找不到足够大的连续块而等待哪怕集群总体空闲资源充足。这时候就需要靠调度器的回填Backfill机制来兜底在小作业不阻塞大作业预期开始时间的前提下提前把零碎资源给到小作业。3.2 动态重调度抢占与迁移静态分配只能解决“作业开始时刻”的均衡问题。一旦跑起来每个作业的实际资源占用可能和申请时差异很大。比如基因测序任务前期内存吃紧后期CPU繁忙比如CFD任务在迭代收敛过程中某个区域网格加密导致部分进程负载暴涨。要做动态均衡就需要调度器支持作业抢占Preemption高优先级作业来了把低优先级的作业挂起或杀掉把节点腾出来。作业迁移Migration通过检查点机制把运行中的作业保存到磁盘然后在另一个节点上恢复执行。实际生产环境里“迁移”通常用得很谨慎因为检查点文件可能非常大动辄几百GB迁移期间的网络开销也不小。所以我一般建议只有在节点确实需要下线维护或者任务在一个节点上长时间单点性能异常时才用迁移不要太轻易触发。3.3 拓扑感知分配当作业规模大到跨多个交换机域时节点之间的相对位置就很关键了。SLURM里有--switches参数可以要求作业的所有节点尽量落在同一个交换机之下。配合scontrol show topology能看到硬件拓扑结构。这种拓扑感知分配本质上是给调度器多一个“成本维度”不仅看资源够不够还要看通信代价高不高。我自己在操作时的经验是对于经常跑大规模MPI的集群把拓扑信息配置好比单纯加带宽带来的性能收益要显著得多。有时候同一套代码拓扑感知分配的版本比非感知版本直接快20%~30%没有任何代码改动纯靠调度器选择节点的方式变了。4. 并行程序内部的动态负载平衡实践4.1 什么时候真的需要动态负载均衡很多并行程序的负载不均衡是随时间演化的。典型有自适应网格加密AMR中高精度区域不断移动分子动力学模拟中粒子在空间分布不均匀且随时间漂移图算法中顶点的度数差异极大稀疏矩阵迭代中非零元分布不规律。如果只是简单地对网格做均匀切分碰上这类问题进程间计算量差距会非常大。有的进程跑完了在那儿干等有的跑到天荒地老整个作业被最慢的进程拖着。4.2 使用ParMETIS等库来做动态再划分对这类问题最主流的技术路线是定期在运行时采集各个进程的工作量指标然后调用图划分工具ParMETIS、Zoltan、Scotch重新划分任务图最后把需要迁移的数据打包搬走。这样做的好处是划分时考虑了通信代价和负载均衡双重目标不会为了追求负载一致而把通信搞成一团乱麻。ParMETIS的接口是MPI原生风格支持直接在分布式环境中做多级图划分性能也很优秀。我踩过的一个坑是迁移策略过频。既然负载在演化那多久重新划分一次太频繁迁移数据开销反而抵消收益太少节点间再次失衡。我的经验是让测量和划分之间有一定的滞回冗余比如只有当集群内“负载偏移度”负载最重进程和最轻进程的完成时间比连续两次超过1.2时才触发一次重划分。宁可在几个时间步里吃点亏也不要频繁在全集群范围内搬数据。4.3 动态负载均衡的高开销隐患动态负载均衡本质是在用“空间交换时间”把数据搬来搬去减少了等待但也引入了传输开销。所以动态均衡算法的核心就是不只要算“当前怎么分最均衡”还要算“按这个分法搬迁的通信开销是多少、重新计算的收益是多少”。这也是最近圈子里“等开销负载均衡”被频繁提及的一个原因。它强调的正是任何负载均衡动作本身也有成本均衡求的是“系统整体完成时间的最小化”而不是“负载方差最小化”。推导下来很多看似不太均衡的分配实际整体开销反而更小——因为迁移数据的代价被省下来了。5. 面向AI训练集群的负载均衡新课题AI大模型训练和传统HPC有相似之处也有明显差异。相似之处在于都要大规模并行、都要高速互联差异在于AI训练的任务拓扑是高度规律的张量并行、流水线并行、数据并行组合而且梯度同步的通信频率极高。5.1 并行策略本身就在做负载均衡数据并行模型每个副本吃一样的计算量靠的是数据批次分配均匀。负载均衡的关键在于让每张卡每次拿到的batch大小、处理难度一致否则梯度更新会卡在最慢的那张卡上。张量并行把一层的权重切成多块放到多卡上关键是让每块的计算量、通信量均衡。切分维度的选择、是否引入序列并行都是在做精细的负载对冲。流水线并行把网络层分段放到多张卡上。最怕的是各个阶段的耗时差太大形成漏斗效应某段卡住整条流水线都在等。5.2 异构集群的均衡问题更突出一个真实的大模型训练集群里GPU型号可能不统一有的卡算力强、有的卡显存大甚至某些节点还挂着不同版本的驱动和NVLink拓扑。这种情况下天真地把batch均匀分给所有卡反而是最不均衡的。正确的做法是先通过Profiling工具测出每张卡的实际算力矩阵然后据此做不等比切分。比如A100的节点多分一点数据H800的节点少分一点代价是每轮迭代结束时需要做梯度AllReduce这时候为了让不同算力的卡尽量同步完成计算可以使用梯度累积或调整microbatch大小来微调负载。这个调整过程本质上就是负载均衡。5.3 训练中断恢复中的负载再平衡AI训练任务通常是超长运行动辄几十天。中途总会有节点故障或性能劣化。这时候调度系统和训练框架就必须配合发现某个节点掉队立刻触发一次“负载再平衡”——把该节点上的模型状态和优化器状态迁移到备用节点同时调整全局并行策略。这里用到的技术栈包括Megatron-LM / DeepSpeed / ColossalAI里的弹性训练能力以及集群管理层的节点健康监测、自动故障转移等。从负载均衡的角度看这就是一个“运行期重排”问题。靠的依然是监控数据采集、成本估算、迁移调度、数据同步这几板斧。6. 网络层负载均衡最容易被低估的一环6.1 不要让通信路径变成隐形瓶颈我带队运维过一套HPC集群从软件层面看每台计算节点的CPU利用率都挺均衡作业调度也很合理。但整个集群跑大规模MPI通信密集型应用时总是达不到预期的加速比。排查下来问题出在网络的ECMP哈希不均。服务器到TOR交换机有两条25G上行链路理论上链路聚合后带宽会翻倍。但由于哈希因子设计得不好几个大流量通信对同一对IP端口全都被哈希到了同一条物理链路上另一条链路闲得发慌。网络层的哈希不是按“链路负载”做的而是按流的五元组做的一旦哈希碰撞多条大流打在同一链路上局部拥塞就出现了。解决办法是调整哈希策略增加哈希输入因子里的“随机熵”比如让MPI库为每条通信流使用不同的端口范围在交换机侧开启自定义哈希字段糅合更多IP/端口信息有条件的话启用自适应路由比如InfiniBand的自适应路由功能让报文动态绕过拥塞链路。6.2 RoCE网络尤其注意PFC和ECN的配合现在很多AI训练集群用的是RoCE网络即RDMA over Converged Ethernet。RoCE依赖无损网络靠PFC优先流控制做逐跳流控靠ECN做拥塞标记配合端到端的DCTCP或DCQCN拥塞控制算法。这里的负载均衡坑非常隐蔽如果PFC配置不当某个端口发生拥塞反向压力会迅速传导到整个网络形成“拥塞树”造成一堆节点无辜躺枪。哪怕你的流量分配在节点层是均衡的网络层也可能因为PFC的“殃及池鱼”效应而出现严重的不均衡。我的建议是在网络建设阶段就明确区分存储流量、计算通信流量、管理流量不同优先级打上不同802.1p优先级标签PFC只对少数高优先级队列开启不要对全部队列开启避免无差别的暂停帧影响所有业务ECN阈值要根据交换机缓冲区大小做实验标定默认值往往不是最优值。6.3 存储I/O负载均衡也别忘HPC应用尤其是AI训练对并行文件系统的I/O压力巨大。检查点和日志写入、数据集随机读取都会在存储层形成热点。如果存储的元数据服务器和对象存储节点上的负载不均衡同样会成为整体性能瓶颈。存储侧的负载均衡通常由并行文件系统自身如Lustre、BeeGFS负责通过条带化把文件均匀分布到多个OST对象存储目标上。实际操作中我会特别关注文件条带宽度和条带大小是否匹配应用访存模式大量小文件读写时元数据服务是否成为瓶颈检查点集中写入时是否造成OST热点。有些时候单个作业把一个大文件条带化到过多OST上反而会因为跨OST的写锁竞争导致性能下降所以条带设置也要讲究“适度”这其实也是一种均衡的艺术。7. 等开销负载均衡从“平均负载”到“最小开销”7.1 理念差异传统负载均衡追求的是“各节点负载差不多”。等开销负载均衡Equal-Cost Load Balancing的逻辑完全不同它认为负载均衡不是目的完成所有任务的总开销最小才是目的。简单说它允许某些节点更忙、某些节点更闲只要整体完成时间最短、系统开销最低就行。举个例子一个计算密集型任务放在GPU节点上可能20分钟跑完放在普通CPU节点上要跑2小时。如果按“等负载”的思路可能因为CPU节点空闲就把它调度到CPU节点上结果是任务变慢、GPU节点闲置看似均衡实则浪费。而“等开销”的思路会优先把任务放到GPU节点上哪怕GPU节点已经很忙因为从全局角度把任务放到最合适的硬件上才是总开销最小的方案。7.2 数学直觉一种加权最短路径思想从设计理念上说等开销负载均衡的核心可以概括为一个优化模型把每个可选目标节点、链路、存储路径视为带权路径权重由实时开销估值决定。负载均衡器每次决策时都会在满足约束条件的前提下选择全局开销最小的那条路径组合。它和传统方法的区别相当于把“平均分配”升级成了“最小成本流分配”顺序也变成了先建立开销模型再求解最优分配最后做决策和调度。这种思路在跨数据中心、混合云、异构集群场景下尤其有价值。不同节点间算力差异大网络带宽差异大存储路径的延迟差异也大传统“一视同仁”的均衡策略天然不适用。7.3 实际落地时怎么操作要做到等开销首先要解决“开销怎么测”的问题。我们团队的做法是搭建一个轻量的集群遥测系统持续采集以下指标指标类别具体采集项用于什么节点算力CPU利用率、内存带宽、GPU利用率、排队任务数评估节点当前承载能力任务历史最近N个同类任务的实际完成时长估计新任务的预期耗时通信链路各链路的带宽利用率、拥塞丢包率、往返延迟评估跨节点通信成本存储状态并行文件系统的I/O吞吐、元数据延迟评估数据读取/写入瓶颈然后将这些指标归一化成统一的“开销分值”调度器在分配任务时直接优化开销总分。实际操作中不需要考虑得特别复杂可以对不同任务类型设置不同的权重通信密集型任务重点看网络开销权重计算密集型任务重点看算力权重I/O密集型任务重点看存储权重。我在团队里做过对比采用等开销策略后混合负载下的任务完成率显著提升平均排队时间也大幅缩短。核心收益在于它把“隐性开销”显式化了——网络拥塞、存储排队、异构算力差异这些过去容易忽略的因素全都变成了调度器可感知、可优化的对象。8. 实操层面的几个关键细节8.1 把节点健康状态纳入均衡决策负载均衡做得好不好前提是“节点数据”准不准。我们生产集群用Slurm自带的slurmctldSlurmDBD做记账和健康检查同时配合Prometheus主动拉取Node Exporter、DCGM、Netdata等指标。更关键的是不能只看当前状态还要看趋势。我们会在调度器前方加一层“节点健康度预测”如果一个节点在过去30分钟内发生过多次心跳超时或NVIDIA驱动报错即使当前报告空闲也不会立刻把新作业调度过去。这个机制比单纯的“当前空闲”判断要可靠得多。8.2 给调度决策留一个观察窗口动态调度系统最怕的就是“抖动”。节点负载是波动曲线如果调度器每次看到某个节点稍微空闲就立刻把任务搬过去很可能刚搬过去节点负载又开始上涨于是又触发下一次迁移形成“抖动-迁移-再抖动”的恶性循环。我们的做法是在策略层加一个观察窗口算法调度器对一个候选节点的负载数据做局部加权平均只有当这个平均值持续偏离目标阈值超过一个设定周期比如120秒时才真正触发重分布动作。本质上给调度器加了个低通滤波器滤掉瞬时尖峰。8.3 迁移前先检查点再走应用层配合做动态迁移时如果直接杀进程再重新拉起来代价极高。我们一般按这个顺序执行负载均衡器判定某节点需要卸载任务通知任务管理器如SLURM、或MPI应用内的检查点库在下一个一致时间点保存检查点检查点数据写入并行文件系统新节点从检查点恢复恢复完成后原节点的资源即可释放。这里有个小技巧迁移前先确认新节点上是否有老节点需要的本地数据。如果有先把数据用rsync或分布式存储预置过去避免恢复时再从慢速存储拉数据。预置这一步做得好迁移停机时间能从分钟级降到秒级。9. 总结一下我的个人建议高性能计算的负载均衡说难也难说简单也简单。难是因为它涉及的面太广从调度器到并行框架从网络到存储每一层都有均衡的课题简单是因为核心逻辑就一句话端到端的瓶颈在哪就把均衡策略的重心放到哪。给正在做相关工作的朋友几点实在建议不要一开始就追求完美均衡。先把基础的数据采集、健康检查、拓扑感知做扎实再逐步迭代策略收益会更明显。均衡不是目标吞吐才是。总有一些分配方案从表面看不够均衡但整体吞吐更高——等开销负载均衡的意义就在于此。不要为了“看起来均衡”而牺牲实际效率。监控一定要做全链路。我见过一个AI训练集群GPU利用率长期偏低排查到最后发现罪魁祸首是存储热点和网卡哈希不均跟GPU本身毫无关系。只盯着单一维度看均衡出了瓶颈你都找不到在哪儿。调度策略永远是“适度优先”。无论是支配节点、划分数据、还是触发迁移都要配合监控数据和历史画像来决定而不是靠拍脑门。动态反馈调节才是HPC负载均衡的正确打开方式。最后如果你刚接触HPC建议先从SLURM的调度配置开始把拓扑感知和回填策略调明白再逐步往并行框架和网络层深入。每一步都有对应的坑但踩过之后你再看整个集群的运行状态会有一种“豁然开朗”的感觉。整个系统的吞吐、时延、资源效率说到底都系于“均衡”这两个字上。