הגדרת התאמה אוטומטית לעומס (autoscaling) לעומסי עבודה של LLM ב-TPU

בדף הזה מוסבר איך להגדיר את התשתית שלכם להרחבה אוטומטית באמצעות Horizontal Pod Autoscaler‏ (HPA) של GKE כדי לפרוס את המודל הגדול של שפה (LLM) Gemma באמצעות JetStream במארח יחיד.

מידע נוסף על בחירת מדדים להתאמה אוטומטית לעומס זמין במאמר שיטות מומלצות להתאמה אוטומטית לעומס של עומסי עבודה של מודלים גדולים של שפה (LLM) באמצעות TPU ב-GKE.

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

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

  • מפעילים את ממשק ה-API של Google Kubernetes Engine.
  • הפעלת Google Kubernetes Engine API
  • אם רוצים להשתמש ב-CLI של Google Cloud למשימה הזו, צריך להתקין ואז להפעיל את ה-CLI של gcloud. אם התקנתם בעבר את ה-CLI של gcloud, מריצים את הפקודה gcloud components update כדי לקבל את הגרסה העדכנית. יכול להיות שגרסאות קודמות של ה-CLI של gcloud לא יתמכו בהרצת הפקודות שמופיעות במסמך הזה.
  • כדאי לעיין בתהליך העבודה במאמר Serve Gemma using TPUs on GKE with JetStream ולבצע אותו. מוודאים שהארגומנט PROMETHEUS_PORT מוגדר במניפסט הפריסה של JetStream.

התאמה אוטומטית של הקיבולת באמצעות מדדים

אתם יכולים להשתמש במדדי הביצועים הספציפיים לעומס העבודה שמופקים על ידי שרת ההיקש של JetStream או במדדי הביצועים של TPU כדי לכוון את ההתאמה האוטומטית לעומס של ה-Pods.

