פריסת אפליקציית Ray Serve עם מודל דיפוזיה יציב ב-Google Kubernetes Engine ‏ (GKE)

פריסה והצגה של מודל Stable Diffusion ב-Google Kubernetes Engine‏ (GKE) באמצעות Ray Serve ותוסף Ray Operator. הפתרון הזה עוזר לכם לבנות אפליקציות של AI גנרטיבי שניתנות להתאמה לעומס (scale) וזמינות מאוד. ‫GKE מציע פלטפורמה גמישה וחסכונית להרצה של עומסי עבודה של AI/ML שמבוססים על קונטיינרים. ‫Ray Serve מספק תכונות כמו עדכונים במקום ושדרוגים ללא השבתה. היכולות האלה חיוניות לניהול מודלים מבוזרים של AI ולשמירה על יעילות ואמינות בהגשה בסביבות ייצור ב-GKE.

מידע על Ray ו-Ray Serve

‫Ray היא מסגרת מחשוב מבוססת קוד פתוח שניתנת להרחבה, לאפליקציות של AI/ML. ‫Ray Serve היא ספרייה לפרסום מודלים ב-Ray, שמשמשת להרחבה ולפרסום של מודלים בסביבה מבוזרת. מידע נוסף זמין במאמר בנושא Ray Serve במסמכי התיעוד של Ray.

אפשר להשתמש במשאב RayCluster או RayService כדי לפרוס את אפליקציות Ray Serve. כדאי להשתמש במשאב RayService בסביבת הייצור מהסיבות הבאות:

  • עדכונים במקום לאפליקציות RayService
  • שדרוג ללא השבתה של משאבי RayCluster
  • אפליקציות Ray Serve עם זמינות גבוהה

מטרות

המדריך הזה מיועד ללקוחות של AI גנרטיבי, למשתמשים חדשים או קיימים ב-GKE, למהנדסי ML, למהנדסי MLOps (DevOps) או לאדמינים של פלטפורמות שרוצים להשתמש ביכולות של Kubernetes לניהול קונטיינרים כדי להפעיל מודלים באמצעות Ray.

  • יוצרים אשכול GKE עם מאגר צומתי GPU.
  • יוצרים אשכול Ray באמצעות המשאב המותאם אישית RayCluster.
  • מריצים אפליקציית Ray Serve.
  • פריסה של משאב מותאם אישית מסוג RayService.

עלויות

במסמך הזה משתמשים ברכיבים הבאים של Google Cloud, והשימוש בהם כרוך בתשלום:

בעזרת מחשבון העלויות אתם יכולים להעריך את העלויות בהתאם לשימוש הצפוי.

משתמשים חדשים של Google Cloud ? יכול להיות שאתם זכאים לתקופת ניסיון בחינם.

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

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

