11. 多机分布式推理服务纳入Volcano
💡 如果想让lws支持gang-scheduling,需要在lws侧使能此能力。如果是首次安装LWS,可以直接修改缺省的values.yaml文件,来添加如下内容。
在前面的 LWS 章节中,我们已经讨论过,LeaderWorkerSet 适合表达多机推理中的 leader + workers 模式。但 LWS 解决的是“这些 Pod 如何作为一个推理副本被管理”,而 Volcano 解决的是“这些 Pod 如何作为一个整体被调度”。两者结合后,可以形成更完整的多机推理运行模型:LeaderWorkerSet:负责表达 leader + workers 的工作负载结构。Volcano:负责让 leader + workers 作为一个整体进入队列、参与优先级、抢占、回收和 gang scheduling
在本文实践中,一个 Qwen2-72B 多机推理任务通过 LWS 配置如下:
apiVersion: scheduling.volcano.sh/v1beta1
kind: PodGroup
metadata:
name: qwen2-72b-unified-pg
namespace: a01
spec:
<span style="color: rgba(212, 76, 71, 1);"> minMember: 2
queue: tenanta</span>
---
apiVersion: leaderworkerset.x-k8s.io/v1
kind: LeaderWorkerSet
metadata:
name: qwen2-72b-tenanta
namespace: a01
spec:
replicas: 1
leaderWorkerTemplate:
size: 2
restartPolicy: RecreateGroupOnPodRestart
leaderTemplate:
metadata:
annotations:
k8s.v1.cni.cncf.io/networks: '[
{ "name": "compute-a-net01", "namespace": "nad" },
{ "name": "compute-b-net01", "namespace": "nad" }
]'
<span style="color: rgba(212, 76, 71, 1);"> scheduling.volcano.sh/queue-name: tenanta
scheduling.k8s.io/group-name: qwen2-72b-unified-pg</span>
labels:
role: leader
spec:
<span style="color: rgba(212, 76, 71, 1);"> schedulerName: volcano
priorityClassName: p0-critical</span>
securityContext:
seccompProfile:
type: Unconfined
containers:
- name: qwen2-72b-leader
image: 10.8.17.100:60066/mars/vllm-mars:0.11.0-hpcc.ai3.3.0.12-torch2.6-py312-ubuntu22.04-amd64-20251224
env:
- name: TRITON_ENABLE_MACA_OPT_MOVE_DOT_OPERANDS_OUT_LOOP
value: "1"
- name: TRITON_ENABLE_MACA_CHAIN_DOT_OPT
value: "1"
- name: TRITON_DISABLE_MACA_OPT_MMA_PREFETCH
value: "1"
- name: RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES
value: "1"
- name: TRITON_ENABLE_MACA_COMPILER_INT8_OPT
value: "True"
- name: GLOO_SOCKET_IFNAME
value: net1
- name: NCCL_SOCKET_IFNAME
value: net1
- name: NCCL_IGNORE_CPU_AFFINITY
value: "1"
- name: NCCL_IB_HCA
value: "mlx5_0,mlx5_4"
- name: NCCL_IB_GID_INDEX
value: "5"
- name: MCCL_IB_HCA
value: "mlx5_0,mlx5_4"
- name: MCCL_IB_GID_INDEX
value: "5"
- name: HCCL_IB_HCA
value: "mlx5_0,mlx5_4"
- name: HCCL_IB_GID_INDEX
value: "5"
command:
- sh
- -c
- |
sed -i 's/ray start/ray start --num-gpus=8 /g' /workspace/multi-node-serving.sh;
bash /workspace/multi-node-serving.sh leader --ray_cluster_size="${LWS_GROUP_SIZE}";
CUDA_VISIBLE_DEVICES="0,1,2,3,4,5,6,7" /opt/conda/bin/vllm serve /workspace/model/Qwen2-72B -pp 2 -tp 8 --trust-remote-code --distributed-executor-backend ray --dtype bfloat16 --max-model-len 4096 --swap-space 16 --gpu-memory-utilization 0.8
--port 8080
--served-model-name Qwen2-72B
resources:
limits:
mars-tech.com/gpu: 8
rdma/hca_cpta: "1"
rdma/hca_cptb: "1"
memory: 64Gi
cpu: 12
requests:
mars-tech.com/gpu: 8
rdma/hca_cpta: "1"
rdma/hca_cptb: "1"
memory: 64Gi
cpu: 12
ports:
- containerPort: 8080
readinessProbe:
tcpSocket:
port: 8080
initialDelaySeconds: 900
periodSeconds: 10
volumeMounts:
- mountPath: /dev/shm
name: dshm
- mountPath: /workspace/model
name: localmodelvolume
volumes:
- name: dshm
emptyDir:
medium: Memory
sizeLimit: 100Gi
- name: localmodelvolume
nfs:
server: 10.8.7.133
path: /zion1/nfs_csi/pvc-f74e271f-769b-420a-afe4-1a044da08fd6/pvc-f74e271f-769b-420a-afe4-1a044da08fd6
readOnly: true
workerTemplate:
metadata:
annotations:
k8s.v1.cni.cncf.io/networks: '[
{ "name": "compute-a-net01", "namespace": "nad" },
{ "name": "compute-b-net01", "namespace": "nad" }
]'
<span style="color: rgba(212, 76, 71, 1);"> scheduling.volcano.sh/queue-name: tenanta
scheduling.k8s.io/group-name: qwen2-72b-unified-pg</span>
spec:
<span style="color: rgba(212, 76, 71, 1);"> schedulerName: volcano
priorityClassName: p0-critical</span>
securityContext:
seccompProfile:
type: Unconfined
containers:
- name: qwen2-72b-worker
image: 10.8.17.100:60066/mars/vllm-mars:0.11.0-hpcc.ai3.3.0.12-torch2.6-py312-ubuntu22.04-amd64-20251224
env:
- name: TRITON_ENABLE_MACA_OPT_MOVE_DOT_OPERANDS_OUT_LOOP
value: "1"
- name: TRITON_ENABLE_MACA_CHAIN_DOT_OPT
value: "1"
- name: TRITON_DISABLE_MACA_OPT_MMA_PREFETCH
value: "1"
- name: RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES
value: "1"
- name: TRITON_ENABLE_MACA_COMPILER_INT8_OPT
value: "True"
- name: GLOO_SOCKET_IFNAME
value: net1
- name: NCCL_SOCKET_IFNAME
value: net1
- name: NCCL_IGNORE_CPU_AFFINITY
value: "1"
- name: NCCL_IB_HCA
value: "mlx5_0,mlx5_4"
- name: NCCL_IB_GID_INDEX
value: "5"
- name: MCCL_IB_HCA
value: "mlx5_0,mlx5_4"
- name: MCCL_IB_GID_INDEX
value: "5"
- name: HCCL_IB_HCA
value: "mlx5_0,mlx5_4"
- name: HCCL_IB_GID_INDEX
value: "5"
command:
- sh
- -c
- |
sed -i 's/ray start/ray start --num-gpus=8 /g' /workspace/multi-node-serving.sh;
bash /workspace/multi-node-serving.sh worker --ray_address=$(LWS_LEADER_ADDRESS)
resources:
limits:
mars-tech.com/gpu: 8
rdma/hca_cpta: "1"
rdma/hca_cptb: "1"
memory: 64Gi
cpu: 12
requests:
mars-tech.com/gpu: 8
rdma/hca_cpta: "1"
rdma/hca_cptb: "1"
memory: 64Gi
cpu: 12
volumeMounts:
- mountPath: /dev/shm
name: dshm
- mountPath: /workspace/model
name: localmodelvolume
volumes:
- name: dshm
emptyDir:
medium: Memory
sizeLimit: 100Gi
- name: localmodelvolume
nfs:
server: 10.8.7.133
path: /zion1/nfs_csi/pvc-f74e271f-769b-420a-afe4-1a044da08fd6/pvc-f74e271f-769b-420a-afe4-1a044da08fd6
readOnly: true
---
apiVersion: v1
kind: Service
metadata:
name: qwen2-72b-leader
namespace: a01
spec:
type: ClusterIP
selector:
leaderworkerset.sigs.k8s.io/name: qwen2-72b-tenanta
role: leader
ports:
- name: http
port: 8080
protocol: TCP
targetPort: 8080
其中 size: 2 表示一个 group 中包含 2 个 Pod,即 1 个 leader 和 1 个 worker。每个 Pod 请求 8 张 GPU,因此这个推理副本总共需要 16 张 GPU。为了让它进入 Volcano 调度体系,需要在 leaderTemplate 和 workerTemplate 中设置:
metadata:
annotations:
scheduling.volcano.sh/queue-name: tenanta
scheduling.k8s.io/group-name: qwen2-72b-unified-pg
spec:
schedulerName: volcano
priorityClassName: p0-critical
这里有三个关键点。
第一,schedulerName: volcano 表示这些 Pod 不再由默认调度器处理,而是交给 Volcano Scheduler。
第二,scheduling.volcano.sh/queue-name: tenanta 表示这些 Pod 属于 tenant A 队列。
第三,scheduling.k8s.io/group-name: qwen2-72b-unified-pg 表示这些 Pod 与对应的 PodGroup 关联,从而参与 gang scheduling。
12. 单机推理服务纳入 Volcano
Volcano 并不只适用于 PyTorchJob 或 LWS 这类复杂工作负载。普通 Deployment 只要显式指定 schedulerName: volcano,同样可以进入 Volcano 的调度体系。例如一个单机多卡或单卡推理服务:
apiVersion: apps/v1
kind: Deployment
metadata:
name: qwen8b-preemptor
namespace: a01
labels:
app: qwen8b
spec:
replicas: 1
selector:
matchLabels:
app: qwen8b
template:
metadata:
labels:
app: qwen8b
annotations:
scheduling.volcano.sh/queue-name: tenanta
spec:
schedulerName: volcano
priorityClassName: p0-critical
containers:
- name: qwen8b
image: 10.8.17.100:60066/mars/vllm-mars:0.11.0-hpcc.ai3.3.0.12-torch2.6-py312-ubuntu22.04-amd64-20251224
imagePullPolicy: IfNotPresent
env:
- name: CUDA_VISIBLE_DEVICES
value: "0"
- name: RAY_EXPERIMENTAL_NOSET_CUDA_VISIBLE_DEVICES
value: "1"
command: ["/bin/bash", "-lc"]
args:
- >
python -m vllm.entrypoints.openai.api_server
--model /workspace/model/Qwen3-8B
--port 8080
--tensor-parallel-size 1
--gpu-memory-utilization 0.95
--rope-scaling '{"rope_type":"yarn","factor":4.0,"original_max_position_embeddings":32768}'
--enable-auto-tool-choice
--tool-call-parser granite
--served-model-name Qwen3-8B
--trust-remote-code
--enforce-eager
ports:
- name: http
containerPort: 8080
resources:
limits:
cpu: "12"
memory: 48Gi
mars-tech.com/gpu: "1"
requests:
cpu: "12"
memory: 48Gi
mars-tech.com/gpu: "1"
volumeMounts:
- name: localmodelvolume
mountPath: /workspace/model
volumes:
- name: localmodelvolume
nfs:
server: 10.8.7.133
path: /zion1/nfs_csi/pvc-f74e271f-769b-420a-afe4-1a044da08fd6/pvc-f74e271f-769b-420a-afe4-1a044da08fd6
对于 AI算力平台来说,不只有大规模训练任务和多机推理任务,也会有大量单机单卡或单机多卡推理服务、调试服务、测试任务和实验任务。把这些任务统一纳入 Volcano 的调度体系后,平台就可以用同一套队列和优先级规则管理不同形态的 AI 工作负载,而且对于这种GPU占用较少的推理服务可以整合到一个节点,来提高节点利用率。
13. 网络拓扑感知调度:让多机任务高性能运行
对于多机训练和多机推理来说,还要考虑这些节点之间的网络半径。在大模型训练和分布式推理场景中,多个节点之间往往需要频繁进行数据交换。例如模型并行、流水线并行、分布式训练梯度同步、Ray worker 通信、NCCL 通信等,都会受到网络拓扑的影响。如果一组任务被调度到网络距离较远的节点上,可能需要跨越更多交换机,通信延迟更高,吞吐量也更低。对于依赖 RoCE、IB、NVSwitch 或多层交换网络的 AI 集群来说,节点之间“离得近不近”,会直接影响任务的实际运行效率。
Volcano 的网络拓扑感知调度,就是为了解决这个问题。它通过 HyperNode 这个 CRD 来描述集群中的网络性能域。一个 HyperNode 可以表示一组网络上更接近的节点,例如同一个 ToR 交换机下的节点、同一个 HyperNode 下的节点,或者更高层级的网络分组。多个 HyperNode 还可以组成树状层级结构,用来表达更复杂的数据中心网络拓扑。
这样,Volcano 在调度多机任务时,就不仅仅看 CPU、内存、GPU 是否满足,还可以结合网络拓扑,尽量把同一个作业的多个 Pod 放到网络性能更好的区域内。
要让拓扑感知真正生效,还需要让 Volcano 知道集群的网络拓扑。Volcano 使用 HyperNode 来描述这些拓扑关系。HyperNode 可以手动创建,也可以通过自动发现机制生成。自动发现可以基于NVIDIA的Unified Fabric Manager、节点标签等数据源维护拓扑信息;在没有自动发现能力的环境中,也可以先通过手动方式描述拓扑。
在本文实践环境中,GPU 节点大致分为两组:
gpu-worker-1 ~ gpu-worker-64 属于 leafpair12
gpu-worker-65 ~ gpu-worker-128 属于 leafpair34
这两个 leaf pair 又属于同一个上层 spine pair:
spinepair12
├── leafpair12
└── leafpair34
为了让 Volcano 能够识别这层网络关系,首先需要把真实网络拓扑映射成 Kubernetes Node label。本文使用两个标签来表达两层拓扑:
topology.aicloud/spine-pair 表示节点所属的上层 spine pair
topology.aicloud/leaf-pair 表示节点所属的 leaf pair
而后我们需要给节点打上标签
leafpair12 下的节点标记为:
for i in $(seq 1 64); do
kubectl label node gpu-worker-${i}
topology.aicloud/spine-pair=spinepair12
topology.aicloud/leaf-pair=leafpair12
--overwrite
done
leafpair34 下的节点标记为:
for i in $(seq 65 128); do
kubectl label node gpu-worker-${i}
topology.aicloud/spine-pair=spinepair12
topology.aicloud/leaf-pair=leafpair34
--overwrite
done
需要注意的是,这里的 label 不是 Kubernetes 自己推导出来的,而是平台管理员根据真实交换机连接关系、网络拓扑信息打上去的。Volcano 后续可以根据这些 label 生成 HyperNode。
节点标签只是原始拓扑信息,Volcano Scheduler 真正使用的是 HyperNode 资源。HyperNode 可以理解为 Volcano 用来描述网络性能域的CRD。一个 HyperNode 可以表示一个 leaf pair,也可以表示一个更上层的 spine pair。多个 HyperNode 可以组成层级结构,从而表达更复杂的数据中心网络拓扑。为了让 Volcano 根据 Node label 自动生成 HyperNode,需要修改 volcano-controller-configmap,启用 networkTopologyDiscovery:
networkTopologyDiscovery:
- source: label
enabled: true
interval: 1m
config:
networkTopologyTypes:
topologyA2:
- nodeLabel: "topology.aicloud/spine-pair" # 第 1 层
- nodeLabel: "topology.aicloud/leaf-pair" # 第 2 层
- nodeLabel: "kubernetes.io/hostname" # 第 3 层
也就是说,Volcano Controller 会根据这些 label 自动发现拓扑关系,并生成对应的 HyperNode。可以通过如下命令查看
kubectl get hypernodes.topology.volcano.sh -o wide
NAME TIER TIERNAME NODECOUNT AGE
hypernode-topologya2-tier1-nm7gj 1 topology.aicloud/leaf-pair 48 65m
hypernode-topologya2-tier1-rssfc 1 topology.aicloud/leaf-pair 63 65m
hypernode-topologya2-tier2-6b65b 2 topology.aicloud/spine-pair 2 65m
这说明 Volcano 已经根据节点标签自动生成了两层 HyperNode:
Tier 1:topology.aicloud/leaf-pair
- leafpair12
- leafpair34
Tier 2:topology.aicloud/spine-pair
- spinepair12
这里需要特别注意 Tier 的含义。在 Volcano 的拓扑模型中,Tier 表示 HyperNode 所处的网络层级。Tier 数字越小,表示越靠近计算节点,覆盖的节点范围越小,组内节点之间通常网络距离更近、通信效率更高;Tier 数字越大,表示越远离计算节点,但覆盖范围更大,组内节点之间需要经过更上层的交换路径。结合本文环境可以这样理解:
Tier 1 = leaf-pair
表示同一组 leaf 交换机下面的节点集合。
这个范围更小,节点之间网络距离更近,更适合放置通信密集型任务。
Tier 2 = spine-pair
表示更上层的 spine 网络域。
它覆盖多个 leaf-pair,范围更大,节点之间需要跨越上层spine层通信。
因此,leafpair12 和 leafpair34 分别是两个更靠近计算节点的网络拓扑域;spinepair12 则是位于上层、包含这两个 leaf-pair 的网络拓扑域。
另外,spinepair12 对应的 HyperNode 显示 NODECOUNT=2,并不是表示它下面只有 2 台服务器,而是表示它下面包含 2 个子 HyperNode,也就是 leafpair12 和 leafpair34。这是因为在 HyperNode 层级结构中,上层 HyperNode 的成员可以不是物理 Node,而是下一层的 HyperNode。其实这描述了一个最简单的两层的spine-leaf网络拓扑,
最后,还需要在 PodGroup 中声明网络拓扑策略。当提交 PodGroup 时,可以通过 networkTopology 字段告诉 Volcano:这一组 Pod 在调度时是否需要考虑网络拓扑。
例如,对于一个 LWS 多机推理任务,如果希望 Leader 和 Worker 优先落在同一个 leaf-pair 内,但在资源不足时允许适当放宽拓扑要求,可以使用 soft 模式:
apiVersion: scheduling.volcano.sh/v1beta1
kind: PodGroup
metadata:
name: qwen2-72b-unified-pg
namespace: a01
spec:
minMember: 2
queue: tenanta
networkTopology:
mode: soft
这里的 mode: soft 表示软约束。调度器会把网络拓扑作为一种调度偏好,尽量把同一个 PodGroup 里的多个 Pod 放到更近的 HyperNode 范围内。例如在本文环境中,就是优先让 Leader 和 Worker 落在同一个 leaf-pair 内。
但 soft 不是硬性限制。当同一个 leaf-pair 内资源不足时,调度器可以放宽拓扑要求,优先保证任务能够运行。因此,soft 更适合这类场景:业务希望多机任务尽量靠近运行,但不希望因为同一个 leaf-pair 资源不足就一直 Pending。
如果业务有更严格的网络边界要求,则应该使用 hard 模式。例如:
networkTopology:
mode: hard
highestTierAllowed: 1
在本文环境中,Tier 1 对应 leaf-pair。因此,hard + highestTierAllowed: 1 表示同一个 PodGroup 里的多个 Pod 必须被限制在同一个 leaf-pair 内。如果所有 leaf-pair 都无法同时满足资源需求,任务会保持 Pending,而不会跨 leaf-pair 调度。
如果配置为:
networkTopology:
mode: hard
highestTierAllowed: 2
则表示硬约束边界被放宽到 Tier 2。在本文环境中,Tier 2 对应 spine-pair。这意味着任务可以跨 leafpair12 和 leafpair34,只要它们仍然属于同一个 spinepair12。需要注意的是,highestTierAllowed: 2 并不是“先尝试同 leaf,资源不足再跨 leaf”的逐级回退策略,而是从一开始就允许任务落在同一个 Tier 2 HyperNode 覆盖范围内。
网络拓扑感知调度的价值在于:它把“节点之间的网络路径”也纳入调度决策。对于多机训练和多机推理来说,这一点非常有用。因为多个 Pod 拿到 GPU 只是第一步,这些 GPU 所在节点之间的网络路径是否最短,最优,往往会直接影响 NCCL、Ray、模型分片通信以及整体推理或训练效率。
14. 小结
到这里,Volcano 在 AI 算力平台中的定位已经比较清晰了。Volcano 是 Kubernetes 之上的调度增强层,负责回答一个更底层、也更现实的问题:当越来越多训练、推理、微调、实验和多机任务同时进入 GPU 集群时,有限的算力资源应该按照什么规则被分配、等待、抢占、回收和复用。
默认 Kubernetes 调度器主要关注单个 Pod 应该放到哪个节点上,而 Volcano 更关注作业、队列和资源秩序。它通过 Queue 把不同租户、不同业务或不同任务类型纳入队列体系;通过 Priority 和 Preemption 表达任务优先级,让关键业务在资源紧张时具备更高的调度优先级;通过 Reclaim 让资源可以在队列之间临时借用,并在需要时被回收;通过 PodGroup 和 Gang Scheduling 让分布式作业作为一个整体参与调度,避免只启动一部分 Pod、另一部分长期 Pending,造成 GPU 被占用却无法产生有效计算。
在 GPU 资源利用率方面,Volcano 的 Binpack 能力同样非常关键。Gang Scheduling 解决的是“一组任务能不能一起启动”,但它并不自动解决“这些任务应该放到哪些节点上才更合理”。如果任务被随机打散,就容易形成大量半空闲节点,最终出现“集群里还有 GPU,但无法满足大规格任务”的资源碎片问题。GPU 碎片化会直接影响集群利用率和任务可调度性,而通过 bin-packing 优化放置策略,可以在保持 gang 调度语义的同时,提高 GPU 占用率和资源完整性。因此,在 AI 算力平台中,Binpack 不应该只被理解为普通的节点打分插件,而应该被看作 GPU 资源治理的一部分。
除了资源是否足够,Volcano 还可以进一步关注资源之间的“距离”。在多机训练和多机推理场景中,多个 Pod 拿到 GPU 只是第一步,这些 GPU 所在节点之间的网络拓扑同样重要。本文通过 Node label 描述 leaf / spine 拓扑关系,启用 Volcano Controller 的 label 自动发现能力生成 HyperNode,并在 Volcano Scheduler 中加入 network-topology-aware 插件,让调度器能够基于 HyperNode 感知网络性能域。对于依赖 RoCE、InfiniBand、NCCL、Ray 或多机模型分片的任务来说,把同一个作业尽量限制在更近的 leaf pair 内,可以减少跨交换机通信带来的延迟和带宽损耗。
Volcano 在云原生 AI 基础设施中的核心价值在于它让 GPU 不再只是被动地等待 Pod 申请,而是成为可以被队列化、优先级化、成组化、拓扑感知化和碎片治理的集群资源。对于一个面向多租户、多模型、多任务形态的 AI 算力平台来说,这种调度秩序,正是从“能运行”走向“高效运行”的关键一步。