כדי להגדיר התאמה אוטומטית לעומס באמצעות מדדים:

  1. ייצוא המדדים משרת JetStream אל Cloud Monitoring. אתם משתמשים בשירות המנוהל של Google Cloud ל-Prometheus, שמפשט את הפריסה וההגדרה של כלי האיסוף של Prometheus. השירות המנוהל של Google Cloud ל-Prometheus מופעל כברירת מחדל באשכול GKE, אבל אפשר גם להפעיל אותו באופן ידני.

    במניפסט לדוגמה הבא מוצג איך מגדירים את ההגדרות של משאב PodMonitoring כדי להנחות את השירות המנוהל של Google Cloud ל-Prometheus לבצע סקריפט של מדדים מה-Pods במרווחי זמן חוזרים של 15 שניות:

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

    apiVersion: monitoring.googleapis.com/v1
    kind: PodMonitoring
    metadata:
      name: jetstream-podmonitoring
    spec:
      selector:
        matchLabels:
          app: maxengine-server
      endpoints:
      - interval: 15s
        path: "/"
        port: PROMETHEUS_PORT
      targetLabels:
        metadata:
        - pod
        - container
        - node
    

    אם אתם צריכים לגרד מדדים של TPU, אתם יכולים להשתמש במניפסט הבא. במדדי מערכת, מרווחי גירוד בתדירות של עד 15 שניות נתמכים.

    apiVersion: monitoring.googleapis.com/v1
    kind: PodMonitoring
    metadata:
      name: tpu-metrics-exporter
      namespace: kube-system
      labels:
        k8s-app: tpu-device-plugin
    spec:
      endpoints:
        - port: 2112
          interval: 15s
      selector:
        matchLabels:
          k8s-app: tpu-device-plugin
    
  2. מתקינים מתאם מדדים. המתאם הזה מאפשר לבקר HPA לראות את מדדי השרת שייצאתם ל-Monitoring. פרטים נוספים זמינים במאמר בנושא שינוי אוטומטי של מספר העותקים של הפודים במסמכי התיעוד של השירות המנוהל של Google Cloud ל-Prometheus.

    Custom Metrics Stackdriver Adapter

    החל מגרסה v0.13.1 של המתאם, אפשר להשתמש במתאם Custom Metrics Stackdriver כדי לשלוח שאילתות לגבי מדדים מהשירות המנוהל של Google Cloud ל-Prometheus.

    כדי להתקין את Custom Metrics Stackdriver Adapter:

    1. מגדירים אוסף מנוהל באשכול.

    2. מתקינים את Custom Metrics Stackdriver Adapter באשכול.

      kubectl apply -f https://raw.githubusercontent.com/GoogleCloudPlatform/k8s-stackdriver/master/custom-metrics-stackdriver-adapter/deploy/production/adapter_new_resource_model.yaml
      
    3. אם הפעלתם איחוד זהויות של עומסי עבודה ל-GKE באשכול Kubernetes ואתם משתמשים באיחוד זהויות של עומסי עבודה ל-GKE, אתם צריכים גם להעניק את התפקיד 'צפייה ב-Monitoring' לחשבון השירות שבו פועל המתאם. מחליפים את PROJECT_ID במזהה הפרויקט.

    export PROJECT_NUMBER=$(gcloud projects describe PROJECT_ID --format 'get(projectNumber)')
    gcloud projects add-iam-policy-binding projects/PROJECT_ID \
      --role roles/monitoring.viewer \
      --member=principal://iam.googleapis.com/projects/$PROJECT_NUMBER/locations/global/workloadIdentityPools/PROJECT_ID.svc.id.goog/subject/ns/custom-metrics/sa/custom-metrics-stackdriver-adapter
    

    ‫Prometheus Adapter

    חשוב לקחת בחשבון את הנקודות הבאות כשמשתמשים ב-prometheus-adapter כדי לשנות את קנה המידה באמצעות השירות המנוהל של Google Cloud ל-Prometheus:

    • הפניית שאילתות דרך פרוקסי של ממשק המשתמש הקצה של Prometheus, בדיוק כמו כשמבצעים שאילתות בשירות המנוהל של Google Cloud ל-Prometheus באמצעות Prometheus API או ממשק המשתמש. הקצה הקדמי הזה מותקן בשלב מאוחר יותר.
    • כברירת מחדל, הארגומנט prometheus-url של prometheus-adapter Deployment מוגדר ל---prometheus-url=http://frontend.default.svc:9090/, כאשר default הוא מרחב השמות שבו פרסתם את חזית האתר. אם פרסתם את חזית האתר במרחב שמות אחר, צריך להגדיר את הארגומנט הזה בהתאם.
    • בשדה .seriesQuery בהגדרת הכללים, אי אפשר להשתמש בביטוי רגולרי (regex) להתאמה בשם של מדד. במקום זאת, מציינים את שמות המדדים באופן מלא.

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

    כל השאילתות שהונפקו על ידי prometheus-adapter הן בהיקף גלובלי. המשמעות היא שאם יש לכם אפליקציות בשני מרחבי שמות שפולטות מדדים עם שמות זהים, הגדרת HPA שמשתמשת במדד הזה תבצע שינוי גודל באמצעות נתונים משתי האפליקציות. כדי להימנע מהרחבת קנה מידה באמצעות נתונים שגויים, תמיד משתמשים במסננים namespace או cluster ב-PromQL.

    כדי להגדיר דוגמה להגדרת HPA באמצעות prometheus-adapter ואוסף מנוהל, פועלים לפי השלבים הבאים:

    1. מגדירים אוסף מנוהל באשכול.
    2. פורסים את שרת ה-Proxy של ממשק המשתמש הקדמי של Prometheus באשכול. יוצרים את קובץ המניפסט הבא בשם prometheus-frontend.yaml:

        apiVersion: apps/v1
        kind: Deployment
        metadata:
          name: frontend
        spec:
          replicas: 2
          selector:
            matchLabels:
              app: frontend
          template:
            metadata:
              labels:
                app: frontend
            spec:
              automountServiceAccountToken: true
              affinity:
                nodeAffinity:
                  requiredDuringSchedulingIgnoredDuringExecution:
                    nodeSelectorTerms:
                    - matchExpressions:
                      - key: kubernetes.io/arch
                        operator: In
                        values:
                        - arm64
                        - amd64
                      - key: kubernetes.io/os
                        operator: In
                        values:
                        - linux
              containers:
              - name: frontend
                image: gke.gcr.io/prometheus-engine/frontend:v0.8.0-gke.4
                args:
                - "--web.listen-address=:9090"
                - "--query.project-id=PROJECT_ID"
                ports:
                - name: web
                  containerPort: 9090
                readinessProbe:
                  httpGet:
                    path: /-/ready
                    port: web
                securityContext:
                  allowPrivilegeEscalation: false
                  capabilities:
                    drop:
                    - all
                  privileged: false
                  runAsGroup: 1000
                  runAsNonRoot: true
                  runAsUser: 1000
                livenessProbe:
                  httpGet:
                    path: /-/healthy
                    port: web
        ---
        apiVersion: v1
        kind: Service
        metadata:
          name: prometheus
        spec:
          clusterIP: None
          selector:
            app: frontend
          ports:
          - name: web
            port: 9090
      

      לאחר מכן, מחילים את המניפסט:

      kubectl apply -f prometheus-frontend.yaml
      
    3. כדי לוודא ש-prometheus-adapter מותקן באשכול, מתקינים את תרשים ה-Helm‏ prometheus-community/prometheus-adapter. יוצרים את הקובץ values.yaml הבא:

      rules:
        default: false
        external:
        - seriesQuery: 'jetstream_prefill_backlog_size'
          resources:
            template: <<.Resource>>
          name:
            matches: ""
            as: "jetstream_prefill_backlog_size"
          metricsQuery: avg(<<.Series>>{<<.LabelMatchers>>,cluster="CLUSTER_NAME"})
        - seriesQuery: 'jetstream_slots_used_percentage'
          resources:
            template: <<.Resource>>
          name:
            matches: ""
            as: "jetstream_slots_used_percentage"
          metricsQuery: avg(<<.Series>>{<<.LabelMatchers>>,cluster="CLUSTER_NAME"})
        - seriesQuery: 'memory_used'
          resources:
            template: <<.Resource>>
          name:
            matches: ""
            as: "memory_used_percentage"
          metricsQuery: avg(memory_used{cluster="CLUSTER_NAME",exported_namespace="default",container="jetstream-http"}) / avg(memory_total{cluster="CLUSTER_NAME",exported_namespace="default",container="jetstream-http"})
      

      לאחר מכן משתמשים בקובץ הזה כקובץ הערכים לפריסת תרשים ה-Helm:

      helm repo add prometheus-community https://prometheus-community.github.io/helm-charts && helm repo update && helm install example-release prometheus-community/prometheus-adapter -f values.yaml
      

    אם אתם משתמשים ב-איחוד זהויות של עומסי עבודה ל-GKE, אתם צריכים גם להגדיר ולאשר חשבון שירות על ידי הפעלת הפקודות הבאות:

    1. קודם יוצרים את חשבונות השירות Google Cloud בתוך האשכול:

      gcloud iam service-accounts create prom-frontend-sa && kubectl create sa prom-frontend-sa
      
    2. לאחר מכן, מקשרים בין שני חשבונות השירות. חשוב להחליף את PROJECT_ID במזהה הפרויקט:

      gcloud iam service-accounts add-iam-policy-binding \
        --role roles/iam.workloadIdentityUser \
        --member "serviceAccount:PROJECT_ID.svc.id.goog[default/prom-frontend-sa]" \
        jetstream-iam-sa@PROJECT_ID.iam.gserviceaccount.com \
      &&
      kubectl annotate serviceaccount \
        --namespace default \
        prom-frontend-sa \
        iam.gke.io/gcp-service-account=jetstream-iam-sa@PROJECT_ID.iam.gserviceaccount.com
      
    3. לאחר מכן, מקצים לחשבון השירות monitoring.viewer את התפקיד monitoring.viewer: Google Cloud

      gcloud projects add-iam-policy-binding PROJECT_ID \
        --member=serviceAccount:jetstream-iam-sa@PROJECT_ID.iam.gserviceaccount.com \
        --role=roles/monitoring.viewer
      
    4. לבסוף, מגדירים את חשבון השירות של פריסות ה-frontend כחשבון השירות החדש בתוך האשכול:

      kubectl set serviceaccount deployment frontend prom-frontend-sa
      
  3. מגדירים את משאב ה-HPA שמבוסס על מדדים. פורסים משאב HPA שמבוסס על מדד השרת המועדף. פרטים נוספים זמינים במאמר בנושא שינוי אוטומטי של מספר העותקים של הפודים במאמרי העזרה של השירות המנוהל של Google Cloud ל-Prometheus. ההגדרה הספציפית של HPA תלויה בסוג המדד (שרת או TPU) ובמתאם המדדים שהותקן.

    יש כמה ערכים שנדרשים בכל ההגדרות של HPA, וצריך להגדיר אותם כדי ליצור משאב HPA:

    • MIN_REPLICAS: מספר הרפליקות המינימלי של פודים של JetStream שמותר. אם לא משנים את מניפסט הפריסה של JetStream מהשלב Deploy JetStream, מומלץ להגדיר את הערך הזה כ-1.
    • MAX_REPLICAS: המספר המקסימלי של רפליקות של פודים של JetStream שמותר להשתמש בהן. הפריסה לדוגמה של JetStream דורשת 8 שבבים לכל עותק, ומאגר הצמתים מכיל 16 שבבים. אם רוצים לשמור על זמן אחזור נמוך של הגדלת הקיבולת, מגדירים את הערך הזה ל-2. ערכים גדולים יותר מפעילים את התכונה Cluster Autoscaler ליצירת צמתים חדשים במאגר הצמתים, וכך מגדילים את זמן האחזור של הגדלת הקיבולת.
    • TARGET: הממוצע הממוקד של המדד הזה בכל המופעים של JetStream. במסמכי התיעוד של Kubernetes בנושא התאמה אוטומטית לעומס (automatic scaling) יש מידע נוסף על האופן שבו נקבע מספר הרפליקות מהערך הזה.

    Custom Metrics Stackdriver Adapter

    מתאם Stackdriver של מדדים בהתאמה אישית תומך בהרחבת עומס העבודה באמצעות הערך הממוצע של שאילתות מדדים נפרדות מהשירות המנוהל של Google Cloud ל-Prometheus בכל ה-Pods. כשמשתמשים במתאם Stackdriver של מדדים מותאמים אישית, מומלץ להשתמש במדדי השרת jetstream_prefill_backlog_size ו-jetstream_slots_used_percentage ובמדד TPU‏ memory_used כדי להגדיל את קנה המידה.

    כדי ליצור מניפסט HPA לצורך שינוי גודל באמצעות מדדי שרת, יוצרים את הקובץ hpa.yaml הבא:

    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: jetstream-hpa
      namespace: default
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: maxengine-server
      minReplicas: MIN_REPLICAS
      maxReplicas: MAX_REPLICAS
      metrics:
      - type: Pods
        pods:
          metric:
            name: prometheus.googleapis.com|jetstream_METRIC|gauge
          target:
            type: AverageValue
            averageValue: TARGET
    

    כשמשתמשים במתאם Stackdriver של מדדים מותאמים אישית עם מדדי TPU, מומלץ להשתמש רק במדד kubernetes.io|node|accelerator|memory_used לצורך שינוי קנה מידה. כדי ליצור מניפסט HPA לצורך שינוי קנה מידה באמצעות המדד הזה, יוצרים את הקובץ hpa.yaml הבא:

    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: jetstream-hpa
      namespace: default
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: maxengine-server
      minReplicas: MIN_REPLICAS
      maxReplicas: MAX_REPLICAS
      metrics:
      - type: External
        external:
          metric:
            name: prometheus.googleapis.com|memory_used|gauge
            selector:
              matchLabels:
                metric.labels.container: jetstream-http
                metric.labels.exported_namespace: default
          target:
            type: AverageValue
            averageValue: TARGET
    

    ‫Prometheus Adapter

    ‫Prometheus Adapter תומך בהרחבת נפח העבודה באמצעות הערך של שאילתות PromQL מהשירות המנוהל של Google Cloud ל-Prometheus. קודם לכן הגדרתם את מדדי השרת jetstream_prefill_backlog_size ו-jetstream_slots_used_percentage שמייצגים את הערך הממוצע בכל ה-Pods.

    כדי ליצור מניפסט HPA לצורך שינוי גודל באמצעות מדדי שרת, יוצרים את הקובץ hpa.yaml הבא:

    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: jetstream-hpa
      namespace: default
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: maxengine-server
      minReplicas: MIN_REPLICAS
      maxReplicas: MAX_REPLICAS
      metrics:
      - type: External
        external:
          metric:
            name: jetstream_METRIC
          target:
            type: AverageValue
            averageValue: TARGET
    

    כדי ליצור מניפסט HPA לצורך שינוי קנה מידה באמצעות מדדי TPU, מומלץ להשתמש רק ב-memory_used_percentage שמוגדר בקובץ הערכים של prometheus-adapter helm. ‫memory_used_percentage הוא השם שניתן לשאילתת PromQL הבאה, שמשקפת את ממוצע הזיכרון הנוכחי שנעשה בו שימוש בכל המאיצים:

    avg(kubernetes_io:node_accelerator_memory_used{cluster_name="CLUSTER_NAME"}) / avg(kubernetes_io:node_accelerator_memory_total{cluster_name="CLUSTER_NAME"})
    

    כדי ליצור מניפסט HPA לצורך שינוי גודל באמצעות memory_used_percentage, יוצרים את קובץ hpa.yaml הבא:

    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: jetstream-hpa
      namespace: default
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: maxengine-server
      minReplicas: MIN_REPLICAS
      maxReplicas: MAX_REPLICAS
      metrics:
      - type: External
        external:
          metric:
            name: memory_used_percentage
          target:
            type: AverageValue
            averageValue: TARGET
    

