כוונון עדין והתאמה לעומס של למידת חיזוק באמצעות Vertex AI ב-GKE

במדריך הזה נסביר איך לתזמן סביבת אימון מבוזרת ללמידת חיזוק ב-Google Kubernetes Engine ‏ (GKE). אתם משתמשים ב-Ray ובמסגרת verl (Volcano Engine Reinforcement Learning) כדי להגדיר סביבת אימון מבוזרת לצורך כוונון עדין של מודל Qwen2.5-32B-Instruct במערך הנתונים GSM8K.

המדריך הזה מתמקד בצינור עיבוד נתונים לאימון של אופטימיזציה של מדיניות יחסית לקבוצה (GRPO) ב-GKE עם Ray ו-verl. ‫GRPO הוא אלגוריתם ללמידה חיזוקית שנועד לשפר את יכולת ההסקה של מודל. האלגוריתם הזה חוסך בזיכרון ומפשט את תהליך הלמידה באמצעות חיזוקים (RL) על ידי ביטול ה-Critic, או מודל הערך, ושימוש בחישוב יחסי שמבוסס על קבוצה במקום זאת.

המדריך הזה הוא נקודת התחלה טובה אם אתם צריכים להגדיר סביבת אימון מבוזרת שבה הנתונים, משקלי המודל ומנוע האימון מופרדים כדי לשפר את היעילות.

המדריך הזה תומך בארכיטקטורות ה-GPU הבאות:

  • צמתים של GPU מבוססי Intel או AMD: הגדרה ושינוי קנה מידה באמצעות NVIDIA B200 או H200 GPUs, באמצעות הקצאת משאבים דינמית (DRA) של GKE לנתיב Autopilot.
  • צמתים מבוססי Arm‏ A4X‏ (GB200): הגדרה והרחבה באמצעות NVIDIA GB200 Grace Blackwell Superchips, באמצעות הקצאת משאבים דינמית (DRA) של GKE ו-Multi-Node NVLink‏ (IMEX).

רקע

בקטעים הבאים מופיעה סקירה כללית קצרה של המושגים שבהם נעשה שימוש במדריך הזה.

למידת חיזוקים (RL)

ב-RL, המודלים לומדים מתוך ניסיון, מחקר ומשוב, ולא מתוך חיקוי סטטי. במהלך האימון המקדים, המודל לומד מה לומר, ובמהלך הלמידה ממשוב אנושי (RLHF), הוא לומד איך להיות מועיל, בטוח והגיוני. RL משמש כגשר בין מודל בסיסי לבין מודל מכוונן לשימוש במקרה שימוש ספציפי.

מה זה למידת חיזוק?

אופטימיזציה יחסית של מדיניות קבוצתית (GRPO)

‫GRPO, אלגוריתם שזכה לפופולריות בזכות DeepSeek, מציע חלופה חסכונית בזיכרון ל-Proximal Policy Optimization ‏ (PPO) להתאמת LLM, על ידי הסרת מודל ה-Critic. במקום רשת מבקרים, GRPO יוצרת קבוצה של תשובות לאותה הנחיה ומשתמשת בתגמול הממוצע של הקבוצה הזו כבסיס.

מידע נוסף זמין במאמר בנושא GRPO.

Volcano Engine Reinforcement Learning (verl)

‫verl הוא פריימוורק לביצועים גבוהים שנועד לטפל בדפוסי זיכרון וחישוב מורכבים של RL שמבוסס על LLM.

מידע נוסף זמין במאמר בנושא verl.

מטרות

במדריך הזה תלמדו איך להגדיר למידת חיזוק ב-GKE באמצעות verl. לשם כך, תצטרכו לבצע את השלבים הבאים:

  1. מגדירים אשכול GKE עם A4X (GB200 Superchips),‏ A4 (B200 GPUs) או A3 Ultra (H200 GPUs).
  2. מגדירים את KubeRay לניהול אשכול Ray מבוזר.
  3. משתמשים ב-Cloud Storage FUSE כדי לטעון קטגוריה של Cloud Storage בכל הצמתים.
  4. מריצים משימת אימון של GRPO באמצעות verl כדי להתאים את מודל Qwen2.5-32B-Instruct למערך הנתונים GSM8K.

לפני שמתחילים

  • נכנסים לחשבון Google Cloud . אנחנו ממליצים למשתמשים חדשים ב- Google Cloud ליצור חשבון כדי שיוכלו להעריך את הביצועים של המוצרים שלנו בתרחישים מהעולם האמיתי. לקוחות חדשים מקבלים בחינם גם קרדיט בשווי 300$ להרצה, לבדיקה ולפריסה של עומסי העבודה.
  • התקינו את ה-CLI של Google Cloud.

  • אם אתם משתמשים בספק זהויות חיצוני (IdP), קודם אתם צריכים להיכנס ל-CLI של gcloud באמצעות המאגר המאוחד לניהול זהויות.

  • כדי לאתחל את ה-CLI של gcloud, הריצו את הפקודה הבאה:

    gcloud init
  • יוצרים או בוחרים Google Cloud פרויקט.

    תפקידים שנדרשים כדי לבחור או ליצור פרויקט

    • Select a project: כדי לבחור פרויקט לא צריך תפקיד 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 .

  • מוודאים שהחיוב מופעל בפרויקט Google Cloud .

  • מפעילים את ממשקי ה-API הנדרשים, אם יש כאלה שלא מופעלים כבר:

    תפקידים שנדרשים להפעלת ממשקי API

    כדי להפעיל ממשקי API, צריך את ההרשאה serviceusage.services.enable. אם יצרתם את הפרויקט, סביר להניח שכבר יש לכם את ההרשאה הזו דרך התפקיד 'בעלים' (roles/owner). אחרת, תוכלו לקבל את ההרשאה הזו דרך התפקיד 'אדמין בממשק Service Usage' (roles/serviceusage.serviceUsageAdmin). איך מקצים תפקידים

    gcloud services enable container.googleapis.com storage.googleapis.com compute.googleapis.com
  • התקינו את ה-CLI של Google Cloud.

  • אם אתם משתמשים בספק זהויות חיצוני (IdP), קודם אתם צריכים להיכנס ל-CLI של gcloud באמצעות המאגר המאוחד לניהול זהויות.

  • כדי לאתחל את ה-CLI של gcloud, הריצו את הפקודה הבאה:

    gcloud init
  • יוצרים או בוחרים Google Cloud פרויקט.

    תפקידים שנדרשים כדי לבחור או ליצור פרויקט

    • Select a project: כדי לבחור פרויקט לא צריך תפקיד 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 .

  • מוודאים שהחיוב מופעל בפרויקט Google Cloud .

  • מפעילים את ממשקי ה-API הנדרשים, אם יש כאלה שלא מופעלים כבר:

    תפקידים שנדרשים להפעלת ממשקי API

    כדי להפעיל ממשקי API, צריך את ההרשאה serviceusage.services.enable. אם יצרתם את הפרויקט, סביר להניח שכבר יש לכם את ההרשאה הזו דרך התפקיד 'בעלים' (roles/owner). אחרת, תוכלו לקבל את ההרשאה הזו דרך התפקיד 'אדמין בממשק Service Usage' (roles/serviceusage.serviceUsageAdmin). איך מקצים תפקידים

    gcloud services enable container.googleapis.com storage.googleapis.com compute.googleapis.com
  • מעניקים תפקידים לחשבון המשתמש. מריצים את הפקודה הבאה לכל אחד מהתפקידים הבאים ב-IAM: roles/container.admin, roles/iam.serviceAccountAdmin, roles/storage.admin, roles/iam.serviceAccountUser

    gcloud projects add-iam-policy-binding PROJECT_ID --member="user:USER_IDENTIFIER" --role=ROLE

    מחליפים את מה שכתוב בשדות הבאים:

    • ‫PROJECT_ID: מזהה הפרויקט.
    • ‫USER_IDENTIFIER: המזהה של חשבון המשתמש . לדוגמה, myemail@example.com.
    • ‫ROLE: תפקיד ה-IAM שאתם מקצים לחשבון המשתמש.

הכנת הסביבה

