TT Lab
博客

한국어 English 日本語 中文

开始
博客

博客

多租户 GPU 调度与容量规划

状态:草稿(2026-10-07 更新,2026-10-09 补充借用上限实测数据)。 本文基于官方文档与论文确认的叙述,其中**第 3 节末尾的三项实测(gang 调度比较、同类群组借用与抢占、借用上限与回收速度)**是在一次性单节点集群中直接测量的值。其余章节尚未实测(未实测)。 请先阅读文末的“确认的版本与日期”以及“未确认的内容”。 编写规则:以实测数据为基础,未能测出的内容标记为“未实测”。

目录

要让多个团队共享使用 GPU 集群,需要三项能力:保证各团队配额但允许借用剩余资源的配额机制、针对分布式训练等需要多个 Pod 同时启动的作业的 gang 调度,以及在保障推理服务的同时挤出批处理作业的抢占策略。默认调度器无法直接提供其中任何一项。本文将探讨辅助手段的设计差异、隔离局限性以及计算容量的顺序。

读完本文后,你将能够在面试中解释以下内容:

  1. 默认调度器的 GPU 模型(整数扩展资源)为什么不足以支持 gang 调度和公平共享,以及 Kueue 是准入层而 Volcano 是调度器的区别。
  2. time-slicing、MIG 和 MPS 能够划分什么以及无法隔离什么,以及不同信任级别下的选择。
  3. 根据请求率和延迟目标逆算 GPU 数量的顺序(吞吐量、并发度、KV 缓存、富余量、容灾)以及利用率指标的陷阱。

1. 默认调度器的局限性

2. Kueue:准入层

flowchart LR
  J["Job (queue-name 레이블)"] --> LQ["LocalQueue (네임스페이스)"]
  LQ --> CQ["ClusterQueue (쿼터, cohort)"]
  CQ -- "쿼터 예약과 승인" --> W["Workload 승인, 파드 생성"]
  W --> KS["kube-scheduler가 노드 배치"]
  KS --> DP["디바이스 플러그인 또는 DRA"]

Kueue 文档指出,将 Pod 放置到节点上的工作由 kube-scheduler 负责,自动扩缩容由 cluster-autoscaler 负责。Kueue 决定作业何时开始(创建 Pod)以及何时停止。

概念 角色
ResourceFlavor 用标签和容忍度表示节点组的特性(GPU 型号等)
ClusterQueue 资源池。配额(nominalQuota, borrowingLimit, lendingLimit)、抢占、公平共享规则
LocalQueue 团队命名空间中提交作业的接入点
cohort 互相借用未使用配额的 ClusterQueue 集合
apiVersion: kueue.x-k8s.io/v1beta2
kind: ClusterQueue
metadata: {name: team-a}
spec:
  cohortName: all-teams
  queueingStrategy: BestEffortFIFO
  preemption: {reclaimWithinCohort: LowerPriority, withinClusterQueue: LowerPriority}
  resourceGroups:
  - coveredResources: ["nvidia.com/gpu"]
    flavors:
    - name: gpu-pool
      resources: [{name: nvidia.com/gpu, nominalQuota: 8, borrowingLimit: 4}]

Job 通过 kueue.x-k8s.io/queue-name 标签选择 LocalQueue,优先级则通过与 Pod 分离的 WorkloadPriorityClass(标签 kueue.x-k8s.io/priority-class)来赋予。抢占分为基于优先级的默认方式,以及比较主导资源份额(Dominant Resource Share, DRS)的公平共享方式。最新的 v0.20.0(2026-09-30)不再提供 v1beta1,仅提供 v1beta2。由于它还支持 Deployment、StatefulSet 和 LeaderWorkerSet,因此可以同时管理推理服务与训练。多集群分发机制 MultiKueue 目前处于 beta 阶段。

waitForPodsReady如果所有 Pod 未能在限时内准备就绪,就会中止作业并将其退回队列。文档将其称为基于时间限制的实现,并指出由于顺序准入,准入可能会变慢。本文将其与原生 gang 调度区分开来探讨。

3. Volcano 与选择标准