ב-Cloud Shell מותקנת מראש התוכנה שדרושה למדריך הזה, כולל kubectl ו-ה-CLI של gcloud. אם אתם לא משתמשים ב-Cloud Shell, אתם צריכים להתקין את ה-CLI של gcloud.

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

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

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

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

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

    • Select a project: כדי לבחור פרויקט לא צריך תפקיד IAM ספציפי – אפשר לבחור כל פרויקט שקיבלתם בו תפקיד.
    • יצירת פרויקט: כדי ליצור פרויקט, צריך את התפקיד "יצירת פרויקטים" (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 .

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

  7. מפעילים את GKE API, אם הוא עדיין לא מופעל:

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

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

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

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

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

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

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

    • Select a project: כדי לבחור פרויקט לא צריך תפקיד IAM ספציפי – אפשר לבחור כל פרויקט שקיבלתם בו תפקיד.
    • יצירת פרויקט: כדי ליצור פרויקט, צריך את התפקיד "יצירת פרויקטים" (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 .

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

  13. מפעילים את GKE API, אם הוא עדיין לא מופעל:

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

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

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

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

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

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

הכנת הסביבה

כדי להכין את הסביבה:

  1. מפעילים סשן של Cloud Shell מGoogle Cloud המסוף על ידי לחיצה על Activate Cloud Shell סמל ההפעלה של Cloud Shell. Google Cloud ייפתח סשן בחלונית התחתונה של מסוף Google Cloud .

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

    export PROJECT_ID=PROJECT_ID
    export CLUSTER_NAME=rayserve-cluster
    export COMPUTE_REGION=us-central1
    export COMPUTE_ZONE=us-central1-c
    export CLUSTER_VERSION=CLUSTER_VERSION
    export TUTORIAL_HOME=`pwd`
    

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

    • ‫PROJECT_ID: מזהה הפרויקט ב- Google Cloud.
    • ‫CLUSTER_VERSION: גרסת GKE לשימוש. הגרסה צריכה להיות 1.30.1 ואילך.
  3. משכפלים את המאגר ב-GitHub:

    git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
    
  4. עוברים לספריית העבודה:

    cd kubernetes-engine-samples/ai-ml/gke-ray/rayserve/stable-diffusion
    
  5. יוצרים סביבה וירטואלית של Python:

    venv

    python -m venv myenv && \
    source myenv/bin/activate
    

    Conda

    1. מתקינים את Conda.

    2. מריצים את הפקודות הבאות:

      conda create -c conda-forge python=3.9.19 -n myenv && \
      conda activate myenv
      

    כשפורסים אפליקציית Serve עם serve run, מערכת Ray מצפה שגרסת Python של הלקוח המקומי תהיה זהה לגרסה שמשמשת באשכול Ray. התמונה rayproject/ray:2.37.0 משתמשת ב-Python 3.9. אם אתם משתמשים בגרסה אחרת של הלקוח, צריך לבחור את תמונת ה-Ray המתאימה.

  6. מתקינים את יחסי התלות הנדרשים כדי להפעיל את אפליקציית Serve:

    pip install ray[serve]==2.37.0
    pip install torch
    pip install requests
    

יצירת אשכול עם מאגר צמתים של GPU

יצירת אשכול GKE במצב Autopilot או Standard עם מאגר צמתים של GPU:

טייס אוטומטי

יוצרים אשכול Autopilot:

gcloud container clusters create-auto ${CLUSTER_NAME}  \
    --enable-ray-operator \
    --cluster-version=${CLUSTER_VERSION} \
    --location=${COMPUTE_REGION}

רגילה

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

    gcloud container clusters create ${CLUSTER_NAME} \
        --addons=RayOperator \
        --cluster-version=${CLUSTER_VERSION}  \
        --machine-type=c3d-standard-8 \
        --location=${COMPUTE_ZONE} \
        --num-nodes=1
    
  2. יוצרים מאגר צמתים של GPU:

    gcloud container node-pools create gpu-pool \
        --cluster=${CLUSTER_NAME} \
        --machine-type=g2-standard-8 \
        --location=${COMPUTE_ZONE} \
        --num-nodes=1 \
        --accelerator type=nvidia-l4,count=1,gpu-driver-version=latest
    

פריסת משאב RayCluster

כדי לפרוס משאב RayCluster:

  1. בודקים את קובץ המניפסט הבא:

    apiVersion: ray.io/v1
    kind: RayCluster
    metadata:
      name: stable-diffusion-cluster
    spec:
      rayVersion: '2.37.0'
      headGroupSpec:
        rayStartParams:
          dashboard-host: '0.0.0.0'
        template:
          metadata:
          spec:
            containers:
            - name: ray-head
              image: rayproject/ray:2.37.0
              ports:
              - containerPort: 6379
                name: gcs
              - containerPort: 8265
                name: dashboard
              - containerPort: 10001
                name: client
              - containerPort: 8000
                name: serve
              resources:
                limits:
                  cpu: "2"
                  ephemeral-storage: "15Gi"
                  memory: "8Gi"
                requests:
                  cpu: "2"
                  ephemeral-storage: "15Gi"
                  memory: "8Gi"
            nodeSelector:
              cloud.google.com/machine-family: c3d
      workerGroupSpecs:
      - replicas: 1
        minReplicas: 1
        maxReplicas: 4
        groupName: gpu-group
        rayStartParams: {}
        template:
          spec:
            containers:
            - name: ray-worker
              image: rayproject/ray:2.37.0-gpu
              resources:
                limits:
                  cpu: 4
                  memory: "16Gi"
                  nvidia.com/gpu: 1
                requests:
                  cpu: 3
                  memory: "16Gi"
                  nvidia.com/gpu: 1
            nodeSelector:
              cloud.google.com/gke-accelerator: nvidia-l4

    המניפסט הזה מתאר משאב RayCluster.

  2. מחילים את המניפסט על האשכול:

    kubectl apply -f ray-cluster.yaml
    
  3. מוודאים שמשאב ה-RayCluster מוכן:

    kubectl get raycluster
    

    הפלט אמור להיראות כך:

    NAME                       DESIRED WORKERS   AVAILABLE WORKERS   CPUS   MEMORY   GPUS   STATUS   AGE
    stable-diffusion-cluster   2                 2                   6      20Gi     0      ready    33s
    

    בפלט הזה, הערך ready בעמודה STATUS מציין שהמשאב RayCluster מוכן.

התחברות למשאב RayCluster

כדי להתחבר למשאב RayCluster:

  1. מוודאים ש-GKE יצר את שירות RayCluster:

    kubectl get svc stable-diffusion-cluster-head-svc
    

    הפלט אמור להיראות כך:

    NAME                             TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                AGE
    pytorch-mnist-cluster-head-svc   ClusterIP   34.118.238.247   <none>        10001/TCP,8265/TCP,6379/TCP,8080/TCP   109s
    
  2. מגדירים סשנים של העברה ליציאה אחרת ל-Ray head:

    kubectl port-forward svc/stable-diffusion-cluster-head-svc 8265:8265 2>&1 >/dev/null &
    kubectl port-forward svc/stable-diffusion-cluster-head-svc 10001:10001 2>&1 >/dev/null &
    
  3. מוודאים שהלקוח של Ray יכול להתחבר לאשכול Ray באמצעות localhost:

    ray list nodes --address http://localhost:8265
    

    הפלט אמור להיראות כך:

    ======== List: 2024-06-19 15:15:15.707336 ========
    Stats:
    ------------------------------
    Total: 3
    
    Table:
    ------------------------------
        NODE_ID                                                   NODE_IP     IS_HEAD_NODE    STATE    NODE_NAME    RESOURCES_TOTAL                 LABELS
    0  1d07447d7d124db641052a3443ed882f913510dbe866719ac36667d2  10.28.1.21  False           ALIVE    10.28.1.21   CPU: 2.0                        ray.io/node_id: 1d07447d7d124db641052a3443ed882f913510dbe866719ac36667d2
    # Several lines of output omitted
    

הרצת אפליקציית Ray Serve

כדי להריץ אפליקציית Ray Serve:

  1. מריצים את אפליקציית Stable Diffusion Ray Serve:

    serve run stable_diffusion:entrypoint --working-dir=. --runtime-env-json='{"pip": ["torch", "torchvision", "diffusers==0.12.1", "huggingface_hub==0.25.2", "transformers", "fastapi==0.113.0"], "excludes": ["myenv"]}' --address ray://localhost:10001
    
    

    הפלט אמור להיראות כך:

    2024-06-19 18:20:58,444 INFO scripts.py:499 -- Running import path: 'stable_diffusion:entrypoint'.
    2024-06-19 18:20:59,730 INFO packaging.py:530 -- Creating a file package for local directory '.'.
    2024-06-19 18:21:04,833 INFO handle.py:126 -- Created DeploymentHandle 'hyil6u9f' for Deployment(name='StableDiffusionV2', app='default').
    2024-06-19 18:21:04,834 INFO handle.py:126 -- Created DeploymentHandle 'xo25rl4k' for Deployment(name='StableDiffusionV2', app='default').
    2024-06-19 18:21:04,836 INFO handle.py:126 -- Created DeploymentHandle '57x9u4fp' for Deployment(name='APIIngress', app='default').
    2024-06-19 18:21:04,836 INFO handle.py:126 -- Created DeploymentHandle 'xr6kt85t' for Deployment(name='StableDiffusionV2', app='default').
    2024-06-19 18:21:04,836 INFO handle.py:126 -- Created DeploymentHandle 'g54qagbz' for Deployment(name='APIIngress', app='default').
    2024-06-19 18:21:19,139 INFO handle.py:126 -- Created DeploymentHandle 'iwuz00mv' for Deployment(name='APIIngress', app='default').
    2024-06-19 18:21:19,139 INFO api.py:583 -- Deployed app 'default' successfully.
    
  2. יוצרים סשן של העברה ליציאה אחרת ליציאה של Ray Serve ‏ (8000):

    kubectl port-forward svc/stable-diffusion-cluster-head-svc 8000:8000 2>&1 >/dev/null &
    
  3. מריצים את הסקריפט של Python:

    python generate_image.py
    

    הסקריפט יוצר תמונה לקובץ בשם output.png. התמונה דומה לזו:

    חוף בשקיעה. תמונה שנוצרה על ידי Stable Diffusion.

פריסת RayService

המשאב המותאם אישית RayService מנהל את מחזור החיים של משאב RayCluster ואת אפליקציית Ray Serve.

מידע נוסף על RayService זמין במאמרים Deploy Ray Serve Applications ו-Production Guide במסמכי העזרה של Ray.

כדי לפרוס משאב של RayService:

  1. בודקים את קובץ המניפסט הבא:

    apiVersion: ray.io/v1
    kind: RayService
    metadata:
      name: stable-diffusion
    spec:
      serveConfigV2: |
        applications:
          - name: stable_diffusion
            import_path: ai-ml.gke-ray.rayserve.stable-diffusion.stable_diffusion:entrypoint
            runtime_env:
              working_dir: "https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/archive/main.zip"
              pip: ["diffusers==0.12.1", "torch", "torchvision", "huggingface_hub==0.25.2", "transformers"]
      rayClusterConfig:
        rayVersion: '2.37.0'
        headGroupSpec:
          rayStartParams:
            dashboard-host: '0.0.0.0'
          template:
            spec:
              containers:
              - name: ray-head
                image:  rayproject/ray:2.37.0
                ports:
                - containerPort: 6379
                  name: gcs
                - containerPort: 8265
                  name: dashboard
                - containerPort: 10001
                  name: client
                - containerPort: 8000
                  name: serve
                resources:
                  limits:
                    cpu: "2"
                    ephemeral-storage: "15Gi"
                    memory: "8Gi"
                  requests:
                    cpu: "2"
                    ephemeral-storage: "15Gi"
                    memory: "8Gi"
              nodeSelector:
                cloud.google.com/machine-family: c3d
        workerGroupSpecs:
        - replicas: 1
          minReplicas: 1
          maxReplicas: 4
          groupName: gpu-group
          rayStartParams: {}
          template:
            spec:
              containers:
              - name: ray-worker
                image: rayproject/ray:2.37.0-gpu
                resources:
                  limits:
                    cpu: 4
                    memory: "16Gi"
                    nvidia.com/gpu: 1
                  requests:
                    cpu: 3
                    memory: "16Gi"
                    nvidia.com/gpu: 1
              nodeSelector:
                cloud.google.com/gke-accelerator: nvidia-l4

    קובץ המניפסט הזה מתאר משאב מותאם אישית של RayService.

  2. מחילים את המניפסט על האשכול:

    kubectl apply -f ray-service.yaml
    
  3. מוודאים שהשירות מוכן:

    kubectl get svc stable-diffusion-serve-svc
    

    הפלט אמור להיראות כך:

    NAME                         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
    
    stable-diffusion-serve-svc   ClusterIP   34.118.236.0   <none>        8000/TCP   31m
    
  4. מגדירים העברה ליציאה אחרת לשירות Ray Serve:

    kubectl port-forward svc/stable-diffusion-serve-svc 8000:8000 2>&1 >/dev/null &
    
  5. מריצים את סקריפט Python מהקטע הקודם:

    python generate_image.py
    

    הסקריפט יוצר תמונה דומה לתמונה שנוצרה בקטע הקודם.

מעקב אחרי עומסי העבודה של Ray

כדי לראות את הפרטים של RayJobs, אפשר לעבור לקטע Kubernetes Engine > AI/ML > Jobs ב Google Cloud מסוף.

הצגת RayJobs ב Google Cloud מסוף

הסרת המשאבים

מחיקת הפרויקט

    כדי למחוק פרויקט ב- Google Cloud :

    gcloud projects delete PROJECT_ID

מחיקת משאבים בודדים

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

gcloud container clusters delete ${CLUSTER_NAME}

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

  • כדאי להעמיק את הקריאה ולהכיר דוגמאות לארכיטקטורות, תרשימים ושיטות מומלצות בנושאי Google Cloud. כל אלה זמינים במרכז הארכיטקטורה של Cloud.