Gang 调度与 RayJob 和 Kueue#
本指南演示了如何使用 Kueue 进行 RayJob 资源的 gang 调度,并利用 Kubernetes 上的动态资源预配和排队。为了说明这些概念,本指南使用了 使用 Ray Data 微调 PyTorch Lightning 文本分类器 示例。
Gang 调度#
Kubernetes 中的 gang 调度可确保一组相关的 Pod(例如 Ray 集群中的 Pod)仅在所有必需资源都可用时才启动。当处理 GPU 等昂贵且有限的资源时,满足此要求至关重要。
Kueue#
Kueue 是一个 Kubernetes 原生系统,用于管理配额及其消费方式。Kueue 决定何时
让作业等待。
允许作业启动,这会触发 Kubernetes 创建 Pod。
抢占作业,这会触发 Kubernetes 删除活动 Pod。
Kueue 对某些 KubeRay API 具有原生支持。具体来说,您可以使用 Kueue 来管理 RayJob、RayCluster 和 RayService 消耗的资源。请参阅 Kueue 文档 了解更多信息。
为何使用 gang 调度#
当处理 GPU 等昂贵且有限的硬件加速器时,gang 调度至关重要。它可以防止 RayJobs 部分预配 Ray 集群并声称但未使用 GPU。Kueue 会暂停 RayJob,直到 Kubernetes 集群和底层云提供商能够保证 RayJob 执行所需的容量。这种方法极大地提高了 GPU 利用率和成本,尤其是在 GPU 可用性有限的情况下。
在 GKE 中创建 Kubernetes 集群#
使用 enable-autoscaling 选项创建 GKE 集群
gcloud container clusters create kuberay-gpu-cluster \
--num-nodes=1 --min-nodes 0 --max-nodes 1 --enable-autoscaling \
--zone=us-east4-c --machine-type e2-standard-4
创建启用了 enable-queued-provisioning 选项的 GPU 节点池
gcloud container node-pools create gpu-node-pool \
--accelerator type=nvidia-l4,count=1,gpu-driver-version=latest \
--enable-queued-provisioning \
--reservation-affinity=none \
--zone us-east4-c \
--cluster kuberay-gpu-cluster \
--num-nodes 0 \
--min-nodes 0 \
--max-nodes 10 \
--enable-autoscaling \
--machine-type g2-standard-4
此命令创建一个节点池,该节点池最初有零个节点。--enable-queued-provisioning 标志使用 ProvisioningRequest API 在 Kubernetes 节点自动缩放器中启用了“排队预配”。下面有更多详细信息。您需要使用 --reservation-affinity=none 标志,因为 GKE 不支持与 ProvisioningRequest 节点预留。
安装 KubeRay 运算符#
请按照 部署 KubeRay Operator 从 Helm 仓库安装最新的稳定版 KubeRay Operator。如果您正确设置了 GPU 节点池的污点,KubeRay Operator Pod 必须位于 CPU 节点上。
安装 Kueue#
安装最新发布的 Kueue 版本。
kubectl apply --server-side -f https://github.com/kubernetes-sigs/kueue/releases/download/v0.13.4/manifests.yaml
有关安装 Kueue 的更多详细信息,请参阅 Kueue 安装。
配置 Kueue 进行 gang 调度#
接下来,配置 Kueue 进行 gang 调度。Kueue 利用 ProvisioningRequest API 来执行两项关键任务:
当作业需要更多资源时,动态地向集群添加新节点。
阻止 admission 需要足够资源的作业。
有关更多详细信息,请参阅 ProvisioningRequest 的工作原理。
创建 Kueue 资源#
此清单创建了以下资源:
ClusterQueue:定义配额和公平共享规则。
LocalQueue:一个属于租户的命名空间队列,引用 ClusterQueue。
ResourceFlavor:定义集群中可用的资源,通常来自节点。
AdmissionCheck:一种允许组件影响工作负载 admission 时机的机制。
# kueue-resources.yaml
apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
name: "default-flavor"
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: AdmissionCheck
metadata:
name: rayjob-gpu
spec:
controllerName: kueue.x-k8s.io/provisioning-request
parameters:
apiGroup: kueue.x-k8s.io
kind: ProvisioningRequestConfig
name: rayjob-gpu-config
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ProvisioningRequestConfig
metadata:
name: rayjob-gpu-config
spec:
provisioningClassName: queued-provisioning.gke.io
managedResources:
- nvidia.com/gpu
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
name: "cluster-queue"
spec:
namespaceSelector: {} # match all
resourceGroups:
- coveredResources: ["cpu", "memory", "nvidia.com/gpu"]
flavors:
- name: "default-flavor"
resources:
- name: "cpu"
nominalQuota: 10000 # infinite quotas
- name: "memory"
nominalQuota: 10000Gi # infinite quotas
- name: "nvidia.com/gpu"
nominalQuota: 10000 # infinite quotas
admissionChecks:
- rayjob-gpu
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
namespace: "default"
name: "user-queue"
spec:
clusterQueue: "cluster-queue"
创建 Kueue 资源
kubectl apply -f kueue-resources.yaml
注意
此示例配置 Kueue 来编排 GPU 的 gang 调度。但是,您也可以使用 CPU 和内存等其他资源。
部署 RayJob#
下载 RayJob,它执行 微调 PyTorch Lightning 文本分类器 中记录的所有步骤。源代码 也位于 KubeRay 存储库中。
curl -LO https://raw.githubusercontent.com/ray-project/kuberay/master/ray-operator/config/samples/pytorch-text-classifier/ray-job.pytorch-distributed-training.yaml
在创建 RayJob 之前,修改 RayJob 元数据,添加一个标签,将 RayJob 分配给您之前创建的 LocalQueue。
metadata:
generateName: pytorch-text-classifier-
labels:
kueue.x-k8s.io/queue-name: user-queue
部署 RayJob
$ kubectl create -f ray-job.pytorch-distributed-training.yaml
rayjob.ray.io/dev-pytorch-text-classifier-r6d4p created
RayJob 的 Gang 调度#
当您将需要 GPU 的 RayJob 部署到最初没有 GPU 的集群时,预期的行为如下:
Kueue 由于集群中 GPU 资源不足而暂停 RayJob。
Kueue 创建一个 ProvisioningRequest,指定 RayJob 的 GPU 要求。
Kubernetes 节点自动缩放器会监视 ProvisioningRequests 并根据需要添加具有 GPU 的节点。
一旦所需的 GPU 节点可用,ProvisioningRequest 就会得到满足。
Kueue 允许 RayJob,从而允许 Kubernetes 在新预配的节点上调度 Ray 节点,RayJob 执行开始。
如果 GPU 不可用,Kueue 会继续暂停 RayJob。此外,节点自动缩放器会避免预配新节点,直到能够完全满足 RayJob 的 GPU 要求为止。
创建 RayJob 后,请注意,即使 ClusterQueue 具有可用的 GPU 配额,RayJob 状态也会立即显示为 suspended。
$ kubectl get rayjob pytorch-text-classifier-rj4sg -o yaml
apiVersion: ray.io/v1
kind: RayJob
metadata:
name: pytorch-text-classifier-rj4sg
labels:
kueue.x-k8s.io/queue-name: user-queue
...
...
...
status:
jobDeploymentStatus: Suspended # RayJob suspended
jobId: pytorch-text-classifier-rj4sg-pj9hx
jobStatus: PENDING
Kueue 会一直暂停此 RayJob,直到其对应的 ProvisioningRequest 得到满足为止。使用此命令列出 ProvisioningRequest 资源及其状态:
$ kubectl get provisioningrequest
NAME ACCEPTED PROVISIONED FAILED AGE
rayjob-pytorch-text-classifier-nv77q-e95ec-rayjob-gpu-1 True False False 22s
请注意输出中的两列:ACCEPTED 和 PROVISIONED。ACCEPTED=True 表示 Kueue 和 Kubernetes 节点自动缩放器已确认请求。PROVISIONED=True 表示 Kubernetes 节点自动缩放器已完成节点预配。一旦这两个条件都为真,ProvisioningRequest 就得到满足。
$ kubectl get provisioningrequest
NAME ACCEPTED PROVISIONED FAILED AGE
rayjob-pytorch-text-classifier-nv77q-e95ec-rayjob-gpu-1 True True False 57s
由于示例 RayJob 需要 1 个 GPU 进行微调,因此通过在 gpu-node-pool 节点池中添加单个 GPU 节点来满足 ProvisioningRequest。
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
gke-kuberay-gpu-cluster-default-pool-8d883840-fd6d Ready <none> 14m v1.29.0-gke.1381000
gke-kuberay-gpu-cluster-gpu-node-pool-b176212e-g3db Ready <none> 46s v1.29.0-gke.1381000 # new node with GPUs
一旦 ProvisioningRequest 得到满足,Kueue 就会允许 RayJob。然后 Kubernetes 调度器会立即将 head 和 worker 节点放置在新预配的资源上。ProvisioningRequest 可确保无缝的 Ray 集群启动,而不会出现任何 Pod 的调度延迟。
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
pytorch-text-classifier-nv77q-g6z57 1/1 Running 0 13s
torch-text-classifier-nv77q-raycluster-gstrk-head-phnfl 1/1 Running 0 6m43s