Volcano 是一个独立的批处理调度器。 PodGroup的 minMember以 gang 为单位, Queue拥有 weight(相对份额)、 capability(上限)和 guarantee, reclaimable。调度器运行 enqueue, allocate, preempt, reclaim, backfill 动作,并在配置文件中以分层方式配置 gang, priority, drf(主导资源公平性,DRF)、 proportion, binpack 等插件。在 v1.15.0 中,gang 感知抢占进入了 alpha 阶段,最新的稳定版本系列是 v1.15.3(2026-09-30)。NVIDIA 的 KAI Scheduler 也作为一个独立的调度器,主张分层队列、基于时间的公平共享和拓扑感知(最新版本未确认)。

标准 Kueue Volcano
位置 准入控制器,保留 kube-scheduler 独立调度器
Gang 基于时间限制的方式 PodGroup 保证最小数量
公平共享 cohort 借用,DRS 公平共享 队列权重,DRF,proportion

如果保留默认调度器且目的是实现团队配额与借用,Kueue 是候选;如果希望在调度器内部强力使用 gang 和队列,则 Volcano 或 KAI 是候选。将两者叠加使用在同一个 Pod 上的支持范围尚未确认。

实测:当两个需要同时启动 4 个 Pod 的作业竞争时

我们亲自测试了第 1 节中的“Pod 级别决策”是否真的会导致死锁,以及两个工具是如何解决它的。

环境与方法。 在 4 vCPU、4GiB 虚拟机内的一次性单节点 k3s(v1.36.5)中,向节点状态通告了 8 个扩展资源 example.com/gpu (实际并没有 GPU,Pod 只是 sleep )。一个作业包含 4 个 Pod,每个 Pod 请求 2 个资源,因此一个作业将耗尽全部 8 个资源。当同时提交作业 A 和 B 时,需求为 16,供给为 8。测量“单个作业的 4 个 Pod 全部变为 Running 的时间”,在删除被接受的作业后,还测量了等待中的作业全部启动 4 个 Pod 所需的时间(Pod 终止宽限期 0 秒,重复 3 次)。分别通过各自的清单(manifest)安装了 Kueue v0.20.0 和 Volcano v1.15.3。

方式 结果
默认调度器(交替创建 A、B 的 Pod) 在 60 秒内仅运行了 A 的 2 个、B 的 2 个便停止。两个作业都无法凑齐 4 个,导致 死锁(等待原因:扩展资源不足,无可抢占的 Pod)
Kueue(以暂停状态提交作业) 仅接受一个作业,在 2.1 秒(均 3 次)内启动 4 个,另一个作业的 Pod 完全没有被创建。删除被接受的作业后,等待作业在 3.0~3.6 秒 后启动 4 个
Volcano(minAvailable: 4) 仅一个作业在 3.3~4.1 秒 内启动 4 个,另一个作业运行 0 个。删除被接受的作业后,等待作业在 3.6~4.2 秒 后启动 4 个

该数值仅在单个节点、虚假资源、两个作业以及已有镜像的轻量级容器这一条件下才有意义。随着节点增多,调度器吞吐量和位置选择将决定成本。此外,Kueue 能够避免死锁,似乎是因为配额(8 个资源)与实际可用资源相匹配。Kueue 文档指出,如果配额与实际可用资源不匹配,可能会发生死锁。生产环境中的恢复时间尺度是以分钟为单位的(如下文外部依据中 Philly 的 2~3 分钟超时和 Kueue 的 30 分钟默认超时限制)。本文中的 2~4 秒是没有竞争时的正常路径下的数值。

实测 2:同类群组借用、归还、优先级抢占

在上述环境(单节点 k3s,扩展资源 8 个, sleep 作业,终止宽限期 0 秒)中,这次我们提交了多个单 Pod(1 个资源)的作业来验证队列策略。使用的是 Kueue v0.20.0,两个团队(team-a、team-b)各自拥有 4 个配额,并且是一个可以互相借用剩余配额的同类群组(cohort)。