במדריך הזה משתמשים ב-Cloud Shell.

  1. עוברים אל Google Cloud המסוף.

  2. בחלק העליון של Google Cloud חלון המסוף, לוחצים על הלחצן Activate Cloud Shell.

  3. מגדירים את משתני הסביבה:

    ‫A4 ו-A3 Ultra

    טייס אוטומטי

    export PROJECT_ID=$(gcloud config get project)
    export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
    export CONTROL_PLANE_REGION="YOUR_REGION"
    export NODE_ZONE="YOUR_ZONE"
    export CLUSTER_NAME="YOUR_CLUSTER_NAME"
    export KSA_NAME="YOUR_KSA_NAME"
    export GS_BUCKET="YOUR_GCS_BUCKET"
    export NAMESPACE="default"
    export GPU_TYPE="YOUR_GPU_TYPE"
    export MACHINE_TYPE="YOUR_MACHINE_TYPE"
    export RESERVATION="YOUR_RESERVATION_NAME"
    export HF_TOKEN="YOUR_HF_TOKEN"

    רגילה

    export PROJECT_ID=$(gcloud config get project)
    export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
    export CONTROL_PLANE_REGION="YOUR_REGION"
    export NODE_ZONE="YOUR_ZONE"
    export CLUSTER_NAME="YOUR_CLUSTER_NAME"
    export KSA_NAME="YOUR_KSA_NAME"
    export GS_BUCKET="YOUR_GCS_BUCKET"
    export NAMESPACE="default"
    export GPU_TYPE="YOUR_GPU_TYPE"
    export MACHINE_TYPE="YOUR_MACHINE_TYPE"
    export CPU_MACHINE_TYPE="YOUR_CPU_MACHINE_TYPE"
    export RESERVATION="YOUR_RESERVATION_NAME"
    export HF_TOKEN="YOUR_HF_TOKEN"
    
    export GVNIC_NETWORK_PREFIX="YOUR_GVNIC_NAME"
    export RDMA_NETWORK_PREFIX="YOUR_RDMA_NAME"

    A4X

    export PROJECT_ID=$(gcloud config get project)
    export PROJECT_NUMBER=$(gcloud projects describe ${PROJECT_ID} --format="value(projectNumber)")
    export CONTROL_PLANE_REGION=YOUR_REGION
    export NODE_ZONE=YOUR_ZONE
    export CLUSTER_NAME=YOUR_CLUSTER_NAME
    export KSA_NAME=YOUR_KSA_NAME
    export GS_BUCKET=YOUR_GCS_BUCKET-${PROJECT_ID}
    export NAMESPACE=default
    export GPU_TYPE=YOUR_GPU_TYPE
    export MACHINE_TYPE=YOUR_MACINE_TYPE
    export RESERVATION=YOUR_RESERVATION_NAME
    export HF_TOKEN=YOUR_HF_TOKEN
    
    # A4X (GB200 Superchips) only variables
    export NUM_GPU_NODES=4
    export VERL_IMAGE=verlai/verl:vllm023.aarch64.dev1
    export VERL_REF=ddbcdb7
    

    מחליפים את הערכים הבאים:

    • ‫YOUR_REGION: האזור ב-Compute Engine למישור הבקרה של אשכול GKE.
    • ‫YOUR_ZONE: האזור שבו הצמתים מוזמנים. מידע נוסף מופיע במאמר בנושא זמינות של GPU.
    • ‫YOUR_CLUSTER_NAME: השם של אשכול GKE.
    • ‫YOUR_KSA_NAME: השם של חשבון השירות של Kubernetes.
    • ‫YOUR_GCS_BUCKET: השם הבסיסי של הקטגוריה ב-Cloud Storage. אין צורך לציין את הקידומת gs://.
    • ‫YOUR_GPU_TYPE: המאיץ שהזמנתם בהזמנת הקיבולת של Compute Engine. הערך חייב להיות אחד מהערכים הבאים:
      • ‫nvidia-gb200: A4X (GB200 Superchips)
      • ‫nvidia-b200: A4 (יחידות GPU מסוג B200)
      • ‫nvidia-h200-141gb: A3 Ultra (מעבדי GPU מדגם H200)
    • ‫YOUR_MACHINE_TYPE: סוג המכונה לשימוש:
      • ‫A4X (GB200 Superchips), use a4x-highgpu-4g.
      • ל-A4 (יחידות GPU מסוג B200), צריך להשתמש בגרסה a4-highgpu-8g ואילך.
      • ל-A3 Ultra (יחידות GPU מדגם H200), צריך להשתמש ב-a3-ultragpu-8g ואילך.
    • ‫YOUR_CPU_MACHINE_TYPE: סוג המכונה של Compute Engine עבור מאגר הצמתים שמוגדר כברירת מחדל ל-CPU. משתמשים בhighmem סוג מכונה עם לפחות 16 מעבדים וירטואליים שנתמך באזור של האשכול, כמו n4-highmem-16 או n2d-highmem-16.
    • YOUR_RESERVATION_NAME: השם של הזמנת הקיבולת.
    • ‫YOUR_HF_TOKEN: האסימון שלכם ב-Hugging Face.
    • כדי להגדיר את הסביבה באשכולות GKE Standard, משתמשים במשתנים הבאים:
      • ‫YOUR_GVNIC_NAME (A4 או A3 Ultra בלבד): הקידומת של שם רשת gVNIC. אפשר להשתמש בכל קידומת שרוצים.
      • ‫YOUR_RDMA_NAME (A4 או A3 Ultra בלבד): הקידומת של רשת הגישה הישירה לזיכרון (RDMA). אתם יכולים להשתמש בכל קידומת שתרצו.
  4. משכפלים את המאגר לדוגמה:

    git clone https://github.com/GoogleCloudSamples/AIHypercomputerSamples.git
  5. עוברים לספריית העבודה של מצב GKE שנבחר:

    ‫A4 ו-A3 Ultra

    טייס אוטומטי

    cd AIHypercomputerSamples/gpu/tuning/verl_rl_autopilot

    רגילה

    cd AIHypercomputerSamples/gpu/tuning/verl_rl_standard

    A4X

    אין צורך לבצע שינוי בספרייה. אפשר להמשיך ישירות לקטע הבא.

הגדרת התשתית

בקטע הזה, יוצרים רשתות VPC רגילות ואת אשכול GKE.

יצירת רשתות ותת-רשתות של RDMA‏ (GKE Standard – A4 ו-A3 Ultra בלבד)

‫A4 ו-A3 Ultra

טייס אוטומטי

הקטע הזה נדרש רק עבור מעבדי GKE Standard A4 ו-A3 Ultra GPU.

אם אתם משתמשים ב-Autopilot, דלגו על הקטע הזה ועברו ישירות אל יצירת אשכול GKE. ‫GKE מקצה באופן אוטומטי את רשתות ה-VPC ורשתות המשנה הנדרשות, ומשתמש ב-DRANET מנוהל של GKE כדי להקצות את המשאבים האלה ל-Pods. לא צריך ליצור ידנית תשתית רשת.

רגילה

  1. יוצרים רשת VPC לממשק gVNIC:

    gcloud compute networks create ${GVNIC_NETWORK_PREFIX}-net \
      --subnet-mode=custom \
      --project=${PROJECT_ID}
    
    gcloud compute networks subnets create ${GVNIC_NETWORK_PREFIX}-sub \
      --network=${GVNIC_NETWORK_PREFIX}-net \
      --region=${CONTROL_PLANE_REGION} \
      --range=192.168.0.0/24 \
      --project=${PROJECT_ID}
    
    gcloud compute firewall-rules create ${GVNIC_NETWORK_PREFIX}-internal \
      --network=${GVNIC_NETWORK_PREFIX}-net \
      --action=ALLOW \
      --rules=tcp:0-65535,udp:0-65535,icmp \
      --source-ranges=192.168.0.0/16 \
      --project=${PROJECT_ID}
  2. יוצרים רשת VPC ל-RDMA:

    gcloud compute networks create ${RDMA_NETWORK_PREFIX}-net \
      --network-profile=${NODE_ZONE}-vpc-roce \
      --subnet-mode=custom \
      --project=${PROJECT_ID}
  3. יוצרים את 8 רשתות המשנה של RDMA עבור 8 יחידות ה-GPU:

    for N in $(seq 0 7); do
      if ! gcloud compute networks subnets describe ${RDMA_NETWORK_PREFIX}-sub-$N --region=${CONTROL_PLANE_REGION} --project=${PROJECT_ID} >/dev/null 2>&1; then
        gcloud compute networks subnets create ${RDMA_NETWORK_PREFIX}-sub-$N \
          --network=${RDMA_NETWORK_PREFIX}-net \
          --region=${CONTROL_PLANE_REGION} \
          --range=192.168.$((N+1)).0/24 \
          --project=${PROJECT_ID} &
      else
        echo "Subnet ${RDMA_NETWORK_PREFIX}-sub-$N already exists."
      fi
    done
    wait

A4X

הקטע הזה נדרש רק עבור מעבדי GKE Standard A4 ו-A3 Ultra GPU.

אם אתם משתמשים ביחידות GPU מסוג A4X ‏ (GB200), דלגו על הקטע הזה ועברו ישירות אל יצירת אשכול GKE. במכשירי GPU מסוג A4X ‏ (GB200) או ב-Autopilot, ‏ GKE יוצר את הרשתות באופן אוטומטי כשמאגר הצמתים משתמש בautoפרופיל רשת של מאיץ. התוכנית Cluster Toolkit מאפשרת את הפרופיל הזה באמצעות הדגל enable_dranet:true.

יצירת אשכול GKE

יוצרים אשכול GKE שמתאים לארכיטקטורת ה-GPU:

‫A4 ו-A3 Ultra

בוחרים את מצב אשכול GKE שבו רוצים להשתמש:

טייס אוטומטי

  1. יצירת אשכול Autopilot:

    gcloud container clusters create-auto ${CLUSTER_NAME} \
        --location=${CONTROL_PLANE_REGION} \
        --release-channel=rapid \
        --enable-ray-operator
  2. מקבלים פרטי כניסה לאשכול:

    gcloud container clusters get-credentials ${CLUSTER_NAME} \
        --location=${CONTROL_PLANE_REGION}

