3. 任务调度基础:调度系统概念、调度策略与GPU资源抽象
3.1 调度系统在GPU集群中的位置与核心职责
在GPU集群中,调度系统是连接“用户提交的计算需求”与“底层GPU硬件资源”之间的核心控制平面。它的本质是一个多目标决策引擎:在任意时刻,集群中可能同时存在数百个来自不同团队、不同框架(PyTorch、TensorFlow、MindSpore)的训练或推理任务,而GPU资源(尤其是高端型号如A100/H800)在物理上是有限且异构的。调度系统需要决定:哪个任务先运行?运行在哪几台机器的哪几块GPU上?每块GPU分配多少显存和多少计算份额?
一个完整的调度决策通常包含三个子过程:资源上报与感知(Agent采集每张GPU的显存总量/已用量、SM占用率、温度等)、任务排队与策略选择(根据队列策略从等待队列中选出下一批任务)、资源分配与绑定(将选中的任务映射到具体的GPU设备上,并设置环境变量如 CUDA_VISIBLE_DEVICES)。
3.2 三种核心调度策略:FIFO、公平调度与优先级
调度策略决定了等待队列中任务的出队顺序。不同策略在集群吞吐量、用户公平性和任务延迟之间做出不同取舍。
3.2.1 FIFO(先来先服务)
FIFO是最朴素的策略:任务按提交时间排序,先到先得。实现简单,完全公平(从时间维度看),且没有抢占开销。但它的致命弱点是队头阻塞——如果一个需要64卡的大任务排在队首,而集群当前只有32卡空闲,那么后面即使有多个只需4卡的小任务可以立即运行,也必须等待大任务凑齐资源。
3.2.2 公平调度(Fair Scheduling)
公平调度(典型实现如YARN的Capacity Scheduler、Kubernetes的Fair Sharing插件)将集群资源按队列/租户划分权重。例如:A团队权重60%,B团队权重40%。当A团队没有任务时,B团队可以借用A的配额(弹性);当A提交新任务时,B需要逐步让出(抢占或等待自然结束)。公平调度的核心是最小资源保障 + 弹性共享,避免单一团队饿死其他团队。
3.2.3 优先级调度
优先级调度允许任务携带优先级标签(如P0/P1/P2)。调度器总是优先选择高优先级任务,甚至允许高优任务抢占低优任务已占用的GPU。抢占有两种实现:冷抢占(直接杀死低优任务,释放资源)和温抢占(挂起低优任务,保存显存状态,待高优任务结束后恢复)。优先级调度能保证关键业务(如线上推理、紧急故障修复)的及时性,但需要配合配额限制,防止高优任务无限挤占导致低优任务饿死。
| 策略 | 核心目标 | 典型场景 | 主要缺陷 |
|---|---|---|---|
| FIFO | 时间公平、实现简单 | 单用户独占集群、任务同质 | 队头阻塞,大任务拖死小任务 |
| 公平调度 | 多租户资源保障与共享 | 多个团队共享集群 | 需要动态调整权重,有抢占开销 |
| 优先级 | 关键任务优先执行 | 混部场景(训练+推理) | 低优任务可能饿死,需额外保护 |
3.3 GPU资源抽象:显存与计算单元
调度器必须理解GPU资源的可量化属性,才能做出合理的分配决策。GPU资源抽象主要分为两个维度:显存(Memory)和计算单元(Compute)。
3.3.1 显存抽象
显存是GPU上最直观、最容易被量化的资源。调度器通过驱动接口(如NVML)读取每张卡的总显存、已用显存和空闲显存。显存分配通常是硬隔离的——一旦分配给某个任务,其他任务无法使用,即使该任务当前并未充分利用。例如,一张A100 80GB的卡,如果任务A申请了40GB,那么剩余40GB可以分配给任务B;但如果任务A实际只用了20GB,那20GB的差额也无法被B使用(除非开启MIG或显存动态共享)。
3.3.2 计算单元抽象
计算单元(Streaming Multiprocessor,SM)是GPU执行计算的核心硬件。与显存不同,计算单元通常不支持细粒度切分。在传统模式下,一张GPU卡要么整卡分配给一个任务,要么通过MIG(Multi-Instance GPU)技术切分为若干实例(如A100可切分为1个40GB+2个20GB的实例),每个实例拥有独立的SM和显存带宽。但在大多数集群调度场景中,计算单元被抽象为“整卡”或“MIG切片”,而不是“SM数量”。
# 查看GPU资源抽象信息的典型命令(NVIDIA驱动层)
nvidia-smi --query-gpu=index,memory.total,memory.used,utilization.gpu \
--format=csv
# 输出示例
# index, memory.total, memory.used, utilization.gpu
# 0, 81920 MiB, 32768 MiB, 45%
# 1, 81920 MiB, 81920 MiB, 100%
# 2, 81920 MiB, 0 MiB, 0%
调度器在分配时,需要同时满足显存约束和计算单元约束。例如,一个任务声明需要“4卡×40GB显存”,调度器必须找到4张空闲显存≥40GB的卡,且这4张卡的计算单元未被其他任务独占。如果集群中只有2张卡满足显存条件,但它们的SM已被MIG切分占用,则任务仍需等待。
3.4 案例:一个典型调度决策的完整过程
假设某集群有2台物理节点,每台节点8张A100 80GB GPU。当前状态如下:
- 节点1:GPU0-3被任务X(4卡训练)占用,GPU4-7空闲
- 节点2:GPU0-1被任务Y(2卡推理)占用,GPU2-7空闲
此时新提交两个任务:任务P(优先级P0,申请8卡×80GB,用于紧急故障恢复训练)和任务Q(优先级P1,申请4卡×40GB,用于常规实验)。调度器执行以下步骤:
- 资源过滤:任务P需要8卡且每卡80GB,但节点1只有4卡空闲,节点2有6卡空闲,均不满足单节点8卡要求。若调度器支持跨节点(需高速互联如NVLink+IB),则可将节点1的4卡+节点2的4卡组合给P;若不支持跨节点,P只能等待。
- 策略排序:假设支持跨节点,P优先级高于Q,P先被调度。调度器为P分配节点1的GPU4-7和节点2的GPU2-5。
- 资源分配:剩余资源为节点2的GPU6-7(2卡×80GB)。任务Q申请4卡×40GB,但剩余卡数不足4张,因此Q进入等待队列。
- 环境注入:调度器通知节点1和节点2的Agent,在P的容器中设置
CUDA_VISIBLE_DEVICES=4,5,6,7(节点1)和CUDA_VISIBLE_DEVICES=2,3,4,5(节点2),并启动分布式训练框架(如torchrun)。
3.5 误区与复盘:调度策略选择中的典型教训
误区一:盲目追求“公平”而忽略业务差异。某公司内部统一使用公平调度,所有团队权重相同。结果导致线上推理任务(延迟敏感)与离线训练任务(吞吐优先)混部时,推理P99延迟从5ms飙升至200ms。复盘发现,公平调度让推理任务和训练任务各分50%资源,但推理任务需要的是低延迟而非高吞吐。修正方案:为推理任务设置独立的高优先级队列,并配置资源预留(最低保障80%),训练任务使用弹性剩余资源。
误区二:将显存作为唯一资源维度。某团队在调度器中只配置了显存限制,没有考虑SM数量。结果两个小任务被分配到同一张卡上,各自申请20GB显存(共40GB,小于80GB),但两个任务都是计算密集型(各自需要50% SM)。实际运行中,两个任务互相干扰,训练速度比独占时下降70%。复盘结论:必须将计算单元(SM/MIG切片)作为独立的资源维度参与调度,不能只靠显存判断。
误区三:FIFO在小集群中“看似够用”。一个4卡GPU服务器,团队5人,每人偶尔提交单卡任务。初期使用FIFO没有明显问题。但当某人提交了一个4卡全占的大任务后,其他4人的小任务全部排队等待数小时。复盘发现:FIFO缺乏任务时长预估和资源抢占机制。改进为:限制单任务最大占用时长,超过后自动挂起;同时允许小任务在预留资源上运行。