情况 结果
reclaimWithinCohort: Never。team-a 提交了 8 个,在配额 4 的基础上额外借用了 team-b 的 4 个份额 全部 8 个在 3.7 秒内运行。随后,如果 team-b 要求其自身的 4 个配额,则会在 60 秒内为 0 个(被驱逐的作业 0 个)。在结束 team-a 的 4 个作业后,3.1 秒后启动 team-b 的 4 个
reclaimWithinCohort: Any。相同情况 team-a 的 8 个用时 1.8 秒。team-b 的 4 个在 4.2 秒 内运行,同时 team-a 减少到 4 个,被驱逐的作业有 4 个(事件 Preempted to accommodate a workload)。结束 team-b 后,team-a 的 8 个在 3.0 秒后恢复
withinClusterQueue: LowerPriority,配额 4,低优先级 4 个正在运行中 两个高优先级任务 3.0秒 内运行,低优先级任务只剩 2 个。被驱逐的任务返回暂停状态等待。清空高优先级任务后,4 个低优先级任务在 3.0 秒后恢复
Volcano v1.15.3 默认设置(actions: "enqueue, allocate, backfill"),队列 qa、qb(权重相同, reclaimable: true) 当 qa 用完全部的 8 个后,qb 请求 4 个时 45 秒内为 0 个。因为默认设置中没有 reclaim 动作
在同样的 Volcano 中 actions 中 reclaim 添加 4 个 qb 在 10.3秒 内运行,qa 减少到 4 个(事件 Evict ... reclaim)。清除 qb 后,8 个 qa 在 1.8 秒后恢复
任务数 默认调度器(限制扩展资源为 8) Kueue(单队列,配额 8) 理想值
100 55.0秒 74.0秒(+35%) 约 12 秒
400 231.0秒 约 343 秒(+48%) 约 50 秒

Kueue 的 400 个任务是通过每 30 秒记录一次进度的单独测试(以约 1.3 个/秒的速度稳定完成)测得的值。由于计算完成数量的方式存在错误,最初的测试未能获取到数值。这两种方式的结果都是理想值(8 个资源从未闲置时)的 5 到 7 倍,因此主导时间的不是调度器,而是在这个 4 vCPU VM 中创建和删除 Pod 的开销,而 Kueue 为每个任务增加了约 0.2~0.3 秒的准入处理(经计算)。在小规模下,这种差异会被 Pod 的启动时间掩盖,但这意味着如果工作负载是每秒提交几十个短任务,队列层可能会成为吞吐量的瓶颈。

实测 3:借用上限与回收速度

在 Mac(Apple M1 Max)的 Docker Desktop(8GB 内存,10 个 CPU)上的 kind 单节点(Kubernetes v1.36.1)中, 使用 Kueue v0.20.0 进行了测量。虚拟扩展资源、 sleep 任务、0 秒终止宽限期与实测 2 相同。 在一个 cohort 中设置 team-a(配额 3)和 team-b(配额 5),并开启了 reclaimWithinCohort: Any 收回。team-a 提交了 10 个占用 1 个资源的任务,随后 team-b 请求其 5 个配额。

team-a 借用上限 team-a 最大运行数 team-b 收回 5 个配额耗时 被驱逐的任务数
无 8 个(配额 3 + 借用 5) 6.4–7.7秒(中位数 6.8秒) 5个
4 7个 5.2–5.8秒 4个
1 4个 1.7–2.7秒(中位数 2.0秒) 1个

4. 拓扑感知批处理

节点内的 NVLink 和节点间的 InfiniBand(IB)邻近性决定了训练通信速度。Kueue 的拓扑感知调度(TAS)从 v0.14 开始处于 Beta 阶段且默认开启。通过节点标签将块和机架等层级定义到 Topology中,并通过 ResourceFlavor.spec.topologyName连接,并使用 Pod 注解 podset-required-topology或 podset-preferred-topology来设定需求级别。文档指出,由于它会追踪所有 Pod 和节点,因此内存消耗会增加。Volcano 使用 HyperNode(文档中的示例 API 为 topology.volcano.sh/v1alpha1)来定义层级,并为任务赋予硬约束或软约束。多节点 NVLink 是 NVIDIA DRA 驱动程序 ComputeDomain的官方支持功能。

5. 优先级与抢占设计