התאמה באמצעות כמה מדדים

אפשר גם להגדיר שינוי גודל על סמך כמה מדדים. מידע על האופן שבו נקבע מספר הרפליקות באמצעות מדדים שונים זמין במאמרי העזרה של Kubernetes בנושא התאמה אוטומטית לעומס. כדי ליצור מניפסט HPA מהסוג הזה, צריך לאסוף את כל הערכים מהשדה spec.metrics של כל משאב HPA למשאב HPA יחיד. בקטע הקוד הבא אפשר לראות דוגמה לאופן שבו אפשר לאגד את משאבי ה-HPA:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: jetstream-hpa-multiple-metrics
  namespace: default
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: maxengine-server
  minReplicas: MIN_REPLICAS
  maxReplicas: MAX_REPLICAS
  metrics:
  - type: Pods
    pods:
      metric:
        name: jetstream_METRIC
      target:
        type: AverageValue
      averageValue: JETSTREAM_METRIC_TARGET
  - type: External
    external:
      metric:
        name: memory_used_percentage
      target:
        type: AverageValue
      averageValue: EXTERNAL_METRIC_TARGET

מעקב ובדיקה של התאמה אוטומטית לעומס

אתם יכולים לראות איך עומסי העבודה של JetStream משתנים בהתאם להגדרות של HPA.

כדי לראות את מספר העותקים בזמן אמת, מריצים את הפקודה הבאה:

kubectl get hpa --watch

הפלט של הפקודה הזו אמור להיראות כך:

NAME            REFERENCE                     TARGETS      MINPODS   MAXPODS   REPLICAS   AGE
jetstream-hpa   Deployment/maxengine-server   0/10 (avg)   1         2         1          1m

כדי לבדוק את היכולת של HPA לבצע שינוי גודל, משתמשים בפקודה הבאה ששולחת פרץ של 100 בקשות לנקודת הקצה של המודל. הפעולה הזו תגרום לניצול מלא של משבצות הפענוח הזמינות, וליצירת עומס של בקשות בתור למילוי מראש. כתוצאה מכך, ה-HPA יגדיל את גודל ה-Deployment (פריסה) של המודל.

seq 100 | xargs -P 100 -n 1 curl --request POST --header "Content-type: application/json" -s localhost:8000/generate --data '{ "prompt": "Can you provide a comprehensive and detailed overview of the history and development of artificial intelligence.", "max_tokens": 200 }'

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