GPU调度器:万卡集群下分布式训练的隐形老板 深夜两点半群里跳出一条告警某张A100掉线一个16卡分布式训练任务的rank 3卡死NCCL开始超时。紧接着值班同事重启任务checkpoint恢复到位计算走到一半调度器又把任务排到了别的节点。这套行云流水的处理背后真正在各个GPU之间做决定的不是运维群里喊话的人而是集群调度器。AI Infra 这个领域训练和调度是一体两面的。模型架构、数据管道、优化器策略决定了训练多快而调度器决定了整个训练场的GPU利用率有多高、任务排队有多久、算力优先级怎么定。标题里“隐形老板”这个说法一点不夸张你提交一个训练任务它什么时候能跑、跑在哪些卡上、遇到冲突谁让路、占多少资源全都是调度器说了算。这篇文章是AI Infra系列的训练与调度下篇核心聊一件事一批训练任务在上万张GPU上怎么排班。我会从任务的生命周期讲起拆解调度器排的资源对象对比几种主流调度架构的取舍再讲规模化后真正难啃的碎片、死锁、饥饿问题最后说说调度器之外配套的可观测和治理手段。适合做AI Infra平台的同学、想把GPU集群管理得更高效的工程师以及被“排队排到崩溃”的算法工程师读一读。1. 为什么GPU过千张之后“人肉排班”就注定翻车1.1 一个训练任务从提交到跑起来中间到底发生了什么很多人以为训练任务提交上去就“跑起来了”其实中间隔着一整套流程。我习惯把它拆成八个阶段提交、配额检查、入队、调度、节点分配、镜像拉取、进程组启动、训练循环。提交阶段算法工程师把pytorch训练脚本、数据集路径、镜像tag写进一个yaml或者通过平台页面提交。配额检查看的是你这个团队在这个队列里还剩多少额度额度用完了任务根本不让你进队。入队之后调度器开始不断尝试为它找资源——找的过程不是简单的“有没有空卡”而是“有没有一组满足条件、能组成一个通信域、还能在指定拓扑下达到预期带宽的卡”。进程组启动是很多人忽略的一步。分布式训练要拉起多卡进程靠的是torch.distributed.init_process_group或者horovod这类框架在节点间做握手。调度器如果没有把任务的所有进程一次性调度起来就会出现“任务在跑但rank一直在等world size凑齐”的尴尬局面。这也是为什么主流调度器对AI任务都要支持gang scheduling后面专门聊。最后才是训练循环前向、反向、梯度同步、参数更新循环往复。这中间每多少个step存一次checkpoint决定了任务被抢占或崩溃后的恢复成本。1.2 手动设置CUDA_VISIBLE_DEVICES的失控现场在GPU数量几十张、甚至一两百张的阶段绝大多数团队是“人肉排班”的。我去过不少公司看到最典型的做法是工程师先ssh到某台GPU服务器上敲nvidia-smi发现卡2和卡5是空的于是export CUDA_VISIBLE_DEVICES2,5再跑训练脚本。这套流程在五六张卡的时候完全没问题但到了几百张就开始失控。失控点有三个。第一个是撞车。十个人同时上来nvidia-smi看到同一个空卡然后各跑各的最后谁都用不好。第二个是算力和显存错配。有人拿了8张卡做LoRA微调实际显存只用了不到四分之一但别人想用这几张卡的算力做点推理发现显存被占了进不来。第三个是网络拓扑被忽略。训练任务如果被分配到了跨机、跨交换机的卡上NCCL在AllReduce时的通信时间会成倍增长GPU算得再快也等不起通信。我记得有个客户一个单机8卡的任务跑在单机上只要11个小时但因为调度随意任务被拆到两台机器上每台4卡结果跑了21个小时。原因就是节点间通信带宽成了瓶颈。人肉排班根本没有能力实时感知这些信息所以大集群上调度器不是“一个工具”而是基础设施。1.3 先到先得为什么在大集群上行不通很多人对调度器的第一直觉是FIFO谁先提交谁先跑。公平吗看似公平但在异构算力、多团队共享的大集群里FIFO会带来两个致命问题。第一个问题是队列头阻塞。一个128卡的大任务排在最前面但集群当前空闲的资源只有110卡。调度器的选择是继续等直到资源凑够128卡再启动。问题是它等的过程中后面那些只需4卡、8卡的小任务也全被堵住了。如果当天刚好没有别的任务释放资源大任务等了三小时后面所有小任务也跟着白等三小时集群整体利用率直线下降。第二个问题是资源需求差异。大模型预训练任务动辄几百卡跑一周算法同学调参的小任务可能8卡跑半小时。这两种任务如果放在同一个优先级级别里排队小任务会被大任务拖到天荒地老。所以调度器必须要支持多队列、多优先级、抢占和被抢占的机制让短小任务也能及时插队跑完否则大家的迭代效率都会被拖垮。大集群调度本质上是把“人来分配资源”这件事变成了一套有规则的自动决策系统并且这套规则要能兼顾效率、公平和业务优先级。2. 调度器在排什么资源模型、队列、优先级与抢占机制2.1 一次调度请求里训练任务真正要的是什么调度器的第一个基本功是理解训练任务请求的资源对象。千万不要以为只是“几张GPU”。一个典型的大模型训练调度请求除了GPU型号和数量还包括显存、CPU核数、内存、本地临时盘、RDMA网卡数、节点亲和性。GPU型号这件事很关键。A100和H100的资源模型不一样H100的NVLink域和A100也不一样。一个在A100上验证过的训练脚本调度到H100上可以但如果你把H100任务调度到P40或者消费级卡上大概率直接OOM或者因为缺失某些算子支持跑不起来。所以调度器必须支持按GPU型号过滤、按显存大小过滤。显存我单独说一句。很多人纠结“显存容量是测算推理还是训练用的”实际上训练时的显存需求要复杂得多模型参数、优化器状态、梯度、激活值、通信缓冲全都要占显存而且不同batch size下激活值的占用差异巨大。调度器看到的“申请显存”只是任务上报的一个静态数字但真实训练过程可能波动。这就导致一个常见现象任务申请了4张80G的A100实际每张只用了40G。从算力角度看GPU是闲着的但从资源分配上看这块卡已经属于这个任务了别的任务进不来。这也是后面要聊的碎片问题。调度请求里还有一类隐性的拓扑需求。对全参数微调和预训练任务来说节点内的NVLink带宽远超跨节点网络所以调度器要尽可能把一个任务放在同一台8卡机器上如果做不到也要尽量放在同一个接入交换机下。很多自研调度器花大力气做拓扑感知就是为了减少NCCL的通信开销。2.2 队列和配额把“队”排成一套治理机制队列不是简单地把任务分成几堆它是一套资源治理机制。我在实际项目里喜欢把队列理解成“不同业务团队之间的一堵墙”每个团队有自己的配额配额不够就排队等待超出配额的部分可以借用其他队列的空闲资源但一旦别人有需求借用的资源可以被收回。一个合理的队列治理模型通常有三层。第一层是按团队或业务线划分的主队列比如算法一组的队列、算法二组的队列、推理服务的队列第二层是每个队列内的弹性限额硬配额是保证的但弹性限额允许某团队在别人空闲时突破配额多占用一些资源第三层是队列之间的优先级比如在线推理服务比离线训练任务优先级高出现资源争抢时推理服务可以抢占离线任务的卡。配额治理还有个很现实的作用成本核算。每张GPU的采购成本、电力成本、折旧成本都要摊到具体团队头上。如果调度器没有队列维度的用量统计月底算账的时候财务和技术负责人只能大眼瞪小眼。所以调度器不仅是技术组件它还是整个算力资源的“计费中心”虽然它自己不关心金额但它记录的用量数据是所有计费系统的基础。2.3 抢占用得好是效率用不好是灾难抢占机制通俗讲就是“后来者能不能把正在跑的任务挤掉”。在训练集群里这是调度器最敏感的开关。先说为什么一定要有抢占。在线推理服务有严格的延迟SLO如果GPU被离线训练任务占满了推理请求进来就会超时。再比如某个实验团队的deadline快到了他提交的任务应该优先跑但集群资源被其他低优先级任务占着。没有抢占机制这两个场景都无解。但抢占在训练场景里比在单纯的在线服务场景里更复杂。你抢一个无状态的HTTP服务杀掉pod重拉就行几乎无成本但抢一个训练任务意味着它的通信域被切断整个训练进程崩溃只能靠checkpoint恢复。如果checkpoint频率是每30分钟一次那一次抢占就白算了几十分钟的GPU算力。我在实际项目里测过一个128卡的任务checkpoint保存一次要四五分钟如果频繁被抢占整个集群的有效算力会肉眼可见地下降。所以成熟的训练集群会用“温和抢占”而不是“暴力抢占”。比如先发一条信号让任务自己停到下一个安全的checkpoint边界再腾资源再比如被抢占的任务自动降级到另一个空闲队列。只有明确的高优任务进来才触发硬抢占。调度器的抢占策略某种程度上就是训练场里的“交通规则”既要保证救护车能快速通过又不能因为频繁急刹车让整条路上的车都瘫痪。3. 万卡场景下的调度器选型Slurm、K8s生态和自研的三条路3.1 SlurmHPC老将为什么还在服役又为什么越来越别扭Slurm是高性能计算领域的老牌调度器很多高校和科研机构用了几十年。它支持分区、优先级、回填调度稳定性没得说而且在MPI类任务上的支持非常成熟。早期的GPU集群很多也直接用Slurm管训练任务用srun提交申请到之后在节点上跑。但Slurm在AI训练的云原生场景下越来越别扭。第一它本质上是面向节点的调度资源抽象粒度比较粗对容器、镜像、GPU虚拟化这些概念支持不够原生。第二它在弹性伸缩上偏弱云上想要动态扩缩容集群Slurm的配置和维护成本不低。第三它在单集群支持规模上虽然能到几万核但几万张GPU的规模对它的调度延迟和状态管理来说压力很大。不是说Slurm不能用很多单位到现在还是Slurm训练框架的组合跑得很稳。只是当你的业务从“周期性提交一批科学计算任务”变成“每天几百上千个训练任务不断提交、频繁调整优先级”的时候Slurm的灵活性会显得捉襟见肘。3.2 K8s Volcano/Kueue云原生训练的主流路线如果团队已经在用Kubernetes管理微服务那很自然会想把训练任务也塞进K8s体系。但K8s原生的默认调度器对AI负载支持很弱它不感知GPU数量、不理解gang scheduling、不做拓扑感知。所以社区生态里出现了几个补位组件。Volcano是其中比较知名的一个。它给K8s带来了队列、优先级、抢占、gang scheduling和拓扑感知能比较好地满足深度学习训练的需求。我在实际项目中用Volcano管过一个约500卡规模的内部训练集群稳定性还不错部署和升级也比较平滑。Kueue是另一个思路它更像一个资源配额和排队管理层把“谁可以进入待调度队列”和“怎么把Pod调度到节点上”分开。Kueue管排队、管配额借用底层仍然交给K8s默认调度器或其他调度器去管具体placement。如果你是中小规模团队比如一两百张卡直接K8sVolcano/Kueue是成本最低的路线社区文档多、踩坑案例多、招人也容易。但到了数千卡以上光靠开源组件可能还不够需要在这个基础上做深度的定制和调优。3.3 自研调度器头部玩家绕不开的终极命题到了真实的一万张GPU规模头部玩家基本都会走向自研调度的路线。不是因为他们喜欢造轮子而是开源组件在几个点上确实顶不住。第一个是调度器本身的性能。一万张GPU意味着如果你要等一个128卡的任务集齐资源扫描全集群状态一遍就要处理大量节点和Pod的心跳数据。原生K8s调度器的调度吞吐大概每秒几十个Pod万卡集群高峰期的任务调度请求远不止这个量不改底层逻辑就会在调度队列里积压。第二个是业务策略的定制。比如断点续训的自动弹性、任务在异构卡间的迁移、多租户成本审计、地域间算力调度这些都要在调度器层面定制数据结构和调度算法。开源调度器通常只提供基本框架深入到业务策略时你会发现什么都要自己改。第三个是GPU资源切分。像A100的MIG或者消费级卡做显存共享这类能力原生调度器根本不理解需要自研调度器去和底层的GPU虚拟化层打通。但我不建议中小团队一上来就自研。自研调度器的维护成本非常高需要懂K8s调度框架、懂NCCL通信原理、懂GPU驱动和RDMA网络一个人搞不定。更现实的路径是先用开源方案把业务跑通等规模和数据确实暴露了开源方案的瓶颈再考虑在开源框架上做插件扩展。实际上Volcano的action/plugin机制、K8s的scheduler framework都允许你写自定义插件很多“自研”其实是基于这些框架做的深度定制比从零写调度器靠谱得多。三套方案的取舍我给个参考表格维度SlurmK8s Volcano/Kueue深度定制/自研上手成本中中高GPU拓扑感知弱中强弹性伸缩能力弱强强多租户配额治理一般较好按需定制万卡规模适用性吃紧需扩展适合维护成本低中高4. 规模化之后最硬的骨头碎片、死锁和饥饿4.1 GPU碎片调度器最心疼的资源浪费碎片这个词最早来自内存管理在GPU集群里同样存在而且更隐蔽。我在1.1里讲过一个任务申请的显存可能远超实际使用这就会形成“算力不可用显存被占住”的碎片状态。碎片分两层。第一层是节点内碎片。一张A100 80G的卡某个任务只用了30G但调度器不允许其他任务共享这块卡剩下的50G就白白浪费。如果一张卡上开了MIG多实例GPU可以把卡切成2个40G或3个20G的小实例让不同任务共用物理卡。但MIG本身也有坑切分后每块实例的算力上限被锁死如果一个任务需要大显存高算力MIG反而帮倒忙。第二层是集群级碎片。比如集群里有12张空闲卡但分布在不同节点上节点A有8张节点B有4张。此时来了一个16卡的任务进队不够又来了一个8卡的任务它可以整整齐齐跑在节点A上没问题。但如果调度策略不聪明每次都把小任务插到碎片化的卡位上大任务可能永远等不到资源集齐。这个场景特别考验调度器的packing算法。业界常用的两个策略binpack和spread。binpack是把任务尽量集中调度到部分节点上把其他节点完全腾空这样可以留出“整块资源”给大任务spread则是把任务打散到尽可能多的节点上有利于改善散热和单点故障影响面。真实场景下没有绝对优劣需要根据任务分布动态切换。我踩过的一个坑是后来为了追求节点利用率把策略调成极端的binpack结果一个单点故障导致几十个任务同时崩溃。平衡点很重要。4.2 Gang Scheduling治了死锁又带来新的排队分布式训练任务有一个特殊的资源需求所有进程必须同时起来通信域才能建立。如果任务申请8张卡调度器只给了7张8个进程里7个在等最后一个整个任务都在空转CPU、内存、网络全被占着但不干活。这就是为什么需要gang scheduling全有或全无要么一次性把所有卡都给够要么一个都不给。它解决了训练任务常见的“部分调度死锁”问题但也引入了一个新问题队头阻塞更严重了。集群只有90张空闲卡一个128卡的任务排在队头它要求必须等齐128卡才启动那20个原本可以用8卡的短任务全都被堵住了。我见过一个案例集群空闲资源一直维持在70%左右但因为要做全保真gang调度一个96卡的任务等了整整一个下午才集齐资源。后来我们把调度策略改成了弹性调度让任务先以64卡启动等资源够了再扩容到96卡。虽然训练过程中要多做一次rebalance但至少任务没有干等整体完成时间反而缩短了。当然弹性调度对训练框架有要求不是所有框架都支持动态增删rank。pytorch上部分场景可以用torch.distributed.elastic去实现。如果你的训练任务比较重又暂时没法改造框架那我建议在调度器里做“超时放弃”机制大任务在队列里等太久了就自动降级打散成小任务先跑避免僵住整个队列。4.3 低优先级任务被饿死调度公平性的暗面调度器另一个容易翻车的地方是低优先级任务的饥饿问题。高优任务源源不断地提交低优任务永远排在后面等到天荒地老也跑不上。这在大集群里非常常见尤其是在“全员跑大模型”的热潮里某核心团队的预训练任务长期霸着资源其他团队的小实验基本做不了。对抗饥饿的机制通常叫aging也就是“老化”。一个低优任务排队超过一定时间后优先级自动上调或者调度器承诺一个“最长等待时间”到点后强制用抢占的方式给这个任务腾资源。但aging也有副作用。它本质上是在抵制高优任务的绝对权威如果触发得太激进高优任务的等待时间也会变长。我常用的策略是分级老化等待30分钟的任务优先级提升一级等待2小时的任务直接提到队列最高优先级但每个队列每天能触发最高级抢占的次数有限额。这样既保证了低优任务不至于饿死也限制了它对核心训练任务的影响范围。5. 隐形老板的另一半可观测、故障自愈和配套治理5.1 不监控GPU就等于闭着眼睛排班调度器再聪明如果它掌握的资源状态是错的排出来的班也一定不对。GPU集群的可观测性是调度系统能够正确工作的前提。我在GPU运维里最常看的几个指标GPU利用率不是nvidia-smi里那个经常跳动的Sm利用率而是实际算力使用情况、显存占用率、温度、NVLink通信错误数、PCIe错误数、Xid错误日志。其中Xid错误尤其关键它往往是GPU硬件故障的前兆。一张卡如果频繁报Xid错误调度器应该自动把它从资源池里摘掉否则任务上去之后跑着跑着就崩整个NCCL通信域都要重建。构建GPU监控的基础设施很多团队直接用PrometheusDCGM exporter搞定。DCGM是NVIDIA提供的GPU管理工具能暴露非常详尽的指标。再配上Grafana做可视化基本能满足日常的大部分监控需求。但现在很多集群还有NPU还在跑IPEX和昇腾环境指标接口不统一建议从落地第一天就考虑多厂商适配的抽象层。5.2 故障卡自动隔离让调度器别把活派给“病号”调度器默认把所有的GPU都当成健康卡来派发但GPU的故障率没有想象中低。长期满载运行的训练卡遇到显存ECC错误、供电不稳、散热失效的概率都不小。如果故障卡没有被及时发现和隔离轻则任务失败重来重则引发连锁故障。我自己的实操经验是必须建立一套自动化的故障卡处理链路。第一步用DCGM或自身驱动定期采集Xid错误、温度、功耗数据第二步设定规则例如“一块卡在24小时内累计3次Xid错误则自动标记为故障”第三步调度器看到故障标记后不再往这张卡上调度新任务同时触发告警通知值班人员第四步值班人员确认后走置换流程把卡下线维修。别看这套链路逻辑简单真正落地很难。难点在于判断“是不是真故障”。A100在满载训练时偶发一次Xid错误可能只是瞬时干扰立刻摘卡会白白浪费算力但如果不摘第二次故障很快就会来任务还是会挂。我见过一个团队用“连续两次硬件错误才摘卡”的规则虽然偶尔会让故障任务多崩一轮但从集群整体稳定性来看收益更大。5.3 调度器和上层工作流编排各自守好边界很多刚接触AI Infra的人会把资源调度器和任务编排系统混在一起。实际上它们各管一段。像Apache DolphinScheduler这类平台负责的是工作流层面的编排拉数据、跑预处理、触发训练、训练完推送模型到审核它关注的是任务依赖关系和定时触发。而资源调度器比如Volcano或者K8s里的调度组件负责的是“这个任务本身分配在哪个节点、哪块GPU上”。这两层可以集成但要守住边界。依赖关系频繁变动是编排层的职责调度器不应该为了迁就某个工作流的依赖去频繁调整资源的捆绑关系。我在项目里见过最舒服的分工是工作流编排层把训练任务包装成一个标准的资源申请丢给调度器调度器只负责在资源池里找最优位置任务结束后编排层收到信号再触发下游流程。边界清晰之后换调度器、升级调度策略都更容易。今天用Volcano明天想换成自研调度器工作流编排层不用改一行代码因为对接的接口还是那套标准的提交和回调协议。回到最开始说的那句调度器是训练场的隐形老板。它不会出现在任何模型效果报告里也不会有算法工程师夸它调度得好但当一万张GPU每天都在高效地出活、任务排队时间短、故障恢复快、各团队配额清清楚楚的时候整个AI Infra体系的负责人心里应该明白这位“老板”干得相当不错。如果你正在搭训练集群我给一个最朴素的建议先别急着追求花哨的自研调度策略把排队指标、资源利用率、故障自动隔离这三件事做扎实。调度器这东西做得好的时候你几乎感觉不到它的存在而调度策略一旦开倒车全场的试炼都会变成等待的游戏。