为服务分配保证配额和高优先级,为批处理训练分配低优先级和借用配额。当服务收回借用分额时(reclaimWithinCohort),批处理会被挤走,因此批处理必须通过检查点来承受中断。牺牲 Pod 的终止宽限期会造成重新调度延迟,因此应将其缩减到仅保留保存检查点所需的时间。此外,根据 Kubernetes 文档,调度器无法抢占使用 DRA 资源的 Pod。

6. GPU 共享与隔离的局限性

方式 划分内容 隔离性
time-slicing 时间 无内存和故障隔离,向所有进程平均分配时间
MIG(Multi-Instance GPU) 硬件分割 内存和故障隔离,最多 7 个实例
MPS(Multi-Process Service) 流式多处理器(SM)并发执行 严重错误可能会影响同一 GPU 上的其他客户端

time-slicing 会将资源按 replicas的倍数进行夸大宣传,并且 DCGM(Data Center GPU Manager)导出器在此模式下无法关联每个容器的指标(GPU Operator 文档)。Ampere 架构之后的某些 GPU(如 A100、H100、B200 等)支持 MIG,但在已确认的 NVIDIA 支持列表中没有消费级 GPU,若要更改配置,该 GPU 上必须没有用户工作负载。从驱动 r610 开始,MPS 开启静态 SM 分割后可实现部分隔离,监控工具会将客户端的使用量汇总到 MPS 服务器进程中。因此,对于互不信任的租户应使用 MIG 或专用节点,而对于同一团队的轻量级任务则使用 time-slicing 或 MPS。Kueue 配额是记账工具,而不是安全边界。

7. 动态资源分配(DRA)的当前状态

DRA 允许通过 ResourceClaim, DeviceClass, ResourceSlice以属性(通用表达式语言(CEL)表达式)的形式请求设备。功能门控数据(确认于 2026-10-05)如下:

功能 阶段
核心 DRA stable (1.34, 门控在 1.35 中锁定,官方页面标注为 "Stable since v1.35")
优先级列表、管理员访问权限 stable (1.36)
现有扩展资源名称映射、设备污点 stable (1.37)
可分割设备、可消费容量、绑定条件 beta (1.36)
工作负载级 ResourceClaim beta (1.37, 默认关闭)
节点可分配资源、兼容组 alpha

NVIDIA DRA 驱动程序(kubernetes-sigs/dra-driver-nvidia-gpu,v0.5.0,2026-08-19)的 README (main) 将 ComputeDomain称为官方支持,并指出 GPU 分配尚不是官方支持且在 Chart 中默认关闭。文档网站将 MIG、time-slicing 和 MPS 列为支持的功能,但未说明其阶段,因此无法确认哪一个是最新的。Kueue v0.20 发行说明中将 DRA 设备可用性检查作为 Alpha 门控列出,而 Volcano v1.15 则将 DRA 资源计算纳入了队列配额中。

8. 容量规划

λ(요청률), 입출력 길이, 지연 목표 -> 복제본 1개의 실측 처리량과 동시성
 -> 처리량 기준 대수, 동시성(Little 법칙) 기준 대수, 키-값(KV) 캐시 한도 확인
 -> 큰 쪽 선택 -> 여유율 -> 장애 대비(N+k) -> 병렬화(텐서 병렬, TP) 단위로 올림

