拓冰建站拓冰建站
首页 / 资讯中心 / 正文

GPU调度器:决定大模型训练效率的隐形指挥官

晚上十一点训练平台的聊天群里照例有人喊了一句卡又全被占了这个问题我听过太多遍后来每次有新同学来团队我都习惯用这句话开场GPU 调度器不生产算力也不写训练代码但一万张 GPU 怎么排班、谁先跑、谁等着、谁被临时撵走全部由它说了算。它就是训练场上那个隐形老板。这篇继续 AI Infra 系列里训练与调度的话题不过这次换个落点不聊并行策略怎么切分模型而是看集群层面的人员管理——当训练任务从单机变成千卡、万卡规模调度器在背后做了哪些决策它对训练效率的影响为什么经常被低估。无论你是做大模型训练的算法工程师还是负责 GPU 集群的运维又或者只是租了几张卡跑微调搞懂调度器的逻辑都能少踩很多坑。1. 没有调度器的训练场抢卡全靠吼早期做训练的人应该都经历过那种原始社会阶段。团队里十几张卡谁要用就在群里问一句或者直接去机器上 nvidia-smi 看一眼哪张空的就 ssh 上去把任务拉起来。运气好一点再写个简单脚本定时检查显存有卡了就自动提交任务。这种模式不是不能用但天花板非常低。真正让人肉调度崩溃的是大模型训练开始之后。以前一个小模型训练任务跑几个小时就结束了等一等无所谓的。现在一个 7B 模型的预训练动辄跑几周一张卡上任务占着的时间从小时级变成周级资源的稀缺性完全不是一个量级。再靠人在群里吼效率就是灾难。我见过一个有几十张卡的小组一个多月的时间里一半的算力其实都处于碎片化闲置状态——不是没有任务要跑而是没人协调谁该让一让。所以调度器的第一个价值很简单把资源分配从人工协商变成系统裁决。一堆任务提交过来每张卡该给谁、该在什么时候给不再依赖某个人的记忆和人情而是由一套规则自动决定。这个转变本质上就是把算力当成一种公共资源来治理。你就想象一个没有红绿灯的十字路口——车不多的时候大家眼神交流一下也就过去了车流量上来之后没有规则约束一定会堵死甚至出事故。GPU 调度器就是那个红绿灯系统。它做三件事确认哪条车道上还有空间资源查询决定放哪辆车先走排队与优先级当有车抛锚了及时把后续车辆引到别的车道故障处理。从技术选型上看小规模团队一般会用 Kubernetes 加上 Volcano 或 Kueue 这类调度组件灵活且生态好传统一些的实验室则可能用 SLURM再往上层云的 GPU 实例集群也有自己的资源管理系统。不管底层是哪一套它们解决的痛点是一样的。你只要记住这个定位后面看到队列配额抢占这些概念就不会觉得它们只是 Kubernetes 里抽象的 YAML 字段而是一套真实的人在真实资源上的协调规则。2. 调度器到底在算什么账显存、带宽、配额与生命周期很多第一次接触 GPU 集群的人都会有个疑问调度不就是谁有空卡就给谁吗好像没什么复杂的。实际上一台八卡 GPU 服务器的有空和适合你的任务跑是两件完全不同的事。调度器要考虑的是四本账显存账、通信账、配额账、生命周期账。2.1 显存账一张卡装不下整个模型显存是调度器最基础的判断依据。这里需要先有点感觉一个 7B 参数的大模型用 BF16 精度存权重光模型参数就是 7×214GB。训练要比推理更吃显存除了权重你还要存梯度以及 AdamW 优化器里的一阶动量和二阶动量——这两个状态通常按每个参数 8 字节来算也就是再加 56GB。这么一加光参数、梯度、优化器状态就超过 80GB一张 80GB 的 A100/H100 直接就不够用了。实际训练中都是靠 ZeRO 系列做显存分片把优化器状态、梯度甚至参数切到多张卡上。可调度器在决策的时候必须知道这个任务申请了 64 张卡每卡需要多少显存然后看目标节点剩余显存是否满足。这个账要是算错了任务一启动就会 OOM或者因为显存碎片化被内核杀掉。所以调度器面对的不是这张卡有没有人用的布尔问题而是这张卡此刻还剩多少可用、任务要多少、要不要做显存预留的资源规划问题。很多训练平台会给每个任务定义 resource request 和 limit本质上就是让调度器能做这个判断。2.2 通信账卡放在哪里直接决定训练快慢显存账只是第一关。比显存更容易被忽视的是通信账。大模型训练里数据并行、张量并行、流水线并行各有各的通信频率和通信量。数据并行每个训练步要做一次全规约AllReduce把各卡梯度加到一起通信量大约是两倍的模型参数量乘以精度字节。7B 模型 BF16 一次 AllReduce 要传 28GB 数据张量并行更夸张每个 Transformer 层的前向和反向都要做通信频率远高于数据并行。这意味着卡与卡之间的物理距离极其关键。两台机器之间走的是机架顶部交换机甚至跨核心交换机带宽和延迟跟单机内部 NVLink 根本没法比。调度器如果不懂拓扑把一个需要张量并行的 8 卡任务散到 4 台不同机器上那这任务的通信开销会直接慢到一个让人想砸键盘的程度。有经验的调度系统在做分配时会做拓扑感知尽量把通信最频繁的那几张卡放在同一个节点内或者同一个交换域内减少跨机通信。这也是为什么有些平台的分配结果看起来就很有讲究——不是随机挑 8 张卡而是把一个完整节点整租给你的任务。2.3 配额账资源永远不够分配规则就很敏感第三本是配额账。一万张 GPU 听起来很多但分给多个团队、多个项目之后每时每刻都会有人觉得不够用。调度器需要维护谁有多少份额这套账目——比如 A 团队总共申请了 1000 卡权重是 3B 团队总量是 500 卡权重是 1。当系统里同时有多个任务在排队时调度器需要按权重决定资源分配比例保证不至于出现一个团队长期独占算力的情况。这种配额和队列的设计很像银行排队的 VIP 通道和普通通道。调度器要决定哪些任务进快速通道哪些要排队同时还要防止某个大任务把整个资源池占满而饿死小任务。这也是为什么真正在训练平台里一个任务除了要多少卡还带着优先级队列名租户这些元数据。2.4 生命周期账任务不是提交完就结束了最后是生命周期账。一个训练任务从提交到结束中间会经历排队、镜像拉取、环境初始化、运行、正常结束、异常退出、被抢占、被迁移……这一长串状态转换全都要调度器记录和推动。我在实际运维里最常见的场景是任务跑了一天后进程崩溃退出如果调度器发现退出的任务还有 checkpointer就会自动重新拉起来并把资源重新分配好。整个过程用户感知到的只是任务重新进入 pending但实际上系统在背后做了一整套状态恢复。如果生命周期管理做得不仔细任务残留在节点上的僵尸进程、没释放的共享内存、没清理的临时文件都会变成下一批任务的隐形炸弹。3. 排班策略背后是价值取舍排队、抢占与超售理解了调度器在算哪几本账之后就该聊策略了。同样是一万张 GPU 怎么排班策略不同给人的体感完全不同——有点像同一家医院有的门口排大队但秩序井然有的看似没人排队但其实乱成一锅粥。调度策略本质上是几个价值取舍。3.1 先来后到还是让优先级高的插队最简单的策略是 FIFO——谁先提交谁先跑。好处是规则简单、确定性好任务什么时候启动基本可预期。但坏处也明显一个大任务只要先来就能把资源占很久后面一堆短小任务全部卡在队列里。所以现代调度器普遍引入优先级队列和权重配额。你可以想象成飞机值机经济舱排队头等舱优先但这不代表经济舱永远轮不到——系统会设定规则比如高优先级任务最多从队列里挤掉多少个低优先级任务避免低优先级被饿死。这类调度的实现通常叫抢占式调度在 Kubernetes 生态里对应 PodPriority 和抢占控制器。它要回答的问题不是要不要优先而是为了一个高优任务把已经在跑的低优任务撵走到底值不值。这个值不值取决于被打断任务的代价。3.2 抢占的代价不是什么任务都经得起一巴掌调度器把正在跑的任务撵走成本远不是手动杀个进程那么简单。训练任务中有大量的昂贵中间状态模型权重更新了一半数据 loader 已经在某个 epoch 的中段随机数生成器的状态、学习率调度器的位置、checkpoint 的保存是否完整——这些都是被打断时需要考虑的。正因如此一个成熟的调度器在发起抢占之前会先通知任务进入优雅退出流程暂停训练把当前状态写入 checkpoint释放显存最后才终止进程。如果任务方没有处理好这个信号进程被强杀后重新启动就要从上一个 checkpoint 重新训练所有中间消耗的算力直接打水漂。这也是为什么训练框架的容错能力几乎和调度器一样重要。PyTorch 自带的一些 checkpoint 工具、NVIDIA 的容错框架比如 NeMo 的保存/恢复机制配合调度器的优雅抢占机制才能让高优任务插队这件事不至于变成一场灾难。只配了调度器不管任务方就相当于老板把员工的工位收走却没给时间保存文件——合理但会让人抓狂。3.3 超售与资源碎片化一张卡能不能多塞任务做 AI Infra 的人对利用率这个词都有点执念。一万张 GPU 的集群如果平均利用率只有 30%管理层一定会问为什么。但现实是很多训练任务申请了 80GB 显存实际峰值只用到 50GB申请了 8 卡其中 4 卡的计算密集度比其他卡低。这种资源碎片化是利用率上不来的主因之一。调度器应对碎片化的一个手段是超售——同一张卡上同时跑多个任务。这听起来有点危险但实践中有很多变体有的靠显存复用把不活跃模型的显存暂时让给另一个任务用有的靠 MIG 把一张 A100 切分成多个独立的 GPU 实例还有的靠 GPU 时间片在任务 A 等待数据加载时插入任务 B 的计算。超售的关键是算好风险账。超售比例太大两个任务同时在显存和算力上打架可能谁都没法好好训练反而拖垮整体吞吐。我在实际生产中见过的比较稳的做法是对显存进行软隔离允许超售但强制设置硬上限同时监控 SM 利用率一旦出现持续争抢就把低优先级的那个迁移走。这里没有什么银弹就是踩数据、看回报、调参数。3.4 不同调度器的策略取向差异调度器典型场景核心特点代价与注意点SLURMHPC、传统实验室FIFO 队列管理成熟稳定性高对容器化、微服务生态支持较弱Kubernetes Kueue云原生训练平台基于队列配额灵活支持抢占、超售需要熟悉大量 CRD运维配置复杂Kubernetes Volcano高性能训练工作负载支持 gang scheduling、拓扑感知面向 AI 场景需要根据集群规模做参数调优Ray分布式训练与弹性扩缩任务内动态调度自动化程度高资源隔离粒度比较粗大规模集群需要额外治理这张表不是让你立刻选型而是说明调度器不是一套固定的算法而是根据团队需求做出的取舍。你的集群是跑大量短任务还是跑少量长训练决定了你该偏向哪一个。4. 万卡集群的日常故障恢复与拓扑感知为什么是调度器的主业很多人以为调度器只在任务提交那一瞬间起作用。真正管理过千卡以上集群的人会告诉你调度器 80% 的代码逻辑和精力都花在处理意外上。因为在这个规模下坏卡不是异常是日常。4.1 在万卡集群里今天没有故障才是新闻一万张 GPU 的集群假设单卡年故障率在百分之几这个量级平均下来每天都会有卡出问题。这还不算那些不会彻底坏、但开始不稳定的情况显存位翻转报 ECC 错误、GPU 温度过高触发降频、网卡丢包率上升、NVLink 链路告警。训练任务最怕的恰恰不是卡直接坏了——坏了调度器检测到就能摘除节点而是那种半死不活的状态卡偶尔报错但任务还挂着或者某张卡的通信延迟突然变高拖慢整个分布式训练。后者会让一个本来该跑 10 天的任务悄悄跑成 15 天而且你很难定位原因。4.2 心跳、ECC 与节点摘除调度器处理故障的第一层手段是心跳检测。每个节点的 agent 定期上报状态节点失联超过阈值调度器就会把上面的任务标记为异常触发重新调度。这个逻辑听起来简单实际执行的时候要非常小心如果网络抖动导致节点短暂失联直接把它摘了那调度器自己反而成了最不安定的因素。第二层是硬件错误检测。NVIDIA 驱动会报告 GPU 的 ECC 错误数量分为可纠正和不可纠正两种。可纠正的 ECC 错误偶尔出现可以容忍但如果某张卡的错误计数在短时间内快速上升调度器就应该把它标记为亚健康不再调度新任务上去并在当前任务跑完后把这张卡下线检修。第三层是慢节点处理。分布式训练是木桶效应——一个节点慢了整个训练步都要等它。调度器不一定能直接判断哪个节点变慢但配合监控系统采集的 step 耗时、通信耗时能把异常的节点挑出来后续调度时优先避开。这个能力后来也成了判断一个调度系统是否够专业的分水岭。4.3 checkpoint 与优雅退出调度器、框架与任务的契约前面提到抢占时任务要优雅退出其实在故障场景下同样成立。一个好的训练任务应该做到三件事定期把模型状态写到共享存储响应调度器发来的终止信号时主动做一次 checkpoint 再退出重启后能从最近的 checkpoint 无缝续跑。调度器在其中的角色是契约执行者它在决定终止任务前会先发一个 SIGTERM给任务一个保存现场的时间窗如果超时未退出再发 SIGKILL。这个时间窗设置多长需要根据任务 checkpoint 耗时来配——设置太短任务来不及保存就被杀掉设置太长高优任务等得着急。顺便提一个让我印象深刻的坑有一个训练任务每隔两小时保存一次 checkpoint但调度器配置的优雅退出宽限期只有 60 秒。任务被抢占时最近的进度最多会丢接近两小时。后来把保存频率改成 10 分钟一次中断损失才降到可接受范围。这种任务的持久化频率和调度器的宽容限期之间的匹配是最容易忽略又影响巨大的细节。4.4 拓扑感知把通信频繁的任务放在同一屋檐下前面讲通信账的时候提过拓扑感知这里展开讲为什么它算调度器的主业之一。在万卡集群里网络规划通常有层次单机内有 NVLink 全互联机架内有交换机往上还有核心交换机。跨层次带宽和延迟逐级恶化。一个好的调度器在分配资源时会尽量把同一个训练任务里的卡集中在一个较小的网络域内。拿张量并行TP举例TP 的通信频率极高最好让 TP 的 8 张卡全部落在同一台 8 卡机器上数据并行DP通信频率低一些可以跨机器流水线并行PP虽然通信频率更低但每一笔通信都直接卡在流水线关键路径上也要尽量避免太多跨跳。这些约束在调度器里叫亲和性规则或拓扑约束。没有这个意识的调度器会把一个任务分散到集群各个角落表面看每张卡都用起来了实际上通信瓶颈让训练效率大打折扣。我经手过一个排查案例同一个训练脚本在 A 平台跑每步 3 秒到 B 平台变成每步 8 秒最后发现就是 B 平台没有做拓扑感知把需要高频通信的 8 张卡散到了多台机器上。4.5 可观测性调度器只知道结果监控系统解释原因调度器做资源分配但它多半不知道自己分配的卡实际跑得好不好。这时候可观测性系统就要补位。真正靠谱的训练平台除了调度器之外一定会配一套 GPU 监控面板能看到每张卡的 SM 利用率、显存带宽利用率、温度、电源功耗还能看到分布式训练任务的通信耗时和梯度同步延迟。这些数据反过来又喂给调度器帮它做更聪明的决策。比如某个节点的 GPU 长期处于低利用率状态调度器就会少派任务过去某台机器的 ECC 错误持续累积调度器会自动调低它的分配优先级。调度器和可观测性系统之间是一个闭环优化的关系。5. 和调度器打交道给训练开发者的实操建议文章写到这里前面偏底层和平台视角。这一部分我想把视角切回来聊聊站在普通训练开发者的立场怎么和调度器相处。毕竟调度器本身不会让你的模型收敛它只是决定你什么时候能开始跑、跑的时候顺不顺。5.1 申请资源别拍脑袋先写出你的资源清单很多人在提交训练任务的时候只填一个我要 8 卡、每卡 80GB然后点击提交。这个粒度太粗了。调度器最怕的不是你写得很大而是你写得不准。建议你在提交之前先算一下自己的资源账单模型参数量多少、用什么精度、用不用 ZeRO 分片、序列长度和 batch size 会引入多大的激活值、数据加载会不会占大量 CPU 和内存。把这些算清楚之后再决定申请几张卡、每卡预留多少显存。申请得比实际需求小任务容易崩申请得太大排队时间长还会被管理员约谈。有个比较实用的经验对训练任务显存请求量不要卡得太死留 10%~20% 的冗余给临时峰值但对 CPU 和内存的请求反而可以少一点因为大部分 GPU 训练任务的 CPU 密集度没有想象中高。在满足需求的前提下资源规格填得越贴合实际调度器分配起来越容易你的排队时间也越短。5.2 让你的脚本学会听调度器的话调度器的优雅退出机制任务方一定要响应。具体来说训练脚本里要能捕获终止信号信号来了之后停下手头的工作保存一次 checkpoint再退出。这在 Python 里可以用 signal 模块实现也可以直接用 PyTorch 的 torch.distributed 里 some 容错回调。另一个容易被忽略的点是日志。任务被调度器杀死或者抢占时一定要能通过日志看到原因。如果调度器因为节点失联把任务杀了日志里要有节点 ID如果是被高优先级任务抢占日志里要有明确的抢占标记。没有这些信息你会花很长时间在我的任务为什么退出上面猜谜。5.3 排队时间别浪费准备好数据和镜像训练任务提交后往往要排队等卡。这段时间看起来是在白等其实能做的事情很多。最实用的是把数据和镜像提前准备好数据提前下载到本地或共享存储镜像提前拉取到目标节点代码和配置做一次预检查。有些调度系统支持排队时预热也就是在任务真正分配到节点之前先把镜像拉好这样一旦资源就绪任务立刻开始跑而不是还要花十分钟拉镜像。我就见过很多团队任务排队三小时真正启动前拉镜像又花了半小时白白浪费了宝贵的时间窗口。这些问题如果不在任务提交前处理好调度器那边再优化也帮不上忙。5.4 多机训练先怀疑网络再看显存多机多卡训练出问题时最常见的报错就是 NCCL 超时或者通信失败。这时候别急着怀疑模型代码先做分层排查用 nvidia-smi 看每张卡的状态是否正常用测试脚本测一下节点间的网络带宽是否达标再看分布式训练框架能不能正确初始化。我在排查中的经验是单机训练跑得好好的一上多机就出问题十有八九是通信层的问题而不是模型逻辑的问题。这时检查几个关键点MASTER_ADDR 和 MASTER_PORT 是否设置正确、NCCL 是否用到了预期的网卡接口有些环境需要 NCCL_IB_DISABLE 关掉 InfiniBand、tcp 超时阈值是否需要调大。另外如果任务提交到集群后总是很快失败先用小规模资源比如 2 卡手动在测试环境跑通一遍再申请大批量资源。直接在万卡集群上拿着 512 卡测一个没调试过的代码出了问题不仅拖慢自己还会浪费调度器的等待资源和所有人的耐心。我自己的体会是调度器这个角色有点像一个严格的排班主管它不关心你的模型多么惊艳只看你的资源申请是否合理、你的任务是否守规矩、你能否在遇到故障时优雅离开。所以与其抱怨排队难、资源少不如花点时间把自己的训练任务打磨成一个好租客——资源申请精确、支持优雅退出、刷新 checkpoint 及时。做到这几点你会发现和调度器的相处会顺畅很多而它能回馈给你的是更高的排队通过率、更稳的运行状态以及更难能可贵的省心。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门