GPU调度器:如何管好万卡训练场的资源排班

GPU调度器:如何管好万卡训练场的资源排班 一万张 GPU 怎么排班调度器才是训练场的隐形老板在刚接触大规模训练的时候我总要经历这么一幕几个项目组同时在群里喊“给我留卡”有人直接冲到机房里插卡占了机器有人偷偷改了别人的任务优先级。那时候集群才几十张卡大家靠微信群和人情世故勉强协调得动。可当集群规模涨到几百张、几千张乃至上万张卡的时候这个问题彻底变成了另外一码事——它已经从“协调”变成了一个系统工程问题。这个问题的答案就是调度器。如果你在 AI Infra 这个方向上工作或者你正在发愁“集群卡不少但训练效率上不去”“任务总是互相抢资源”“GPU 利用率只有 30%”那你一定会对这篇文章里的内容有共鸣。这篇是 AI Infra 系列训练与调度的下篇核心聊聊当集群有上万张 GPU 时调度器到底在调度什么它凭什么能决定整个训练场的效率上限以及我们实际部署调度器时坑都在哪里。一万张 GPU 的真实瓶颈不是卡不够是“人不配”1.1 卡多了以后麻烦呈指数级增长很多人下意识觉得GPU 数量越多训练能力越强。这个直觉在单机多卡的时代基本成立但一旦进入万卡规模整个问题的性质就变了。我见过太多集群硬件堆到了几千张 A100结果整体利用率不到 50%。不是卡不行是调度系统根本没法把卡高效地用起来。打个比方假设你有一万个快递员GPU但派单系统调度器是个拍脑袋定的——有人手快抢走了一堆订单干不完有人闲得发呆订单有大有小有的必须在某个区域完成有的必须连着跑好几个小时不能断。没有一套科学的派单逻辑快递员再多也是乱的甚至越乱。在 GPU 集群里这个“乱”的具体表现有三类资源碎片化任务需要 8 卡、16 卡、64 卡不等的资源集群里卡的总数是够的但分别散落在不同节点上单节点凑不出连续的空闲卡任务就只能干等。依赖复杂性大模型训练不是单跑一个进程而是 Parameter Server、DataLoader、集合通信、Checkpoint 定时落盘等多个模块协作。每个模块对资源的需求不同、生命周期不同调度器如果只盯着“有没有空卡”就会陷入局部最优。故障放大效应一万张卡的集群MTBF平均故障间隔时间会被显著压缩。假设单卡 MTBF 是 50 万小时一万张卡规模下平均每 50 小时就有一张卡出故障。如果调度系统没有快速感知、自动摘除、无缝迁移的能力故障就会放大成整个训练任务的停摆。所以万卡时代的调度器本质上是处理这三个问题的总协调者。它不只是分配资源还要管任务生命周期、优先级、亲和性、容错恢复。它是一个训练场里那个从不露面、却管着所有排班的隐形老板。1.2 先搞清楚你调度的是什么“资源”很多人会把调度器当成“排队机”做一个小红点排队面板就够了。真做起来你会发现难点在于 GPU 资源本身是分层的每一层都要调度资源维度说明调度难度显存容量决定能不能塞下一个模型基本是独占逻辑低但碎片化后分配变难算力SM/流处理器决定计算速度可切片、可抢占高算力切分会影响任务性能NVLink / PCIe 带宽单节点内卡间通信影响张量并行效率中需感知拓扑网络带宽IB/RoCE跨节点通信影响数据并行和流水线并行效率高网络拓扑感知调度是核心难点电力与散热封顶集群供电限制某些节点不能全量跑常被忽略但实际中很致命调度器如果只解决显存分配那是“内存条管理员”。真正让调度器成为隐形老板的是它能把算力、显存、带宽、电力这些变量统一建模然后在成千上万个任务之间做多维度的最优匹配。调度器凭什么当“隐形老板”核心职责是排班2.1 排队也要排出水平如果你只用过几个人的小集群可能会觉得排队有什么难的先来后到呗。但训练任务的排队不是这么简单。场景一深夜有一个 512 卡的大任务要提交但只剩 500 卡的空闲资源。是先让大任务等到第二天有人释放资源后启动还是先让两个 256 卡的小任务跑起来把碎片资源用掉 场景二两个团队的训练任务优先级不同A 团队是在跑线上模型的紧急微调B 团队是在做长达一周的预训练。谁插队谁能被抢占 场景三集群高层级管理员有预留的“保底资源”这个保底资源什么时候释放给普通任务用这些问题本质上就是“排班表”的问题。调度器内部需要有一套多维度的排队策略包括优先级Priority、配额Quota、公平性Fairness、抢占Preemption、预留Reservation和装箱Bin Packing。如果只看表面调度器像个“任务收发室”真正深入进去你会发现它其实是个“作战指挥室”——每个任务占用多少资源、预计跑多久、关键程度多高、失败后怎么重试所有信息都在这个系统里建模调度器据此做出最优决策。2.2 从“手工抢卡”到“声明式调度”在早期训练集群里我们惯用手工方式谁的模型大谁说了算谁跟运维关系好谁先拿到卡。这种方式在 10 卡规模勉强能转100 卡规模就开始出乱子1000 卡以上根本不可维持——因为人在回路里的决策速度太慢而且公平性没法保证。现代调度器普遍是“声明式”的。用 Kubernetes 生态的术语说用户不需要去“挑选”某张 GPU而是声明自己的需求我要 16 张卡每张卡 80G 显存NVLink 必须全互联与其他任务不要共享节点。调度器根据集群当前状态自动完成节点选择、绑定和启动。这个转变非常关键它把用户从资源管理的细节中彻底剥离开来。用户只需要声明意图至于底层是哪个节点、哪块物理卡、怎么做到满足约束条件都是调度器的事情。这也是云原生训练平台能够成立的核心理由。提示声明式调度的前提是调度器本身足够智能。如果你的调度器只会简单匹配“有多少卡、要不要”那声明式只会把用户和资源之间的距离拉远问题反而暴露得更慢。2.3 优先级与抢占机制谁打断谁的人生在 GPU 高度紧张的集群里抢占是常态。但是抢占训练任务和抢占在线 Web 服务的逻辑完全不同——训练任务是有状态的它需要定期保存 checkpoint被抢占时如果没来得及保存损失的是几个小时的算力。所以调度器要处理的抢占不只是“终止进程”而是优雅抢占先给任务一个宽限期让它把当前 step 的梯度同步完、把模型权重存入 checkpoint再回收资源。优先级倒置高优先级任务依赖低优先级任务的产出比如共享数据集、共享缓存一味抢占反而会让高优先级任务卡在依赖上。成本感知如果是云上异构集群从竞价实例上被抢占和从按量付费实例上被抢占损失模型完全不同。好的调度器会把“抢占”也作为一个可编排的过程而不是一个粗暴的 Kill 操作。这方面Volcano 和 Kueue 都在抢占策略上做了很多文章值得研究。从 CPU 调度到 GPU 调度不只是资源翻倍3.1 CPU 调度的逻辑拿到 GPU 上往往会失灵早期做 Kubernetes 调度的人都习惯于 CPU 调度那套打法看 CPU 核数、内存大小、镜像拉取然后做节点打分。但 GPU 调度完全是另一种生物。CPU 是时分复用模型内核在多个进程之间快速切换看起来像“同时运行”。GPU 更偏向批量并行模型一张卡可以同时跑多个 CUDA context但计算吞吐会受到很大影响。更麻烦的是GPU 显存是独占且不可压缩的——进程申请了显存就算只是占着不用别人也用不了。这种特性导致一个必然结果GPU 调度必须在“显存独占”和“算力共享”之间找平衡。整卡调度最简单一张卡只给一个任务隔离性好但利用率低。小模型也独占一张 A100纯浪费。算力切片用 MIGMulti-Instance GPU或类似技术把物理卡切成多份每份有独立的显存和算力。隔离性好利用率高但切分的粒度有限制。时间片共享多个任务以时间片方式共享一张卡利用率最高但性能不稳定一个任务可能干扰另一个。显存共享 算力竞争允许多个任务同时在一张卡上跑每个任务吃一部分显存算力自行竞争。这是目前很多“共享 GPU”方案的底层逻辑也是问题最多的方案。调度器的职责就是根据任务特性自动选择上述模式。比如一个大模型训练任务占整卡就是合理的而一堆小模型推理任务走显存共享才划算。如果没有调度器在中间做决策就会出现“一个 Bert 占据了整张 A100 却只用 2% 算力”的荒谬场景。3.2 为什么传统的 HPC 调度器不够用说到 GPU 调度很多人第一反应是 Slurm。Slurm 在传统 HPC 领域确实是王者但到了云原生、容器化、微服务化的 AI 训练场景它的短板就露出来了容器支持不够原生Slurm 也能跑容器但和 Kubernetes 对 Pod、Service、ConfigMap 的一等公民支持相比体验差距明显。弹性能力弱大模型训练经常需要动态扩缩容Slurm 的作业模型更偏向静态提交。生态割裂AI 平台一般需要和监控、日志、CI/CD 流水线无缝集成Kubernetes 生态显然更顺手。所以在现实的主流方案里大家不太纠结“Slurm 还是 Kubernetes”而是“以 Kubernetes 为底座上面叠加一层面向 AI 的调度插件”。Volcano、Kueue、Koordinator 都是这个思路的代表。开源调度器横评Volcano、Kueue、Slurm 与 Ray 的定位分岔4.1 VolcanoKubernetes 原生的批量调度老将Volcano 是 CNCF 孵化的项目定位很明确为 Kubernetes 提供高性能批量调度能力。它很早就提出了几个概念后来成了 AI 调度的标配Queue定义一组 Pod 的资源配额、优先级上限相当于给不同的团队、不同的业务线划定资源池边界。PodGroup把一组强关联的 Pod 组合成一个调度单元。这里解决的是“任务Job”级别的原子性调度——要么所有 Pod 统一被调度成功要么都不调度避免资源死锁。Gang 调度All-or-Nothing这是大多数 AI 分布式训练场景的硬性需求。比如 Open MPI 任务是 64 卡一起启动的调度器必须先找到足够 64 卡同时满足的节点集合一次性全部启动。否则会出现 50 个 Pod 起来了、14 个还在等那 50 个只能干等浪费资源。实际用下来Volcano 对 PyTorch、TensorFlow 分布式训练的集成度相当高尤其和 Kubeflow、PaddlePaddle 这些框架配合时非常顺手。如果你已经决定在 Kubernetes 上做训练平台Volcano 是绕不开的一个选项。4.2 Kueue更关注“排队”和“配额”的轻量级方案Kueue 来自 Kubernetes SIG定位比 Volcano 轻一些。它不直接管理 Pod 的调度细节底层仍是 kube-scheduler而是专注在 Job 层面的队列管理和资源配额。Kueue 的核心抽象是ClusterQueue集群级别的资源池定义可用资源总量、公平策略。LocalQueue命名空间下的队列用户可以往这个队列提交 Job。Cohort把多个 ClusterQueue 组成一个资源共享组允许有空闲资源的队列临时借用资源给其他队列解释权在调度策略。这个设计和 Volcano 有重叠之处但 Kueue 更强调“借还”逻辑比较适合那些业务线多、资源水位波动明显的团队。我自己偏向于在“以多租户配额管理为核心诉求”的平台里用 Kueue在“以复杂作业调度和 GPU 共享为核心诉求”的平台里用 Volcano。4.3 Slurm老而弥坚的 HPC 旗舰如果你不是纯云原生环境手上还管着一堆裸金属服务器且科研人员习惯用sbatch提交作业那 Slurm 依然是值得考虑的方案。Slurm 的优势成熟可靠几十年生产验证各种边缘 case 都踩得差不多了。作业管理模型强大对依赖、数组作业、抢占、QOS 的支持都很完善。对 InfiniBand 等高速互联拓扑的感知能力强毕竟 HPC 是它的老家。但你需要接受它和 Kubernetes 生态割裂的现实。现在很多单位走的是“裸机 Slurm 计算节点上装容器运行时”的折中路线既保留 HPC 作业管理界面也能跑容器化训练任务。4.4 Ray任务内调度与弹性扩缩容的另一条路严格来说Ray 不是一个“调度器”它更像一个“分布式执行引擎”。但当你需要在单任务内部进一步做细粒度资源分配比如同一个训练任务里一部分工作做数据预处理另一部分做模型训练Ray 的 Placement Group 和 Autoscaler 可以作为 K8s 调度器之上的补充层。Ray 的调度能力是“第二层调度”它和 K8s 的关系不是替代而是互补。Ray 在物化 DAG 的调度、动态 actor 的创建、失败恢复上比 K8s 原生调度器灵活得多。但代价是整个技术栈多了一层运维负担。我个人的选择倾向是场景推荐方案大型预训练任务需求稳定的多节点并发Volcano 或 Slurm多团队共享集群配额和公平性是第一诉求Kueue kube-scheduler需要同一份代码做训练、调参、推理部署全流程Ray混合场景HPC 作业与容器云原生作业并存Slurm Kubernetes 双轨制没有银弹只有适不适合。调度器只是起点真正决定训练效率的是隐藏配套5.1 拓扑感知一张卡在哪个位置直接决定速度上限同样是一百张卡全部在同一个节点内、分散在十个节点内、跨两个机柜这三种布局下的模型训练速度可能差出 3 倍以上。这个差异来自网络拓扑。大模型分布式训练里张量并行Tensor Parallelism需要在卡间频繁交换数据对带宽和时延极其敏感数据并行Data Parallelism则相对宽容只需在 step 结束时同步梯度。调度器如果不懂这个区别把需要张量并行的 8 张卡散落在 8 个不同节点上那性能会死得很难看。所以调度器必须有拓扑感知能力支持将一组 Pod 调度到同一个节点或同一对叶子交换机下的“亲和性”约束。支持 GPU 索引级别的控制比如“必须用 NVLink 全互联的 8 卡”。在节点故障或资源不足时根据训练框架的并行模式动态调整调度策略而不是一刀切。很多团队曾经踩过的坑任务在 8 卡 A100 上测试时吞吐很漂亮一上到 64 卡集群就直线下降最后查出来是调度策略把同一台机器的卡分到了 8 个不同租户。这个锅得调度器背。5.2 有状态调度与 Checkpoint 感知训练任务不像 Web 服务挂了重启就行。一个训练任务可能已经跑了 3 天如果因为某个节点故障导致任务直接意味那损失不是“几分钟不可用”而是“几十万 GPU 时的算力充值打水漂”。所以调度器还必须具备“有状态”的调度思维Checkpoint 亲和性一个任务在节点 A 上挂了重启时优先调度到能最快访问它 Checkpoint 的位置比如共享存储挂载点或者具有本地缓存副本的节点。优雅退避任务失败后不能立即无限重启要根据失败的阶段启动失败、运行中失败、OOM 被杀设置退避策略。故障域隔离调度时尽量把同一任务的副卡分布在不同故障域不同电源、不同机柜、不同交换机下避免“一台交换机挂了整个任务团灭”。这些需求已经超出“资源分配”的范畴了本质上是在做任务生命周期的全流程管理。5.3 观察性调度器看不见但不能没有“眼睛”调度器做决策的前提是它的信息是准确的。如果调度器不知道哪张卡已经 ECC 错误频发、哪个节点的温度经常报警、哪块网卡的丢包率严重超标那它再聪明的策略也没用。我们在实际运维里会把以下几类信息接入调度器DCGM 指标NVIDIA 官方提供的 GPU 监控指标包括利用率、显存占用、温度、功耗、NVLink 错误率。节点级健康状态在一个训练任务结束后节点必须经过健康检查才能重新进入资源池。历史失败记录调度器对某些节点有“记忆”比如过去 24 小时内在该节点上失败的任务特别多动态降低这个节点的权重。调度器不是凭空做决策的它需要一套可靠的健康数据管道作为“眼睛”。没有这套眼睛调度器就是个瞎子排班再勤奋也白搭。从排班到治理构建你的训练调度体系6.1 第一步别急着上复杂调度器先做好资源池化很多团队一开始用裸金属机然后直接上 Kubernetes发现调度策略写不好又去改代码越改越乱。我的建议是先不要纠结调度器选型先把资源池化做好。资源池化的意思是把 GPU 资源无差别地交给一个统一的控制面而不是让每个团队都指名道姓要某台机器。你可以按团队分命名空间按项目分队列但底层物理机的资源是共享的谁要用谁申请。这一步本身就逼着你往声明式调度的方向走。6.2 第二步定义清晰的配额体系和优先级策略调度器再聪明也需要你先把规则定清楚。我见过的团队最常见的错误是“配额只限制上限不限制抢占”。结果就是平时大家的配额都用不满一到关键时期所有团队都超发任务调度器直接懵掉。一个相对合理的配额体系应该是每个团队有“保底配额”这是调度器必须确保的相当于最低工资。有“弹性配额”当集群空闲时团队可以借用超过自己保底额度的资源但必须允许在集群繁忙时被高优先级任务抢占。全局有一个“公平性原则”保证任何团队都不能长期霸占资源否则其他团队的等待时间会无限长。这一步要想清楚最好能在调度策略落地之前就定义好各种量化指标比如“高优先级任务等待不超过 10 分钟”“低优先级任务最长等待 2 小时”。有了指标调度策略才能像排班表一样被验证、被优化。6.3 第三步从“能跑”到“好跑”持续迭代调度参数调度器不是部署完就能一劳永逸的工具。至少在每个训练周期比如每次大版本模型训练完成之后我建议做一次调度复盘任务平均排队时间是多少是资源不足还是调度策略不够激进任务实际 GPU 利用率是多少有没有大量“装箱空洞”抢占发生的频率和成功率优雅抢占的检查点保存成功率是多少节点故障对上架任务的影响有多大是否需要调整故障检测频率这些反馈一定要回到调度策略里做闭环调整。调度器是我们用来排班的工具但真正决定这个工具好不好的是背后持续运营和维护它的人。运维侧的真实感受调度器是一个训练场的隐形老板7.1 一次真实的故障排场案例去年我们有一个 256 卡的任务一直是 Volcano 在调度。某天突然发现任务的训练吞吐下降了一半检查后发现任务从 64 卡扩展到 128 卡时Volcano 把新增的 64 张卡调度到了另一个机柜而那个机柜的交换机负责两个租户的流量。跨机柜通信导致集合通信的时间急剧上升。排查链路是这样的先看 DCGM 里单卡算力利用率发现 GPU 算力不满怀疑数据加载问题。排查 DataLoader发现 CPU 和存储带宽都不是瓶颈。抓网络指标发现跨机柜流量比之前多了几个量级。复盘调度记录确认是调度器对拓扑感知不足新增 Pod 被分散调度了。后来我们给 Volcano 加了拓扑感知的插件确保同任务的 Pod 尽量在同一个叶子交换机的范围内训练吞吐才恢复。这个案例给我最大的启发是在万卡集群里调度器的一个微小策略偏差放大到训练框架层面就是巨大的性能损失。“隐形老板”不是随便说说的它每个决策都在直接影响每一位训练工程师的体验。7.2 调度器要能解释自己不然运维就是玄学有一个特别容易忽略的点调度器需要能“解释”自己的决策。否则当用户问“我的任务为什么没有被调度上”时运维只能回答“它在哪里排队我也不清楚”这会让团队的信任度直线下降。去看 Volcano 和 Kueue 的日志时你会发现它们都有类似“unschedulable reason”的字段。你要做的是把这些原因透出给最终用户让他们在提交任务时就知道自己缺的是配额、是资源、还是标签选择器不匹配。我之前参与的一个平台在任务详情页里加了一个“等待原因”的字段用户可以看到具体是“集群资源不足等待中预计剩余 3 分钟”还是“当前队列配额已用完请申请提升配额”。就这么一个小小的展示群里的“资源客服”工作量骤降 90%。这个回报比很多花里胡哨的调度优化都值。写在最后的建议8.1 如果你才开始做训练集群建议从 50 卡起步刚开始做 AI Infra 的团队千万不要一上来就追求“万卡调度”的宏大叙事。从 50 卡集群起步先把 Kubernetes 跑顺把 GPU 设备插件、DCGM 监控、健康检查、Pod 清理机制都做扎实。50 卡的集群里调度器的作用看起来不大但你可以在这个规模下把“排队、配额、优先级、抢占、恢复”这些概念都理解透。等到 500 卡的时候用同样的机制去扩展就已经比那些从 0 直接跳到 5000 卡的团队稳太多了。8.2 调度器的本质是“组织能力”不是“技术指标”最后想说一个容易被忽略的点调度器表面上是在调度 GPU实际上是在调度人类的协作方式。它的排队策略映射着不同团队之间的资源关系它的优先级策略映射着公司业务的优先级排序它的抢占策略映射着对未来不确定性的容忍度。一个好用的调度器不只是它在技术上多先进而是它能把你所在组织的运行规则表达清楚、执行到位。所以当你为手头的训练集群设计调度方案时先别急着选型。先问团队一个问题我们的资源到底该按什么规则分配把这个问题回答清楚了选调度器就会变得非常顺理成章。这也是为什么我说调度器才是训练场的隐形老板——它不跑模型不调参数但它用一套规则管住了整个训练场里最稀缺、最昂贵的资源。一个训练系统能否高效运转秘诀往往不在模型代码里而在这个“隐形老板”怎么当。