这是一个用于说明的示例。基准点是作者在实验环境中测得的值:在 24GB 级消费级 GPU 上,Qwen3.8-27B(NVFP4)在并发 4 请求下的总计吞吐量为 138 tok/s,KV 缓存约为 57,000 个 Token。假设请求率为 0.5 req/s,输入 1,500 个 Token,输出 400 个 Token。

  1. 吞吐量:0.5×400=200 tok/s ÷ 138 = 1.45,因此需要 2 台。
  2. 并发性:如果每个请求 138÷4=34.5 tok/s,则响应时间约为 11.6 秒,根据利特尔法则(Little's Law),0.5×11.6≈5.8 个请求同时进行。由于每个副本限制并发 4 个,因此至少需要 2 台(8 个槽位),槽位利用率约为 72%。
  3. KV 确认:6×(1,500+400)=11,400 个 Token,在 57,000 以内。
  4. 加上 30% 的负载波动余量和 1 台用于应对故障的冗余机器,总共确定为 3 台。

如果模型跨越多张 GPU(张量并行 TP),则复制单元会随 GPU 数量按比例扩大,因此需要将实例数乘以 TP 大小,当一个节点宕机时,容量会以副本单元进行缩减。

利用率指标的陷阱:NVML(NVIDIA Management Library)的 utilization.gpu是采样期间内核至少运行一次的时间比例,无法说明 SM 的实际使用程度。像 DCGM 的 SM_ACTIVE, PIPE_TENSOR_ACTIVE 这类性能剖析指标更为细致。从成本角度来看,需要同时考察分配的 GPU 时间与实际工作时间的比率(分配率)以及服务指标(队列长度、KV 缓存占用率、满足目标延迟的吞吐量)。

小规模共享中常见的陷阱

由于作者的实验环境主要基于消费级 GPU(6~24GB),且不使用多节点训练或 Kueue,因此本节并非大规模多租户的证据,而是对小规模共享中容易遇到的陷阱进行了归纳。

动手尝试

此操作为只读,或者在临时命名空间中结束。在撰写本文时并未实际运行验证。

# 읽기 전용: 노드별 GPU 광고량과 할당 상태
kubectl get node -o custom-columns='NAME:.metadata.name,GPU:.status.allocatable.nvidia\.com/gpu'
kubectl describe node NODE | grep -A8 "Allocated resources"

# 임시: 쿼터 0에서 거절, 쿼터 해제 후 Pending 사유 확인 (정리 포함)
pod() { cat <<EOF
apiVersion: v1
kind: Pod
metadata: {name: want-gpu}
spec:
  containers:
  - {name: c, image: busybox, command: ["sleep","60"], resources: {limits: {nvidia.com/gpu: $1}}}
EOF
}
kubectl create ns lab-gpu
kubectl -n lab-gpu create quota gpu-zero --hard=requests.nvidia.com/gpu=0
pod 1 | kubectl -n lab-gpu create --dry-run=server -f -
kubectl -n lab-gpu delete quota gpu-zero
pod 99 | kubectl -n lab-gpu create -f -
kubectl -n lab-gpu describe pod want-gpu | tail -8
kubectl delete ns lab-gpu

第一个请求会因超出配额而被拒绝,第二个 Pod 应保持 Pending 状态并通过事件提示 GPU 不足。双方都不会占用 GPU。请不要在与他人共用的集群中进行 Kueue 练习,而只在一次性集群(如 kind 等)中进行。安装命令(kubectl apply --server-side -f https://github.com/kubernetes-sigs/kueue/releases/download/v0.20.0/manifests.yaml)会向整个集群添加自定义资源(CRD)。

面试中可能会出现的问题

  1. 使用默认调度器运行分布式训练会有什么问题? 可能会按 Pod 单位进行调度,导致部分 Pod 持有 GPU 的同时发生死锁。可以通过 Volcano 的 PodGroup、处于 Beta 阶段的原生 PodGroup,或者 Kueue 的超时机制来弥补,需了解最后一种是基于超时实现的。
  2. 如何选择 Kueue 和 Volcano? Kueue 适合在保留 kube-scheduler 的同时叠加配额、借用和公平共享功能。Volcano 则适合即便需要运营新的调度器,也要强力使用 Gang、队列和拓扑结构的情况。
  3. 如何在保障推理服务的同时运行批处理训练? 为推理服务赋予保障配额和高优先级,并让批处理从 cohort 中借用资源。由于回收时会被抢占,因此需要设计好检查点(checkpoint)和终止宽限期。
  4. GPU 共享方式与隔离限制有哪些? 时间切片(time-slicing)没有内存和故障隔离;MIG 提供硬件隔离,但支持的 GPU 受限;MPS 可能会传播错误。对于不信任的租户,应使用 MIG 或专用节点。DRA 正朝着以属性方式表达这一方向发展,各项功能的成熟度各有不同。
  5. 如何根据请求率和延迟目标计算 GPU 数量? 分别计算吞吐量、利特尔法则(Little's Law)的并发度以及 KV 缓存限制,选择较大的一方,并根据冗余率、N+k 和并行化单位进行提升。观察使用率时,不能只看 utilization.gpu,还要结合 SM(流式多处理器)活跃度和推理延迟指标一起看。

常见误区

参考资料

外部依据

未能亲自测量的规模以及死锁的实际发生频率已通过以下资料进行确认。论文中的数据直接取自 PDF 原文,未采用仅包含摘要的数据。

观点 资料 确认内容
部分获取资源并处于等待状态的问题在生产调度器中确实被处理过 Jeon 等人, USENIX ATC 2019, Analysis of Large-Scale Multi-Tenant GPU Clusters for DNN Training Workloads (arXiv 1901.05758) 微软 Philly 调度器如果在 2~3 分钟的超时时间内无法获取所需的全部 GPU,则会归还已获取的资源,并在退避(backoff)2 分钟后重试。在对 75 天、96,260 个任务的追踪中,大部分等待时间是由碎片化延迟造成的(申请超过 8 个 GPU 的任务占 97.9%)。并未测量死锁的发生频率。
超时机制并非真正的 Gang 调度 Kueue KEP-349 (All-or-nothing), waitForPodsReady 文档 当配额与可用资源不匹配时可能会发生死锁,默认限制时间为 30 分钟,超时后从 60 秒开始翻倍,最大退避至 3,600 秒。其文档中自我记载道:顺序批准是以牺牲吞吐量为代价来换取可靠性的。
Volcano 的 Gang Volcano 文档的 gang 插件, PodGroup 仅在满足最小运行数量时才调度所有 Pod 的“全有或全无”方式
队列等待时间的规模 Kueue 仓库的性能测试配置(test/performance/scheduler) 在 30 个队列、严重超额订阅配额的负载下,整体约为 351 秒(上限 425 秒),小型任务到批准的平均等待时间约为 215 秒。这是配置文件中的期望值,并非批准处理延迟,因此无法与本文的 2~4 秒进行比较。
大规模吞吐量 Google Cloud 博客, 13 万节点 GKE 集群 (2025-11-22) Pod 创建和绑定约 1,000 个/秒,抢占 39,000 个 Pod 耗时 93 秒(厂商测试)
Volcano 的规模 Volcano 性能调优指南 (2026-05-26 更新) 在 Kind 环境中,10,000 个 Pod(10,000 个单任务)耗时约 250 秒,调整 Webhook 后约 180 秒(计算得出约从每秒 40 个提升至 56 个)。描述称 Webhook 超时设为 10 秒时,98.7% 的 Pod 创建请求失败。
DRA 阶段 Kubernetes 功能门控文档 核心 DRA 达到稳定(stable)阶段。见上文第 7 节的表格

在外部资料中,没有直接测量 Gang 死锁发生频率的资料。《MLaaS in the Wild》(NSDI 2022) 的 PDF 文件由于超出大小限制而无法打开,因此没有使用其中的数据。

确认的版本与日期

以 2026-10-05 为准,包括 Kubernetes 1.37.1、Kueue v0.20.0 (2026-09-30)、Volcano v1.15.3 (2026-09-30)、NVIDIA DRA 驱动程序 v0.5.0 (2026-08-19)。作者的实验环境为 Kubernetes 1.34.10、GPU Operator v26.3.3。Gang 调度的实际测量是在一个独立的一次性 k3s v1.36.5 环境中进行的。

未确认的内容:在大规模节点下 Kueue 与 Volcano 的对比(测试仅使用了一个节点、几十个以下的任务)以及混合优先级的队列的长期运行表现、KAI Scheduler 的最新版本与成熟度、同时使用 Kueue 和 Volcano 配置的支持范围、NVIDIA DRA 驱动程序的 GPU 分配功能阶段(README 与文档网站内容不一致)、Kueue TAS 与 NVLink 标签的具体联动、上述命令的实际输出。容量计算属于解释性算术,应当用负载测试来代替。

登录后即可点赞

评论

还没有评论。

登录后即可发表评论