이 튜토리얼에서는 멀티 노드 GKE 클러스터에서 Ray 프레임워크를 사용하여 Gemma 3 모델을 파인 튜닝하는 방법을 보여줍니다. 클러스터는 각각 8개의 NVIDIA B200 GPU가 연결된 2개의 A4 가상 머신 (VM) 인스턴스를 사용합니다.
이 튜토리얼의 콘텐츠는 두 부분으로 나뉩니다.
- 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)을 통해 이 권한이 이미 있을 수 있습니다. 그렇지 않으면 서비스 사용량 관리자 역할 (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)을 통해 이 권한이 이미 있을 수 있습니다. 그렇지 않으면 서비스 사용량 관리자 역할 (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 보안 비밀 만들기
Cloud Shell에서 Hugging Face 사용자 인증 정보용 Kubernetes 보안 비밀을 만들려면 다음 단계를 따르세요.
클러스터에 연결하도록
kubectl을 구성합니다.Hugging Face 토큰을 저장할 Kubernetes 보안 비밀을 만듭니다.
Cloud Storage 버킷 만들기
새 버킷을 사용하여 학습 아티팩트를 저장하려면 다음을 실행하세요.
기존 버킷을 사용하려면 이 단계를 건너뛰면 됩니다. 하지만 버킷이 클러스터와 동일한 리전에 있어야 합니다.
IAM 서비스 계정 만들기
Cloud Shell에서 IAM 서비스 계정 (Google 서비스 계정 또는 GSA라고도 함)을 만들고 Cloud Storage 버킷에 액세스할 수 있는 권한을 부여하려면 다음 단계를 따르세요.
IAM 서비스 계정을 만듭니다.
Cloud Storage 버킷의 IAM 서비스 계정에 스토리지 관리자 역할 (
roles/storage.admin)을 부여합니다.
Kubernetes 서비스 계정 만들기
Cloud Shell에서 Kubernetes 서비스 계정을 만들고 워크로드 아이덴티티를 구성하여 Ray 포드에 Google Cloud 리소스에 대한 액세스 권한을 부여합니다.
Kubernetes 서비스 계정을 만듭니다.
워크로드 아이덴티티를 사용 설정하기 위해 이전에 만든 IAM 서비스 계정에 Kubernetes 서비스 계정을 바인딩합니다.
Kubernetes 서비스 계정에 IAM 서비스 계정의 이메일 주소를 주석으로 추가합니다.
학습 코드를 ConfigMap으로 저장
학습 스크립트를 컨테이너 이미지에 삽입하지 않으려면 클러스터에 ConfigMap으로 저장하세요. 이 ConfigMap은 포드 파일 시스템에 마운트되므로 전체 Ray 클러스터를 다시 만들지 않고도 학습 스크립트를 업데이트할 수 있습니다.
학습 스크립트를 클러스터에 ConfigMap으로 저장하려면 다음 단계를 완료하세요.
이름이
code인 디렉터리를 만들고 그 디렉터리에 이름이vision_train.py인 파일을 만듭니다.다음 코드를
vision_train.py파일에 복사합니다.파일을 저장합니다.
클러스터에서 ConfigMap 객체를 만듭니다.
학습 스크립트를 업데이트하려면 이전 명령어를 다시 실행합니다. 변경사항이 모든 포드에 전파되기까지 1분 정도 걸릴 수 있습니다.
Ray 클러스터 구성
GKE 클러스터에서 Ray 클러스터를 만들려면 다음 YAML을
ray_cluster.yaml이라는 파일로 저장합니다.다음 명령어를 사용하여 이 YAML 정의를 클러스터에 적용합니다.
$RESERVATION플래그는 환경 변수로 구성한 이름으로 자동 대체됩니다.Ray Operator는 raylet 포드를 만들고, 이 포드는 클러스터의 자동 확장을 트리거하여 해당 포드에 적절한 노드를 제공합니다. 클러스터에 포드 3개가 생성됩니다. 헤드 노드 1개와 워커 노드 2개입니다. 작업자 노드에는 B200 GPU가 장착되어 있습니다.
포드 3개가 모두 준비되었는지 확인하려면 다음을 실행합니다.
준비된 Ray 클러스터의 포드 목록은 다음과 비슷합니다.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파일로 저장합니다.RayCluster에 RayJob 정의를 제출합니다.
새 포드가 클러스터에 있는지 확인합니다.
출력에 표시되는
test-ray-job-포드의 전체 이름을 기록해 둡니다. 이 이름은 작업에 고유합니다.학습 진행 상황을 검사합니다.
gemma-training-ray-job-UNIQUE_ID을 이전 단계에서 기록한 고유한 포드 이름으로 바꿉니다.표시되는 출력은 다음과 비슷합니다.
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