训练作业排队:优先级、多队列与资源预留
训练作业排队:优先级、多队列与资源预留
在深度学习团队或研究机构中,GPU 资源总是稀缺的。当多个用户同时提交训练任务时,如果没有合理的调度机制,会导致资源争抢、任务饿死或利用率低下。训练作业排队 正是为解决这类问题而设计的一整套策略,其核心组件包括:优先级定义、多队列划分与资源预留。本文将带你从零理解这些概念,并给出可落地的配置思路。
为什么需要排队系统
裸用 GPU 服务器时,常见走两个极端:要么完全靠人工协商,要么任由任务争抢。前者效率低下,后者容易出现:
- 一个用户抢占全部 GPU,其他人长期无法运行。
- 小任务被大任务阻塞,实验迭代速度变慢。
- 紧急训练无法及时获得资源。
一个良好的排队系统必须能够:
- 公平分配:防止资源被少数人垄断。
- 区分轻重缓急:让重要任务优先执行。
- 提高整体吞吐量:通过填满 GPU 空闲时间,减少闲置。
- 提供可预期的等待时间:让使用者知道大概多久能开始运行。
核心概念一:优先级
优先级决定了在资源紧张时,哪个作业可以先被调度。通常用一个数值或级别表示,例如 P0 > P1 > P2,或者 HIGH / NORMAL / LOW。
如何定义优先级
不同组织有不同规则,常见维度:
- 任务紧急性:例如线上模型热修复、赶论文截止日期、客户演示等,可设为最高级。
- 资源体量:短时小任务(几分钟到半小时)可以给予更高优先级,快速轮转,提升体感。
- 用户角色:管理员 > 核心研发 > 实习生,但容易引起争议,需与团队协商。
- 项目重要性:公司战略项目可配置固定高优先级。
优先级的实现方式
调度器会维护一个优先级队列,高优先级作业总是在低优先级之前被考虑。但要注意:
- 饥饿预防:如果一直有高优先级任务提交,低优先级可能永远得不到执行。需要搭配 “老化” 机制,等待时间越长,优先级动态提升。
- 抢占:是否允许高优先级任务抢占正在运行的低优先级任务?
- 非抢占式:简单,但对紧急任务不够友好。
- 抢占式:需要保存检查点,优雅终止,稍后恢复或重新排队。该功能对训练作业尤其重要,因为训练往往耗时数天,抢占代价高。
示例配置(伪代码)
priority:
levels: [urgent, high, normal, low]
default: normal
preemption: true # 允许抢占
aging:
max_wait: 7200 # 最大等待秒数
boost_factor: 1 # 每等待3600秒优先级提升一级
核心概念二:多队列
将所有任务丢进同一个队列是混乱的根源。多队列允许按组织、项目、资源类型或任务性质将提交入口隔离开来。
为什么需要多队列
- 资源隔离:为不同团队或项目划分固定资源比例,互不干扰。
- 策略差异:每个队列可绑定不同的优先级策略、资源限制和使用配额。
- 管理清晰:运维能直观看到各队列的负载和排队情况,便于扩缩容决策。
常见队列设计模式
| 队列类型 | 说明 | 适用场景 |
|---|---|---|
| 部门队列 | 按算法组、工程组、数据组划分 | 中型团队,需独立核算资源 |
| 项目队列 | 项目 A、项目 B 各有一个队列 | 每个项目有固定预算 |
| 任务类型队列 | 交互式调试队列、训练队列、推理队列 | 隔离不同生命周期任务,避免调试占用训练资源 |
| 混合队列 | 组合以上维度 | 大型组织,资源精细化运营 |
队列配置要素
一个队列通常包含:
- 资源配额:最多能用多少卡、多少内存。
- 优先级范围:该队列允许设置的最高优先级。
- 提交权限:谁可以向该队列提交作业。
- 排队深度限制:避免过多任务堆积使调度器压力过大。
示例配置片段:
queues:
- name: team-nlp-high
resources:
max_gpu: 16
min_gpu: 4 # 预留最低保证
priority: {max: urgent, default: high}
acl: [nlp-core-members]
max_pending_jobs: 20
- name: debug
resources:
max_gpu: 4
max_walltime: 3600 # 最大运行时间1小时
priority: {max: normal, default: low}
acl: [all_users]
通过将调试任务限制在独立队列并设置最大运行时间,可以防止调试 session 长时间占用 GPU。
核心概念三:资源预留
资源预留是指在调度系统中事先为特定队列或作业保留一定数量的 GPU,即便这些 GPU 当前空闲,其他作业也不能占用。这通常用于保障关键业务的资源可用性。
预留的类型
- 静态预留:在集群配置中硬性划分,例如总是为 NLP 团队保留 8 张 A100。优点是确定性高;缺点是当 NLP 团队不用时,资源白白浪费。
- 动态预留:仅在特定时间窗口或满足条件时生效。例如:每天 20:00-08:00 为特定项目预留资源,或者当队列深度达到一定阈值时启用预留。
- 弹性预留:允许低优先级任务使用,但一旦预留方提交任务,可抢占回去。结合抢占与优先级,是一种资源利用率的折中。
预留的表示形式
在配置系统中,通常用 reservation 或 min_guaranteed 标识:
queue:
- name: production-training
resources:
guaranteed_gpu: 32 # 32卡保证可用,其他队列不可占用
max_gpu: 64
priority: urgent
同时,可以在集群层面设置一个 “资源共享池”,未被预留且未被使用的 GPU 供任何队列按优先级竞争。
预留的代价与策略
- 预留过多会导致集群整体利用率偏低。
- 建议先观察历史使用情况,然后逐步调整。
- 结合超发策略:允许借用预留资源,但有明确的归还机制。
实际调度流程(以 Kubernetes + Volcano 为例)
许多团队使用 Kubernetes 搭配 Volcano、Yunikorn 或 Armada 来管理训练作业。一个典型的调度周期如下:
- 用户提交
Job,携带队列名称与优先级。 - 准入控制检查权限、配额与队列容量。
- 调度器将所有未调度作业按 队列优先级 → 作业优先级 → 提交时间 排序。
- 先尝试满足带有资源预留的队列需求,从总资源池扣除预留量。
- 按排序结果,将作业派发到满足其节点亲和性、资源要求的 GPU 节点。
- 若开启抢占且资源不足,按规则选择受害者,保存检查点,驱逐并重新调度。
- 闲置预留资源可被低优先级作业短暂使用(需开启借用)。
设计最佳实践
1. 优先级的颗粒度不宜过细
3-4 个级别(如 P0-P3)即可满足绝大多数场景。级别太多导致使用者困惑,且调度决策变复杂。
2. 强制执行最大运行时间
对每个队列设置 max_walltime,防止任务霸占资源。超时可自动终止或转为挂起,并通知用户保存检查点。
3. 可视化排队状态
为用户提供仪表板,展示:
- 我所提交的任务在队列中的位置
- 预计启动时间
- 当前队列负载与已用/总量 GPU
透明性能极大缓解焦虑。
4. 定期审计与调整
资源分配不是一劳永逸的。每季度根据实际使用数据调整队列配额与预留比例,剔除僵尸队列,回收闲置资源。
5. 定义清晰的抢占策略
若允许抢占,要规定:
- 只允许高优先级抢占低优先级正在运行的任务。
- 只能抢占使用“借用”资源的任务。
- 抢占比前发送宽限期通知(例如 15 分钟),让作业有机会保存进度。
- 记录抢占事件,便于回溯。
6. 自动化回收
结合任务监控,当检测到 GPU 利用率持续偏低时,自动降级其优先级、发送报警或将其挂起,把资源让给真正需要的任务。
总结
- 优先级解决了谁先运行的问题,防止资源被非关键任务挤占。
- 多队列从架构层面实现资源隔离与策略划分,让管理更有序。
- 资源预留为关键业务提供了兜底能力,避免因突发大流量导致重要训练延迟。
三者结合,便能构建一个既公平又高效、同时兼顾组织治理需求的训练作业排队系统。初学者在搭建时,建议先从简单的单队列+固定优先级开始,逐步引入多队列和预留,并在使用中依据反馈数据持续迭代优化。