想象一家主打满汉全席的餐厅——豪庭餐厅。灶台数量有限,每桌宴席却要同时使用 3 到 8 个灶口才能开火;餐厅原本只有一个服务员,他一次只能端一道菜。于是常常出现这种局面:一道菜占着灶台,另外几道菜却因为灶口不够迟迟无法下锅,整桌客人只能干等。这就是分布式深度学习训练的真实写照:GPU 是金贵的灶台,一个训练任务往往要拆成几十个 Pod(菜),只有所有 Pod 都领到 GPU,任务才能真正开始算。 Kubernetes 原生调度器就像那个端菜的服务员,它只认识单个 Pod,不具备“整桌上菜”的全局视野。当任务需要 8 个 GPU,而集群只剩 6 个时,它先调度 6 个,剩下 2 个 Pod 无限 Pending;那 6 个 Pod 虽然占着 GPU,却因为没凑齐而不能执行——这就是 GPU 集群中著名的“死锁”与资源碎片化。于是,两个专门解决这类问题的调度官登场了:Volcano 先生和 YuniKorn 小姐。
掌握 GPU 集群调度器的权衡,是云原生 AI 平台架构师做出高性能、高可用设计决策的关键能力之一。
可视化演示
展示Volcano gang调度全Pod原子启动、YuniKorn队列公平分配多租户GPU池、三种调度器利用率对比,步进切换探索。
本节配有交互动效(进入视口后自动加载,约 3KB)。
Volcano Gang调度:全有或全无 PodGroup所有Worker原子调度 资源不足时全部等待避免死锁 资源满足后同时启动训练 gang = all-or-nothing scheduling 分布式训练Worker必须同时启动
调度背景
想象一家主打满汉全席的餐厅——豪庭餐厅。灶台不多,每桌宴席却要同时使用 3 到 8 个灶口才能开火;餐厅原本只有一个服务员,他一次只能端一道菜。于是常常出现这种局面:一道菜占用着灶台,另外几道菜却因为灶口不够迟迟无法下锅,整桌客人只能干等。这是分布式深度学习训练的真实写照:GPU 是金贵的灶台,一个训练任务往往要拆成几十个 Pod(菜),只有所有 Pod 都领到 GPU,任务才能真正开始算。
Kubernetes 原生调度器就像那个端菜的服务员,它只认识单个 Pod,不具备“整桌上菜”的全局视野。当任务需要 8 个 GPU,而集群只剩 6 个时,它先调度 6 个,剩下 2 个 Pod 无限 Pending;那 6 个 Pod 虽然占着 GPU,却因为没凑齐而不能执行——这就是著名的“死锁”与资源碎片化。深度学习任务的资源画像很特殊:多卡同步训练需要组内所有 Pod 同时启动;训练时长通常以小时计,一旦被“卡死”,GPU 空转成本极高。理想情况下,训练耗时 T 与总计算量 Q 和并行算力 N×S 的关系是:
回到餐厅:要解决半桌菜的尴尬,必须有一位懂宴席的调度总控。GPU 集群调度器的任务,就是把零散的 Pod 组合成“一桌菜”来统一安排,这正是我们接下来要认识的调度官。
- 总工作量 QQ 表示整个训练的数据量与模型计算量,相当于宴席所有菜品的工作量总和。
- 并行算力 N×SN 卡并行时,理论上每秒能处理的工作量为单卡速度 S 的 N 倍。
- 理想耗时 TT 是理想完成时间,说明加卡能缩短周期,但现实中因同步等待不可能无限加速,这正是需要 Gang 调度的重要原因。
Volcano调度器
Volcano 先生是豪庭餐厅的“包席经理”。他接手后,立刻引入了一套新规矩:顾客下单时,不再一盘一盘算,而是把整桌菜登记成一份“宴席单”(Job),每道菜安排一个编号(Task),这些菜要凑成一个“菜组”(PodGroup)。
Volcano 是 Kubernetes 生态中的开源批量调度器,运行时会监听 PodGroup、Queue、Job 等 CRD。它的核心本领是 Gang scheduling:一个 PodGroup 里的所有 Pod 必须全部满足资源条件,调度器才允许它们同时被绑定;有任何一道菜没着落,整桌菜都按兵不动。这与默认调度器的“逐个试错”形成鲜明对比。
Volcano 也提供队列(Queue)模型来承载资源配额。每个任务属于一个队列,队列可以配置 capacity 和 share,用于控制团队之间的资源边界;同时通过插件机制支持优先级抢占、拓扑感知等功能。这套设计让 AI 平台能够把训练任务作为整体管理,而不是只盯着单个容器。
举个例子,一个分布式训练作业有 4 个 worker 和 2 个 PS,如果集群只剩 3 张 GPU,Volcano 会让整个作业一致等待;一旦凑齐 6 张卡,6 个 Pod 像宴席上的菜一样同时上桌,杜绝了半桌菜占灶台的现象。豪庭餐厅也终于能招待那些讲究“同进同出”的大客户了。
YuniKorn调度器
与包席经理不同,YuniKorn 小姐更像餐厅的“公平叫号总台”。散客大厅里顾客身份复杂:有大客户、普通食客、临时访客,大家都想用灶台,但灶台有限。她的职责不是等一桌菜齐,而是让所有顾客都能按规则公平地排到位子。
Apache YuniKorn 起家于 YARN 调度器,专为 Kubernetes 多租户场景设计。它最著名的能力是层级队列管理——租户可以建根队列,下面再挂子队列,像餐厅的大厅、包厢、VIP 区层层划分。调度时采用 Weighted Max-Min Fairness(加权最大最小公平):先给每个队列计算应得份额。
在此基础上,YuniKorn 还支持优先级抢占和弹性配额:队列空闲时,可以把 GPU 临时借给其他队列;当原租户任务回来时,再通过抢占回收,形成“高水位借用、低水位保障”的机制。这让多个团队共享一个 GPU 池时,既能互相救急,也不会被某一个大户永久占领。
包席经理管团队上桌,公平叫号台管客人排队——YuniKorn 的答卷,正是给多租户餐厅一份“大家都满意”的调度策略。
- 队列权重w_i 表示租户 i 的权重,权重越高,理论上能分到越多 GPU。
- 总权重Σw_j 是所有队列权重之和,用来把单个权重归一化。
- GPU 总量R_total 是集群可分配的 GPU 总数,即所有灶台数量。
- 公平份额S_i 是租户 i 应得的资源份额,相当于按权重占比切分总灶台数。
对比分析
包席经理和公平叫号员各怀绝技,但餐厅老板最关心的是:到底该用谁?先看队列模型:Volcano 的关注点是任务,队列只是资源容器,结构相对扁平;YuniKorn 的立身之本就是多层级队列,可以严格表达“部门—项目—子任务”的评级体系。
再看 Gang 调度:Volcano 原生支持 PodGroup,是分布式训练的事实标准;YuniKorn 最初面向 YARN 的 MapReduce 场景,虽然后续版本也通过插件支持 Gang,但社区心智更偏向队列公平与抢占。公平分配策略也有差别:Volcano 主要通过队列容量和任务优先级竞争,YuniKorn 则按权重做 Max-Min 公平,并设计了抢占与弹性回收。
因此现实项目中常有三种姿势:单团队重训练任务,用 Volcano 一马当先;跨部门多租户治理,选 YuniKorn 做总调度;大型 AI 平台则把两者组合——Volcano 负责 PodGroup 级别的 Gang 调度,YuniKorn 负责多队列间的公平分配,各管一段。
这就像餐厅并设“包席部”和“散客叫号台”:整桌宴席找包席经理,散客排队找叫号员。两者不打架,反而让整个餐厅的灶台运转得更顺畅。
多租户优化
多租户 GPU 资源池,就像大餐厅把灶台分给不同顾客群体:有些是长期包席,有些是散客,有些是临时访客。如果每个群体都独占部分灶台,高峰时有人排队、低峰时空转一片;如果没有边界,大客户一来就把灶台全占走。
YuniKorn 的弹性配额是化解矛盾的关键:给每个租户设一个“最低保障”,在此之上允许共享空闲 GPU。这样某团队跑大批量训练时,可以借用其他队列的空闲卡;当原租户提交任务后,调度器再通过抢占把卡还回去,既保证公平,又提高利用率。Volcano 则可以在任务层面用 binpacking(紧密打包)和节点亲和把 Pod 集中到同一批 GPU 节点,减少跨节点碎片;再配合队列 capacity 的限制,避免单个租户无限膨胀。
实践中,平台通常用两把尺子评估效果:GPU 平均利用率和任务排队等待时间。引入弹性共享后,原本某部门夜间闲置的 30% 资源可以被其他团队利用;同时通过 PodGroup 排队机制,高峰期的任务也不会把集群资源占满而饿死小任务。
豪庭餐厅的灶台因此从“包间私有”走向“闲时共享、忙时归还”,客人更多了,翻台率也上去了。
生产实战
餐厅运营数周后,老板决定复盘:调度官们如何上岗,出了问题又怎么排查?部署阶段,Volcano 通常以 Helm 或 DaemonSet 方式安装一到两个 scheduler 实例,配置默认调度器为 volcano;YuniKorn 则通过 admission controller 拦截 Pod 创建,并把队列配置写入 ConfigMap。二者都能以 CRD 方式暴露队列和任务状态。
故障排查时,最常见的信号是任务一直 Pending。首先看队列残余容量是否足够;其次,用 kubectl get podgroup 看 PodGroup 是否满员;如果部分 Pod 显示 Running 但主进程迟迟不启动,多半是 Gang 条件未满足或 PS/worker 角色互相等待。YuniKorn 场景下,还要查队列状态与抢占事件。
某互联网公司 AI 平台复盘:训练集群有 40 台 A100 节点,早期用 K8s 默认调度器,Ray 任务频繁因部分 Pod 阻塞而超时;切换到 Volcano 后分布式训练任务的排队时间下降约 40%,同一集群支持的任务并发数提升 50%。随后接入 YuniKorn 做部门级队列治理,GPU 平均利用率从 45% 提升到 70% 以上。
选型上,没有银弹:优先看业务形态是“整桌宴席”还是“散客排队”,再用两个调度器的组合拳完成最终方案。豪庭餐厅的复盘结论很直接:好的调度,是让每一口灶台都在最需要它的时刻烧起来。
AI 应用场景
深度学习训练平台调度优化
阶段:模型训练与资源调度
解决的问题:传统 K8s 调度器将每个 worker 和 PS 视为独立 Pod,常出现部分 Pod 已占用 GPU 但训练无法开始(死锁)。Volcano 的 Gang 调度将整个训练作业作为 PodGroup 统一调度,显著降低等待与资源空转。
具体案例:某自动驾驶公司训练平台每天提交 200 多个多卡训练任务,切换到 Volcano 后,单任务排队时间从 15 分钟降到 5 分钟,A100 集群平均利用率提高 30%。
多租户 GPU 资源池治理
阶段:资源管理与多租户隔离
解决的问题:多部门共享 GPU 集群时,缺乏队列模型会造成“大户霸占、小户饿死”。YuniKorn 的层级队列与加权公平分配为各部门设定权重和配额,空闲资源可弹性借用。
具体案例:某集团 AI 中台将算法、数据、搜索三个部门划入 3 个队列,权重 2:2:1;周末大数据任务少,算法队借走空闲 GPU 跑训练,周一归还,月度 GPU 利用率从 38% 提升到 66%。
GPU 调度故障排查
阶段:运维监控与故障诊断
解决的问题:任务一直 Pending 或 Running 不工作,排查目标模糊。使用 Volcano/YuniKorn 后可通过 PodGroup 和队列状态快速判断是 Gang 未齐还是配额不足。
具体案例:某团队报障:8 卡训练任务有 6 卡已 Running,2 卡 Pending。接入 Volcano 后,PodGroup 显示未满员,调度器不再绑定前 6 个 Pod,资源得到释放,问题从 2 小时人工排查缩短到 10 分钟定位。
Python 演示
#!/usr/bin/env python# -*- coding: utf-8 -*-"""GPU scheduler demo for gang scheduling and fair queue allocation."""from collections import deque class GangScheduler: def __init__(self, gpu_total): self.gpu_total = gpu_total self.pending = deque() self.running = [] def submit(self, group_id, pod_count): self.pending.append({"id": group_id, "pods": pod_count}) def tick(self): allocated = 0 keep = deque() for job in self.pending: need = job["pods"] if allocated + need <= self.gpu_total: allocated += need self.running.append(job) else: keep.append(job) self.pending = keep def show(self): print("running:", [job["id"] for job in self.running]) print("pending:", [job["id"] for job in self.pending]) class FairShareScheduler: def __init__(self, queues, total_gpus): self.queues = queues self.total_gpus = total_gpus def allocate(self): total_weight = sum(queue["weight"] for queue in self.queues) result = {} for queue in self.queues: share = self.total_gpus * queue["weight"] / total_weight result[queue["name"]] = min(share, queue["demand"]) return result if __name__ == "__main__": print("== Gang scheduling ==") gang_scheduler = GangScheduler(8) gang_scheduler.submit("job-a", 4) gang_scheduler.submit("job-b", 3) gang_scheduler.submit("job-c", 2) gang_scheduler.tick() gang_scheduler.show() print("== Weighted fair queue ==") queues = [ {"name": "cv", "weight": 2, "demand": 4}, {"name": "nlp", "weight": 1, "demand": 3}, {"name": "rec", "weight": 1, "demand": 2}, ] fair_scheduler = FairShareScheduler(queues, 8) result = fair_scheduler.allocate() for name, share in result.items(): print(f"{name}: {share:.2f} GPU")
if allocated + need <= self.gpu_total:只有整个任务组的资源需求在当前轮次都被满足时才分配,体现 Gang 调度的精髓。self.pending = keep没有被分配的任务不丢弃,继续留在队列中等待下一轮调度。total_weight = sum(queue["weight"] for queue in self.queues)计算所有队列的权重总和,用来把权重归一化。share = self.total_gpus * queue["weight"] / total_weight按权重占比切分 GPU 总量,计算每个队列的理论份额。result[queue["name"]] = min(share, queue["demand"])实际分配不能超过队列需求,避免分配用不掉的资源。gang_scheduler.tick()触发一轮 Gang 调度,观察任务是否整组启动或继续等待。fair_scheduler.allocate()触发一轮加权公平分配,观察不同权重队列获得的 GPU 数量。
常见误区
误区Volcano 和 YuniKorn 都是 Kubernetes 调度器,功能重复,装多了会冲突,所以只能选一个。
正解它们其实是互补关系。Volcano 的重点在任务级 PodGroup 的 Gang 调度,YuniKorn 的重点在多租户队列的公平调度。生产中常组合部署:Volcano 负责训练任务整组上桌,YuniKorn 负责各租户之间排队分权,两个调度器可以共存协作。
为何常见:两者都出现在 K8s 调度领域,容易下意识认为是在竞争同一个生态位,忽略了它们解决的是不同层面的问题。
误区Gang 调度就是给 Pod 加 nodeAffinity 或 podAffinity,让它们跑到同一节点上,保证“在一起”。
正解亲和性只决定“位置希望”,Gang 调度解决“资源同步”。Gang 要求一组 Pod 的所有资源请求同时被满足,才会执行绑定;否则全部不调度。就算 Pod 都落在同一节点,只要资源不够,默认调度器仍会部分调度,造成死锁。
为何常见:看起来都和“把 Pod 放到一起”有关,初学者容易把两种机制混为一谈。
误区公平调度就是把 GPU 平均分给每个队列,比如 3 个队列各拿三分之一,谁也不多吃多占。
正解YuniKorn 用的是加权最大最小公平:先按权重算出理论份额,再在某个队列不用足时把剩余资源分给更需要的队列。“公平”指的是权重契约下的公平,不是简单的算术平均。
为何常见:“公平”的日常语义就是平均,忽略了调度器中的权重、需求与弹性机制。
误区给队列设置的 GPU 配额越大越好,这样任务不容易排队,用户体验最好。
正解配额过大但任务不饱和,会造成 GPU 空转;配额过小又会饿死其他重要任务。YuniKorn 的弹性配额和 Volcano 的 capacity 一起用,设定“保障值 + 可借出”,才能平衡隔离与利用率。
为何常见:直觉上资源越多越好,没有考虑多租户共享时的副作用,比如低峰期无人使用、高峰期相互争抢。
面试题
Q1. Volcano 的 Gang 调度主要用于解决什么问题?
- A.提高单卡运算速度
- B.保证整组 Pod 同时调度← 正确答案
- C.降低容器日志开销
- D.增加节点 GPU 数量
答案:B。Gang 调度要求同一 PodGroup 的所有 Pod 全部满足资源后才绑定,避免部分 Pod 先占用 GPU 导致任务卡死。单卡速度、日志开销和节点数量都不是调度器关心的。
Q2. YuniKorn 的多租户公平分配策略主要基于哪种思想?
- A.先来先服务策略
- B.加权最大最小公平← 正确答案
- C.完全随机调度策略
- D.固定队列轮流策略
答案:B。YuniKorn 使用 Weighted Max-Min Fairness,先按权重计算每个队列应得份额,再在队列需求不足时重新分配剩余资源,避免小任务被大任务饿死。
Q3. 以下哪一项最能描述 Kubernetes 默认调度器对多卡训练任务的常见问题?
- A.单 Pod 请求过多 CPU
- B.多卡任务部分调度后等待← 正确答案
- C.所有节点均拥有 GPU
- D.Pod 数量少于节点数
答案:B。默认调度器逐个调度 Pod,不会把同一训练任务当作整体看待。当资源不足以满足全部 Pod 时,一部分 Pod 先被调度占住资源,剩余 Pod 等待,形成死锁与碎片化。
Q4. Volcano 中使用哪个资源对象表示一组需要同时调度的 Pod?
- A.Deployment
- B.PodGroup← 正确答案
- C.Service
- D.Namespace
答案:B。PodGroup 是 Volcano 的 CRD,用来把一组 Pod 聚合为一个调度单元。Deployment、Service、Namespace 都不具备“同进同出”的调度语义。
Q5. YuniKorn 弹性配额的主要价值是什么?
- A.固定资源上限防止借出
- B.空闲资源借出提升利用率← 正确答案
- C.减少集群队列数量
- D.关闭所有任务优先级
答案:B。弹性配额允许队列在空闲时把 GPU 临时借给其他队列,提高整体利用率;当原租户提交任务后再抢占回收,兼顾隔离与共享。
Q6. 当集群剩余 GPU 不足以满足 PodGroup 全部 Pod 时,Volcano 会怎么做?
- A.先调度可满足的一半
- B.整个 PodGroup 全部等待← 正确答案
- C.随机选择一个 Pod 调度
- D.自动降级为单卡任务
答案:B。Gang 调度的核心是“全有或全无”。只要有一个 Pod 无法安放,整个 PodGroup 都不会被绑定,避免“部分资源被占但任务无法执行”的浪费。
Q7. 关于 Volcano 与 YuniKorn 的协同使用,正确的是?
- A.二者只能二选一部署
- B.Volcano 管公平,YuniKorn 管 Gang
- C.Volcano 管 Gang,YuniKorn 管公平← 正确答案
- D.两者调度结果完全相同
答案:C。Volcano 擅长 PodGroup 的 Gang 调度,YuniKorn 擅长多租户队列的公平分配。大型平台常将两者组合,各管一段,而不是二选一。
Q8. 以下哪些属于 YuniKorn 的核心特性?
- A.支持层级队列管理← 正确答案
- B.实现加权公平分配← 正确答案
- C.支持优先级抢占← 正确答案
- D.提供虚拟机快照
答案:ABC。层级队列、加权公平分配、优先级抢占都是 YuniKorn 的典型能力。虚拟机快照属于虚拟化平台功能,与调度器无关。
Q9. 关于 Gang 调度的副作用,以下描述合理的有?
- A.可能造成低优先级饿死← 正确答案
- B.组内等待增加时延← 正确答案
- C.提高整组启动成功率← 正确答案
- D.一定降低集群利用率
答案:ABC。Gang 调度让低优先级任务可能被高优任务抢占或排挤,组内等待也会增加调度时延,但其价值是提高整组启动成功率。“一定降低利用率”过于绝对,资源充足时反而能减少空转。
Q10. 在多租户 GPU 资源池中,兼顾隔离与共享的正确做法有哪些?
- A.队列强制独占不可借用
- B.设置保障配额并允许弹性借用← 正确答案
- C.通过抢占回收被占用资源← 正确答案
- D.取消权重实现完全平均
答案:BC。先为每个队列设置最低保障配额,再允许空闲资源弹性借出,并通过抢占机制回收,是兼顾隔离与共享的成熟做法。强制独占会降低利用率,完全平均则会忽略权重与真实需求。