このチュートリアルでは、マルチノード GKE クラスタで Ray フレームワークを使用して Gemma 3 モデルをファインチューニングする方法について説明します。このクラスタは、それぞれ 8 個の NVIDIA B200 GPU が割り当てられた 2 つの A4 仮想マシン(VM)インスタンスを使用します。
このチュートリアルの内容は、次の 2 つの部分に分かれています。
- GKE Autopilot クラスタ上に Ray クラスタを準備する。
- それぞれ 8 個の B200 GPU を搭載した 2 つの A4 インスタンスを使用して、分散トレーニング ジョブを実行します。
このチュートリアルは、複数のノードと GPU に AI ワークロードを分散することに関心がある ML エンジニア、研究者、プラットフォーム管理者、オペレーター、データと AI のスペシャリストを対象としています。
目標
Hugging Face を使用して Gemma 3 モデルにアクセスします。
環境を準備します。
Ray Operator がインストールされた GKE Autopilot クラスタを作成します。
GKE クラスタで Ray クラスタを構成して、Ray ジョブを受け入れるようにします。
画像入力に基づいて Gemma 3 モデルをチューニングする Ray ジョブを構成して実行します。
ワークロードをモニタリングします。
クリーンアップする。
費用
このドキュメントでは、課金対象である次の Google Cloudコンポーネントを使用します。
料金計算ツールを使うと、予想使用量に基づいて費用の見積もりを生成できます。
始める前に
- Google Cloud アカウントにログインします。 Google Cloudを初めて使用する場合は、 アカウントを作成して、実際のシナリオでの Google プロダクトのパフォーマンスを評価してください。新規のお客様には、ワークロードの実行、テスト、デプロイができる無料クレジット $300 分を差し上げます。
-
Google Cloud CLI をインストールします。
-
外部 ID プロバイダ(IdP)を使用している場合は、まず連携 ID を使用して gcloud CLI にログインする必要があります。
-
gcloud CLI を初期化するには、次のコマンドを実行します。
gcloud init -
Google Cloud プロジェクトを作成または選択します。
プロジェクトの選択または作成に必要なロール
- プロジェクトを選択する: プロジェクトの選択に特定の IAM ロールは必要ありません。ロールが付与されているプロジェクトであれば、どのプロジェクトでも選択できます。
-
プロジェクトを作成する: プロジェクトを作成するには、
resourcemanager.projects.create権限を含むプロジェクト作成者ロール(roles/resourcemanager.projectCreator)が必要です。詳しくは、ロールを付与する方法をご覧ください。
-
Google Cloud プロジェクトを作成します。
gcloud projects create PROJECT_ID
PROJECT_IDは、作成する Google Cloud プロジェクトの名前に置き換えます。 -
作成した Google Cloud プロジェクトを選択します。
gcloud config set project PROJECT_ID
PROJECT_IDは、 Google Cloud プロジェクトの名前に置き換えます。
必要な API を有効にします。
API を有効にするために必要なロール
API を有効にするには、
serviceusage.services.enable権限が必要です。プロジェクトを作成した場合は、オーナーロール(roles/owner)を介してこの権限がすでに付与されている可能性があります。それ以外の場合は、Service Usage 管理者ロール(roles/serviceusage.serviceUsageAdmin)を介してこの権限を取得できます。ロールを付与する方法をご覧ください。gcloud services enable compute.googleapis.com logging.googleapis.com cloudresourcemanager.googleapis.com servicenetworking.googleapis.com container.googleapis.com
-
Google Cloud CLI をインストールします。
-
外部 ID プロバイダ(IdP)を使用している場合は、まず連携 ID を使用して gcloud CLI にログインする必要があります。
-
gcloud CLI を初期化するには、次のコマンドを実行します。
gcloud init -
Google Cloud プロジェクトを作成または選択します。
プロジェクトの選択または作成に必要なロール
- プロジェクトを選択する: プロジェクトの選択に特定の IAM ロールは必要ありません。ロールが付与されているプロジェクトであれば、どのプロジェクトでも選択できます。
-
プロジェクトを作成する: プロジェクトを作成するには、
resourcemanager.projects.create権限を含むプロジェクト作成者ロール(roles/resourcemanager.projectCreator)が必要です。詳しくは、ロールを付与する方法をご覧ください。
-
Google Cloud プロジェクトを作成します。
gcloud projects create PROJECT_ID
PROJECT_IDは、作成する Google Cloud プロジェクトの名前に置き換えます。 -
作成した Google Cloud プロジェクトを選択します。
gcloud config set project PROJECT_ID
PROJECT_IDは、 Google Cloud プロジェクトの名前に置き換えます。
必要な API を有効にします。
API を有効にするために必要なロール
API を有効にするには、
serviceusage.services.enable権限が必要です。プロジェクトを作成した場合は、オーナーロール(roles/owner)を介してこの権限がすでに付与されている可能性があります。それ以外の場合は、Service Usage 管理者ロール(roles/serviceusage.serviceUsageAdmin)を介してこの権限を取得できます。ロールを付与する方法をご覧ください。gcloud services enable compute.googleapis.com logging.googleapis.com cloudresourcemanager.googleapis.com servicenetworking.googleapis.com container.googleapis.com
-
ユーザー アカウントにロールを付与します。次の IAM ロールごとに次のコマンドを 1 回実行します。
roles/compute.admin, roles/iam.serviceAccountUser, roles/file.editor, roles/storage.admin, roles/container.clusterAdmin, roles/serviceusage.serviceUsageAdmingcloud projects add-iam-policy-binding PROJECT_ID --member="user:USER_IDENTIFIER" --role=ROLE
次のように置き換えます。
PROJECT_ID: プロジェクト ID。USER_IDENTIFIER: ユーザー アカウントの識別子。例:myemail@example.com。ROLE: ユーザー アカウントに付与する IAM ロール。
- Google Cloud プロジェクトのデフォルトのサービス アカウントを有効にします。
gcloud iam service-accounts enable PROJECT_NUMBER-compute@developer.gserviceaccount.com \ --project=PROJECT_ID
PROJECT_NUMBER は、使用するプロジェクト番号に置き換えます。プロジェクト番号を確認するには、 既存のプロジェクトを取得するをご覧ください。
- デフォルトのサービス アカウントに編集者ロール(
roles/editor)を付与します。gcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:PROJECT_NUMBER-compute@developer.gserviceaccount.com" \ --role=roles/editor
- ユーザー アカウントのローカル認証情報を作成します。
gcloud auth application-default login
- Hugging Face アカウントにログインするか、アカウントを作成します。
Hugging Face を使用して Gemma 3 にアクセスする
Hugging Face を使用して Gemma 3 にアクセスする手順は次のとおりです。
read accessトークンの値をコピーして保存します。これは、このチュートリアルの後半で使用します。
環境を準備する
必要な設定を構成し、環境変数を設定して、環境を準備します。
以下のコマンドを実行します。
次のように置き換えます。
YOUR_PROJECT_ID: GKE クラスタを作成する Google Cloud プロジェクトの名前。YOUR_RESERVATION_ID: クラスタの作成に使用する予約の URL。予約が存在するプロジェクトに基づいて、次のいずれかの値を指定します。- 予約がプロジェクトに存在する場合: 予約名を指定します(例:
my-reservation)。 - 予約が別のプロジェクトに存在する場合:
projects/RESERVATION_PROJECT_ID/reservations/RESERVATION_NAME形式でフルパスを指定します。
- 予約がプロジェクトに存在する場合: 予約名を指定します(例:
YOUR_REGION: GKE クラスタを作成するリージョン。クラスタを作成できるのは、予約が存在するリージョンのみです。YOUR_CLUSTER_NAME: 作成する GKE クラスタの名前。HUGGING_FACE_TOKEN: 前の手順で作成した Hugging Face トークン。YOUR_RAY_SA: Kubernetes クラスタ内のサービス アカウント名。YOUR_GSA_NAME: Google サービス アカウントの名前。YOUR_GCS_BUCKET: トレーニング チェックポイントの結果を保存するバケットの名前。
Autopilot モードの GKE クラスタを作成する
Autopilot モードで GKE クラスタを作成するには、次のコマンドを実行します。
GKE クラスタの作成が完了するまでに時間がかかることがあります。 Google Cloud がクラスタの作成を完了したかどうかを確認するには、 Google Cloud コンソールの [Kubernetes クラスタ] に移動します。
Hugging Face の認証情報用の Kubernetes Secret を作成する
Cloud Shell で、次の操作を行って Hugging Face の認証情報用の Kubernetes Secret を作成します。
クラスタに接続するように
kubectlを構成します。Hugging Face トークンを保存する Kubernetes Secret を作成します。
Cloud Storage バケットを作成する
新しいバケットを使用してトレーニング アーティファクトを保存する場合は、次のコマンドを実行します。
既存のバケットを使用する場合は、この手順をスキップできます。ただし、バケットがクラスタと同じリージョンにあることを確認する必要があります。
IAM サービス アカウントを作成する
Cloud Shell で、IAM サービス アカウント(Google サービス アカウント、GSA とも呼ばれます)を作成し、Cloud Storage バケットにアクセスする権限を付与するには、次の操作を行います。
IAM サービス アカウントを作成します。
Cloud Storage バケットの IAM サービス アカウントにストレージ管理者ロール(
roles/storage.admin)を付与します。
Kubernetes サービス アカウントを作成する
Cloud Shell で Kubernetes サービス アカウントを作成し、Workload Identity を構成して、Ray Pod に Google Cloud リソースへのアクセス権を付与します。
Kubernetes サービス アカウントを作成します。
Workload Identity を有効にするために以前に作成した IAM サービス アカウントに Kubernetes サービス アカウントをバインドします。
IAM サービス アカウントのメールアドレスで Kubernetes Service Account にアノテーションを付けます。
トレーニング コードを ConfigMap として保存する
トレーニング スクリプトをコンテナ イメージに埋め込む必要がないように、クラスタに ConfigMap として保存します。この ConfigMap は Pod ファイル システムにマウントされます。これにより、Ray クラスタ全体を再作成することなく、トレーニング スクリプトを更新できます。
トレーニング スクリプトをクラスタの ConfigMap として保存する手順は次のとおりです。
codeという名前のディレクトリを作成し、そのディレクトリにvision_train.pyという名前のファイルを作成します。次のコードを
vision_train.pyファイルにコピーします。ファイルを保存します。
クラスタに ConfigMap オブジェクトを作成します。
トレーニング スクリプトを更新するには、上記のコマンドを再実行します。変更がすべての Pod に伝播されるまでに 1 分ほどかかることがあります。
Ray クラスタを構成する
GKE クラスタに Ray クラスタを作成するには、次の YAML を
ray_cluster.yamlという名前のファイルとして保存します。次のコマンドを使用して、この YAML 定義をクラスタに適用します。
$RESERVATIONフラグは、環境変数として構成した名前に自動的に置き換えられます。Ray Operator は raylet Pod を作成します。これにより、クラスタの自動スケーリングがトリガーされ、これらの Pod に適切なノードが提供されます。クラスタに 3 つの Pod(1 つのヘッドノードと 2 つのワーカーノード)が作成されます。ワーカーノードには B200 GPU が搭載されています。
3 つの Pod がすべて準備完了していることを確認するには、次のコマンドを実行します。
準備完了した Ray クラスタの Pod リストは次のようになります。NAME READY STATUS RESTARTS AGE gemma3-tuning-gpu-group-worker-s4h8f 2/2 Running 0 16m gemma3-tuning-gpu-group-worker-stg5f 2/2 Running 0 5m34s gemma3-tuning-head-zbdvp 2/2 Running 0 16m
トレーニング ジョブをスケジュールする
次の内容を
ray_job.yamlファイルとして保存します。RayJob 定義を RayCluster に送信します。
新しい Pod がクラスタに存在することを確認します。
出力に表示される
test-ray-job-Pod のフルネームをメモします。この名前はジョブに固有です。トレーニングの進行状況を検査します。
gemma-training-ray-job-UNIQUE_IDは、前の手順でメモした一意の Pod 名に置き換えます。表示される出力は次のようになります。
2025-08-20 08:29:34,966 INFO cli.py:41 -- Job submission server address: http://gemma3-tuning-head-svc.default.svc.cluster.local:8265 2025-08-20 08:29:34,991 SUCC cli.py:65 -- ----------------------------------------------- 2025-08-20 08:29:34,991 SUCC cli.py:66 -- Job 'test-ray-job-82mm7' submitted successfully 2025-08-20 08:29:34,991 SUCC cli.py:67 -- ----------------------------------------------- 2025-08-20 08:29:34,992 INFO cli.py:291 -- Next steps 2025-08-20 08:29:34,992 INFO cli.py:292 -- Query the logs of the job: 2025-08-20 08:29:34,992 INFO cli.py:294 -- ray job logs test-ray-job-82mm7 2025-08-20 08:29:34,992 INFO cli.py:296 -- Query the status of the job: 2025-08-20 08:29:34,992 INFO cli.py:298 -- ray job status test-ray-job-82mm7 2025-08-20 08:29:34,992 INFO cli.py:300 -- Request the job to be stopped: 2025-08-20 08:29:34,992 INFO cli.py:302 -- ray job stop test-ray-job-82mm7 2025-08-20 08:29:35,003 INFO cli.py:312 -- Tailing logs until the job exits (disable with --no-wait): 2025-08-20 08:29:34,982 INFO job_manager.py:531 -- Runtime env is setting up. Starting training task! Commencing training! 2025-08-20 08:30:08,498 INFO worker.py:1606 -- Using address 10.76.0.17:6379 set in the environment variable RAY_ADDRESS 2025-08-20 08:30:08,506 INFO worker.py:1747 -- Connecting to existing Ray cluster at address: 10.76.0.17:6379... 2025-08-20 08:30:08,527 INFO worker.py:1918 -- Connected to Ray cluster. View the dashboard at 10.76.0.17:8265 2025-08-20 08:30:08,701 INFO tune.py:253 -- Initializing Ray automatically. For cluster usage or custom Ray initialization, call `ray.init(...)` before `<FrameworkTrainer>(...)`. 2025-08-20 08:30:08,951 WARNING tune_controller.py:2132 -- The maximum number of pending trials has been automatically set to the number of available cluster CPUs, which is high (519 CPUs/pending trials). If you're running an experiment with a large number of trials, this could lead to scheduling overhead. In this case, consider setting the `TUNE_MAX_PENDING_TRIALS_PG` environment variable to the desired maximum number of concurrent pending trials. 2025-08-20 08:30:08,953 WARNING tune_controller.py:2132 -- The maximum number of pending trials has been automatically set to the number of available cluster CPUs, which is high (519 CPUs/pending trials). If you're running an experiment with a large number of trials, this could lead to scheduling overhead. In this case, consider setting the `TUNE_MAX_PENDING_TRIALS_PG` environment variable to the desired maximum number of concurrent pending trials. View detailed results here: YOUR_GCS_BUCKET/gemma_vision_train_2025_08_20_08_30_07 To visualize your results with TensorBoard, run: `tensorboard --logdir /tmp/ray/session_2025-08-20_04-43-14_215096_1/artifacts/2025-08-20_08-30-08/gemma_vision_train_2025_08_20_08_30_07/driver_artifacts` Training started with configuration: ╭──────────────────────────────────────────────────────────────────────╮ │ Training config │ ├──────────────────────────────────────────────────────────────────────┤ │ train_loop_config/dataset_name ...-descriptions-vlm │ │ train_loop_config/gcs_bucket ...-bucket-yooo-west │ │ train_loop_config/gradient_accumulation_steps 4 │ │ train_loop_config/learning_rate 0.0002 │ │ train_loop_config/logging_steps 10 │ │ train_loop_config/lora_alpha 16 │ │ train_loop_config/lora_dropout 0.05 │ │ train_loop_config/lora_r 16 │ │ train_loop_config/max_seq_length 512 │ │ train_loop_config/model_id google/gemma-3-4b-it │ │ train_loop_config/num_train_epochs 3 │ │ train_loop_config/output_dir ...-4b-seo-optimized │ │ train_loop_config/per_device_train_batch_size 1 │ │ train_loop_config/push_to_hub False │ │ train_loop_config/save_steps 100 │ │ train_loop_config/save_strategy epoch │ ╰──────────────────────────────────────────────────────────────────────╯ (RayTrainWorker pid=45455, ip=10.76.0.71) Setting up process group for: env:// [rank=0, world_size=16] (TorchTrainer pid=45197, ip=10.76.0.71) Started distributed worker processes: (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=4c934ab2f646a578b03cc335586f30b943e811b645526a74c50bfca1, ip=10.76.0.71, pid=45455) world_rank=0, local_rank=0, node_rank=0 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=4c934ab2f646a578b03cc335586f30b943e811b645526a74c50bfca1, ip=10.76.0.71, pid=45450) world_rank=1, local_rank=1, node_rank=0 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=4c934ab2f646a578b03cc335586f30b943e811b645526a74c50bfca1, ip=10.76.0.71, pid=45454) world_rank=2, local_rank=2, node_rank=0 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=4c934ab2f646a578b03cc335586f30b943e811b645526a74c50bfca1, ip=10.76.0.71, pid=45448) world_rank=3, local_rank=3, node_rank=0 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=4c934ab2f646a578b03cc335586f30b943e811b645526a74c50bfca1, ip=10.76.0.71, pid=45453) world_rank=4, local_rank=4, node_rank=0 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=4c934ab2f646a578b03cc335586f30b943e811b645526a74c50bfca1, ip=10.76.0.71, pid=45452) world_rank=5, local_rank=5, node_rank=0 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=4c934ab2f646a578b03cc335586f30b943e811b645526a74c50bfca1, ip=10.76.0.71, pid=45451) world_rank=6, local_rank=6, node_rank=0 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=4c934ab2f646a578b03cc335586f30b943e811b645526a74c50bfca1, ip=10.76.0.71, pid=45449) world_rank=7, local_rank=7, node_rank=0 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=c0db52b44f891f3d6a1cedcbea4c6beb2c8434c66ef414dc15e65743, ip=10.76.0.135, pid=45729) world_rank=8, local_rank=0, node_rank=1 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=c0db52b44f891f3d6a1cedcbea4c6beb2c8434c66ef414dc15e65743, ip=10.76.0.135, pid=45726) world_rank=9, local_rank=1, node_rank=1 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=c0db52b44f891f3d6a1cedcbea4c6beb2c8434c66ef414dc15e65743, ip=10.76.0.135, pid=45728) world_rank=10, local_rank=2, node_rank=1 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=c0db52b44f891f3d6a1cedcbea4c6beb2c8434c66ef414dc15e65743, ip=10.76.0.135, pid=45727) world_rank=11, local_rank=3, node_rank=1 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=c0db52b44f891f3d6a1cedcbea4c6beb2c8434c66ef414dc15e65743, ip=10.76.0.135, pid=45725) world_rank=12, local_rank=4, node_rank=1 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=c0db52b44f891f3d6a1cedcbea4c6beb2c8434c66ef414dc15e65743, ip=10.76.0.135, pid=45724) world_rank=13, local_rank=5, node_rank=1 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=c0db52b44f891f3d6a1cedcbea4c6beb2c8434c66ef414dc15e65743, ip=10.76.0.135, pid=45723) world_rank=14, local_rank=6, node_rank=1 (TorchTrainer pid=45197, ip=10.76.0.71) - (node_id=c0db52b44f891f3d6a1cedcbea4c6beb2c8434c66ef414dc15e65743, ip=10.76.0.135, pid=45722) world_rank=15, local_rank=7, node_rank=1 ... Training finished iteration 3 at 2025-08-20 08:40:43. Total running time: 10min 34s ╭─────────────────────────────────────────╮ │ Training result │ ├─────────────────────────────────────────┤ │ checkpoint_dir_name checkpoint_000002 │ │ time_this_iter_s 152.6374 │ │ time_total_s 525.88585 │ │ training_iteration 3 │ │ epoch 2.75294 │ │ grad_norm 47.27161 │ │ learning_rate 0.0002 │ │ loss 22.5275 │ │ mean_token_accuracy 0.90325 │ │ num_tokens 1583017. │ │ step 60 │ ╰─────────────────────────────────────────╯ ... Training completed after 3 iterations at 2025-08-20 08:40:52. Total running time: 10min 43s 2025-08-20 08:40:53,113 INFO tune.py:1009 -- Wrote the latest version of all result files and experiment state to 'YOUR_GCS_BUCKET/gemma_vision_train_2025_08_20_08_30_07' in 0.1663s. 2025-08-20 08:40:58,304 SUCC cli.py:65 -- ---------------------------------- 2025-08-20 08:40:58,305 SUCC cli.py:66 -- Job 'test-ray-job-82mm7' succeeded 2025-08-20 08:40:58,305 SUCC cli.py:67 -- ----------------------------------ワークロードをモニタリングする
Ray のダッシュボードを使用して、クラスタでスケジュールされているワークロードをモニタリングできます。
このダッシュボードにアクセスするには、新しいターミナル ウィンドウで次のコマンドを実行して、クラスタへのポート転送を設定する必要があります。
ブラウザで次のリンク(
http://localhost:8265)を開きます。必要に応じて、Cloud Shell を使用している場合は、前の手順でコマンドを実行した後、[ウェブでプレビュー] ボタンをクリックします。
[ポートを変更] オプションを選択し、「
8265」と入力して、[変更してプレビュー] をクリックします。Ray ダッシュボードが新しいタブで開きます。
クリーンアップ
このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにするには、リソースを含むプロジェクトを削除するか、プロジェクトを維持して個々のリソースを削除します。
リソースを削除する
Ray クラスタを削除して GPU 搭載ノードを解放するには、次のコマンドを実行します。
GKE はクラスタを自動的にスケールダウンし、Ray で使用される A4 マシンを解放します。GKE クラスタ全体を削除するには、次のコマンドを実行します。
Cloud Storage バケットとそのすべてのコンテンツを削除するには、次のコマンドを実行します。
プロジェクトの削除
Google Cloud プロジェクトを削除する:
gcloud projects delete PROJECT_ID