6. Queue:把算力资源纳入队列秩序
Volcano 的另一个核心能力是 Queue。在多租户 AI 算力平台中,不同团队、不同业务线、不同任务类型都可能共享同一个 GPU 集群。如果没有队列,所有任务都会直接竞争集群资源,最终很容易出现两个问题:一类任务长期占满资源,其他任务无法进入;或者高优先级业务和低优先级实验任务混在一起,缺少清晰的调度边界。Queue 的作用,就是把资源竞争纳入队列体系。在本文实践中,分别为租户 A 和租户 B 创建队列:
apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: tenanta
spec:
weight: 1
reclaimable: true
---
apiVersion: scheduling.volcano.sh/v1beta1
kind: Queue
metadata:
name: tenantb
spec:
weight: 1
reclaimable: true
在Volcano中,有两个核心的队列调度插件,一个是capacity,一个是proportion,在此示例中,为简化配置我们使用proportion插件来势能队列的调度,proportion插件可以通过配置队列的Weight值来自动计算队列资源应得量。weight: 1 表示两个队列在公平分配时具有相同权重;reclaimable: true 表示队列资源可以参与回收。当某个队列暂时没有任务时,它的资源可以被其他队列借用;当该队列后续提交任务时,可以通过 reclaim 机制尝试回收资源。
这里需要注意的是,如果 Volcano Job 没有写 queue 字段,就会使用 default 队列。Volcano 启动时也会默认创建一个叫 root 的队列。这个队列用于启用层级队列功能时,作为所有队列的根队列,default 队列是 root 的子队列。所以 root 更像一个“父目录”或者“总资源池入口”。
队列状态中可以看到当前已分配资源,例如 CPU、Memory、GPU、Pods 等:
kubectl describe queue tenanta
Name: default
Namespace:
Labels: <none>
Annotations: <none>
API Version: scheduling.volcano.sh/v1beta1
Kind: Queue
Metadata:
Creation Timestamp: 2026-02-10T00:11:47Z
Generation: 2
Resource Version: 830976463
UID: efb68898-81e0-4f19-aaf0-def3048af68a
Spec:
Dequeue Strategy: traverse
Guarantee:
Parent: root
Reclaimable: true
Weight: 1
Status:
Allocated:
attachable-volumes-csi-dev-ips-server-nfs.csi.k8s.io: 40m
Cpu: 5816
Ephemeral - Storage: 1986422374400
mars-tech.com/gpu: 540
Memory: 45760Gi
Pods: 131
rdma/hca_cpta: 44
rdma/hca_cptb: 44
Reservation:
State: Open
Events: <none>
这意味着,平台不再只是从 Pod 视角看资源,而是可以从队列视角观察不同租户、不同业务组对 GPU 资源的占用情况。
7. Priority 与 Preemption:保障关键业务优先运行
在实际 AI 算力平台中,并不是所有任务都具有相同优先级。在线推理服务通常比离线实验任务更敏感;核心业务通常比普通调试任务更重要;生产任务通常比测试任务更需要稳定的资源保障。因此,平台需要通过优先级机制表达任务的重要程度。本文实践中创建了三个 PriorityClass:
# 核心生产业务
# 线上推理服务、客户生产模型、平台关键组件、SLA要求高的模型服务
# 可以抢占低优先级任务
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: p0-critical
value: 1000000
preemptionPolicy: PreemptLowerPriority
globalDefault: false
---
# 重要业务
# 重要训练任务、重要微调任务、预生产推理服务、演示环境、交付验证任务
# 可以抢占 p2-regular,但一般不应抢占 p0-critical
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: p1-high
value: 10000
preemptionPolicy: PreemptLowerPriority
globalDefault: false
---
# 普通任务
# 日常实验、开发测试、临时 Notebook、普通离线训练、低优先级批量任务
# 不应该抢占别人,主要作为被抢占对象
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: p2-regular
value: 100
preemptionPolicy: PreemptLowerPriority
globalDefault: true
可以通过如下命令检查:
kubectl get priorityclass | grep p
p0-critical 1000000 false 23s PreemptLowerPriority
p1-high 10000 false 23s PreemptLowerPriority
p2-regular 100 true 23s PreemptLowerPriority
在工作负载中通过 priorityClassName 指定优先级:
spec:
schedulerName: volcano
priorityClassName: p0-critical
当集群资源不足时,高优先级任务可以通过 Volcano 的 priority、preempt 等机制,尝试抢占低优先级任务所占用的资源。这样可以避免关键推理服务或核心训练任务长期等待在 Pending 状态。
需要注意的是,抢占不是简单粗暴地“随意删除低优先级 Pod”。它应该结合队列、优先级、PodGroup、资源回收策略一起设计。对于生产平台来说,抢占策略需要谨慎使用,避免频繁驱逐造成业务抖动。
8. Reclaim:让队列资源可以借用,也可以回收
在多租户 GPU 集群中,如果每个租户都严格保留一部分 GPU,资源利用率往往会比较低。某个租户暂时没有任务时,它的 GPU 可能长期闲置;而另一个租户此时可能有大量任务在排队。更合理的方式是资源可以被临时借用,但在需要时可以被回收。这就是 Reclaim 的价值。
例如,租户 A 和租户 B 都有自己的队列,并且权重相同。当租户 A 暂时没有任务时,租户 B 可以运行更多训练任务,临时占用较多 GPU。等租户 A 提交高优先级任务后,Volcano 可以根据队列权重、reclaimable 配置和调度策略,尝试从租户 B 已占用的资源中回收一部分,使租户 A 的任务能够被调度。
这类能力对于 AI 平台非常重要。它让集群不必在“资源严格隔离”和“完全自由竞争”之间二选一,而是可以在共享和公平之间取得平衡。在本文实践中,租户 B 的 PyTorchJob (优先级为p2-regular)占用了 32 张 GPU:
kubectl get pg -A
NAMESPACE NAME STATUS MINMEMBER RUNNINGS AGE
b01 tenantb-heavy-training Running 4 4 24s
kubectl get pods -n b01
NAME READY STATUS RESTARTS AGE
tenantb-heavy-training-master-0 1/1 Running 0 65s
tenantb-heavy-training-worker-0 1/1 Running 0 65s
tenantb-heavy-training-worker-1 1/1 Running 0 65s
tenantb-heavy-training-worker-2 1/1 Running 0 65s
对应队列状态中可以看到:
kubectl describe queues tenantb
Name: tenantb
Namespace:
Labels: <none>
Annotations: <none>
API Version: scheduling.volcano.sh/v1beta1
Kind: Queue
Metadata:
Creation Timestamp: 2026-02-05T16:14:56Z
Generation: 2
Resource Version: 59890519
UID: 8ef20101-d631-4ca2-b102-aa023202cc52
Spec:
Dequeue Strategy: traverse
Parent: root
Reclaimable: true
Weight: 1
Status:
Allocated:
Cpu: 150m
mars-tech.com/gpu: 32
Memory: 30Mi
Pods: 4
Reservation:
State: Open
Events: <none>
随后提交租户 A 的高优先级推理任务。如果 tenant A 队列需要资源,而 tenant B 已经超出其公平份额,Volcano 就可以通过 reclaim / preempt 相关动作尝试释放资源,让 tenant A 的任务获得调度机会。
这就是 Volcano 在多租户 AI 平台中的核心价值之一:让 GPU 不只是被“谁先提交谁占用”,而是按照队列、权重、优先级和回收策略进行有序流转。
9. PodGroup 与 Gang Scheduling:让分布式作业整体调度
AI 训练和多机推理经常不是单个 Pod 就能完成。例如,一个 PyTorchJob 可能包含:1 个 Master,3 个 Workers。一个多机推理服务可能包含:1 个 Leader和1 个或多个 Workers。这类任务如果只启动了一部分 Pod,通常无法真正工作。比如 4 个训练 Pod 中只启动了 1 个,剩余 3 个 Pending,那么已经启动的那个 Pod 也可能只是等待其他节点加入,无法进行有效训练。这会导致 GPU 被占用但没有产生有效计算。
Gang Scheduling 解决的就是这个问题。它要求一组强关联 Pod 满足最小成员数后才整体调度。Volcano 通过 PodGroup 表达这组 Pod 的整体关系:
apiVersion: scheduling.volcano.sh/v1beta1
kind: PodGroup
metadata:
name: qwen2-72b-unified-pg
namespace: a01
spec:
minMember: 2
queue: tenanta
这里的 minMember: 2 表示至少需要 2 个 Pod 同时满足调度条件,任务才应该进入运行状态。对于一个 LeaderWorkerSet 形式的多机推理任务来说,这可以对应:
1 个 Leader + 1 个 Worker = 至少 2 个成员同时调度
在 LWS 的 leaderTemplate 和 workerTemplate 中,可以通过注解把 Pod 关联到这个 PodGroup 和 Queue:
metadata:
annotations:
scheduling.volcano.sh/queue-name: tenanta
scheduling.k8s.io/group-name: qwen2-72b-unified-pg
spec:
schedulerName: volcano
priorityClassName: p0-critical
这样,这组 leader / worker Pod 就不再是孤立参与调度,而是作为一个整体进入 Volcano 的队列和成组调度体系。
10. 将训练任务纳入Volcano 调度
💡 对于 PyTorchJob,Kubeflow Training Operator 在集成 Volcano 后,也可以自动创建对应的 PodGroup,并将调度信息传递给 Volcano。
在训练场景中,PyTorchJob 是非常典型的分布式 AI 作业类型。为了让 PyTorchJob 使用 Volcano 的 Gang Scheduling,需要在 Training Operator 中启用 gang scheduler。例如:
kubectl -n kubeflow patch deploy training-operator --type='json' -p='[
{"op":"add","path":"/spec/template/spec/containers/0/args","value":["--gang-scheduler-name=volcano"]}
]'
然后创建一个使用 tenantb 队列的 PyTorchJob:
cat <<EOF | kubectl apply -f -
apiVersion: kubeflow.org/v1
kind: PyTorchJob
metadata:
name: tenantb-heavy-training
namespace: b01
<span style="color: rgba(212, 76, 71, 1);"> annotations:
scheduling.volcano.sh/queue-name: tenantb
</span>spec:
runPolicy:
<span style="color: rgba(212, 76, 71, 1);"> schedulingPolicy:
queue: tenantb
minAvailable: 1</span>
pytorchReplicaSpecs:
Master:
replicas: 1
template:
metadata:
<span style="color: rgba(212, 76, 71, 1);"> annotations:
scheduling.volcano.sh/queue-name: tenantb</span>
spec:
<span style="background-color: rgb(251, 243, 219);"> schedulerName: volcano</span><span style="color: rgba(212, 76, 71, 1);">
priorityClassName: p2-regular</span>
containers:
- name: pytorch
image: 10.8.17.100:60066/tenant_public/training-pytorch:v20260106
command: ["sleep","3600"]
resources:
limits:
mars-tech.com/gpu: 8
rdma/hca_cpta: "1"
rdma/hca_cptb: "1"
memory: 96Gi
cpu: 48
requests:
mars-tech.com/gpu: 8
rdma/hca_cpta: "1"
rdma/hca_cptb: "1"
memory: 96Gi
cpu: 48
Worker:
replicas: 3
template:
metadata:
<span style="color: rgba(212, 76, 71, 1);"> annotations:
scheduling.volcano.sh/queue-name: tenantb</span>
spec:
<span style="background-color: rgb(251, 243, 219);"> schedulerName: volcano</span><span style="color: rgba(212, 76, 71, 1);">
priorityClassName: p2-regular</span>
containers:
- name: pytorch
image: 10.8.17.100:60066/tenant_public/training-pytorch:v20260106
command: ["sleep","3600"]
resources:
limits:
mars-tech.com/gpu: 8
rdma/hca_cpta: "1"
rdma/hca_cptb: "1"
memory: 96Gi
cpu: 48
requests:
mars-tech.com/gpu: 8
rdma/hca_cpta: "1"
rdma/hca_cptb: "1"
memory: 96Gi
cpu: 48
EOF
在每个 Pod 模板中指定:
spec:
schedulerName: volcano
priorityClassName: p2-regular
这个任务包含 1 个 Master 和 3 个 Workers,每个 Pod 请求 8 张 GPU,总共占用 32 张 GPU。任务启动后,可以查看 PodGroup:
kubectl get pg -A
NAMESPACE NAME STATUS MINMEMBER RUNNINGS
b01 tenantb-heavy-training Running 4 4
这说明 PyTorchJob 已经被转换为 Volcano 可以识别的 PodGroup,并且 4 个成员都已经运行起来。
