使用 KAI Scheduler 为 RayClusters 进行抢占式调度、队列优先级和 GPU 共享#
本指南演示了如何使用 KAI Scheduler 为 RayClusters 设置具有配额、抢占式调度和 GPU 共享的分层队列。
KAI Scheduler#
KAI Scheduler 是一个高性能、可扩展的 Kubernetes 调度器,专为 AI/ML 工作负载而构建。KAI 旨在大规模协调 GPU 集群,优化 GPU 分配,并支持完整的 AI 生命周期——从交互式开发到大规模分布式训练和推理。一些主要功能包括:
装箱和分散调度:通过使用装箱最小化碎片来优化节点使用,或通过使用分散调度来提高弹性和负载均衡。
GPU 共享:允许 KAI 将来自不同团队的多个 Ray 工作负载合并到同一 GPU 上,使您的组织能够将更多工作负载部署到现有硬件上,并减少 GPU 空闲时间。
工作负载自动伸缩:在 min/max 范围内缩放 Ray 副本或工作程序,同时尊重抢占式约束。
集群自动伸缩:兼容动态云基础设施(包括像 Karpenter 这样的自动伸缩器)。
工作负载优先级:在队列中有效地区分 Ray 工作负载的优先级。
分层队列和公平性:具有配额、超额权重、限制的两级队列,并使用 DRF 等实现队列之间的公平资源分配。有关更多详细信息和关键功能,请参阅文档。
核心组件#
PodGroups:PodGroups 是用于调度的原子单元,代表一个或多个相互依赖的 Pod,调度器将它们作为一个单元执行,也称为抢占式调度。它们对于分布式工作负载至关重要。KAI Scheduler 包括一个 PodGrouper,可自动处理抢占式调度。
PodGrouper 的工作原理
RayCluster "distributed-training":
├── Head Pod: 1 GPU
└── Worker Group: 4 × 0.5 GPU = 2 GPUs
Total Group Requirement: 3 GPUs
PodGrouper schedules all 5 pods (1 head + 4 workers) together or none at all.
队列:队列通过以下方式强制执行资源分配的公平性:
配额:为队列保证的基线资源量。调度器首先分配配额以确保公平性。
队列优先级:确定队列在获得超出其配额的资源时的顺序。调度器首先为高优先级队列服务。
超额权重:控制调度器如何在同一优先级级别内的队列之间分配剩余资源。具有较高权重的队列将获得更大比例的额外资源。
限制:定义队列可以消耗的最大资源量。
您可以为具有多个团队的组织(例如,拥有多个团队的部门)安排分层队列。
先决条件#
具有 GPU 节点的 Kubernetes 集群
NVIDIA GPU Operator
已配置 kubectl 以访问您的集群
安装启用了 GPU 共享的 KAI Scheduler。从 KAI Scheduler releases 中选择所需的版本,并替换以下命令中的
<KAI_SCHEDULER_VERSION>。建议选择 v0.10.0 或更高版本。
# Install KAI Scheduler
helm upgrade -i kai-scheduler oci://ghcr.io/nvidia/kai-scheduler/kai-scheduler -n kai-scheduler --create-namespace --version <KAI_SCHEDULER_VERSION> --set "global.gpuSharing=true"
步骤 1:安装 KubeRay Operator 并将 KAI Scheduler 设置为批处理调度器#
遵循 KubeRay Operator 的官方安装文档,并添加以下配置以启用 KAI Scheduler 集成。
--set batchScheduler.name=kai-scheduler
步骤 2:创建 KAI Scheduler 队列#
为 department-1 及其子团队 team-a 创建基本的队列结构。出于演示目的,此示例不强制执行任何配额、超额权重或限制。您可以根据需要配置这些参数。
apiVersion: scheduling.run.ai/v2
kind: Queue
metadata:
name: department-1
spec:
#priority: 100 (optional)
resources:
cpu:
quota: -1
limit: -1
overQuotaWeight: 1
gpu:
quota: -1
limit: -1
overQuotaWeight: 1
memory:
quota: -1
limit: -1
overQuotaWeight: 1
---
apiVersion: scheduling.run.ai/v2
kind: Queue
metadata:
name: team-a
spec:
#priority: 200 (optional)
parentQueue: department-1
resources:
cpu:
quota: -1
limit: -1
overQuotaWeight: 1
gpu:
quota: -1
limit: -1
overQuotaWeight: 1
memory:
quota: -1
limit: -1
overQuotaWeight: 1
注意:为使此演示更易于理解,我们在下一步的 RayCluster 示例中组合了这些队列定义。您可以使用单个合并的 YAML 文件,一次性应用队列和工作负载。
步骤 3:使用 KAI Scheduler 进行抢占式调度#
关键模式是将队列标签添加到您的 RayCluster。KubeRay 存储库中的这是一个基本示例。
metadata:
name: raycluster-sample
labels:
kai.scheduler/queue: team-a # This is the essential configuration.
应用此带有队列的 RayCluster。
curl -LO https://raw.githubusercontent.com/ray-project/kuberay/master/ray-operator/config/samples/ray-cluster.kai-scheduler.yaml
kubectl apply -f ray-cluster.kai-scheduler.yaml
#Verify queues are created
kubectl get queues
# NAME PRIORITY PARENT CHILDREN DISPLAYNAME
# department-1 ["team-a"]
# team-a department-1
# Watch the pods get scheduled
kubectl get pods -w
# NAME READY STATUS RESTARTS AGE
# kuberay-operator-7d86f4f46b-dq22x 1/1 Running 0 50s
# raycluster-sample-head-rvrkz 0/1 ContainerCreating 0 13s
# raycluster-sample-worker-worker-mlvtz 0/1 Init:0/1 0 13s
# raycluster-sample-worker-worker-rcb54 0/1 Init:0/1 0 13s
# raycluster-sample-worker-worker-mlvtz 0/1 Init:0/1 0 40s
# raycluster-sample-worker-worker-rcb54 0/1 Init:0/1 0 41s
# raycluster-sample-head-rvrkz 0/1 Running 0 42s
# raycluster-sample-head-rvrkz 1/1 Running 0 54s
# raycluster-sample-worker-worker-rcb54 0/1 PodInitializing 0 59s
# raycluster-sample-worker-worker-mlvtz 0/1 PodInitializing 0 59s
# raycluster-sample-worker-worker-rcb54 0/1 Running 0 60s
# raycluster-sample-worker-worker-mlvtz 0/1 Running 0 60s
# raycluster-sample-worker-worker-rcb54 1/1 Running 0 71s
# raycluster-sample-worker-worker-mlvtz 1/1 Running 0 71s
为工作负载设置优先级#
在 Kubernetes 中,为工作负载分配不同的优先级可确保高效的资源管理,最大限度地减少服务中断,并支持更好的伸缩。通过为工作负载设置优先级,KAI Scheduler 会根据分配的优先级来调度作业。当没有足够的资源供工作负载使用时,调度器可以抢占低优先级的工作负载,为高优先级的工作负载释放资源。此方法可确保调度器在资源分配中始终优先处理任务关键型服务。
KAI Scheduler 部署带有多项预定义优先级类别
train (50) - 用于可抢占的训练工作负载
build-preemptible (75) - 用于可抢占的构建/交互式工作负载
build (100) - 用于构建/交互式工作负载(不可抢占)
inference (125) - 用于推理工作负载(不可抢占)
您可以提交具有特定优先级的前缀的相同工作负载。将前面的示例修改为 build 类工作负载。
labels:
kai.scheduler/queue: team-a # This is the essential configuration.
priorityClassName: build # Here you can specify the priority class in metadata.labels (optional)
有关更多信息,请参阅文档。
步骤 4:提交具有 GPU 共享的 Ray 工作程序#
此示例创建了两个工作程序,它们在 RayCluster 中共享一个 GPU,每个工作程序占用 0.5 的 GPU(通过时间切片)。请参阅YAML 文件。
curl -LO https://raw.githubusercontent.com/ray-project/kuberay/master/ray-operator/config/samples/ray-cluster.kai-gpu-sharing.yaml
kubectl apply -f ray-cluster.kai-gpu-sharing.yaml
# Watch the pods get scheduled
kubectl get pods -w
# NAME READY STATUS RESTARTS AGE
# kuberay-operator-7d86f4f46b-dq22x 1/1 Running 0 4m9s
# raycluster-half-gpu-head-9rtxf 0/1 Running 0 4s
# raycluster-half-gpu-shared-gpu-worker-5l7cn 0/1 Pending 0 4s
# raycluster-half-gpu-shared-gpu-worker-98tzh 0/1 Pending 0 4s
# ... (skip for brevity)
# raycluster-half-gpu-shared-gpu-worker-5l7cn 0/1 Init:0/1 0 6s
# raycluster-half-gpu-shared-gpu-worker-5l7cn 0/1 Init:0/1 0 7s
# raycluster-half-gpu-shared-gpu-worker-98tzh 0/1 Init:0/1 0 8s
# raycluster-half-gpu-head-9rtxf 1/1 Running 0 19s
# raycluster-half-gpu-shared-gpu-worker-5l7cn 0/1 PodInitializing 0 19s
# raycluster-half-gpu-shared-gpu-worker-98tzh 0/1 PodInitializing 0 19s
# raycluster-half-gpu-shared-gpu-worker-5l7cn 0/1 Running 0 20s
# raycluster-half-gpu-shared-gpu-worker-98tzh 0/1 Running 0 20s
# raycluster-half-gpu-shared-gpu-worker-5l7cn 1/1 Running 0 31s
# raycluster-half-gpu-shared-gpu-worker-98tzh 1/1 Running 0 31s
注意:此示例中的 GPU 共享(带时间切片)仅发生在 Kubernetes 层,允许多个 Pod 共享单个 GPU 设备。调度器不强制执行内存隔离,因此应用程序必须自行管理其使用情况以防止干扰。有关其他 GPU 共享方法(例如 MPS),请参阅 KAI 的文档。
验证 GPU 共享是否正常工作#
要确认 GPU 共享是否正常工作,请使用以下命令:
# 1. Check GPU fraction annotations and shared GPU groups
kubectl get pods -l ray.io/cluster=raycluster-half-gpu -o custom-columns="NAME:.metadata.name,NODE:.spec.nodeName,GPU-FRACTION:.metadata.annotations.gpu-fraction,GPU-GROUP:.metadata.labels.runai-gpu-group"
您应该在同一个节点上看到两个工作程序 Pod,其 GPU-FRACTION: 0.5 和相同的 GPU-GROUP ID。
NAME NODE GPU-FRACTION GPU-GROUP
raycluster-half-gpu-head ip-xxx-xx-xx-xxx <none> <none>
raycluster-half-gpu-shared-gpu-worker-67tvw ip-xxx-xx-xx-xxx 0.5 3e456911-a6ea-4b1a-8f55-e90fba89ad76
raycluster-half-gpu-shared-gpu-worker-v5tpp ip-xxx-xx-xx-xxx 0.5 3e456911-a6ea-4b1a-8f55-e90fba89ad76
这表明两个工作程序具有相同的 NVIDIA_VISIBLE_DEVICES(相同的物理 GPU)和 GPU-FRACTION: 0.50。
故障排除#
检查是否缺少队列标签#
如果 Pod 仍然处于 Pending 状态,最常见的问题是缺少队列标签。
检查 Operator 日志中是否有 KAI Scheduler 错误,并查找类似以下的错误消息:
"Queue label missing from RayCluster; pods will remain pending"
解决方案:确保您的 RayCluster 具有集群中存在的队列标签。
metadata:
labels:
kai.scheduler/queue: default # Add this label