1. GPU调度概述
大家好,我是你们的讲师。今天咱们聊聊GPU调度。说实话,这玩意儿刚接触时我也觉得头大——GPU不像CPU,它是个“黑箱”,你扔进去任务,它怎么跑、跑多快,很多时候你根本看不见。但搞运维的,必须得懂。
我个人习惯把GPU调度理解成“分蛋糕”。蛋糕就是GPU的计算资源,谁吃、吃多少、什么时候吃,得有个规矩。这个规矩,就是调度器。
1.1 GPU工作原理:它到底在忙什么?
GPU最初设计是为了图形渲染。你想想看,一张图片上几百万个像素点,每个点都要做矩阵运算。CPU是“一个大脑处理复杂问题”,GPU是“几千个小脑同时算简单问题”。
现代GPU用于通用计算(GPGPU),核心还是那套:大规模并行。一个典型的深度学习训练任务,比如ResNet-50,里面全是卷积、池化、全连接。这些操作可以拆成无数个小矩阵乘法,扔给GPU的几千个CUDA核心同时算。
关键点:GPU不是“快”,而是“多”。它用数量换时间。一个任务如果没法并行化,扔给GPU反而更慢——因为数据搬运的开销太大了。
我记得有一次,一个团队抱怨说“GPU利用率只有30%”。我上去一看,他们写了个循环,每次只处理一条数据。说白了,GPU在等CPU喂数据。这就是典型的“数据饥饿”。
GPU内部结构大致如下:
- 流多处理器(SM):GPU的基本计算单元。每个SM包含多个CUDA核心。
- 显存(VRAM):GPU自己的内存,带宽极高。训练大模型时,显存往往是瓶颈。
- NVLink/NVSwitch:多卡通信的“高速公路”。我见过有人用PCIe跑多卡训练,结果通信开销占了40%——换NVLink后直接翻倍。
我的经验:排查GPU性能问题,先看显存带宽利用率,再看SM活跃度。很多“GPU跑不满”的问题,根源在数据搬运。
1.2 调度器角色:谁来决定GPU给谁用?
调度器,说白了就是个“管家”。它要回答三个问题:
- 谁有资格用?(资源配额、优先级)
- 用哪块卡?(拓扑亲和性、显存大小)
- 用多久?(时间片、抢占策略)
我曾经遇到过一个事故:两个团队同时提交了训练任务,结果互相抢卡,导致双双崩溃。原因就是调度器没有做资源隔离。从那以后,我坚持所有GPU集群必须配调度器,哪怕只有两块卡。
调度器的核心职责包括:
| 职责 | 说明 | 我的踩坑记录 |
|---|---|---|
| 资源抽象 | 把物理GPU抽象成可分配的资源单位 | 有人把一块V100切成4份,结果显存碎片化严重 |
| 任务排队 | 按优先级/FIFO/公平策略排队 | 优先级队列没设上限,高优任务把低优饿死了 |
| 拓扑感知 | 尽量把任务调度到同一PCIe交换机下的卡 | 跨NUMA节点通信,延迟高了3倍 |
| 故障处理 | GPU报错时自动迁移任务 | Xid错误没处理,任务卡死了一周 |
警告:调度器不是万能的。它只能管理它“知道”的资源。如果你手动在节点上跑了nvidia-smi占用了显存,调度器是看不见的——这就是为什么我强调“所有GPU必须通过调度器分配”。
1.3 常见调度框架:Kubernetes、Slurm、YARN
目前主流就这三个。我三个都用过,各有各的脾气。
1.3.1 Kubernetes + GPU
K8s是目前最火的。它通过device plugin暴露GPU资源。你只需要在Pod里声明nvidia.com/gpu: 1,调度器就会自动绑卡。
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: cuda-container
image: nvidia/cuda:11.0-base
resources:
limits:
nvidia.com/gpu: 1 # 申请1块GPU
嗯,这里要注意:K8s默认不支持GPU的“时间片”共享。你申请1块卡,它就独占整块。如果你想让多任务共享一块卡,得装额外的插件,比如GPU Share或MIG(A100以上支持)。
我个人习惯用K8s做在线推理服务。因为它的弹性伸缩做得很好,流量大了自动加Pod,流量小了缩回来。但训练任务嘛...我建议还是用Slurm。
1.3.2 Slurm + GPU
Slurm是HPC领域的“老大哥”。它设计之初就是管大规模并行计算的。GPU支持也很成熟,通过--gres=gpu:2指定卡数。
# 申请2块GPU,跑24小时
srun --gres=gpu:2 --time=24:00:00 train.py
Slurm的优势在于:
- 细粒度控制:可以指定具体哪块卡(
--gres-flags=enforce-binding) - 抢占式调度:高优任务可以抢低优任务的卡
- 作业依赖:A任务跑完自动启动B任务
我曾经用Slurm管理过一个256卡集群。最头疼的是“碎片化”——任务有大有小,大的要8卡,小的要1卡,调度器经常把卡拆得七零八落,导致大任务等半天。后来我启用了--partition分区策略,把卡按规格分组,才解决。
避坑指南:Slurm默认不检查GPU健康状态。我曾经遇到过一块卡已经报Xid错误了,Slurm还继续往上面调度任务。解决方案是写个Proctrack插件,每次分配前跑一次nvidia-smi。
1.3.3 YARN + GPU
YARN是Hadoop生态的调度器。它本来是为MapReduce设计的,后来扩展支持了GPU。但说实话,YARN的GPU调度比较“粗糙”。
YARN通过yarn.nodemanager.resource.gpu配置GPU数量。但它不感知GPU的拓扑结构,也不支持MIG。你申请2块卡,它可能给你分配两块跨NUMA的卡,通信效率很低。
# 在container中申请GPU
<property>
<name>yarn.nodemanager.resource.gpu</name>
<value>4</value>
</property>
我个人觉得,YARN适合“顺便用用GPU”的场景。比如Spark MLlib里有些算法可以跑在GPU上,但又不是强依赖。如果你主要做深度学习,还是别用YARN了。
1.4 三种框架对比
| 特性 | Kubernetes | Slurm | YARN |
|---|---|---|---|
| 设计目标 | 微服务、容器化 | HPC、批处理 | 大数据、MapReduce |
| GPU共享 | 需插件(GPU Share/MIG) | 原生支持(--gres) | 不支持 |
| 拓扑感知 | 弱(需手动配置) | 强(支持NUMA绑定) | 无 |
| 抢占式调度 | 支持(通过PriorityClass) | 原生支持 | 支持(通过FairScheduler) |
| 我的推荐场景 | 在线推理、小规模训练 | 大规模训练、HPC | 大数据+GPU混合负载 |
总结一下:选哪个框架,取决于你的业务。我见过有人用K8s跑千卡训练,结果调度延迟高得离谱。也见过有人用Slurm跑推理服务,结果弹性伸缩跟不上。没有银弹,只有最适合的。
1.5 本章知识体系图
下面这张图是我自己画的,把GPU调度的核心逻辑串起来了。你仔细看看,应该能理解调度器在整个系统中的位置。
这张图从左到右看:用户提交任务 → 调度器决策 → 选择框架 → 分配GPU资源。每个环节都有坑,后面章节我会一个个拆解。
最后说一句:GPU调度不是“装个调度器就完事”的。你得理解GPU的工作原理,知道调度器在做什么,才能把集群用好。我见过太多人,装了个K8s就以为万事大吉,结果性能一塌糊涂。嗯,慢慢来,后面几章我会带你手把手排查。