基于K8s的云原生AI基础设施:架构、部署与实践【016】-AI算力的队列秩序(中)

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 个成员都已经运行起来。

Leave a Reply