1. 为什么万卡集群离不开一个靠谱的调度器
1.1 资源规模上去之后,人工分配彻底失效
一万张GPU是什么概念?按照目前主流的8卡服务器来算,大概是一千两百多台8卡机,再加上高速互联网络、分布式存储、管理节点,整个集群规模差不多有几千台服务器。到这个体量之后,人工分配资源这件事就彻底不现实了——不是说你算不过来,而是资源分配的决策维度实在太多。
你要同时考虑每台机器还剩几张卡、显存型号是不是满足要求、网卡带宽够不够、任务之间的通信拓扑要多近、谁有优先级、谁在等配额、哪些节点有硬件隐患需要避开……这些维度叠加在一起,组合空间是爆炸级的,人工根本无法枚举。我早期维护小规模集群的时候,确实见过不少团队是“谁喊得凶谁先跑”或者管理员手工安排,5台8卡机的时候还能勉强维持,到三五十台以上就彻底乱套了。真到了万卡规模,没有调度器的集群就是一场资源抢夺战,谁占住卡谁说了算,整个训练场的产出效率会低到你怀疑人生。
1.2 调度器管的不只是“分卡”,而是整个任务生命周期
很多人以为调度器就是一个“谁有空卡分给谁”的分配器,其实它是训练平台里管理任务全生命周期的核心模块。具体来说,它至少要负责下面这些事情:
- 排队管理:所有提交上来的训练任务进入队列,调度器按规则决定谁先被处理。
- 资源分配:基于任务的卡数、显存、内存、网络等需求,在集群中找到合适的节点组合。
- 生命周期管理:任务跑起来之后,持续监控状态,处理失败、重试、停止等事件。
- 配额与限额:给团队、项目设置资源上限,防止某个任务或团队把整个集群吃光。
- 抢占与重排:高优先级任务到来时,调度器决定是否驱逐或抢占低优先级任务的资源。
- 故障转移:节点宕机、GPU异常时,自动把受影响的任务重新调度到健康节点。
把这些放到一起看,你会发现它本质上是一个资源市场规则的制定者。它通过队列、配额、优先级、亲和性等手段,在有限资源里做最合理的分配。这也是为什么业内经常说调度器是训练场的“隐形老板”——用户感知不到它的存在,但你的任务什么时候能跑、能跑多快,它说了算。
注意,我这里说的是“合理的分配”,不是“最优的分配”。在万卡集群里,找到全局最优分配几乎是不可能的,调度器实现的都是一套启发式策略,在公平性、吞吐量和单个任务性能之间做平衡。这一点在下面的章节会展开讲。
2. 万卡调度的几大核心难点
2.1 资源碎片化:GPU明明很多,但凑不齐一桌
大模型训练任务对资源的请求通常是“一整块”的,比如要128张卡,或者要32张卡加对应的大显存。但集群里实际空闲的GPU资源往往东一块西一块:这台机器剩2张,那台机器剩4张,另外几台各有几张不同型号的卡。从总量上看,集群的空闲卡数完全够,但从“能够组成满足任务需求的一个连续资源块”来看,可能一块都凑不齐。这就是典型的资源碎片化问题。
GPU集群的碎片化比普通CPU集群更致命,因为GPU资源是多维的:卡数、显存、算力代际、节点间的互联带宽,全都得一起满足。调度器要做的,就是把零散的空闲资源“拼”成能满足任务的大块资源。这一步做得不好,整个集群的利用率会非常难看,真实利用率可能连50%都不到。
我见过一个真实案例:某个集群里空闲GPU总量一直维持在30%以上,但都是碎片卡和零散的几张小卡,结果一个大模型训练任务在排队状态等了两周都没排上。后来通过调整调度策略、为大任务单独保留资源池,才把这个僵局解开。这不是调度器“智能”不智能的问题,而是调度算法要做的搜索空间本身就很大,如何尽可能把碎片拼接成整块,是调度器设计的核心难题之一。
2.2 排队与优先级:训练场里也有“插队”和“回头客”
任务一多,排队是必然的。调度器怎么决定谁先跑?常见的方法有先来先服务(FCFS)、公平共享(Fair Share)、优先级队列等。但在真实的训练场景里,排队不是简单地按提交时间排序就行——不同团队、不同项目、不同任务的紧急程度完全不一样,而且还有“某个实验马上要出结果”“上一轮占过资源需要回补”这类现实因素。调度器必须支持多层次优先级,并在队列间做权重分配。
同时,调度器还必须处理一个矛盾:低优先级任务一直占着资源不放,高优先级任务进不来。这时候就需要抢占机制。但训练任务的抢占和微服务的重启不是一个概念——正在训练的模型如果被强杀,损失的是好几个小时的训练进度。所以很多系统在做抢占的时候,会优先选择“等当前任务做完一个checkpoint再抢占”,或者调度器先把高优先级任务安排到其他资源充足的队列。这些细节都是大量实践打磨出来的,不是看一遍文档就能想明白的。
2.3 Gang Scheduling:分布式训练的最强约束
这里要展开讲一个核心概念:Gang Scheduling,中文一般叫组调度或集合调度。分布式训练任务通常有多个worker(实例),这些worker之间要通过集合通信(AllReduce等)同步梯度,因此它们必须几乎同时启动。如果128个worker里只有120个拿到了资源,剩下8个还空着,那前120个worker启动后也会一直卡在通信等待上,整个训练任务不仅没进展,还白白占着120张卡的资源。
所以调度器对这样的任务必须做“全有或全无”的判断——要么一次性把128个worker的资源全部找到,要么一个都不分配。这个过程就是Gang Scheduling。用一个生活化的类比:四个人约好了打麻将,来了三个,第四个迟迟不来,那这桌就一直开不了。调度器要做的事情,就是凑齐四个人才让你上桌。如果没有这个机制,会出现大量“三缺一”的任务,资源被占着但训练没进度,集群白烧电。很多调度器专门实现了类似PodGroup这样的抽象来支持Gang Scheduling,核心逻辑就是等一个任务的所有worker都满足条件后,再统一调度。
3. 调度器排班的具体策略与参数
3.1 拓扑感知调度:把卡分得“近”一点
训练任务不是随便拿到卡就能跑得快的。目前主流的A100、H100这些GPU之间的通信,同一节点内走的是NVLink和NVSwitch,8张卡之间的通信带宽极高,延迟也低;跨节点的通信要走InfiniBand或RoCE,带宽和延迟都差一个量级。所以对数据并行、张量并行这类通信密集的任务来说,卡分得越“近”越好——最好都在同一个节点内,其次是同一个机架,最后才考虑跨交换机。
拓扑感知调度就是让调度器在分配资源的时候,尽量考虑通信拓扑,把任务分配到互连距离较近的一组GPU上。具体做法上,调度器需要提前获取集群的物理拓扑(机架、交换机、节点之间的连接关系),然后根据任务的通信模式,尝试把资源分配在同一节点或同一机架。这里有几个实际优化技巧可以分享:
- 先做节点级筛选,再做卡组合,用“最坏情况下的通信带宽”作为过滤条件。
- 把“同节点内的卡”作为第一优先级,“同机架但跨节点”作为第二优先级,尽量避免跨交换机分配。
- 对通信极其敏感的大模型训练任务,可以干脆设置成“只用同节点卡”,代价是利用率会降低,需要根据业务权衡。
对万卡级别的集群来说,这一步做得好不好,直接影响训练性能。同样一个模型,通信拓扑优化前后的step时间可能差20%到30%。这不是夸张,张量并行场景下跨节点通信占比很高,拓扑分得不好,算力再强也被通信拖死。
3.2 Binpack和Spread:省电还是稳定,你得选一个
调度器在决定“把任务放到哪些节点上”时,有两套典型的放置策略。Binpack是“尽量把任务塞到同一个节点上”,让空闲节点空着,好处是碎片少、可以关掉空闲机器省电,对追求集群吞吐的场景很友好。Spread则相反,是“尽量把任务分散到不同节点”,让每个节点的资源用量比较平均,好处是单个节点故障时影响面小,坏处是更容易产生资源碎片。
这两种策略各有各的道理,关键看你集群的目标。如果追求吞吐量和功耗控制,Binpack优先;如果追求稳定性和故障域隔离,Spread优先。实际生产环境中,很多系统会采用混合模式:根据任务大小、租户属性来动态调整。比如大任务优先Binpack,保证能拿到整块资源;小任务优先Spread,也尽量不打散现有的大块资源块。
实操心得:从我的经验来看,万卡集群上不要只配置一种放置策略。建议把默认策略设成Binpack,但对某些特殊租户或特殊任务(比如对故障隔离要求极高的核心交易模型训练)设置独立的Queue并指定Spread策略。这样既保证了大部分任务的调度效率和功耗表现,也能满足少部分关键任务的稳定需求。
3.3 配额、权重与抢占:多团队共享集群的“游戏规则”
万卡集群一定是多团队共享的。怎么保证A团队的实验不会被B团队的大模型训练活活饿死?这就需要配额和权重的设计。一套典型的做法是:每个租户(团队)有一个资源配额上限(Quota),但上限不等于随时可用——当租户的资源需求超过配额时,任务只能排队。同时,租户之间还有“权重”,权重高的租户在资源紧张时能拿到更大的分配比例。
调度器还会支持Backfill机制:在大任务排队的间隙,如果剩余资源跑得动一些小任务,就把这些小任务“见缝插针”地调度起来,提高资源利用率。这有点像高铁售票:大任务是大部队需要一整节车厢,小任务是散客,有空位就塞进去,既不耽误大部队,也能把座位利用率提上去。
排队场景中还需要小心一个问题:任务饥饿。如果某些租户的优先级一直很低,而且资源一直被高优先级租户占满,低优先级租户的任务可能永远排不上。调度器需要有抢占或配额保护机制,保证所有租户都有一定的“最低保障资源”。这里我特别强调一下:资源分配不是单纯比“谁更急”,而是要在多租户之间维持一个基本公平。否则底层研发团队永远跑不过核心团队,整个组织的AI产出结构会失衡。
3.4 实操参考:一个典型调度器的配置长什么样
先说明,下面这段不是某个特定产品的完整生产配置,而是我在实际项目中常用的配置思路,用来帮助大家理解调度器配置的结构。以Volcano + Kubernetes为例(这个组合目前使用率比较高),调度策略一般围绕Queue和PodGroup来定义。
比如有一个队列专门给大模型预训练用:
apiVersion: scheduling.volcano.sh/v1beta1 kind: Queue metadata: name: pretrain-queue spec: weight: 10 capability: cpu: "2000" memory: 8Ti nvidia.com/gpu: 512 reclaimable: true这个Queue里设置了weight=10,表示这个队列在竞争资源时的权重;capability限定了队列最大可用资源是512张GPU卡;reclaimable=true表示当高优先级任务到来时,这个队列可以被回收一些资源。同时,训练任务对应的PodGroup可以设置最小成员数:
apiVersion: scheduling.volcano.sh/v1beta1 kind: PodGroup metadata: name: train-128gpu-pg spec: minMember: 128 queue: pretrain-queue priorityClassName: highminMember=128就是让调度器等128个Pod全部满足条件后再一起调度,这就是Gang Scheduling的具体落地。跑训练任务时,在Job或Deployment上指定对应的PodGroup即可。实际项目中,我们还会给Pod设置GPU资源上限为1,并加上nvidia.com/gpu的请求,确保每个worker严格绑定一张卡。
实操心得:调度器的配置一定要和训练框架对齐。比如你用Megatron-LM做张量并行,模型会自己计算需要多少个GPU,你只要把PodGroup的minMember和训练脚本里的world size保持一致就行。不一致的话,要么资源浪费,要么直接调度不了,这两个问题都很隐蔽,排起来特别坑。
4. 主流开源调度器选型与落地对比
4.1 Kubernetes + Volcano:云原生时代的“爆款组合”
为什么现在大厂内部搞训练平台基本都绕不开Kubernetes?核心原因是Kubernetes已经成了基础设施的“标准底座”,提供容器编排、服务发现、存储接入等能力;而Volcano这类调度器补上了它在批处理和Gang Scheduling上的短板。Volcano是华为云开源、后来捐给CNCF的项目,定位是Kubernetes上的高性能批量调度器,专为AI、大数据这类负载设计。它引入的Queue、PodGroup、Task等抽象,正好对上了大模型训练的调度诉求。
部署上,直接通过Helm安装即可,配一个默认的调度器配置,把volcano-scheduler设为集群的默认调度器,原有业务不受影响。这套组合的好处是生态成熟、社区活跃、排障资料多;坏处是Kubernetes本身的复杂度不低,维护一个万卡级别的K8s集群,需要懂网络、存储、调度、安全等一堆东西。对于10人以下的小团队来说,上手成本其实是有点高的。但如果你的团队已经有K8s基础,那Volcano基本是性价比最高的选择。
4.2 Slurm:HPC老牌劲旅,稳定但偏传统
Slurm是高性能计算领域的老牌调度器,在AI时代之前就存在很久了,大量科研机构、超算中心都在用。它的设计思路偏传统HPC:用户通过sbatch提交作业,调度器在节点之间分配资源。它天生支持作业排队、节点独占、作业依赖、抢占等能力,而且稳定性极强。缺点是它的资源抽象不如Kubernetes灵活,对容器支持是后加的,和云原生生态的集成需要额外开发。很多从超算中心迁移到云原生架构的团队会有一段阵痛期,因为两套体系的思维方式和API完全不一样。
不过如果你是科研机构,或者现有的训练环境本来就基于Slurm,那真没必要为了赶时髦强迁到K8s。Slurm在万卡集群上照样能管得很好,关键是作业依赖、资源上限、节点分区这些要做好。从实际经验来看,Slurm更适合“任务排队式”的使用习惯,K8s + Volcano更适合平台化、多租户、微服务混合部署的场景。选型不是越新越好,要对齐你自己的使用模型。
4.3 大型厂商的自研调度器:往往都是“K8s + 定制”
国内一线大厂和不少头部AI公司,在超大规模集群上的调度器基本都是基于Kubernetes二次开发的,比如字节跳动的ByteSchedule、阿里的Batch Scheduler、快手的各类调度组件。这些自研调度器的核心工作是什么呢?主要是三块:
- 解决Kubernetes原生调度器在万卡级集群上的性能瓶颈,比如调度吞吐、调度延迟。
- 扩展Gang Scheduling、抢占、回填、拓扑感知等面向训练负载的专属策略。
- 和底层集群管理系统(节点池、自动扩缩容、故障自愈)做深度联动。
他们一般不会再从零写一个调度框架,而是围绕K8s做扩展,或者在Volcano等开源项目上做定制。这里有一个很现实的考虑:训练平台不只是调度器一个组件,它要跟数据加载、日志、监控、故障恢复、配额管理等模块联调。基于成熟开源生态二次开发,比从零造轮子安全得多。调度器的Bug一旦出在万卡集群上,影响面是灾难级的——轻则整个队列卡死,重则资源分配错乱导致误抢占,训练任务被大面积误杀。
5. 万卡集群调度常见问题与排查实录
5.1 任务一直Pending,怎么排查?
这是训练平台上问得最多的问题。任务提交之后一直处于Pending状态,最常见的原因有四个:一是配额不足,你请求的卡数超过队列或账号配额;二是集群确实没有满足需求的资源块;三是Gang Scheduling条件不满足,等的那几个Pod还没到齐;四是调度器本身有问题,比如配置错误或者角色权限不对。排查的顺序我建议是:先看调度事件和队列状态,再看节点可用资源,最后看PodGroup的状态。Kubernetes里一条kubectl describe pod能看到调度器拒绝的原因,Volcano也有对应的Queue状态的CRD可以查看。大部分Pending问题只要看调度事件就一目了然,不要上来就去改调度器配置。
注意,万卡集群里有一种特别隐蔽的Pending原因:你的任务请求了某种GPU型号(比如A100-80G),但集群里虽然有大量空闲的A100-40G,两者不匹配就会卡住。看起来是“资源充足但任务排不上”,实际上是显存约束不满足。排查时一定要把GPU型号、显存大小、驱动版本这些资源属性一并检查。
5.2 高优先级任务被低优先级任务挤住,怎么办?
训练任务的抢占是个技术活。早期很多调度器实现抢占就是简单的“杀掉低优先级任务的Pod,释放资源给高优先级任务”。但在大模型训练场景,这种硬抢占的代价极高——模型训练到一半被杀掉,所有进程退出,之前几个小时的训练进度可能全部丢失。更合理的做法是“优雅抢占”:调度器检测到高优先级任务需要资源,会给低优先级任务一个宽限期,让它在保存checkpoint之后主动退出。
这个能力不是所有调度器原生支持的,需要在应用侧配合:训练框架要能接收外部信号(比如SIGTERM),然后触发checkpoint保存再退出。这也是我强烈建议所有跑大规模训练任务的团队,一定要做好checkpoint机制的原因。没有checkpoint,任何调度策略都救不了你的训练进度。我们团队踩过这个坑之后,在训练镜像里默认加了一个信号处理脚本,捕获SIGTERM后自动保存checkpoint再退出,从此抢占导致的任务回退问题基本消失。
5.3 GPU利用率低,但任务又占着卡不放
这个问题我在很多团队都遇到过。现象是资源明明分配出去了,但GPU利用率很低(比如10%),任务半天跑不完。这时候第一反应不要怪调度器,先看训练框架本身——是不是数据加载成了瓶颈、是不是并行策略没配好、是不是模型太小但卡太多。只有当资源利用率和任务实际需求不匹配,且集群整体排队严重时,才需要从调度策略上考虑:比如限制单任务的卡数上限、禁止超规格申请、根据任务类型做资源画像。
调度器能做的事情是前置检测:在任务启动时检查它申请的GPU数量和模型规模是否明显不匹配,必要时拒绝调度或者提示用户。这种“智能温饱管理”在万卡集群里非常有用,能避免大量资源被“大而无当”的任务空转烧掉。但也要注意别矫枉过正,限制太死会让用户觉得平台不灵活。
5.4 节点故障了,调度器怎么兜底?
万卡集群里,节点故障是常态,不是意外。一块GPU卡损坏、一个节点宕机,都是每天都可能发生的事情。调度器的故障兜底一般分两层:一层是Kubernetes层面的Pod自动重新调度,另一层是调度器把故障节点上的任务标记为失败,并根据重启策略重新排队。这里有一个关键的配合要求:训练任务本身要支持断点续训。如果你没有做checkpoint和自动重启逻辑,那节点一挂,就算调度器把任务重新排上,它也是从头开始训练,等于白跑。
所以在设计整套训练平台时,调度器、checkpoint机制、训练框架的容错能力必须通盘考虑,三者缺一不可。我见过一个团队,调度器做得非常完善,但训练脚本没有checkpoint和自动重启逻辑,结果节点一故障就全盘重跑,集群利用率直接腰斩。这不是调度器的问题,是整个系统设计的问题。
5.5 日常巡检:盯哪些指标?
最后分享一组我在实际运维中会长期盯的调度侧指标,供参考:
- 队列等待时间中位数和P99:这个反映排队是否严重。
- 已分配GPU数 / 集群总GPU数:整体利用率。
- Pending任务数:有积压说明资源或调度策略需要关注。
- 调度成功率和平均调度延迟:排查调度器性能问题。
- 抢占发生次数和失败次数:抢占太多会影响训练稳定性。
- 各租户实际使用量 vs 配额:防止个别租户超分。
这些指标建议全部接入Prometheus和Grafana,做成可视化看板。不要等用户来反馈“我的任务排不上”你才开始查,主动监控才是一个调度系统健康运行的前提。
从我个人的经验来看,给一万张GPU的集群设计调度方案,最大的坑不是技术,而是“你以为你了解资源使用情况”。太多团队在搭建训练平台时,对调度器的作用理解停留在“分卡”层面,结果真正上线之后,各种排队、抢占、碎片化问题扑面而来。最让我印象深刻的教训是,调度策略一定要和训练框架、checkpoint机制、监控体系放在一起设计,而不是单独把调度器当成一个独立组件选型。这也是为什么我在前面花了那么多篇幅讲Gang Scheduling、讲优雅抢占、讲故障兜底——因为这些东西只有在整个AI Infra链条里相互配合,才能让一万张GPU真正做到“排班有序”。如果你正在建设训练平台,建议先从一个小规模的调度器验证开始,模拟真实任务的资源需求,把队列、配额、抢占、故障这些场景都跑一遍,再上大规模。这套流程走下来,你对调度器的理解肯定比只读文档要深得多。