רגילה

  1. יצירת אשכול רגיל:

    gcloud container clusters create ${CLUSTER_NAME} \
        --location=${CONTROL_PLANE_REGION} \
        --enable-dataplane-v2 \
        --workload-pool=${PROJECT_ID}.svc.id.goog \
        --enable-ip-alias \
        --enable-multi-networking \
        --addons=RayOperator,GcsFuseCsiDriver \
        --machine-type=${CPU_MACHINE_TYPE} \
        --num-nodes=1 \
        --min-nodes=1 \
        --max-nodes=5 \
        --enable-autoscaling \
        --project=${PROJECT_ID}
  2. מקבלים פרטי כניסה לאשכול:

    gcloud container clusters get-credentials ${CLUSTER_NAME} \
        --location=${CONTROL_PLANE_REGION} \
        --project=${PROJECT_ID}
  3. יוצרים את מאגר הצמתים של ה-GPU. מאגרי הצמתים האלה משתמשים בהזמנה שלכם כדי להבטיח זמינות. מתחילים עם שני צמתים:

    CMD=(
      gcloud container node-pools create gpu-pool
      --cluster="${CLUSTER_NAME}"
      --location="${CONTROL_PLANE_REGION}"
      --node-locations="${NODE_ZONE}"
      --machine-type="${MACHINE_TYPE}"
      --accelerator="type=${GPU_TYPE},count=8,gpu-driver-version=DEFAULT"
      --enable-autoscaling
      --num-nodes=2
      --total-max-nodes=10
      --additional-node-network="network=${GVNIC_NETWORK_PREFIX}-net,subnetwork=${GVNIC_NETWORK_PREFIX}-sub"
      --additional-node-network="network=${RDMA_NETWORK_PREFIX}-net,subnetwork=${RDMA_NETWORK_PREFIX}-sub-0"
      --additional-node-network="network=${RDMA_NETWORK_PREFIX}-net,subnetwork=${RDMA_NETWORK_PREFIX}-sub-1"
      --additional-node-network="network=${RDMA_NETWORK_PREFIX}-net,subnetwork=${RDMA_NETWORK_PREFIX}-sub-2"
      --additional-node-network="network=${RDMA_NETWORK_PREFIX}-net,subnetwork=${RDMA_NETWORK_PREFIX}-sub-3"
      --additional-node-network="network=${RDMA_NETWORK_PREFIX}-net,subnetwork=${RDMA_NETWORK_PREFIX}-sub-4"
      --additional-node-network="network=${RDMA_NETWORK_PREFIX}-net,subnetwork=${RDMA_NETWORK_PREFIX}-sub-5"
      --additional-node-network="network=${RDMA_NETWORK_PREFIX}-net,subnetwork=${RDMA_NETWORK_PREFIX}-sub-6"
      --additional-node-network="network=${RDMA_NETWORK_PREFIX}-net,subnetwork=${RDMA_NETWORK_PREFIX}-sub-7"
      --project="${PROJECT_ID}"
      --async
      --format="value(name)"
    )
    
    if [ -n "${RESERVATION:-}" ]; then
      CMD+=("--reservation-affinity=specific" "--reservation=${RESERVATION}")
    else
      CMD+=("--reservation-affinity=none")
    fi
    
    if ! OPERATION_ID="$("${CMD[@]}")" || [[ -z "${OPERATION_ID}" ]]; then
      echo "Error: Failed to initiate nodepool creation."
      exit 1
    fi
    
    # Wait for nodepool creation to complete
    if [[ "${NODEPOOL_TIMEOUT:-YOUR_NODEPOOL_TIMEOUT}" == "YOUR_NODEPOOL_TIMEOUT" ]]; then
      NODEPOOL_TIMEOUT="3600"
      echo "Debug: NODEPOOL_TIMEOUT is not set to a custom value, using default: ${NODEPOOL_TIMEOUT}"
    fi
    echo "Waiting for nodepool ${OPERATION_ID} to be created..."
    if ! timeout "${NODEPOOL_TIMEOUT}" gcloud container operations wait "${OPERATION_ID}" --location="${CONTROL_PLANE_REGION}" --project="${PROJECT_ID}"; then
      echo "Error: Nodepool creation timed out or failed after ${NODEPOOL_TIMEOUT} seconds."
    
      # Describe the operation to get more details
      gcloud container operations describe "${OPERATION_ID}" \
        --location="${CONTROL_PLANE_REGION}" \
        --project="${PROJECT_ID}"
      exit 1
    fi
    echo "Nodepool creation completed successfully."
  4. מתקינים את NCCL RDMA installer שמשמש לאשכולות רגילים:

    kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/container-engine-accelerators/refs/heads/master/gpudirect-rdma/nccl-rdma-installer.yaml

A4X

  1. יוצרים אשכול GKE ומאגר צמתים באמצעות תוכנית ה-Cluster Toolkit gke-a4x. תוכנית האב-טיפוס מספקת את אשכול GKE, כולל מאגר הצמתים A4X שקשור להזמנה, רשתות המאיצים (gVNIC נוסף אחד בתוספת ארבע מסילות RDMA) ומנהל ההתקן המנוהל של DRANET שחושף את כרטיסי ה-NIC של CX-7 כמכשירי DRA.

    משתמשים בהוראות הפריסה של התוכנית כדי להגדיר את הפרמטרים (כמו PROJECT_ID, CONTROL_PLANE_REGION, NODE_ZONE, הזמנה ו-NUM_GPU_NODES), ואז פורסים את האשכול. לחלופין, אפשר לפעול לפי המדריך ליצירת אשכול GKE ב-A4X כדי ליצור אשכול באופן ידני.

    1. מקבלים פרטי כניסה לאשכול:
    gcloud container clusters get-credentials ${CLUSTER_NAME} --location=${CONTROL_PLANE_REGION}
    
  2. מוודאים שהאשכול חושף כרטיסי רשת של RDMA דרך DRA:

    kubectl get deviceclasses
    

    הפלט חייב לכלול את המחרוזת mrdma.google.com.

  3. מוודאים שצמתי A4X קיימים:

    kubectl get nodes -l cloud.google.com/gke-accelerator=nvidia-gb200
    
  4. מתקינים את הפלאגין gIB NCCL (גרסת A4X):

    kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/container-engine-accelerators/master/gpudirect-rdma/nccl-rdma-installer-a4x.yaml
    
  5. מתקינים את הדרייבר NVIDIA DRA, שמספק ערוצי ComputeDomain (IMEX) ל-NVLink מרובה צמתים:

    helm repo add nvidia https://helm.ngc.nvidia.com/nvidia && helm repo update
    kubectl create namespace nvidia-dra-driver-gpu
    kubectl apply -f - <<EOF
    apiVersion: v1
    kind: ResourceQuota
    metadata:
      name: nvidia-dra-driver-gpu-quota
      namespace: nvidia-dra-driver-gpu
    spec:
      hard:
        pods: "$((2 * NUM_GPU_NODES + 1))"
      scopeSelector:
        matchExpressions:
        - operator: In
          scopeName: PriorityClass
          values:
          - system-node-critical
          - system-cluster-critical
    EOF
    helm upgrade --install nvidia-dra-driver-gpu nvidia/nvidia-dra-driver-gpu \
      --version=25.3.1 --namespace nvidia-dra-driver-gpu \
      --set nvidiaDriverRoot=/home/kubernetes/bin/nvidia \
      --set resources.gpus.enabled=false \
      --set kubeletPlugin.tolerations[0].key=nvidia.com/gpu \
      --set kubeletPlugin.tolerations[0].operator=Exists \
      --set kubeletPlugin.tolerations[1].key=kubernetes.io/arch \
      --set kubeletPlugin.tolerations[1].operator=Exists
    
  6. מתקינים את האופרטור KubeRay, בהיקף של מרחב השמות של עומס העבודה:

    kubectl create namespace ${NAMESPACE}
    helm repo add kuberay https://ray-project.github.io/kuberay-helm/ && helm repo update
    helm upgrade --install kuberay-operator kuberay/kuberay-operator \
      --namespace ${NAMESPACE} \
      --set singleNamespaceInstall=true --set "watchNamespace={${NAMESPACE}}"
    

הגדרת מיפויי רשת (GKE Standard – A4 ו-A3 Ultra בלבד)

‫A4 ו-A3 Ultra

טייס אוטומטי

השלב הזה נדרש להגדרות GPU ב-GKE Standard (רק A4 ו-A3 Ultra). אם אתם משתמשים ב-A4X‏ (GB200), ‏ GKE מנהל את ממשקי הרשת באופן אוטומטי, ולכן אפשר לדלג על הקטע הזה.

רגילה

  1. בודקים את המניפסט network-mapping.yaml:

    apiVersion: networking.gke.io/v1
    kind: GKENetworkParamSet
    metadata:
      name: gvnic-1
    spec:
      vpc: ${GVNIC_NETWORK_PREFIX}-net
      vpcSubnet: ${GVNIC_NETWORK_PREFIX}-sub
      deviceMode: NetDevice
    ---
    apiVersion: networking.gke.io/v1
    kind: Network
    metadata:
      name: gvnic-1
    spec:
      type: "Device"
      parametersRef:
        group: networking.gke.io
        kind: GKENetworkParamSet
        name: gvnic-1
    ---
    apiVersion: networking.gke.io/v1
    kind: GKENetworkParamSet
    metadata:
      name: rdma-0
    spec:
      vpc: ${RDMA_NETWORK_PREFIX}-net
      vpcSubnet: ${RDMA_NETWORK_PREFIX}-sub-0
      deviceMode: RDMA
    ---
    apiVersion: networking.gke.io/v1
    kind: Network
    metadata:
      name: rdma-0
    spec:
      type: "Device"
      parametersRef:
        group: networking.gke.io
        kind: GKENetworkParamSet
        name: rdma-0
    ---
    apiVersion: networking.gke.io/v1
    kind: GKENetworkParamSet
    metadata:
      name: rdma-1
    spec:
      vpc: ${RDMA_NETWORK_PREFIX}-net
      vpcSubnet: ${RDMA_NETWORK_PREFIX}-sub-1
      deviceMode: RDMA
    ---
    apiVersion: networking.gke.io/v1
    kind: Network
    metadata:
      name: rdma-1
    spec:
      type: "Device"
      parametersRef:
        group: networking.gke.io
        kind: GKENetworkParamSet
        name: rdma-1
    ---
    apiVersion: networking.gke.io/v1
    kind: GKENetworkParamSet
    metadata:
      name: rdma-2
    spec:
      vpc: ${RDMA_NETWORK_PREFIX}-net
      vpcSubnet: ${RDMA_NETWORK_PREFIX}-sub-2
      deviceMode: RDMA
    ---
    apiVersion: networking.gke.io/v1
    kind: Network
    metadata:
      name: rdma-2
    spec:
      type: "Device"
      parametersRef:
        group: networking.gke.io
        kind: GKENetworkParamSet
        name: rdma-2
    ---
    apiVersion: networking.gke.io/v1
    kind: GKENetworkParamSet
    metadata:
      name: rdma-3
    spec:
      vpc: ${RDMA_NETWORK_PREFIX}-net
      vpcSubnet: ${RDMA_NETWORK_PREFIX}-sub-3
      deviceMode: RDMA
    ---
    apiVersion: networking.gke.io/v1
    kind: Network
    metadata:
      name: rdma-3
    spec:
      type: "Device"
      parametersRef:
        group: networking.gke.io
        kind: GKENetworkParamSet
        name: rdma-3
    ---
    apiVersion: networking.gke.io/v1
    kind: GKENetworkParamSet
    metadata:
      name: rdma-4
    spec:
      vpc: ${RDMA_NETWORK_PREFIX}-net
      vpcSubnet: ${RDMA_NETWORK_PREFIX}-sub-4
      deviceMode: RDMA
    ---
    apiVersion: networking.gke.io/v1
    kind: Network
    metadata:
      name: rdma-4
    spec:
      type: "Device"
      parametersRef:
        group: networking.gke.io
        kind: GKENetworkParamSet
        name: rdma-4
    ---
    apiVersion: networking.gke.io/v1
    kind: GKENetworkParamSet
    metadata:
      name: rdma-5
    spec:
      vpc: ${RDMA_NETWORK_PREFIX}-net
      vpcSubnet: ${RDMA_NETWORK_PREFIX}-sub-5
      deviceMode: RDMA
    ---
    apiVersion: networking.gke.io/v1
    kind: Network
    metadata:
      name: rdma-5
    spec:
      type: "Device"
      parametersRef:
        group: networking.gke.io
        kind: GKENetworkParamSet
        name: rdma-5
    ---
    apiVersion: networking.gke.io/v1
    kind: GKENetworkParamSet
    metadata:
      name: rdma-6
    spec:
      vpc: ${RDMA_NETWORK_PREFIX}-net
      vpcSubnet: ${RDMA_NETWORK_PREFIX}-sub-6
      deviceMode: RDMA
    ---
    apiVersion: networking.gke.io/v1
    kind: Network
    metadata:
      name: rdma-6
    spec:
      type: "Device"
      parametersRef:
        group: networking.gke.io
        kind: GKENetworkParamSet
        name: rdma-6
    ---
    apiVersion: networking.gke.io/v1
    kind: GKENetworkParamSet
    metadata:
      name: rdma-7
    spec:
      vpc: ${RDMA_NETWORK_PREFIX}-net
      vpcSubnet: ${RDMA_NETWORK_PREFIX}-sub-7
      deviceMode: RDMA
    ---
    apiVersion: networking.gke.io/v1
    kind: Network
    metadata:
      name: rdma-7
    spec:
      type: "Device"
      parametersRef:
        group: networking.gke.io
        kind: GKENetworkParamSet
        name: rdma-7
  2. החלת המניפסט:

    envsubst < network-mapping.yaml | kubectl apply -f -

