この記事では、KubeRay を使用して Azure Kubernetes Service (AKS) 上で Ray クラスターを構成してデプロイします。 また、Ray クラスターを使用して単純な機械学習モデルをトレーニングし、結果を Ray ダッシュボードに表示する方法についても説明します。
この記事では、AKS に Ray クラスターをデプロイする 2 つの方法について説明します。
-
非対話型デプロイ: GitHub リポジトリの
deploy.shスクリプトを使用して、完全な Ray サンプルを非対話形式でデプロイします。 - 手動デプロイ: 手動デプロイの手順に従って、Ray サンプルを AKS にデプロイします。
前提条件
- AKS 上の Ray クラスターの概要を確認して、コンポーネントとデプロイ プロセスを理解します。
- Azure サブスクリプション。 Azure サブスクリプションをお持ちでない場合は、こちらで無料のアカウントを作成できます。
- ローカル コンピューターにインストールされている Azure CLI。 「Azure CLI をインストールする方法」の手順に従ってインストールできます。
- Azure Kubernetes Service Preview 拡張機能がインストールされていること。
- Helm がインストールされていること。
- Terraform クライアント ツールまたは OpenTofu がインストールされていること。 この記事では Terraform を使用しますが、使用されるモジュールは 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 portal のリソース グループを確認して、作成されたインフラストラクチャを確かめます。
Ray サンプルを手動でデプロイする
Fashion MNIST は、60,000 個の例からなるトレーニング セットと 10,000 個の例からなるテスト セットで構成される Zalando の記事画像のデータセットです。 各例は、10 個のクラスのラベルが関連付けられた 28 x 28 のグレースケール画像です。 このガイドでは、Ray クラスターを使用して、簡単な PyTorch モデルをこのデータセットでトレーニングします。
RayJob 仕様をデプロイする
モデルをトレーニングするには、プライベート AKS クラスターで実行されている KubeRay オペレーターに Ray Job 仕様を送信する必要があります。 Ray Job 仕様は、Docker イメージ、実行するコマンド、使用するワーカーの数など、ジョブの実行に必要なリソースを記述した YAML ファイルです。
Ray Job の説明を確認して、環境に合わせていくつかのフィールドを変更することが必要になる場合があります。
-
replicasのworkerGroupSpecsセクションにあるrayClusterSpecフィールドには、KubeRay によって Kubernetes クラスターにスケジュールされるワーカー ポッドの数を指定します。 各ワーカー ポッドには、"3 個の CPU" と "4 GB のメモリ" が必要です。 ヘッド ポッドには、"1 個の CPU" と "4 GB のメモリ" が必要です。replicasフィールドを 2 に設定するには、ジョブの RayCluster の実装に使用するノード プールに "8 個の vCPU" が必要です。 -
NUM_WORKERSのruntimeEnvYAMLにあるspecフィールドには、起動する Ray アクターの数を指定します。 各 Ray アクターは Kubernetes クラスター内のワーカー ポッドによって処理されなければならないため、このフィールドはreplicasフィールドに等しいかそれより少ない必要があります。 この例では、NUM_WORKERSを 2 に設定します。これはreplicasフィールドに一致します。 -
CPUS_PER_WORKERフィールドは、"各ワーカー ポッドに割り当てられた CPU の数から 1 を引いた値に等しいかそれより少なく" 設定する必要があります。 この例では、ワーカー ポッドあたりの CPU リソース要求は 3 であるため、CPUS_PER_WORKERは 2 に設定されます。
要約すると、PyTorch モデルのトレーニング ジョブを実行するには、ノード プールに合計 "8 個の vCPU" が必要です。 ユーザー ポッドをスケジュールできないようにシステム ノード プールにテイントを追加したので、Ray クラスターをホストするために、少なくとも "8 個の vCPU" を含む新しいノード プールを作成する必要があります。
次のコマンドを使用して、Ray Job 仕様ファイルをダウンロードします。
curl -LO https://raw.githubusercontent.com/ray-project/kuberay/master/ray-operator/config/samples/pytorch-mnist/ray-job.pytorch-mnist.yamlRay Job 仕様ファイルに必要な変更を加えます。
kubectl applyコマンドを使用して、PyTorch モデルのトレーニング ジョブを起動します。kubectl apply -n kuberay -f ray-job.pytorch-mnist.yaml
RayJob のデプロイを検証する
kubectl get podsコマンドを使用して、名前空間で 2 つのワーカー ポッドと 1 つのヘッド ポッドが実行されていることを確認します。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 6m15skubectl 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 9m36sRayJob が完了するまで待ちます。 この処理には数分かかる場合があります。
JOB STATUSがSUCCEEDEDになったら、トレーニング ログを確認できます。 そのためには、kubectl get podsコマンドを使用して、RayJob を実行しているポッドの名前を最初に取得します。kubectl get pods -n kuberay出力には、次の出力例のように、
rayjob-pytorch-mnistで始まる名前のポッドが表示されます。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 14mkubectl logsコマンドを使用して RayJob のログを表示します。rayjob-pytorch-mnist-fc959は、RayJob を実行しているポッドの名前に置き換えてください。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 ダッシュボードにアクセスするには、ポート 8265 ではなく、ポート 80 で Ray ヘッド サービスを公開する "サービス shim" を作成して、Ray ヘッド サービスをパブリック インターネットに公開する必要があります。
Note
前のセクションで説明した deploy.sh は、Ray ヘッド サービスをパブリック インターネットに自動的に公開します。 以下の手順は、deploy.sh スクリプトに含まれています。
次のコマンドを使用して、Ray ヘッド サービスの名前を取得してシェル変数に保存します。
rayclusterhead=$(kubectl get service -n $kuberay_namespace | grep 'rayjob-pytorch-mnist-raycluster' | grep 'ClusterIP' | awk '{print $1}')kubectl expose serviceコマンドを使用して、ポート 80 で Ray ヘッド サービスを公開するサービス shim を作成します。kubectl expose service $rayclusterhead \ -n $kuberay_namespace \ --port=80 \ --target-port=8265 \ --type=NodePort \ --name=ray-dash次のコマンドを使用して、イングレス コントローラーを使用してサービス shim を公開するイングレスを作成します。
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 EOFkubectl get serviceコマンドを使用して、イングレス コントローラーのパブリック IP アドレスを取得します。kubectl get service -n app-routing-system出力には、イングレス コントローラーに接続されているロード バランサーのパブリック IP アドレスが表示されます。 パブリック IP アドレスをコピーし、Web ブラウザーに貼り付けます。 Ray ダッシュボードが表示されます。
リソースをクリーンアップする
このガイドで作成したリソースをクリーンアップするには、AKS クラスターを含む Azure リソース グループを削除します。
次のステップ
AKS での AI と機械学習のワークロードの詳細については、次の記事を参照してください。
- Azure Kubernetes Service (AKS) 上に OpenAI を使用するアプリケーションをデプロイする
- Azure Kubernetes Service (AKS) で Flyte を使用してデータと機械学習パイプラインを構築してデプロイする
- AI ツールチェーン オペレーターを使用して Azure Kubernetes Service (AKS) に AI モデルをデプロイする
共同作成者
Microsoft では、この記事を保持しています。 最初の寄稿者は次のとおりです。
- Russell de Pina | プリンシパル TPM
- Ken Kilty | プリンシパル TPM
- Erin Schaffer |コンテンツ開発者 2
- Adrian Joian | プリンシパル カスタマー エンジニア
- Ryan Graham | プリンシパル テクニカル スペシャリスト