你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn。
本文介绍了如何使用 KubeRay 在 Azure Kubernetes 服务 (AKS) 上配置和部署 Ray 群集, 还介绍了如何使用 Ray 群集训练简单的机器学习模型,并在 Ray 仪表板上显示结果。
本文提供了两种在 AKS 上部署 Ray 群集的方法:
先决条件
- 请查看 AKS 上的 Ray 群集概述以了解组件和部署过程。
- Azure 订阅。 如果没有 Azure 订阅,可在此处创建一个免费帐户。
- 已在本地计算机上安装 Azure CLI。 你可以按照如何安装 Azure CLI中的说明进行安装。
- 已安装 Azure Kubernetes 服务预览版扩展。
- 已安装 Helm。
- 已安装 Terraform 客户端工具或 OpenTofu。 本文中使用的是 Terrafrom,但使用的模块应与 OpenTofu 兼容。
以非交互方式部署 Ray 示例
如果要以非交互方式部署完整的 Ray 示例,可以使用 GitHub 存储库中的 deploy.sh
脚本 (https://github.com/Azure-Samples/aks-ray-sample)。 此脚本将完成 Ray 部署过程部分中所述的步骤。
使用以下命令在本地克隆 GitHub 存储库,但要更改为存储库的根目录:
git clone https://github.com/Azure-Samples/aks-ray-sample cd aks-ray-sample
使用以下命令部署完整的示例:
chmod +x deploy.sh ./deploy.sh
部署完成后,请在 Azure 门户中查看日志输出和资源组,以查看创建的基础结构。
手动部署 Ray 示例
Fashion MNIST 是 Zalando 的文章图像的数据集,其中包含一个由 60,000 个示例组成的训练集和一个由 10,000 个示例组成的测试集。 每个示例都是一个 28x28 的灰度图像,并与十个类中的某个标签关联。 在本指南中,你将使用 Ray 群集来基于此数据集训练一个简单的 PyTorch 模型。
部署 RayJob 规范
若要训练模型,需要向在专用 AKS 群集上运行的 KubeRay 操作器提交 RayJob 规范。 RayJob 规范是一个 YAML 文件,用于描述运行相应作业所需的资源,包括 Docker 映像、要运行的命令以及要使用的工作器数。
查看 RayJob 说明时,可能需要修改一些字段以匹配自己的环境:
rayClusterSpec
中workerGroupSpecs
部分下的replicas
字段指定 KubeRay 计划到 Kubernetes 群集的工作器 Pod 数。 每个工作器 Pod 需要 3 个 CPU 以及 4 GB 内存。 每个头 Pod 需要 1 个 CPU 以及 4 GB 内存。 如果将replicas
字段设置为 2,则需要在用于为相应作业实现 RayCluster 的节点池中具有 8 个 vCPU。spec
中runtimeEnvYAML
下的NUM_WORKERS
字段指定要启动的 Ray 执行器的数量。 每个 Ray 执行器都必须由 Kubernetes 群集中的工作器 Pod 提供服务,因此此字段必须小于或等于replicas
字段。 在此示例中,我们将NUM_WORKERS
设置为 2,这与replicas
字段匹配。CPUS_PER_WORKER
字段必须设置为小于或等于由分配给每个工作器 Pod 的 CPU 数减 1 得到的值。 在此示例中,每个工作器 Pod 的 CPU 资源请求为 3,因此应将CPUS_PER_WORKER
设置为 2。
总之,节点池中总共需要有 8 个 vCPU 才能运行 PyTorch 模型训练作业。 由于我们在系统节点池中添加了一个污点,因此无法为其计划任何用户 Pod,我们必须创建一个具有至少 8 个 vCPU 的新节点池来托管 Ray 群集。
使用以下命令下载 RayJob 规范文件:
curl -LO https://raw.githubusercontent.com/ray-project/kuberay/master/ray-operator/config/samples/pytorch-mnist/ray-job.pytorch-mnist.yaml
对 RayJob 规范文件进行任何必要的修改。
使用
kubectl apply
命令启动 PyTorch 模型训练作业。kubectl apply -n kuberay -f ray-job.pytorch-mnist.yaml
验证 RayJob 部署
使用
kubectl get pods
命令验证命名空间中是否具有两个工作器 Pod 和一个头 Pod 在运行。kubectl get pods -n kuberay
输出应类似于以下示例输出:
NAME READY STATUS RESTARTS AGE kuberay-operator-7d7998bcdb-9h8hx 1/1 Running 0 3d2h pytorch-mnist-raycluster-s7xd9-worker-small-group-knpgl 1/1 Running 0 6m15s pytorch-mnist-raycluster-s7xd9-worker-small-group-p74cm 1/1 Running 0 6m15s rayjob-pytorch-mnist-fc959 1/1 Running 0 5m35s rayjob-pytorch-mnist-raycluster-s7xd9-head-l24hn 1/1 Running 0 6m15s
使用
kubectl get
命令检查 RayJob 的状态。kubectl get rayjob -n kuberay
输出应类似于以下示例输出:
NAME JOB STATUS DEPLOYMENT STATUS START TIME END TIME AGE rayjob-pytorch-mnist RUNNING Running 2024-11-22T03:08:22Z 9m36s
等待 RayJob 完成。 这可能需要几分钟的时间。 当
JOB STATUS
变为SUCCEEDED
后,可以检查训练日志。 为此,可以首先使用kubectl get pods
命令获取运行 RayJob 的 Pod 的名称。kubectl get pods -n kuberay
在输出中,应该会有一个名称以
rayjob-pytorch-mnist
开头的 Pod,类似于以下示例输出:NAME READY STATUS RESTARTS AGE kuberay-operator-7d7998bcdb-9h8hx 1/1 Running 0 3d2h pytorch-mnist-raycluster-s7xd9-worker-small-group-knpgl 1/1 Running 0 14m pytorch-mnist-raycluster-s7xd9-worker-small-group-p74cm 1/1 Running 0 14m rayjob-pytorch-mnist-fc959 0/1 Completed 0 13m rayjob-pytorch-mnist-raycluster-s7xd9-head-l24hn 1/1 Running 0 14m
使用
kubectl logs
命令查看 RayJob 的日志。 请确保将rayjob-pytorch-mnist-fc959
替换为运行 RayJob 的 Pod 的名称。kubectl logs -n kuberay rayjob-pytorch-mnist-fc959
在输出中,应该会显示 PyTorch 模型的训练日志,类似于以下示例输出:
2024-11-21 19:09:04,986 INFO cli.py:39 -- Job submission server address: http://rayjob-pytorch-mnist-raycluster-s7xd9-head-svc.kuberay.svc.cluster.local:8265 2024-11-21 19:09:05,712 SUCC cli.py:63 -- ------------------------------------------------------- 2024-11-21 19:09:05,713 SUCC cli.py:64 -- Job 'rayjob-pytorch-mnist-hndpx' submitted successfully 2024-11-21 19:09:05,713 SUCC cli.py:65 -- ------------------------------------------------------- 2024-11-21 19:09:05,713 INFO cli.py:289 -- Next steps 2024-11-21 19:09:05,713 INFO cli.py:290 -- Query the logs of the job: 2024-11-21 19:09:05,713 INFO cli.py:292 -- ray job logs rayjob-pytorch-mnist-hndpx 2024-11-21 19:09:05,713 INFO cli.py:294 -- Query the status of the job: ... View detailed results here: /home/ray/ray_results/TorchTrainer_2024-11-21_19-11-23 To visualize your results with TensorBoard, run: `tensorboard --logdir /tmp/ray/session_2024-11-21_19-08-24_556164_1/artifacts/2024-11-21_19-11-24/TorchTrainer_2024-11-21_19-11-23/driver_artifacts` Training started with configuration: ╭─────────────────────────────────────────────────╮ │ Training config │ ├─────────────────────────────────────────────────┤ │ train_loop_config/batch_size_per_worker 16 │ │ train_loop_config/epochs 10 │ │ train_loop_config/lr 0.001 │ ╰─────────────────────────────────────────────────╯ (RayTrainWorker pid=1193, ip=10.244.4.193) Setting up process group for: env:// [rank=0, world_size=2] (TorchTrainer pid=1138, ip=10.244.4.193) Started distributed worker processes: (TorchTrainer pid=1138, ip=10.244.4.193) - (node_id=3ea81f12c0f73ebfbd5b46664e29ced00266e69355c699970e1d824b, ip=10.244.4.193, pid=1193) world_rank=0, local_rank=0, node_rank=0 (TorchTrainer pid=1138, ip=10.244.4.193) - (node_id=2b00ea2b369c9d27de9596ce329daad1d24626b149975cf23cd10ea3, ip=10.244.1.42, pid=1341) world_rank=1, local_rank=0, node_rank=1 (RayTrainWorker pid=1341, ip=10.244.1.42) Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz (RayTrainWorker pid=1193, ip=10.244.4.193) Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to /home/ray/data/FashionMNIST/raw/train-images-idx3-ubyte.gz (RayTrainWorker pid=1193, ip=10.244.4.193) 0%| | 0.00/26.4M [00:00<?, ?B/s] (RayTrainWorker pid=1193, ip=10.244.4.193) 0%| | 65.5k/26.4M [00:00<01:13, 356kB/s] (RayTrainWorker pid=1193, ip=10.244.4.193) 100%|██████████| 26.4M/26.4M [00:01<00:00, 18.9MB/s] (RayTrainWorker pid=1193, ip=10.244.4.193) Extracting /home/ray/data/FashionMNIST/raw/train-images-idx3-ubyte.gz to /home/ray/data/FashionMNIST/raw (RayTrainWorker pid=1341, ip=10.244.1.42) 100%|██████████| 26.4M/26.4M [00:01<00:00, 18.7MB/s] ... Training finished iteration 1 at 2024-11-21 19:15:46. Total running time: 4min 22s ╭───────────────────────────────╮ │ Training result │ ├───────────────────────────────┤ │ checkpoint_dir_name │ │ time_this_iter_s 144.9 │ │ time_total_s 144.9 │ │ training_iteration 1 │ │ accuracy 0.805 │ │ loss 0.52336 │ ╰───────────────────────────────╯ (RayTrainWorker pid=1193, ip=10.244.4.193) Test Epoch 0: 97%|█████████▋| 303/313 [00:01<00:00, 269.60it/s] Test Epoch 0: 100%|██████████| 313/313 [00:01<00:00, 267.14it/s] (RayTrainWorker pid=1193, ip=10.244.4.193) Train Epoch 1: 0%| | 0/1875 [00:00<?, ?it/s] (RayTrainWorker pid=1341, ip=10.244.1.42) Test Epoch 0: 100%|██████████| 313/313 [00:01<00:00, 270.44it/s] (RayTrainWorker pid=1341, ip=10.244.1.42) Train Epoch 0: 100%|█████████▉| 1866/1875 [00:24<00:00, 82.49it/s] [repeated 35x across cluster] (RayTrainWorker pid=1193, ip=10.244.4.193) Train Epoch 0: 100%|██████████| 1875/1875 [00:24<00:00, 77.99it/s] Train Epoch 0: 100%|██████████| 1875/1875 [00:24<00:00, 76.19it/s] (RayTrainWorker pid=1193, ip=10.244.4.193) Test Epoch 0: 0%| | 0/313 [00:00<?, ?it/s] (RayTrainWorker pid=1193, ip=10.244.4.193) Test Epoch 0: 88%|████████▊ | 275/313 [00:01<00:00, 265.39it/s] [repeated 19x across cluster] (RayTrainWorker pid=1341, ip=10.244.1.42) Train Epoch 1: 19%|█▉ | 354/1875 [00:04<00:18, 82.66it/s] [repeated 80x across cluster] (RayTrainWorker pid=1341, ip=10.244.1.42) Train Epoch 1: 0%| | 0/1875 [00:00<?, ?it/s] (RayTrainWorker pid=1341, ip=10.244.1.42) Train Epoch 1: 40%|████ | 757/1875 [00:09<00:13, 83.01it/s] [repeated 90x across cluster] (RayTrainWorker pid=1341, ip=10.244.1.42) Train Epoch 1: 62%|██████▏ | 1164/1875 [00:14<00:08, 83.39it/s] [repeated 92x across cluster] (RayTrainWorker pid=1341, ip=10.244.1.42) Train Epoch 1: 82%|████████▏ | 1533/1875 [00:19<00:05, 68.09it/s] [repeated 91x across cluster] (RayTrainWorker pid=1341, ip=10.244.1.42) Train Epoch 1: 91%|█████████▏| 1713/1875 [00:22<00:02, 70.20it/s] (RayTrainWorker pid=1193, ip=10.244.4.193) Train Epoch 1: 91%|█████████ | 1707/1875 [00:22<00:02, 70.04it/s] [repeated 47x across cluster] (RayTrainWorker pid=1341, ip=10.244.1.42) Test Epoch 1: 0%| | 0/313 [00:00<?, ?it/s] (RayTrainWorker pid=1341, ip=10.244.1.42) Test Epoch 1: 8%|▊ | 24/313 [00:00<00:01, 237.98it/s] (RayTrainWorker pid=1193, ip=10.244.4.193) Test Epoch 1: 96%|█████████▋| 302/313 [00:01<00:00, 250.76it/s] Test Epoch 1: 100%|██████████| 313/313 [00:01<00:00, 262.94it/s] (RayTrainWorker pid=1193, ip=10.244.4.193) Train Epoch 2: 0%| | 0/1875 [00:00<?, ?it/s] (RayTrainWorker pid=1341, ip=10.244.1.42) Test Epoch 1: 92%|█████████▏| 289/313 [00:01<00:00, 222.57it/s] Training finished iteration 2 at 2024-11-21 19:16:12. Total running time: 4min 48s ╭───────────────────────────────╮ │ Training result │ ├───────────────────────────────┤ │ checkpoint_dir_name │ │ time_this_iter_s 25.975 │ │ time_total_s 170.875 │ │ training_iteration 2 │ │ accuracy 0.828 │ │ loss 0.45946 │ ╰───────────────────────────────╯ (RayTrainWorker pid=1341, ip=10.244.1.42) Test Epoch 1: 100%|██████████| 313/313 [00:01<00:00, 226.04it/s] (RayTrainWorker pid=1193, ip=10.244.4.193) Train Epoch 1: 100%|██████████| 1875/1875 [00:24<00:00, 76.24it/s] [repeated 45x across cluster] (RayTrainWorker pid=1341, ip=10.244.1.42) Train Epoch 2: 13%|█▎ | 239/1875 [00:03<00:24, 67.30it/s] [repeated 64x across cluster] (RayTrainWorker pid=1193, ip=10.244.4.193) Test Epoch 1: 0%| | 0/313 [00:00<?, ?it/s] (RayTrainWorker pid=1341, ip=10.244.1.42) Test Epoch 1: 85%|████████▍ | 266/313 [00:01<00:00, 222.54it/s] [repeated 20x across cluster] (RayTrainWorker pid=1341, ip=10.244.1.42) .. Training completed after 10 iterations at 2024-11-21 19:19:47. Total running time: 8min 23s 2024-11-21 19:19:47,596 INFO tune.py:1009 -- Wrote the latest version of all result files and experiment state to '/home/ray/ray_results/TorchTrainer_2024-11-21_19-11-23' in 0.0029s. Training result: Result( metrics={'loss': 0.35892221605786073, 'accuracy': 0.872}, path='/home/ray/ray_results/TorchTrainer_2024-11-21_19-11-23/TorchTrainer_74867_00000_0_2024-11-21_19-11-24', filesystem='local', checkpoint=None ) (RayTrainWorker pid=1341, ip=10.244.1.42) Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz [repeated 7x across cluster] (RayTrainWorker pid=1341, ip=10.244.1.42) Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to /home/ray/data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz [repeated 7x across cluster] (RayTrainWorker pid=1341, ip=10.244.1.42) Extracting /home/ray/data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to /home/ray/data/FashionMNIST/raw [repeated 7x across cluster] (RayTrainWorker pid=1341, ip=10.244.1.42) Train Epoch 9: 91%|█████████ | 1708/1875 [00:21<00:01, 83.84it/s] [repeated 23x across cluster] (RayTrainWorker pid=1341, ip=10.244.1.42) Train Epoch 9: 100%|██████████| 1875/1875 [00:23<00:00, 78.52it/s] [repeated 37x across cluster] (RayTrainWorker pid=1341, ip=10.244.1.42) Test Epoch 9: 0%| | 0/313 [00:00<?, ?it/s] (RayTrainWorker pid=1193, ip=10.244.4.193) Test Epoch 9: 89%|████████▉ | 278/313 [00:01<00:00, 266.46it/s] [repeated 19x across cluster] (RayTrainWorker pid=1193, ip=10.244.4.193) Test Epoch 9: 97%|█████████▋| 305/313 [00:01<00:00, 256.69it/s] Test Epoch 9: 100%|██████████| 313/313 [00:01<00:00, 267.35it/s] 2024-11-21 19:19:51,728 SUCC cli.py:63 -- ------------------------------------------ 2024-11-21 19:19:51,728 SUCC cli.py:64 -- Job 'rayjob-pytorch-mnist-hndpx' succeeded 2024-11-21 19:19:51,728 SUCC cli.py:65 -- ------------------------------------------
在 Ray 仪表板上查看训练结果
RayJob 成功完成后,可以在 Ray 仪表板上查看训练结果。 Ray 仪表板将提供对 Ray 群集的实时监视信息和可视化结果。 你可以使用 Ray 仪表板监视 Ray 群集的状态、查看日志以及直观呈现机器学习作业的结果。
若要访问 Ray 仪表板,需要通过创建服务填充码来向公共 Internet 公开 Ray 头服务,以在端口 80(而不是端口 8265)上公开 Ray 头服务。
注意
上一部分所述的 deploy.sh
会自动向公共 Internet 公开 Ray 头服务。 deploy.sh
脚本中包括以下步骤。
使用以下命令获取 Ray 头服务的名称并将其保存在 shell 变量中:
rayclusterhead=$(kubectl get service -n $kuberay_namespace | grep 'rayjob-pytorch-mnist-raycluster' | grep 'ClusterIP' | awk '{print $1}')
使用
kubectl expose service
命令创建服务填充码,以在端口 80 上公开 Ray 头服务。kubectl expose service $rayclusterhead \ -n $kuberay_namespace \ --port=80 \ --target-port=8265 \ --type=NodePort \ --name=ray-dash
使用以下命令创建入口,以使用入口控制器公开服务填充码:
cat <<EOF | kubectl apply -f - apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ray-dash namespace: kuberay annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: webapprouting.kubernetes.azure.com rules: - http: paths: - backend: service: name: ray-dash port: number: 80 path: / pathType: Prefix EOF
使用
kubectl get service
命令获取入口控制器的公共 IP 地址。kubectl get service -n app-routing-system
在输出中,应该会显示附加到入口控制器的负载均衡器的公共 IP 地址。 复制该公共 IP 地址并将其粘贴到 Web 浏览器中。 这样,便可看到 Ray 仪表板。
清理资源
若要清理在本指南中创建的资源,可以删除包含 AKS 群集的 Azure 资源组。
后续步骤
若要详细了解 AKS 上的 AI 和机器学习工作负载,请参阅以下文章:
- 部署使用 Azure Kubernetes 服务 (AKS) 上的 OpenAI 的应用程序
- 在 Azure Kubernetes 服务 (AKS) 上使用 Flyte 构建和部署数据与机器学习管道
- 使用 AI 工具链操作器在 Azure Kubernetes 服务 (AKS) 上部署 AI 模型(预览版)
供稿人
Microsoft 会维护本文。 本系列文章为以下参与者的原创作品:
- Russell de Pina | 首席 TPM
- Ken Kilty | 首席 TPM
- Erin Schaffer | 内容开发人员 2
- Adrian Joian | 首席客户工程师
- Ryan Graham | 首席技术专家