A4X

השלב הזה נדרש להגדרות GPU ב-GKE Standard (רק A4 ו-A3 Ultra). אם אתם משתמשים ב-A4X ‏ (GB200), ‏ GKE מנהל את ממשקי הרשת באופן אוטומטי, ולכן אפשר לדלג על הקטע הזה.

הכנת הנתונים והאחסון

הגדרת משאבים של Cloud Storage ו-Kubernetes:

  1. יוצרים קטגוריה של Cloud Storage:

    gcloud storage buckets create "gs://${GS_BUCKET}" \
      --location="${CONTROL_PLANE_REGION}" \
      --project="${PROJECT_ID}" \
      --enable-hierarchical-namespace \
      --uniform-bucket-level-access
  2. יוצרים חשבון שירות של Kubernetes‏ (KSA) ומקשרים אותו לדלי:

    kubectl create serviceaccount ${KSA_NAME} -n ${NAMESPACE}
    gcloud storage buckets add-iam-policy-binding "gs://${GS_BUCKET}" \
      --member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${PROJECT_ID}.svc.id.goog/subject/ns/${NAMESPACE}/sa/${KSA_NAME}" \
      --role="roles/storage.objectUser"
  3. יוצרים את ה-Secret עבור Hugging Face:

    kubectl create secret generic hf-secret --from-literal=hf_token=${HF_TOKEN}
  4. בודקים את המניפסט gcsfuse-storage.yaml:

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: training-bucket-pv
    spec:
      accessModes:
      -   ReadWriteMany
      capacity:
        storage: 768Gi
      persistentVolumeReclaimPolicy: Delete
      storageClassName: gcsfuse-sc
      mountOptions:
      -   implicit-dirs
      -   metadata-cache:negative-ttl-secs:0
      -   metadata-cache:ttl-secs:0
      -   metadata-cache:stat-cache-max-size-mb:-1
      -   metadata-cache:type-cache-max-size-mb:-1
      -   file-cache:max-size-mb:-1
      -   file-cache:cache-file-for-range-read:true
      -   file-cache:enable-parallel-downloads:true
      -   read_ahead_kb=1024
      -   write:enable-streaming-writes:true
      -   write:global-max-blocks:200000
      csi:
        driver: gcsfuse.csi.storage.gke.io
        volumeHandle: ${GS_BUCKET}
        volumeAttributes:
          skipCSIBucketAccessCheck: "true"
          gcsfuseMetadataPrefetchOnMount: "true"
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: training-bucket-pvc
    spec:
      accessModes:
      -   ReadWriteMany
      resources:
        requests:
          storage: 768Gi
      storageClassName: gcsfuse-sc
  5. החלת המניפסט:

    envsubst < gcsfuse-storage.yaml | kubectl apply -f - 

הגדרת DRANET

מגדירים את DRANET:

‫A4 ו-A3 Ultra

טייס אוטומטי

  1. יוצרים את מניפסט ComputeClass:

    echo "Generating computeclass-dranet.yaml..."
    cat <<EOF > computeclass-dranet.yaml
    apiVersion: cloud.google.com/v1
    kind: ComputeClass
    metadata:
      name: dranet-a4-computeclass-v3
    spec:
      nodePoolAutoCreation:
        enabled: true
      nodePoolConfig:
        dra:
          networking:
            enabled: true
      priorities:
      - machineType: ${MACHINE_TYPE}
        gpu:
          count: 8
          type: ${GPU_TYPE}
        acceleratorNetworkProfile: auto
    EOF
    
    if [ -n "${RESERVATION:-}" ]; then
      echo "Adding reservation affinity for ${RESERVATION} to ComputeClass..."
      cat <<EOF >> computeclass-dranet.yaml
        reservations:
          affinity: Specific
          specific:
          - name: ${RESERVATION}
            project: ${PROJECT_ID}
    EOF
    fi
  2. מחילים את computeclass-dranet.yaml המניפסט (שנוצר בשלב הקודם) ואת resourceclaim-dranet.yaml המניפסט (שנכלל במאגר הדוגמאות):

    echo "Applying ComputeClass..."
    kubectl apply -f computeclass-dranet.yaml
    
    echo "Applying ResourceClaimTemplate..."
    kubectl apply -f resourceclaim-dranet.yaml

רגילה

אין צורך בהגדרה של DRANET. אפשר להמשיך ישירות לקטע הבא.

A4X

המשתנה DRANET מוגדר על ידי Cluster Toolkit. אפשר להמשיך ישירות לקטע הבא.

הכנת המודל והנתונים

מאכלסים את הקטגוריה של Cloud Storage במשקלי המודל ובמערכי הנתונים. אפשר להריץ את הפקודות האלה באופן מקומי או ב-Pod של GKE כדי לאכלס את הדלי:

‫A4 ו-A3 Ultra

טייס אוטומטי

  1. בודקים את קובץ המניפסט data-prep-job.yaml שבו מוגדרת משימת הכנת הנתונים:

    apiVersion: batch/v1
    kind: Job
    metadata:
      name: data-prep-job
      namespace: ${NAMESPACE}
    spec:
      template:
        metadata:
          annotations:
            gke-gcsfuse/volumes: "true"
            gke-gcsfuse/cpu-limit: "4"
            gke-gcsfuse/memory-limit: "8Gi"
            gke-gcsfuse/ephemeral-storage-limit: "50Gi"
        spec:
          serviceAccountName: ${KSA_NAME}
          restartPolicy: OnFailure
          nodeSelector:
            cloud.google.com/compute-class: Performance
            cloud.google.com/machine-family: c3
          containers:
          - name: prep-data
            image: verlai/verl:vllm011.latest
            resources:
              requests:
                cpu: "8"
                memory: "16Gi"
                ephemeral-storage: "50Gi"
              limits:
                cpu: "8"
                memory: "16Gi"
                ephemeral-storage: "50Gi"
            env:
            - name: HF_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-secret
                  key: hf_token
            - name: HF_HOME
              value: /data/.cache/huggingface
            - name: HF_HUB_DISABLE_XET
              value: "1"
            command: ["/bin/bash", "-c"]
            args:
            - |
              set -euo pipefail
    
              # Clone verl to GCS (for worker pods)
              if [ ! -d "/data/verl" ]; then
                echo "Cloning verl to GCS..."
                git clone --depth 1 --branch v0.6.1 https://github.com/volcengine/verl.git /data/verl
    
                echo "Installing verl on GCS to generate metadata..."
                pip3 install --no-deps -e /data/verl
              else
                echo "verl already exists in /data/verl"
              fi
    
              # Clone verl locally for fast installation
              echo "Cloning verl locally..."
              git clone --depth 1 --branch v0.6.1 https://github.com/volcengine/verl.git /tmp/verl
    
              # Install verl package from local clone
              echo "Installing verl package..."
              pip3 install --no-cache-dir --no-deps /tmp/verl
              rm -rf /tmp/verl
    
              # Preprocess GSM8K
              if [ ! -d "/data/gsm8k" ]; then
                echo "Preprocessing GSM8K..."
                python /data/verl/examples/data_preprocess/gsm8k.py --local_save_dir /data/gsm8k
              else
                echo "GSM8K data already exists in /data/gsm8k"
              fi
    
              # Download model sequentially to support GCS FUSE direct streaming writes
              if [ ! -d "/data/Qwen2.5-32B-Instruct" ]; then
                echo "Downloading Qwen2.5-32B-Instruct..."
                huggingface-cli download Qwen/Qwen2.5-32B-Instruct --local-dir /data/Qwen2.5-32B-Instruct --local-dir-use-symlinks False
              else
                echo "Model Qwen2.5-32B-Instruct already exists in /data/Qwen2.5-32B-Instruct"
              fi
    
              echo "Data preparation complete!"
            volumeMounts:
            - name: training-bucket-vol
              mountPath: /data
          volumes:
          - name: training-bucket-vol
            persistentVolumeClaim:
              claimName: training-bucket-pvc
  2. מפעילים את המשימה:

    envsubst < "data-prep-job.yaml" | kubectl apply -f -
  3. בודקים את הסטטוס של ה-Pod:

    watch kubectl get pods -n ${NAMESPACE}

    אחרי שהסטטוס של data-prep-job Pod משתנה ל-Running, לוחצים על Control+C כדי להפסיק את הצפייה.

  4. עוקבים אחרי העבודה:

    kubectl logs -n ${NAMESPACE} -l job-name=data-prep-job -f

    הפקודה kubectl logs מחזירה פלט מינימלי או ללא פלט עד שה-Pod מגיע לסטטוס Running.

