从云原生走向 AI 原生:一套面向未来的架构方法论 → 阅读《AI 原生基础设施》

GPU 集群调度问题域:碎片、Gang 与放置策略

草稿

GPU 调度器的选型讨论常常从工具开始(用 Volcano 还是 Kueue),但工具只是答案的一半:先要理解 CPU 世界不存在的三个问题。

阅读位置:上一章《GPU 资源控制面地图》 · 下一章《Volcano》。

本章是VolcanoKueue两章的问题域前置:那两章讲“谁提供了这些能力”,本章讲“为什么必须有这些能力”。这些问题的物理背景(拓扑、通信)见互连与集合通信

问题一:碎片化,闲着的卡拼不出一组

GPU 以整卡为单位、单价高昂,碎片直接等于重金闲置:

节点 A(8卡): 3×1卡 + 1×2卡 任务  → 剩 3 卡
节点 B(8卡): 5×1卡 任务           → 剩 3 卡
节点 C(8卡): 1×6卡 任务           → 剩 2 卡
队列: 一个 4 卡任务 → 所有节点都装不下 → 排队
全集群空闲 8 张卡, 却没有一张能被这个任务使用

两种基本放置策略(与调度器评分函数一一对应):

  • 装箱(binpack):任务塞向“最满但装得下”的节点,目标是腾出整节点;
  • 打散(spread):负载均匀铺开,直觉是“给未来的大任务留整节点”。
反直觉但重要
小任务为主的流量结构下,装箱通常优于打散:把小任务塞进半满节点,恰好保住了空节点给大任务;打散反而把容量撒得到处都是。“打散保护大任务”这条直觉,只有在任务规模分布偏大时才成立。判定依据应该是你自己集群的任务规模分布与排队数据,而不是直觉。

碎片的三个缓解手段:优先级抢占(杀低优任务凑大任务,前提是被抢任务有 checkpoint)、回填(backfill)(给排队任务找“现在能塞”的位置,避免队头阻塞)、弹性配额(见组合架构)。

问题二:Gang 语义,部分启动等于死锁

分布式训练的经典事故:一个 8 卡任务由 8 个 Pod 组成,原生调度器逐 Pod 处理,资源紧张时只起来了 5 个。这 5 个 Pod 里的 NCCL 在初始化时等待全部 8 个 rank,于是各占着卡互相等;第 6 个 Pod 排在队列里,而它前面的任务又在等这 5 个 Pod 占的卡,锁死了数十张卡谁也不动,直到超时

Gang(成组)调度的语义是:任务的 minMember 个 Pod 要么全部一起调度,要么全部不调。Volcano 的 PodGroup、Kueue 的 Workload 准入都实现了这一语义。

Gang 的代价是队头阻塞:第一个等不到资源的任务可能拖住后面的任务,所以 gang 必须与 backfill 配对使用。模拟与生产数据的共同结论是:无 backfill 的 gang 在高负载下平均等待可达逐 Pod 调度的 5 倍;而逐 Pod 调度的“零等待”对分布式任务是虚假的:它用死锁换排队指标。

工程经验
“训练任务 hang 在初始化”是缺 gang 语义的标准签名。反过来,上线 gang 后排队变长,先查 backfill 是否启用,而不是回退 gang。

问题三:放置与拓扑的耦合

互连与集合通信给出的三条硬规则在调度层的落地:

  • 张量并行任务需要“同一 NVSwitch 域”的约束,而原生调度器没有这个词汇;
  • 现实践:大任务节点级独占(污点/标签)、注入有序的 CUDA_VISIBLE_DEVICES、跨 MIG/vGPU 实例禁止 TP;
  • 长期方案:DRA 让设备属性与拓扑成为可声明约束。

值得注意的是,软切分会放大这个问题:HAMi 一类方案把“选哪张卡”从 kubelet 上移到自己的调度扩展,这既是机会(可以做拓扑感知的落位),也是责任(不感知就是新的盲区)。

问题域到工具的映射

问题原生 K8sVolcanoKueueHAMi(数据面协同)
碎片无策略钩子装箱/打散插件、backfill准入期配额借还卡内份额装箱(落位层)
GangPodGroup(调度器内)Workload 准入(调度器外)
队列/配额ResourceQuota(计数)队列 + 比例公平ClusterQueue/Cohort 借用份额配额(gpumem/gpucores)
拓扑约束节点级近似配合拓扑感知演进注入设备序(可做拓扑感知)
表 1: 问题域与控制面工具的对应

工具组合的常见形态:Kueue 管队列与配额(租户维度),gang 语义随 Kueue/JobSet 或 Volcano 提供,HAMi 管节点内落位与份额兑现。三者职责不同层,不是三选一,这正是组合架构的主题。

常见误区

误区真相
Pending 就是资源不够,加机器先区分:容量不足、碎片拼不出、还是 gang 等待
打散调度天然保护大任务小任务为主的流量下装箱常常更好,用数据说话
K8s 会处理分布式任务的 all-or-nothing原生不会,逐 Pod 调度 + NCCL 等待 = 死锁
抢占能解决一切排队被抢任务没有 checkpoint 就是数据丢失事故
上了 HAMi 就不需要队列系统份额配额与队列配额在两层,缺一不可
表 2: 调度层的常见误区

总结

GPU 调度的三个特有问题(碎片、Gang、拓扑放置)都源于“昂贵、整卡、分布式”的资源形态,而不是调度器实现不够聪明。看懂问题域之后,VolcanoKueue的每个特性都可以对号入座;可观测与计量里的排队与碎片指标,也正是为这三个问题提供决策数据。

参考资料

创建于 2026/08/28 更新于 2026/08/28 1877 字 阅读约 4 分钟