【高阶·云原生】自研 AI 调度器架构设计深度解析:从 K8S 调度框架到 GPU 集群调度系统的工程实战
【高阶·云原生】自研 AI 调度器架构设计深度解析:从 K8S 调度框架到 GPU 集群调度系统的工程实战专栏:《AI 工程与安全深度实战》· 第7轮·第1篇核心痛点:现有通用调度器无法满足 AI 工作负载对 GPU 拓扑感知、Gang 调度、队列公平共享、弹性资源抢占的复合需求,迫使团队建设专用 AI 调度系统适配人群:具备 K8S 基础的 AI 基础设施工程师 / 平台工程架构师 / 集群调度系统开发者收获能力:理解 K8S 调度框架(Scheduling Framework)的 11 个扩展点机制,掌握 Volcano / KAI Scheduler / Kueue 三大 AI 调度方案的设计范式与架构权衡,能够基于 Scheduling Framework + 自定义插件构建生产级 AI 专用调度器技术背景与演进逻辑容器调度器的演进脉络2015-2018:K8S 默认调度器(kube-scheduler)以通用 Pod 为调度单元,采用 Predicates(过滤)+ Priorities(打分)两阶段模型,满足无状态微服务的基础需求2019:K8S 1.15 引入 Scheduling Framework(KEP-624),将调度流程拆分为 11 个标准化扩展点,允许通过插件注册定制调度逻辑,成为后续所有 AI 调度器的事实标准基座2020-2022:AI 训练/推理规模化爆发,Volcano(华为云开源)、Yunikorn(Apache)等批量调度器涌现,引入 PodGroup 原子调度、队列管理、公平共享等概念2023-2024:GPU 资源管理精细化需求驱动 DRA(Dynamic Resource Allocation,K8S 1.26 Alpha)出现,MIG 分区、时间切片共享等技术成熟2025:NVIDIA 收购 Run:ai 后开源 KAI Scheduler(Apache 2.0),将企业级 GPU 调度能力(Gang 调度、Bin Packing、分级队列)带入社区,成为 AI 调度的事实参考架构2026:DRA 在 K8S 1.31+ 进入 Stable,GPU 成为一等公民资源;Kueue 在批量作业队列化领域成熟;社区形成 Volcano / KAI Scheduler / Kueue 三足鼎立格局演进时间线:年份里程碑核心贡献2019Scheduling Framework (K8S 1.15)11 扩展点插件化调度基座2020Volcano v1.0Gang 调度 + 队列管理 + 任务拓扑2022Kueue 项目启动批量作业队列化 + 配额管理 + 资源风味2023DRA (K8S 1.26, Alpha)GPU 等异构资源的声明式分配2025KAI Scheduler 开源NVIDIA 企业级 GPU 调度能力输出2026DRA Stable (K8S 1.31+)GPU 成为一等公民;三足鼎立格局成熟为什么通用调度器不够用Pod 粒度不匹配:AI 训练 Job 由多 Pod 组成(Rank 0…N),调度器必须将它们视为一个不可分割的原子单元——单 Pod 成功其余等待 = 资源空占,全部调度必须同时成功或同时失败GPU 拓扑盲区:kube-scheduler 将 GPU 视为普通扩展资源(nvidia.com/gpu: 1),不了解 NVLink 互联拓扑、PCIe Switch 拓扑、跨 NUMA 对等关系——分配到不同 PCIe Switch 的 2 张 GPU 意味着 NCCL AllReduce 带宽减半无作业级生命周期:默认调度器不知道"这个 Job 需要 8 张 GPU 跑 4 小时"——无法做跨时间维度的资源预留、无法实施公平共享、无法决策抢占优先级碎片化严重:Bin Packing 缺失导致 GPU 节点碎片化——8 GPU 节点上运行 3 个各需 4 GPU 的 Job 时,通用调度器默认 Spread 策略可能将 3 个 Job 各占 1 个节点(浪费 4×3=12 GPU 槽位),而 Bin Packing 可将 2 个 Job 紧密排入 1 个节点无队列与优先级:当集群 GPU 资源不足时,新 Job 直接 Pending 无限等待,没有队列管理、优先级排序、抢占回收机制——紧急任务无法插队,低优先级 Job 占着资源不放核心原理深度解析Scheduling Framework:11 扩展点架构调度周期模型:每个 Pod 经过一次完整的 Scheduling Cycle(绑定前)和一个可选的异步 Binding Cycle(绑定后),Cycle 间状态通过 VolumeBinding 共享扩展点全景(按调度流程排序):┌──────────┐ │ QueueSort │ 排序等待队列中的 Pod(默认按 Priority) └────┬─────┘ ↓ ┌──────────┐ │ PreFilter │ 预过滤:收集 Pod 条件信息,预处理节点集 └────┬─────┘ ↓ ┌──────────┐ │ Filter │ 硬过滤:排除不满足条件的节点(GPU 数量/NUMA) └────┬─────┘ ↓ ┌───────────┐ │PostFilter │ 过滤失败补救:无节点时触发抢占 / 重调度 └────┬──────┘ ↓ ┌──────────┐ │ PreScore │ 预打分:准备打分所需数据 └────┬─────┘ ↓ ┌──────────┐ │ Score │ 打分排序:Bin Packing / Spread / 亲和性加权 └────┬─────┘ ↓ ┌──────────┐ │ Normalize │ 归一化:将各 Score 插件结果统一到 [0,100] └────┬─────┘ ↓ ┌──────────┐ │ Reserve │ 资源预留(乐观并发):原子标记占用 └────┬─────┘ ↓ ┌──────────┐ │ Permit │ 许可门控:Gang 调度的关键——等待 PodGroup 内所有 Pod 就绪 └────┬─────┘ ↓ ┌──────────┐ ┌──────────┐ │ PreBind │ ──→ │ Bind │ 绑定到节点 └──────────┘ └────┬─────┘ ↑ ↓ ┌────┴──────┐ ┌──────────┐ │ Unreserve │ ←── │ PostBind │ 绑定后事件 └───────────┘ └──────────┘Gang 调度与 Permit 扩展点:AI 调度器最核心的利用点在 Permit 阶段。调度器对 PodGroup 内的每个 Pod 执行 Filter → Score → Reserve,但每个 Pod 在 Permit 阶段阻塞等待,仅当 PodGroup 内所有 Pod 的 Reserve 全部成功时,Permit 统一放行进入 Bind;任何 Pod 失败则触发 Unreserve 回滚所有已预留资源QueueSort 与优先级:AI 调度器在此阶段插入队列感知逻辑——同一 Queue 内的 Pod 按 Priority 排序;跨 Queue 按 FairShare(公平份额)/ DRF(Dominant Resource Fairness)排序PostFilter 与抢占:当 Filter 阶段所有节点被排除时,PostFilter 触发抢占逻辑——根据 Pod Priority 和 Queue 优先级选择受害者 Pod,踢出后释放资源AI 调度器对扩展点的利用矩阵:扩展点通用调度器作用AI 调度器增强QueueSort按 Priority 排序按 PodGroup / Queue / FairShare 排序PreFilter节点亲和性预处理收集 GPU 拓扑信息、检查 PodGroup 完整性Filter资源满足性检查GPU 数量、NVLink 互联距离、MIG 分区匹配、NUMA 对齐PostFilter抢占逻辑Gang 感知抢占:按 PodGroup 原子抢占ScoreNodeResourcesFit 等Bin Packing / Spread / GPU 亲和性加权 / 拓扑感知打分Reserve资源乐观预留原子批量预留:整个 PodGroup 的资源一次性记录Permit审批门控Gang 同步点:等待 PodGroup 全或全不三大 AI 调度器架构对比Volcano(2019-):批量调度先驱架构模式:独立 CRD + Controller + 调度插件双通道核心 CRD:PodGroup(原子调度单元)、Queue(资源队列)、Job(任务模板,兼容 TFJob/PyTorchJob/MPIJob)调度流程:Volcano Scheduler 作为独立二进制运行,通过 K8S Scheduler Extender HTTP 接口或直接 Watch Pods 进行调度强项:Gang 调度成熟、支持任务拓扑(Task Topology,Master/Worker 亲和反亲和)、Bin Packing、Proportion(比例公平)、DRF局限:独立调度器需替换 kube-scheduler,与社区标准扩展点不完全对齐;CRD 体系较重KAI Scheduler(2025-):NVIDIA GPU 原生调度架构模式:K8S Scheduling Framework 原生插件 + Operator 管理生命周期核心抽象:PodGroup(不可分割调度单元)、Queue(分级队列,支持层级嵌套)、ResourceFlavor(GPU 类型规格描述)调度流程:作为 Scheduling Framework 的 QueueSort / Filter / Score / Permit / PostBind 插件注入 kube-scheduler,不替换默认调度器强项:与 Scheduling Framework 深度集成、Bin Packing + GPU 共享(时间切片/MIG)、动态资源配额(ElasticQuota)、拓扑感知局限:强依赖 NVIDIA GPU Operator 生态,非 NVIDIA GPU 支持有限Kueue(2022-):批量作业队列化架构模式:Admission Controller + Controller Manager,不替代调度器核心抽象:ClusterQueue(资源池 + 配额)、LocalQueue(租户命名空间级队列)、Workload(对 Job 的包装)、ResourceFlavor调度流程:通过 Admission Webhook 拦截 Job/Pod 创建,将 Workload 入队管理,仅在配额允许时放行给 kube-scheduler 后续调度强项:与 kube-scheduler 解耦、无需替换调度器、支持跨 ClusterQueue 配额借用和抢占、FairShare局限:仅管"什么时候调度",不管"调度到哪里"——需要下游调度器具备 GPU 感知能力三方案架构矩阵:维度VolcanoKAI SchedulerKueue调度角色替换 kube-schedulerScheduling Framework 插件准入控制 + 队列Gang 调度✅ 原生支持✅ 原生支持❌ 需配合 Volcano/KAIGPU 拓扑感知⚠️ 需自定义配置✅ NVLink/NUMA 原生感知❌ 依赖下游调度器队列/配额✅ Queue + 比例✅ Queue + ElasticQuota✅ ClusterQueue + LocalQueue抢占✅ PodGroup 级✅ PodGroup 级✅ Workload 级公平共享✅ DRF / Proportion✅ FairShare✅ FairShareGPU 共享⚠️ 有限✅ MIG + 时间切片❌ 不涉及与 kube-scheduler 关系替换插件注入解耦(上游)学习成本中低(Framework 原生)低(非侵入)核心模块/机制详解Gang 调度机制:PodGroup 原子调度问题定义:分布式训练 Job(如 4 机 32 卡的 PyTorch DDP Job)必须所有 Worker Pod 同时启动——NCCL 初始化依赖全体成员就位,部分 Pod 先跑 = 等待超时 + 资源空占架构设计:┌─────────────────────────────────────┐ │ PodGroup CRD │ │ spec.minMember: 4 │ │ spec.queue: "ai-training" │ │ spec.priorityClassName: "high" │ └──────────────┬──────────────────────┘ │ owns ┌──────────────┼──────────────────────┐ │ ↓ │ │ Pod-0 Pod-1 Pod-2 Pod-3 │ │ (Rank 0) (Rank 1) (Rank 2) (Rank 3)│ └──────────────┬──────────────────────┘ │ ┌──────────────↓──────────────────────┐ │ Scheduler Permit Gate │ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │Pod-0│ │Pod-1│ │Pod-2│ │Pod-3│ │ │ │ ↓ │ │ ↓ │ │ ↓ │ │ ↓ │ │ │ │Resvd│ │Resvd│ │Resvd│ │Resvd│ │ │ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ │ │ │ │ │ │ │ │ └───────┴───┬───┴───────┘ │ │ ↓ │ │ All Reserved? → Wait / Timeout │ │ ↓ Yes ↓ No │ │ ALLOW all DENY + Unreserve │ └──────────────────────────────────────┘核心代码(KAI Scheduler 风格的 Gang Permit 插件逻辑,Go 伪代码已无害化):// PermitPlugin implements the Permit extension point for Gang scheduling.// It holds Pods until minMember count is reached or timeout fires.func