רגילה

  1. בודקים את קובץ המניפסט data-prep-job.yaml שבו מוגדרת משימת הכנת הנתונים:

    apiVersion: batch/v1
    kind: Job
    metadata:
      name: data-prep-job
      namespace: ${NAMESPACE}
    spec:
      template:
        metadata:
          annotations:
            gke-gcsfuse/volumes: "true"
            gke-gcsfuse/cpu-limit: "2"
            gke-gcsfuse/memory-limit: "4Gi"
            gke-gcsfuse/ephemeral-storage-limit: "20Gi"
        spec:
          serviceAccountName: ${KSA_NAME}
          restartPolicy: OnFailure
          nodeSelector:
            cloud.google.com/gke-nodepool: "default-pool"
          containers:
          - name: prep-data
            image: verlai/verl:vllm011.latest
            resources:
              requests:
                cpu: "4"
                memory: "8Gi"
                ephemeral-storage: "10Gi"
              limits:
                cpu: "4"
                memory: "8Gi"
                ephemeral-storage: "10Gi"
            env:
            - name: HF_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-secret
                  key: hf_token
            - name: HF_HOME
              value: /data/.cache/huggingface
            - name: HF_HUB_DISABLE_XET
              value: "1"
            command: ["/bin/bash", "-c"]
            args:
            - |
              set -euo pipefail
    
              # Clone verl to GCS (for worker pods)
              if [ ! -d "/data/verl" ]; then
                echo "Cloning verl to GCS..."
                git clone --branch v0.6.1 https://github.com/volcengine/verl.git /data/verl
              else
                echo "verl already exists in /data/verl"
              fi
    
              # Clone verl locally for fast installation
              echo "Cloning verl locally..."
              git clone --branch v0.6.1 https://github.com/volcengine/verl.git /tmp/verl
    
              # Install verl package from local clone
              echo "Installing verl package..."
              pip3 install --no-cache-dir --no-deps /tmp/verl
              rm -rf /tmp/verl
    
              # Preprocess GSM8K
              if [ ! -d "/data/gsm8k" ]; then
                echo "Preprocessing GSM8K..."
                python /data/verl/examples/data_preprocess/gsm8k.py --local_save_dir /data/gsm8k
              else
                echo "GSM8K data already exists in /data/gsm8k"
              fi
    
              # Download model
              if [ ! -d "/data/Qwen2.5-32B-Instruct" ]; then
                echo "Downloading Qwen2.5-32B-Instruct..."
                huggingface-cli download Qwen/Qwen2.5-32B-Instruct --local-dir /data/Qwen2.5-32B-Instruct --local-dir-use-symlinks False
              else
                echo "Model Qwen2.5-32B-Instruct already exists in /data/Qwen2.5-32B-Instruct"
              fi
    
              echo "Data preparation complete!"
            volumeMounts:
            - name: training-bucket-vol
              mountPath: /data
          volumes:
          - name: training-bucket-vol
            persistentVolumeClaim:
              claimName: training-bucket-pvc
  2. הפעלת המשימה:

    envsubst < data-prep-job.yaml | kubectl apply -f -
  3. מעקב אחרי העבודה:

    kubectl logs -n ${NAMESPACE} -l job-name=data-prep-job -f

