本教程介绍了如何使用 Ray 框架在多节点 GKE 集群上微调 Gemma 3 模型。该集群使用两个 A4 虚拟机 (VM) 实例,每个实例都挂接了八个 NVIDIA B200 GPU。
本教程的内容分为两部分:
- 准备在 GKE Autopilot 集群上运行的 Ray 集群。
- 运行分布式训练作业,利用 2 个 A4 实例,每个实例配备 8 个 B200 GPU。
本教程适用于机器学习 (ML) 工程师、研究人员、平台管理员和运维人员,以及对在多个节点和 GPU 之间分配 AI 工作负载感兴趣的数据和 AI 专家。
目标
使用 Hugging Face 访问 Gemma 3 模型。
准备环境。
创建已安装 Ray Operator 的 GKE Autopilot 集群。
配置 GKE 集群上的 Ray 集群以接受 Ray 作业。
配置并运行一个 Ray 作业,该作业可根据视觉输入调整 Gemma 3 模型。
监控工作负载。
清理。
费用
在本文档中,您将使用 Google Cloud的以下收费组件:
如需根据您的预计使用情况来估算费用,请使用价格计算器。
准备工作
-
安装 Google Cloud CLI。
-
配置 gcloud CLI 以使用您的联合身份。
如需了解详情,请参阅使用联合身份登录 gcloud CLI。
-
如需初始化 gcloud CLI,请运行以下命令:
gcloud init -
选择或创建项目所需的角色
- 选择项目:选择项目不需要特定的 IAM 角色,您可以选择已获授角色的任何项目。
-
创建项目:如需创建项目,您需要拥有 Project Creator 角色 (
roles/resourcemanager.projectCreator),该角色包含resourcemanager.projects.create权限。了解如何授予角色。
-
创建 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权限。如果您创建了项目,则可能已经通过 Owner 角色 (roles/owner) 获得了此权限。否则,您可以通过 Service Usage Admin 角色 (roles/serviceusage.serviceUsageAdmin) 获得此权限。了解如何授予角色。gcloud services enable compute.googleapis.com logging.googleapis.com cloudresourcemanager.googleapis.com servicenetworking.googleapis.com container.googleapis.com
-
向您的用户账号授予角色。对以下每个 IAM 角色运行以下命令一次:
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:您的用户 个账号。如需查看示例,请参阅 在 IAM 政策中表示员工池用户。ROLE:您向用户账号授予的 IAM 角色。
- 为您的 Google Cloud 项目启用默认服务账号:
gcloud iam service-accounts enable PROJECT_NUMBER-compute@ \ --project=PROJECT_ID
将 PROJECT_NUMBER 替换为您的项目编号。如需查看项目编号,请参阅 获取现有项目。
- 向默认服务账号授予 Editor 角色 (
roles/editor):gcloud projects add-iam-policy-binding PROJECT_ID \ --member="serviceAccount:PROJECT_NUMBER-compute@" \ --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:您要用于创建集群的预留的网址。根据预留所在的项目的不同,指定以下值之一:- 预留存在于您的项目中:指定预留名称(例如
my-reservation)。 - 预留位于其他项目中:请指定完整路径,格式为
projects/RESERVATION_PROJECT_ID/reservations/RESERVATION_NAME。
- 预留存在于您的项目中:指定预留名称(例如
YOUR_REGION:您要在其中创建 GKE 集群的区域。 您只能在预留所在的区域中创建集群。YOUR_CLUSTER_NAME:要创建的 GKE 集群的名称。YOUR_HF_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以连接到您的集群:创建一个 Kubernetes Secret 来存储您的 Hugging Face 令牌:
创建 Cloud Storage 存储桶
如果您想使用新的存储桶来存储训练工件,请运行以下命令:
如果您想使用现有存储桶,可以跳过此步骤。不过,您必须确保存储桶与集群位于同一区域。
创建 IAM 服务账号
在 Cloud Shell 中,如需创建 IAM 服务账号(也称为 Google 服务账号或 GSA)并授予其访问 Cloud Storage 存储桶的权限,请执行以下操作:
创建 IAM 服务账号
向您的 IAM 服务账号授予 Cloud Storage 存储桶的 Storage Admin 角色 (
roles/storage.admin):
创建 Kubernetes 服务账号
在 Cloud Shell 中,创建 Kubernetes 服务账号并配置 Workload Identity,以授予 Ray pod 对 Google Cloud 资源的访问权限:
创建 Kubernetes 服务账号:
将您的 Kubernetes 服务账号绑定到您之前创建的 IAM 服务账号,以启用 Workload Identity:
使用 IAM 服务账号的电子邮件地址为 Kubernetes 服务账号添加注解:
将训练代码保存为 ConfigMap
为避免将训练脚本嵌入到容器映像中,您可以将其作为 ConfigMap 存储在集群中。此 ConfigMap 会装载到 Pod 文件系统,这样一来,您无需重新创建整个 Ray 集群即可更新训练脚本。
如需将训练脚本存储为集群中的 ConfigMap,请完成以下步骤:
创建一个名为
code的目录,并在该目录中创建一个名为vision_train.py的文件。将以下代码复制到
vision_train.py文件中:保存文件。
在集群中创建 ConfigMap 对象:
如需更新训练脚本,请重新运行上述命令。任何更改传播到所有 pod 之前,可能需要等待一分钟。
配置 Ray 集群
如需在 GKE 集群中创建 Ray 集群,请将以下 YAML 保存为名为
ray_cluster.yaml的文件。使用以下命令将此 YAML 定义应用于您的集群:
$RESERVATION标志会自动替换为您配置为环境变量的名称。Ray Operator 会创建 raylet Pod,这会触发集群自动扩缩,以便为这些 Pod 提供合适的节点。系统会在集群中创建三个 pod:一个头节点和两个工作器节点。工作器节点配备了 B200 GPU。
如需验证所有三个 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