A4X

  1. משכפלים את מאגר verl, מכינים את הסביבה הווירטואלית ומעבדים את קבוצת הנתונים GSM8K:

    git clone https://github.com/volcengine/verl.git
    git -C verl checkout ${VERL_REF}
    
    VENV_DIR=.venv
    python3 -m venv $VENV_DIR
    source $VENV_DIR/bin/activate
    pip install verl
    
    python verl/examples/data_preprocess/gsm8k.py --local_save_dir ~/data/gsm8k
    
  2. מורידים את המודל Qwen2.5-32B-Instruct באמצעות Hugging Face CLI (ההורדה הזו דורשת כ-66 GB של שטח דיסק):

    hf download Qwen/Qwen2.5-32B-Instruct --local-dir Qwen2.5-32B-Instruct
    
  3. מעלים את המודל, הנתונים וקוד ה-VERL לקטגוריה של Cloud Storage:

    gcloud storage cp --recursive verl gs://${GS_BUCKET}/verl
    gcloud storage cp --recursive Qwen2.5-32B-Instruct gs://${GS_BUCKET}/Qwen2.5-32B-Instruct
    gcloud storage cp --recursive ~/data/gsm8k/* gs://${GS_BUCKET}/gsm8k/
    

פריסת משאב בהתאמה אישית מסוג RayCluster

פריסת משאב מותאם אישית של RayCluster, שמורכב מ-Pod אחד של מערכת ראשית ומכמה Pods של עובדים עם תמיכה ב-GPU.

‫A4 ו-A3 Ultra

בוחרים את מצב האשכול של GKE שבו השתמשתם כדי ליצור את האשכול:

טייס אוטומטי

  1. בודקים את עומס העבודה של RayCluster:

    apiVersion: ray.io/v1
    kind: RayCluster
    metadata:
      name: b200-ray-cluster-dranet
    spec:
      rayVersion: '2.47.0'
      headGroupSpec:
        rayStartParams:
          dashboard-host: '0.0.0.0'
        template:
          metadata:
            annotations:
              gke-gcsfuse/volumes: "true"
          spec:
            serviceAccountName: ${KSA_NAME}
            nodeSelector:
              cloud.google.com/gke-spot: "true"
              cloud.google.com/machine-family: "c2"
              cloud.google.com/compute-class: Performance
            containers:
            - name: ray-head
              image: verlai/verl:vllm011.latest 
              ports:
                - containerPort: 6379
                  name: gcs-server
                - containerPort: 8265
                  name: dashboard
                - containerPort: 10001
                  name: client
              resources:
                limits:
                  cpu: "12"
                  memory: "32G"
                  ephemeral-storage: "9Gi"
                requests:
                  cpu: "12"
                  memory: "32G"
                  ephemeral-storage: "9Gi"
              volumeMounts:
                - mountPath: /tmp/ray
                  name: ray-logs
                - name: training-bucket-vol
                  mountPath: /data
            volumes:
              - name: ray-logs
                emptyDir: {}
              - name: training-bucket-vol
                persistentVolumeClaim:
                  claimName: training-bucket-pvc
      workerGroupSpecs:
      - replicas: 2
        minReplicas: 2
        maxReplicas: 2
        groupName: gpu-group
        rayStartParams:
          num-cpus: "220"
        template:
          metadata:
            annotations:
              gke-gcsfuse/volumes: "true"
          spec:
            resourceClaims:
              - name: rdma-claim
                resourceClaimTemplateName: all-mrdma
            initContainers:
            - name: verl-setup
              image: verlai/verl:vllm011.latest
              command: ["/bin/bash", "-c"]
              args:
                - |
                  echo "Installing GIB plugins..."
                  curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | gpg --dearmor -o /etc/apt/trusted.gpg.d/cloud.google.gpg
                  echo 'deb https://packages.cloud.google.com/apt gpudirect-gib-apt main' | tee /etc/apt/sources.list.d/nccl-gib.list
                  apt-get update && apt-get install -y nccl-gib-plugins
    
                  echo "Local install skipped (handled by data prep)"
              volumeMounts:
              - name: training-bucket-vol
                mountPath: /data
              - name: gib
                mountPath: /usr/local/gib
            serviceAccountName: ${KSA_NAME}
            nodeSelector:
              cloud.google.com/compute-class: dranet-a4-computeclass-v3
            tolerations:
              - key: "nvidia.com/gpu"
                operator: "Exists"
                effect: "NoSchedule"
            containers:
            - name: ray-worker
              image: verlai/verl:vllm011.latest
              env:
               - name: LD_LIBRARY_PATH
                 value: /usr/local/nvidia/lib64:/usr/local/gib/lib64
              resources:
                limits:
                  cpu: "180"
                  memory: "2000Gi"
                  nvidia.com/gpu: "8"
                  ephemeral-storage: "1000Gi"
                requests:
                  cpu: "180"
                  memory: "2000Gi"
                  nvidia.com/gpu: "8"
                  ephemeral-storage: "1000Gi"
                claims:
                - name: rdma-claim
              volumeMounts:
              - name: gib
                mountPath: /usr/local/gib
                readOnly: true
              - name: shared-memory
                mountPath: /dev/shm
              - name: ray-tmp-storage
                mountPath: /tmp
              - name: training-bucket-vol
                mountPath: /data
            volumes:
            - name: gib
              emptyDir: {}
            - name: shared-memory
              emptyDir:
                medium: "Memory"
                sizeLimit: 250Gi 
            - name: ray-tmp-storage
              emptyDir: {}
            - name: training-bucket-vol
              persistentVolumeClaim:
                claimName: training-bucket-pvc
  2. מחילים את RayCluster:

    envsubst < "ray-cluster-auto-dranet.yaml" | kubectl apply -n "${NAMESPACE}" -f -

רגילה

  1. בודקים את עומס העבודה של RayCluster:

    apiVersion: ray.io/v1
    kind: RayCluster
    metadata:
      name: b200-ray-cluster
      annotations:
    spec:
      rayVersion: '2.47.0'
      headGroupSpec:
        rayStartParams:
          dashboard-host: '0.0.0.0'
        template:
          metadata:
            annotations:
              gke-gcsfuse/volumes: "true"
          spec:
            serviceAccountName: ${KSA_NAME}
            nodeSelector:
              cloud.google.com/gke-nodepool: "default-pool"
            containers:
            - name: ray-head
              image: verlai/verl:vllm011.latest 
              ports:
                - containerPort: 6379
                  name: gcs-server
                - containerPort: 8265
                  name: dashboard
                - containerPort: 10001
                  name: client
              resources:
                limits:
                  cpu: "12"
                  memory: "32G"
                  ephemeral-storage: "9Gi"
                requests:
                  cpu: "12"
                  memory: "32G"
                  ephemeral-storage: "9Gi"
              volumeMounts:
                - mountPath: /tmp/ray
                  name: ray-logs
                - name: training-bucket-vol
                  mountPath: /data
            volumes:
              - name: ray-logs
                emptyDir: {}
              - name: training-bucket-vol
                persistentVolumeClaim:
                  claimName: training-bucket-pvc
      workerGroupSpecs:
      - replicas: 2
        minReplicas: 2
        maxReplicas: 2
        groupName: gpu-group
        rayStartParams:
          num-cpus: "220"
        template:
          metadata:
            annotations:
              gke-gcsfuse/volumes: "true"
              networking.gke.io/default-interface: 'eth0'
              networking.gke.io/interfaces: |
                [
                  {"interfaceName":"eth0","network":"default"},
                  {"interfaceName":"eth1","network":"gvnic-1"},
                  {"interfaceName":"eth2","network":"rdma-0"},
                  {"interfaceName":"eth3","network":"rdma-1"},
                  {"interfaceName":"eth4","network":"rdma-2"},
                  {"interfaceName":"eth5","network":"rdma-3"},
                  {"interfaceName":"eth6","network":"rdma-4"},
                  {"interfaceName":"eth7","network":"rdma-5"},
                  {"interfaceName":"eth8","network":"rdma-6"},
                  {"interfaceName":"eth9","network":"rdma-7"}
                ]
          spec:
            initContainers:
            - name: verl-setup
              image: verlai/verl:vllm011.latest
              command: ["/bin/bash", "-c"]
              args:
                - |
                  echo "Performing local editable install..."
                  cd /data/verl && pip3 install --no-deps -e .
              volumeMounts:
              - name: training-bucket-vol
                mountPath: /data
            serviceAccountName: ${KSA_NAME}
            nodeSelector:
              cloud.google.com/gke-accelerator: ${GPU_TYPE}
            tolerations:
              - key: "nvidia.com/gpu"
                operator: "Exists"
                effect: "NoSchedule"
            containers:
            - name: ray-worker
              image: verlai/verl:vllm011.latest
              env:
               - name: LD_LIBRARY_PATH
                 value: /usr/local/nvidia/lib64
              resources:
                limits:
                  cpu: "220"
                  memory: "2800Gi"
                  nvidia.com/gpu: "8"
                  ephemeral-storage: "1000Gi"
                requests:
                  cpu: "220"
                  memory: "2800Gi"
                  nvidia.com/gpu: "8"
                  ephemeral-storage: "1000Gi"
              volumeMounts:
              - name: nvidia
                mountPath: /usr/local/nvidia
              - name: gib
                mountPath: /usr/local/gib
              - name: shared-memory
                mountPath: /dev/shm
              - name: ray-tmp-storage
                mountPath: /tmp
              - name: training-bucket-vol
                mountPath: /data
            volumes:
            - name: gib
              hostPath:
                path: /home/kubernetes/bin/gib
            - name: nvidia
              hostPath:
                path: /home/kubernetes/bin/nvidia
            - name: lib64
              hostPath:
                path: /lib64
            - name: shared-memory
              emptyDir:
                medium: "Memory"
                sizeLimit: 250Gi 
            - name: sys
              hostPath:
                path: /sys
            - name: proc-sys
              hostPath:
                path: /proc/sys
            - name: ray-tmp-storage
              emptyDir: {}
            - name: training-bucket-vol
              persistentVolumeClaim:
                claimName: training-bucket-pvc
  2. מחילים את RayCluster:

    envsubst < "ray-cluster-standard.yaml" | kubectl apply -f -

A4X

  1. יוצרים את RDMA ResourceClaimTemplate ואת NVIDIA ComputeDomain. כל פוד של עובד GPU תופס ארבעה כרטיסי רשת של RDMA (כל המסילות של הצומת שלו) וערוץ IMEX אחד. שומרים את קובץ המניפסט הבא ב-compute-domain-a4x.yaml:

    apiVersion: resource.k8s.io/v1
    kind: ResourceClaimTemplate
    metadata:
      name: verl-rdma-nic
      namespace: ${NAMESPACE}
    spec:
      spec:
        devices:
          requests:
          - name: nic
            exactly:
              deviceClassName: mrdma.google.com
              allocationMode: ExactCount
              count: 1
    ---
    apiVersion: resource.nvidia.com/v1beta1
    kind: ComputeDomain
    metadata:
      name: verl-compute-domain
      namespace: ${NAMESPACE}
    spec:
      numNodes: ${NUM_GPU_NODES}
      channel:
        resourceClaimTemplate:
          name: verl-compute-domain-channel
    
  2. החלת המניפסט:

    kubectl apply -f compute-domain-a4x.yaml
    
  3. פורסים את RayCluster. ה-Pod של Ray head פועל בצומת A4X בלי לבקש GPU (כי התמונה היא רק arm64). שומרים את ההגדרות הבאות ב-ray-cluster-a4x.yaml:

    apiVersion: ray.io/v1
    kind: RayCluster
    metadata:
      name: gb200-ray-cluster
      namespace: ${NAMESPACE}
    spec:
      rayVersion: '2.49.0'
      headGroupSpec:
        rayStartParams:
          dashboard-host: '0.0.0.0'
          num-cpus: "0"
        template:
          metadata:
            annotations:
              gke-gcsfuse/volumes: "true"
          spec:
            serviceAccountName: ${KSA_NAME}
            nodeSelector:
              cloud.google.com/gke-accelerator: nvidia-gb200
            tolerations:
            - key: nvidia.com/gpu
              operator: Exists
              effect: NoSchedule
            - key: kubernetes.io/arch
              operator: Exists
              effect: NoSchedule
            containers:
            - name: ray-head
              image: ${VERL_IMAGE}
              lifecycle:
                postStart:
                  exec:
                    command:
                    - /bin/bash
                    - -c
                    - pip3 install --quiet TransferQueue==0.1.8
              ports:
              - containerPort: 6379
                name: gcs-server
              - containerPort: 8265
                name: dashboard
              - containerPort: 10001
                name: client
              resources:
                limits:
                  cpu: "12"
                  memory: 32Gi
                  ephemeral-storage: 20Gi
                requests:
                  cpu: "12"
                  memory: 32Gi
                  ephemeral-storage: 20Gi
              volumeMounts:
              - mountPath: /tmp/ray
                name: ray-logs
              - name: training-bucket-vol
                mountPath: /data
            volumes:
            - name: ray-logs
              emptyDir: {}
            - name: training-bucket-vol
              persistentVolumeClaim:
                claimName: training-bucket-pvc
      workerGroupSpecs:
      - replicas: ${NUM_GPU_NODES}
        minReplicas: ${NUM_GPU_NODES}
        maxReplicas: ${NUM_GPU_NODES}
        groupName: gpu-group
        rayStartParams:
          num-cpus: "120"
        template:
          metadata:
            annotations:
              gke-gcsfuse/volumes: "true"
          spec:
            serviceAccountName: ${KSA_NAME}
            nodeSelector:
              cloud.google.com/gke-accelerator: nvidia-gb200
            affinity:
              podAntiAffinity:
                requiredDuringSchedulingIgnoredDuringExecution:
                - labelSelector:
                    matchLabels:
                      ray.io/group: gpu-group
                  topologyKey: kubernetes.io/hostname
            tolerations:
            - key: nvidia.com/gpu
              operator: Exists
              effect: NoSchedule
            - key: kubernetes.io/arch
              operator: Exists
              effect: NoSchedule
            containers:
            - name: ray-worker
              image: ${VERL_IMAGE}
              lifecycle:
                postStart:
                  exec:
                    command:
                    - /bin/bash
                    - -c
                    - pip3 install --quiet TransferQueue==0.1.8
              env:
              - name: LD_LIBRARY_PATH
                value: /usr/local/nvidia/lib64
              resources:
                limits:
                  cpu: "120"
                  memory: 600Gi
                  nvidia.com/gpu: "4"
                  ephemeral-storage: 500Gi
                requests:
                  cpu: "120"
                  memory: 600Gi
                  nvidia.com/gpu: "4"
                  ephemeral-storage: 500Gi
                claims:
                - name: rdma-nic-0
                - name: rdma-nic-1
                - name: rdma-nic-2
                - name: rdma-nic-3
                - name: compute-domain-channel
              volumeMounts:
              - name: nvidia
                mountPath: /usr/local/nvidia
              - name: gib
                mountPath: /usr/local/gib
              - name: shared-memory
                mountPath: /dev/shm
              - name: ray-tmp-storage
                mountPath: /tmp
              - name: training-bucket-vol
                mountPath: /data
            resourceClaims:
            - name: rdma-nic-0
              resourceClaimTemplateName: verl-rdma-nic
            - name: rdma-nic-1
              resourceClaimTemplateName: verl-rdma-nic
            - name: rdma-nic-2
              resourceClaimTemplateName: verl-rdma-nic
            - name: rdma-nic-3
              resourceClaimTemplateName: verl-rdma-nic
            - name: compute-domain-channel
              resourceClaimTemplateName: verl-compute-domain-channel
            volumes:
            - name: gib
              hostPath:
                path: /home/kubernetes/bin/gib
            - name: nvidia
              hostPath:
                path: /home/kubernetes/bin/nvidia
            - name: shared-memory
              emptyDir:
                medium: Memory
                sizeLimit: 200Gi
            - name: ray-tmp-storage
              emptyDir: {}
            - name: training-bucket-vol
              persistentVolumeClaim:
                claimName: training-bucket-pvc
    
  4. מחילים את מניפסט RayCluster:

    envsubst < ray-cluster-a4x.yaml | kubectl apply -f -
    
  5. מחכים עד שמצב של Pod ראשי אחד וארבעה Pods של עובדים יהיה Running:

    kubectl get pods -w
    

הפעלת משימת GRPO

מגדירים את משימת האימון של למידת חיזוקים ושולחים אותה:

‫A4 ו-A3 Ultra

בוחרים את מצב האשכול של GKE שבו השתמשתם כדי ליצור את האשכול:

טייס אוטומטי

  1. מגדירים את Ray Client:

    if [ ! -d "env" ]; then
      python3 -m venv env
    else
      echo "Found virtual environment env, not recreating"
    fi
    
    source env/bin/activate
    pip3 install --upgrade pip
    pip3 install ray[default]
  2. שחזור של שירות Ray Head:

    SVC_NAME="$(kubectl get svc -l "ray.io/node-type=head" -o jsonpath='{..metadata.name}')"
    echo "Ray head service name: ${SVC_NAME}"
  3. מגדירים העברה ליציאה אחרת אל צומת לוח הבקרה של Ray. התהליך פועל ברקע, כך שאפשר להמשיך להריץ פקודות באותו סשן של מסוף.

    echo "Starting auto-restarting port-forwarding to ${SVC_NAME} on port 8265..."
    run_port_forward() {
        while true; do
            kubectl port-forward svc/"${SVC_NAME}" 8265:8265 -n "${NAMESPACE}" >/dev/null 2>&1 || true
            sleep 1
        done
    }
    run_port_forward &
    PF_LOOP_PID=$!

    כדי לעצור את לולאת העברת הפורטים, משתמשים בפקודה kill ${PF_LOOP_PID}.

  4. בודקים את המניפסט runtime-env.yaml:

    py_modules: ["."]
    working_dir: "."
    excludes:
      - ".git"
      - "env"
      - "venv"
      - ".venv"
      - "__pycache__"
      - "*.pyc"
      - ".cache"
      - "*.log"
      - "*.tmp"
      - "build"
      - "dist"
    env_vars:
      PYTHONPATH: "/data/verl"
      LD_LIBRARY_PATH: "/usr/local/nvidia/lib64:/usr/local/gib/lib64"
      NCCL_DEBUG: "INFO"
      NCCL_ENV_PLUGIN: "gcp"
      NCCL_CONF_FILE: "/usr/local/gib/configs/nccl.a4.conf"
      NCCL_IB_GID_INDEX: "3"
      NCCL_NVLS_ENABLE: "0"
      NCCL_TUNER_CONFIG_PATH: "/usr/local/gib/configs"
      NUM_WORKERS: "2"
      CPUS_PER_WORKER: "192"
      GPUS_PER_WORKER: "8"
      HF_HOME: "/data/huggingface_cache"
      GLOO_SOCKET_IFNAME: "eth0"

    אם אתם משתמשים במעבדי GPU מסוג H200, אתם צריכים לשנות את הערך של משתנה הסביבה NCCL_CONF_FILE ל-/usr/local/gib/configs/nccl.a3u.conf.

    הקובץ הזה משמש את לקוח Ray. אין צורך להחיל את המניפסט הזה על האשכול.

  5. שולחים את העבודה באמצעות הפקודה ray job submit:

    SUBMIT_OUT=$(ray job submit \
      --address "http://localhost:8265" \
      --no-wait \
      --runtime-env runtime-env.yaml \
      -- \
      bash -c "
          cd /data/verl && PYTHONUNBUFFERED=1 python3 -m verl.trainer.main_ppo \
          data.train_files=/data/gsm8k/train.parquet \
          data.val_files=/data/gsm8k/test.parquet \
          data.train_batch_size=256 \
          data.max_prompt_length=512 \
          data.max_response_length=512 \
          actor_rollout_ref.model.path=/data/Qwen2.5-32B-Instruct \
          actor_rollout_ref.actor.optim.lr=1e-5 \
          actor_rollout_ref.actor.ppo_mini_batch_size=256 \
          actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=16 \
          actor_rollout_ref.rollout.name=vllm \
          actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=8 \
          actor_rollout_ref.rollout.tensor_model_parallel_size=8 \
          actor_rollout_ref.rollout.gpu_memory_utilization=0.6 \
          actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=4 \
          actor_rollout_ref.actor.strategy=fsdp2 \
          algorithm.kl_ctrl.kl_coef=0.001 \
          trainer.logger=console \
          trainer.val_before_train=False \
          trainer.n_gpus_per_node=8 \
          trainer.nnodes=2 \
          trainer.save_freq=10 \
          trainer.test_freq=10 \
          trainer.total_training_steps=10 \
          trainer.default_local_dir=/data/verl/checkpoints \
          algorithm.adv_estimator=grpo \
          actor_rollout_ref.rollout.n=8 \
          actor_rollout_ref.actor.use_torch_compile=False \
          actor_rollout_ref.actor.fsdp_config.use_torch_compile=False \
          actor_rollout_ref.ref.use_torch_compile=False \
          actor_rollout_ref.ref.fsdp_config.use_torch_compile=False \
          trainer.total_epochs=1")
    echo "${SUBMIT_OUT}"
    JOB_ID=$(echo "${SUBMIT_OUT}" | grep "submitted successfully" | awk -F"'" '{print $2}' || true)
  6. עוקבים אחרי היומנים של משימת האימון ומחפשים את המדד critic/score/mean שעולה, מה שמצביע על כך שהמודל לומד:

    • לוח הבקרה של Ray:
      1. פותחים את http://localhost:8265 בדפדפן. אם משתמשים ב-Cloud Shell, לוחצים על תצוגה מקדימה באינטרנט, בוחרים באפשרות שינוי יציאה ומזינים 8265.
      2. עוברים לכרטיסייה משרות.
      3. לוחצים על מזהה המשרה.
      4. מסננים את היומנים לפי מילת המפתח critic כדי לבדוק את critic/score/mean.
    • טרמינל: מכיוון שהעבודה נשלחת באופן אסינכרוני עם הדגל --no-wait, מריצים את הפקודה הבאה כדי להזרים את יומני העבודה בטרמינל:

      ray job logs "${JOB_ID}" --address "http://localhost:8265" --follow
  7. אחרי שהאימון מסתיים, מאמתים שנקודות הביקורת של המודל נשמרו בקטגוריה שלכם ב-Cloud Storage על ידי הצגת התוכן של הספרייה:

    gcloud storage ls "gs://${GS_BUCKET}/verl/checkpoints/"

    אפשר גם לראות את נקודות הבדיקה במסוף Google Cloud : עוברים אל Cloud Storage > Buckets, בוחרים באפשרות YOUR_GCS_BUCKET ופותחים את התיקייה verl/checkpoints.

רגילה

  1. מגדירים את Ray Client:

    if [ ! -d "env" ]; then
      echo "Creating local virtual environment for Ray client..."
      python3 -m venv env
    else
      echo "Found virtual environment env, not recreating"
    fi
    source env/bin/activate
    pip3 install --upgrade pip
    pip3 install ray[default]
  2. שחזור של שירות Ray Head:

    SVC_NAME="$(kubectl get svc -l "ray.io/node-type=head" -o jsonpath='{..metadata.name}')"
    echo "Ray head service name: ${SVC_NAME}"
  3. מגדירים העברה ליציאה אחרת אל צומת לוח הבקרה של Ray. התהליך פועל ברקע, כך שאפשר להמשיך להריץ פקודות באותו סשן של מסוף.

    echo "Starting port-forwarding to ${SVC_NAME} on port 8265..."
    kubectl port-forward svc/"${SVC_NAME}" 8265:8265 -n "${NAMESPACE}" &
    PF_PID=$!

    כדי לעצור את לולאת העברת הפורטים, משתמשים בפקודה kill ${PF_PID}.

  4. בודקים את המניפסט runtime-env.yaml:

    working_dir: "."
    excludes:
      - ".git"
      - "env"
      - ".cache"
      - ".zip"
      - ".log"
    env_vars:
      PYTHONPATH: "/data/verl"
      LD_LIBRARY_PATH: "/usr/local/nvidia/lib64"
      NCCL_DEBUG: "INFO"
      NUM_WORKERS: "2"
      CPUS_PER_WORKER: "192"
      GPUS_PER_WORKER: "8"
      NCCL_NET_PLUGIN: "/usr/local/gib/lib64/libnccl-net_internal.so"
      NCCL_CROSS_NIC: "0"
      NCCL_NET_GDR_LEVEL: "PIX"
      NCCL_P2P_NET_CHUNKSIZE: "131072"
      NCCL_NVLS_CHUNKSIZE: "524288"
      NCCL_IB_ADAPTIVE_ROUTING: "1"
      NCCL_IB_QPS_PER_CONNECTION: "4"
      NCCL_IB_TC: "52"
      NCCL_IB_FIFO_TC: "84"
      NCCL_TUNER_CONFIG_PATH: "/usr/local/gib/configs/tuner_config_a4.txtpb"
      HF_HOME: "/data/huggingface_cache"
      GLOO_SOCKET_IFNAME: "eth0"

    אם אתם משתמשים במעבדי GPU מסוג H200, אתם צריכים לשנות את הערך של משתנה הסביבה NCCL_TUNER_CONFIG_PATH ל-/usr/local/gib/configs/tuner_config_a3u.txtpb.

    הקובץ הזה משמש את לקוח Ray. אין צורך להחיל את המניפסט הזה על האשכול.

  5. שולחים את המשימה:

    ray job submit \
    --address "http://localhost:8265" \
    --runtime-env runtime-env-local.yaml \
    -- \
    bash -c "
        cd /data/verl && PYTHONUNBUFFERED=1 python3 -m verl.trainer.main_ppo \
        data.train_files=/data/gsm8k/train.parquet \
        data.val_files=/data/gsm8k/test.parquet \
        data.train_batch_size=256 \
        data.max_prompt_length=512 \
        data.max_response_length=512 \
        actor_rollout_ref.model.path=/data/Qwen2.5-32B-Instruct \
        actor_rollout_ref.actor.optim.lr=1e-5 \
        actor_rollout_ref.actor.ppo_mini_batch_size=256 \
        actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=8 \
        actor_rollout_ref.rollout.name=vllm \
        actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=2 \
        actor_rollout_ref.rollout.tensor_model_parallel_size=8 \
        actor_rollout_ref.rollout.gpu_memory_utilization=0.5 \
        actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=2 \
        actor_rollout_ref.actor.strategy=fsdp2 \
        algorithm.kl_ctrl.kl_coef=0.001 \
        trainer.logger=console \
        trainer.val_before_train=False \
        trainer.n_gpus_per_node=8 \
        trainer.nnodes=2 \
        trainer.save_freq=10 \
        trainer.test_freq=10 \
        trainer.default_local_dir=/data/verl/checkpoints \
        algorithm.adv_estimator=grpo \
        actor_rollout_ref.rollout.n=8 \
        trainer.total_epochs=2"
  6. עוקבים אחרי היומנים של משימת האימון ומחפשים את המדד critic/score/mean שעולה, מה שמצביע על כך שהמודל לומד:

    • לוח הבקרה של Ray:
      1. פותחים את http://localhost:8265 בדפדפן. אם משתמשים ב-Cloud Shell, לוחצים על תצוגה מקדימה באינטרנט, בוחרים באפשרות שינוי יציאה ומזינים 8265.
      2. עוברים לכרטיסייה משרות.
      3. לוחצים על מזהה המשרה.
      4. מסננים את היומנים לפי מילת המפתח critic כדי לבדוק את המדד critic/score/mean.
    • פלט המסוף: מכיוון שהעבודה נשלחת באופן אסינכרוני עם הדגל --no-wait, מריצים את הפקודה הבאה כדי להזרים את יומני העבודה בטרמינל:

      ray job logs "${JOB_ID}" --address "http://localhost:8265" --follow
  7. אחרי שהאימון מסתיים, כדי לוודא שנקודות הבדיקה של המודל נשמרו בקטגוריה שלכם ב-Cloud Storage, מריצים את הפקודה הבאה כדי להציג את התוכן של הספרייה:

    gcloud storage ls "gs://${GS_BUCKET}/verl/checkpoints/"

    אפשר גם לראות את נקודות הבדיקה במסוף Google Cloud : עוברים אל Cloud Storage > Buckets, בוחרים באפשרות YOUR_GCS_BUCKET ופותחים את התיקייה verl/checkpoints.

A4X

  1. מאחזרים את שם ה-Pod של Ray head:

    export HEAD_POD=$(kubectl get pod -n ${NAMESPACE} -l ray.io/node-type=head -o jsonpath='{.items[0].metadata.name}')
    
  2. מגדירים את קובץ סביבת זמן הריצה של Ray ישירות ב-Pod הראשי:

    kubectl exec ${HEAD_POD} -c ray-head -- bash -c 'mkdir -p /tmp/submit && cat > /tmp/submit/runtime-env.yaml <<EOF
    working_dir: "."
    env_vars:
      PYTHONPATH: "/data/verl"
      LD_LIBRARY_PATH: "/usr/local/nvidia/lib64:/usr/local/gib/lib64"
      NCCL_DEBUG: "INFO"
      NCCL_ENV_PLUGIN: "gcp"
      HF_HOME: "/data/huggingface_cache"
      GLOO_SOCKET_IFNAME: "eth0"
    EOF'
    
  3. שולחים את משימת האימון של GRPO על ידי הרצתה ב-Pod הראשי של Ray:

    kubectl exec ${HEAD_POD} -c ray-head -- bash -c 'cd /tmp/submit && \
    ray job submit --runtime-env runtime-env.yaml --no-wait -- \
      python3 -m verl.trainer.main_ppo \
        algorithm.adv_estimator=grpo \
        data.train_files=/data/gsm8k/train.parquet \
        data.val_files=/data/gsm8k/test.parquet \
        data.train_batch_size=256 \
        data.max_prompt_length=512 \
        data.max_response_length=512 \
        actor_rollout_ref.model.path=/data/Qwen2.5-32B-Instruct \
        actor_rollout_ref.actor.optim.lr=1e-5 \
        actor_rollout_ref.actor.ppo_mini_batch_size=64 \
        actor_rollout_ref.actor.ppo_micro_batch_size_per_gpu=8 \
        actor_rollout_ref.actor.use_kl_loss=True \
        actor_rollout_ref.actor.strategy=fsdp2 \
        actor_rollout_ref.rollout.name=vllm \
        actor_rollout_ref.rollout.tensor_model_parallel_size=4 \
        actor_rollout_ref.rollout.gpu_memory_utilization=0.6 \
        actor_rollout_ref.rollout.n=8 \
        actor_rollout_ref.rollout.log_prob_micro_batch_size_per_gpu=16 \
        actor_rollout_ref.ref.log_prob_micro_batch_size_per_gpu=16 \
        algorithm.kl_ctrl.kl_coef=0.001 \
        trainer.logger=console \
        trainer.n_gpus_per_node=4 \
        trainer.nnodes=4 \
        trainer.save_freq=10 \
        trainer.test_freq=10 \
        trainer.total_epochs=2 \
        trainer.default_local_dir=/data/verl/checkpoints'
    
  4. עוקבים אחרי יומני המשימות (באמצעות המזהה הייחודי שמוחזר על ידי ray job submit):

    kubectl exec ${HEAD_POD} -c ray-head -- ray job logs <var>JOB_ID</var> --follow
    

    החלפה של JOB_ID. כדי לוודא ש-NVLink בין צמתים פעיל, מחפשים ביומנים שורות של NCCL שמכילות via P2P/MNNVL.

הסרת המשאבים

כדי להימנע מחיובים, מוחקים את המשאבים:

‫A4 ו-A3 Ultra

טייס אוטומטי

  1. אם לולאת העברת הפורטים עדיין פועלת בסשן הרקע, צריך לסיים אותה:

    kill ${PF_LOOP_PID}
  2. מחיקת אשכול Ray:

    envsubst < "ray-cluster-auto-dranet.yaml" | kubectl delete -f - --ignore-not-found=true || true
  3. מחיקת Cloud Storage FUSE:

    envsubst < "gcsfuse-storage.yaml" | kubectl delete -f - --ignore-not-found=true || true
  4. מוחקים את המשאבים של DRANET:

    kubectl delete -f "resourceclaim-dranet.yaml" --ignore-not-found=true 2>/dev/null || true
    kubectl delete -f "computeclass-dranet.yaml" --ignore-not-found=true 2>/dev/null || true
  5. מוחקים את הקטגוריה של Cloud Storage:

    gcloud storage rm -r "gs://${GS_BUCKET}" --project="${PROJECT_ID:-$(gcloud config get project)}" --quiet || true
  6. מוחקים את אשכול GKE:

    gcloud container clusters delete "${CLUSTER_NAME}" --location="${CONTROL_PLANE_REGION}" --quiet || true

רגילה

  1. אם לולאת העברת הפורטים עדיין פועלת ברקע של הסשן, צריך לסיים אותה:

    kill ${PF_PID}
  2. מחיקת אשכול Ray:

    envsubst < "ray-cluster-standard.yaml" | kubectl delete --timeout=30s -f - --ignore-not-found=true || true
  3. מחיקת Cloud Storage FUSE:

    envsubst < "gcsfuse-storage.yaml" | kubectl delete --timeout=30s -f - --ignore-not-found=true || true
  4. מוחקים את הקטגוריה של Cloud Storage:

    gcloud storage rm -r "gs://${GS_BUCKET}" --project="${PROJECT_ID}" || true
  5. מוחקים את אשכול GKE:

    gcloud container clusters delete ${CLUSTER_NAME} --location=${CONTROL_PLANE_REGION} --project=${PROJECT_ID} --quiet || true
  6. מחיקת רשתות ה-VPC ותת-הרשתות:

    # Function to safely delete all firewall rules for a given network
    delete_network_firewalls() {
      local NET_NAME=$1
      echo "Deleting all firewall rules associated with network ${NET_NAME}..."
      for rule in $(gcloud compute firewall-rules list --filter="network:${NET_NAME}" --format="value(name)" --project=${PROJECT_ID} 2>/dev/null); do
        echo "Deleting firewall rule ${rule}..."
        gcloud compute firewall-rules delete ${rule} --project=${PROJECT_ID} --quiet || true
      done
    }
    
    # Delete Firewalls for both networks
    delete_network_firewalls "${RDMA_NETWORK_PREFIX}-net"
    delete_network_firewalls "${GVNIC_NETWORK_PREFIX}-net"
    
    # Delete RDMA subnets
    echo "Deleting RDMA subnets..."
    for N in $(seq 0 7); do
      if gcloud compute networks subnets describe ${RDMA_NETWORK_PREFIX}-sub-$N --region=${CONTROL_PLANE_REGION} --project=${PROJECT_ID} >/dev/null 2>&1; then
        echo "Deleting subnet ${RDMA_NETWORK_PREFIX}-sub-$N..."
        gcloud compute networks subnets delete ${RDMA_NETWORK_PREFIX}-sub-$N --region=${CONTROL_PLANE_REGION} --project=${PROJECT_ID} --quiet || true
      fi
    done
    
    # Delete GVNIC subnet
    if gcloud compute networks subnets describe ${GVNIC_NETWORK_PREFIX}-sub --region=${CONTROL_PLANE_REGION} --project=${PROJECT_ID} >/dev/null 2>&1; then
      echo "Deleting GVNIC subnet ${GVNIC_NETWORK_PREFIX}-sub..."
      gcloud compute networks subnets delete ${GVNIC_NETWORK_PREFIX}-sub --region=${CONTROL_PLANE_REGION} --project=${PROJECT_ID} --quiet || true
    fi
    
    # Function to delete VPC Network with retry and firewall re-purge
    delete_vpc_network_with_retry() {
      local NET_NAME=$1
      if gcloud compute networks describe ${NET_NAME} --project=${PROJECT_ID} >/dev/null 2>&1; then
        echo "Deleting network ${NET_NAME}..."
        for attempt in {1..6}; do
          delete_network_firewalls "${NET_NAME}"
          if gcloud compute networks delete ${NET_NAME} --project=${PROJECT_ID} --quiet; then
            echo "Successfully deleted network ${NET_NAME}"
            break
          fi
          echo "Failed to delete network ${NET_NAME} on attempt ${attempt}. Retrying in 10s..."
          sleep 10
        done
      fi
    }
    
    # Delete Networks safely with retry
    delete_vpc_network_with_retry "${RDMA_NETWORK_PREFIX}-net"
    delete_vpc_network_with_retry "${GVNIC_NETWORK_PREFIX}-net"

A4X

kubectl delete raycluster gb200-ray-cluster
kubectl delete computedomain verl-compute-domain
gcloud storage rm -r gs://${GS_BUCKET}
gcloud container clusters delete ${CLUSTER_NAME} --location=${CONTROL_PLANE_REGION}

המאמרים הבאים