在 GKE 中提供具備多個 GPU 的 LLM


本教學課程示範如何在 GKE 上使用多個 GPU 部署及提供大型語言模型 (LLM),以進行有效率且可擴充的推論。建立使用多個 L4 GPU 的 GKE 叢集,並準備基礎架構來提供下列任一模型:

視模型的資料格式而定,所需的 GPU 數量也會有所不同。在本教學課程中,每個模型都會使用兩個 L4 GPU。詳情請參閱「計算 GPU 數量」。

本教學課程的適用對象為機器學習 (ML) 工程師、平台管理員和操作員,以及有興趣使用 Kubernetes 容器自動化調度管理功能提供 LLM 服務的資料和 AI 專家。如要進一步瞭解內容中提及的常見角色和範例工作,請參閱「常見的 GKE Enterprise 使用者角色和工作」。 Google Cloud

閱讀本頁面之前,請先熟悉下列概念:

目標

在本教學課程中,您將執行下列作業:

  1. 建立叢集和節點集區。
  2. 準備工作負載。
  3. 部署工作負載。
  4. 與 LLM 介面互動。

事前準備

開始之前,請確認你已完成下列工作:

  • 啟用 Google Kubernetes Engine API。
  • 啟用 Google Kubernetes Engine API
  • 如要使用 Google Cloud CLI 執行這項工作,請安裝初始化 gcloud CLI。如果您先前已安裝 gcloud CLI,請執行 gcloud components update,取得最新版本。
  • 部分機型可能有其他使用規定。請確認符合下列規定:

準備環境

  1. 在 Google Cloud 控制台中啟動 Cloud Shell 執行個體:
    開啟 Cloud Shell

  2. 設定預設環境變數:

    gcloud config set project PROJECT_ID
    gcloud config set billing/quota_project PROJECT_ID
    export PROJECT_ID=$(gcloud config get project)
    export REGION=us-central1
    

    PROJECT_ID 替換為您的 Google Cloud 專案 ID

建立 GKE 叢集和節點集區

您可以在 GKE Autopilot 或 Standard 叢集的 GPU 上提供大型語言模型。建議您使用 Autopilot 叢集,享受全代管 Kubernetes 體驗。如要為工作負載選擇最合適的 GKE 作業模式,請參閱「選擇 GKE 作業模式」。

Autopilot

  1. 在 Cloud Shell 中執行下列指令:

    gcloud container clusters create-auto l4-demo \
      --project=${PROJECT_ID} \
      --region=${REGION} \
      --release-channel=rapid
    

    GKE 會根據部署的工作負載要求,建立含 CPU 和 GPU 節點的 Autopilot 叢集。

  2. 設定 kubectl 與叢集通訊:

    gcloud container clusters get-credentials l4-demo --region=${REGION}
    

標準

  1. 在 Cloud Shell 中執行下列指令,建立使用 Workload Identity Federation for GKE 的 Standard 叢集:

    gcloud container clusters create l4-demo --location ${REGION} \
      --workload-pool ${PROJECT_ID}.svc.id.goog \
      --enable-image-streaming \
      --node-locations=$REGION-a \
      --workload-pool=${PROJECT_ID}.svc.id.goog \
      --machine-type n2d-standard-4 \
      --num-nodes 1 --min-nodes 1 --max-nodes 5 \
      --release-channel=rapid
    

    建立叢集可能需要幾分鐘的時間。

  2. 執行下列指令,為叢集建立節點集區

    gcloud container node-pools create g2-standard-24 --cluster l4-demo \
      --accelerator type=nvidia-l4,count=2,gpu-driver-version=latest \
      --machine-type g2-standard-24 \
      --enable-autoscaling --enable-image-streaming \
      --num-nodes=0 --min-nodes=0 --max-nodes=3 \
      --node-locations $REGION-a,$REGION-c --region $REGION --spot
    

    GKE 會為 LLM 建立下列資源:

    • 公開的 Google Kubernetes Engine (GKE) Standard 版叢集。
    • 機型為 g2-standard-24 的節點集區已縮減至 0 個節點。 在啟動要求 GPU 的 Pod 之前,系統不會向您收取任何 GPU 費用。這個節點集區會佈建 Spot VM,價格比預設的標準 Compute Engine VM 低,但不保證可用性。您可以從這個指令中移除 --spot 旗標,並在 text-generation-inference.yaml 設定中移除 cloud.google.com/gke-spot 節點選取器,改用隨選 VM。
  3. 設定 kubectl 與叢集通訊:

    gcloud container clusters get-credentials l4-demo --region=${REGION}
    

準備工作負載

本節說明如何根據要使用的模型設定工作負載。本教學課程使用 Kubernetes 部署作業來部署模型。Deployment 是 Kubernetes API 物件,可讓您執行多個 Pod 副本,並將這些副本分散到叢集的節點中。

Llama 3 70b

  1. 設定預設環境變數:

    export HF_TOKEN=HUGGING_FACE_TOKEN
    

    HUGGING_FACE_TOKEN 替換為您的 HuggingFace 權杖。

  2. 為 HuggingFace 權杖建立 Kubernetes 密鑰

    kubectl create secret generic l4-demo \
        --from-literal=HUGGING_FACE_TOKEN=${HF_TOKEN} \
        --dry-run=client -o yaml | kubectl apply -f -
    
  3. 建立下列 text-generation-inference.yaml Deployment 資訊清單:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: llm
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: llm
      template:
        metadata:
          labels:
            app: llm
        spec:
          containers:
          - name: llm
            image: us-docker.pkg.dev/deeplearning-platform-release/gcr.io/huggingface-text-generation-inference-cu121.2-1.ubuntu2204.py310
            resources:
              requests:
                cpu: "10"
                memory: "60Gi"
                nvidia.com/gpu: "2"
              limits:
                cpu: "10"
                memory: "60Gi"
                nvidia.com/gpu: "2"
            env:
            - name: MODEL_ID
              value: meta-llama/Meta-Llama-3-70B-Instruct
            - name: NUM_SHARD
              value: "2"
            - name: MAX_INPUT_TOKENS
              value: "2048"
            - name: PORT
              value: "8080"
            - name: QUANTIZE
              value: bitsandbytes-nf4
            - name: HUGGING_FACE_HUB_TOKEN
              valueFrom:
                secretKeyRef:
                  name: l4-demo
                  key: HUGGING_FACE_TOKEN
            volumeMounts:
              - mountPath: /dev/shm
                name: dshm
              # mountPath is set to /tmp as it's the path where the HUGGINGFACE_HUB_CACHE environment
              # variable in the TGI DLCs is set to instead of the default /data set within the TGI default image.
              # i.e. where the downloaded model from the Hub will be stored
              - mountPath: /tmp
                name: ephemeral-volume
          volumes:
            - name: dshm
              emptyDir:
                  medium: Memory
            - name: ephemeral-volume
              ephemeral:
                volumeClaimTemplate:
                  metadata:
                    labels:
                      type: ephemeral
                  spec:
                    accessModes: ["ReadWriteOnce"]
                    storageClassName: "premium-rwo"
                    resources:
                      requests:
                        storage: 150Gi
          nodeSelector:
            cloud.google.com/gke-accelerator: "nvidia-l4"
            cloud.google.com/gke-spot: "true"

    在這個資訊清單中:

    • NUM_SHARD 必須為 2,因為模型需要兩個 NVIDIA L4 GPU。
    • QUANTIZE 設為 bitsandbytes-nf4,表示模型會以 4 位元而非 32 位元載入。這可讓 GKE 減少所需的 GPU 記憶體量,並提升推論速度。不過,模型準確度可能會降低。如要瞭解如何計算要申請的 GPU 數量,請參閱「計算 GPU 數量」。
  4. 套用資訊清單:

    kubectl apply -f text-generation-inference.yaml
    

    輸出結果會與下列內容相似:

    deployment.apps/llm created
    
  5. 確認模型狀態:

    kubectl get deploy
    

    輸出結果會與下列內容相似:

    NAME          READY   UP-TO-DATE   AVAILABLE   AGE
    llm           1/1     1            1           20m
    
  6. 查看執行中部署作業的記錄:

    kubectl logs -l app=llm
    

    輸出結果會與下列內容相似:

    {"timestamp":"2024-03-09T05:08:14.751646Z","level":"INFO","message":"Warming up model","target":"text_generation_router","filename":"router/src/main.rs","line_number":291}
    {"timestamp":"2024-03-09T05:08:19.961136Z","level":"INFO","message":"Setting max batch total tokens to 133696","target":"text_generation_router","filename":"router/src/main.rs","line_number":328}
    {"timestamp":"2024-03-09T05:08:19.961164Z","level":"INFO","message":"Connected","target":"text_generation_router","filename":"router/src/main.rs","line_number":329}
    {"timestamp":"2024-03-09T05:08:19.961171Z","level":"WARN","message":"Invalid hostname, defaulting to 0.0.0.0","target":"text_generation_router","filename":"router/src/main.rs","line_number":343}
    

Mixtral 8x7b

  1. 設定預設環境變數:

    export HF_TOKEN=HUGGING_FACE_TOKEN
    

    HUGGING_FACE_TOKEN 替換為您的 HuggingFace 權杖。

  2. 為 HuggingFace 權杖建立 Kubernetes 密鑰

    kubectl create secret generic l4-demo \
        --from-literal=HUGGING_FACE_TOKEN=${HF_TOKEN} \
        --dry-run=client -o yaml | kubectl apply -f -
    
  3. 建立下列 text-generation-inference.yaml Deployment 資訊清單:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: llm
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: llm
      template:
        metadata:
          labels:
            app: llm
        spec:
          containers:
          - name: llm
            image: us-docker.pkg.dev/deeplearning-platform-release/gcr.io/huggingface-text-generation-inference-cu124.2-3.ubuntu2204.py311
            resources:
              requests:
                cpu: "5"
                memory: "40Gi"
                nvidia.com/gpu: "2"
              limits:
                cpu: "5"
                memory: "40Gi"
                nvidia.com/gpu: "2"
            env:
            - name: MODEL_ID
              value: mistralai/Mixtral-8x7B-Instruct-v0.1
            - name: NUM_SHARD
              value: "2"
            - name: PORT
              value: "8080"
            - name: QUANTIZE
              value: bitsandbytes-nf4
            - name: HUGGING_FACE_HUB_TOKEN
              valueFrom:
                secretKeyRef:
                  name: l4-demo
                  key: HUGGING_FACE_TOKEN          
            volumeMounts:
              - mountPath: /dev/shm
                name: dshm
              # mountPath is set to /tmp as it's the path where the HF_HOME environment
              # variable in the TGI DLCs is set to instead of the default /data set within the TGI default image.
              # i.e. where the downloaded model from the Hub will be stored
              - mountPath: /tmp
                name: ephemeral-volume
          volumes:
            - name: dshm
              emptyDir:
                  medium: Memory
            - name: ephemeral-volume
              ephemeral:
                volumeClaimTemplate:
                  metadata:
                    labels:
                      type: ephemeral
                  spec:
                    accessModes: ["ReadWriteOnce"]
                    storageClassName: "premium-rwo"
                    resources:
                      requests:
                        storage: 100Gi
          nodeSelector:
            cloud.google.com/gke-accelerator: "nvidia-l4"
            cloud.google.com/gke-spot: "true"

    在這個資訊清單中:

    • NUM_SHARD 必須為 2,因為模型需要兩個 NVIDIA L4 GPU。
    • QUANTIZE 設為 bitsandbytes-nf4,表示模型會以 4 位元而非 32 位元載入。這可讓 GKE 減少所需的 GPU 記憶體量,並提升推論速度。不過,這可能會降低模型準確度。如要瞭解如何計算要申請的 GPU 數量,請參閱「計算 GPU 數量」。
  4. 套用資訊清單:

    kubectl apply -f text-generation-inference.yaml
    

    輸出結果會與下列內容相似:

    deployment.apps/llm created
    
  5. 確認模型狀態:

    watch kubectl get deploy
    

    部署作業完成後,輸出內容會與下列內容相似:

    NAME          READY   UP-TO-DATE   AVAILABLE   AGE
    llm           1/1     1            1           10m
    

    如要退出手錶,請輸入 CTRL + C

  6. 查看執行中部署作業的記錄:

    kubectl logs -l app=llm
    

    輸出結果會與下列內容相似:

    {"timestamp":"2024-03-09T05:08:14.751646Z","level":"INFO","message":"Warming up model","target":"text_generation_router","filename":"router/src/main.rs","line_number":291}
    {"timestamp":"2024-03-09T05:08:19.961136Z","level":"INFO","message":"Setting max batch total tokens to 133696","target":"text_generation_router","filename":"router/src/main.rs","line_number":328}
    {"timestamp":"2024-03-09T05:08:19.961164Z","level":"INFO","message":"Connected","target":"text_generation_router","filename":"router/src/main.rs","line_number":329}
    {"timestamp":"2024-03-09T05:08:19.961171Z","level":"WARN","message":"Invalid hostname, defaulting to 0.0.0.0","target":"text_generation_router","filename":"router/src/main.rs","line_number":343}
    

Falcon 40b

  1. 建立下列 text-generation-inference.yaml Deployment 資訊清單:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: llm
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: llm
      template:
        metadata:
          labels:
            app: llm
        spec:
          containers:
          - name: llm
            image: us-docker.pkg.dev/deeplearning-platform-release/gcr.io/huggingface-text-generation-inference-cu121.1-4.ubuntu2204.py310
            resources:
              requests:
                cpu: "10"
                memory: "60Gi"
                nvidia.com/gpu: "2"
              limits:
                cpu: "10"
                memory: "60Gi"
                nvidia.com/gpu: "2"
            env:
            - name: MODEL_ID
              value: tiiuae/falcon-40b-instruct
            - name: NUM_SHARD
              value: "2"
            - name: PORT
              value: "8080"
            - name: QUANTIZE
              value: bitsandbytes-nf4
            volumeMounts:
              - mountPath: /dev/shm
                name: dshm
              # mountPath is set to /data as it's the path where the HUGGINGFACE_HUB_CACHE environment
              # variable points to in the TGI container image i.e. where the downloaded model from the Hub will be
              # stored
              - mountPath: /data
                name: ephemeral-volume
          volumes:
            - name: dshm
              emptyDir:
                  medium: Memory
            - name: ephemeral-volume
              ephemeral:
                volumeClaimTemplate:
                  metadata:
                    labels:
                      type: ephemeral
                  spec:
                    accessModes: ["ReadWriteOnce"]
                    storageClassName: "premium-rwo"
                    resources:
                      requests:
                        storage: 175Gi
          nodeSelector:
            cloud.google.com/gke-accelerator: "nvidia-l4"
            cloud.google.com/gke-spot: "true"

    在這個資訊清單中:

    • NUM_SHARD 必須為 2,因為模型需要兩個 NVIDIA L4 GPU。
    • QUANTIZE 設為 bitsandbytes-nf4,表示模型會以 4 位元而非 32 位元載入。這可讓 GKE 減少所需的 GPU 記憶體量,並提升推論速度。不過,模型準確度可能會降低。如要瞭解如何計算要申請的 GPU 數量,請參閱「計算 GPU 數量」。
  2. 套用資訊清單:

    kubectl apply -f text-generation-inference.yaml
    

    輸出結果會與下列內容相似:

    deployment.apps/llm created
    
  3. 確認模型狀態:

    watch kubectl get deploy
    

    部署作業完成後,輸出內容會類似於下列內容:

    NAME          READY   UP-TO-DATE   AVAILABLE   AGE
    llm           1/1     1            1           10m
    

    如要退出手錶,請輸入 CTRL + C

  4. 查看執行中部署作業的記錄:

    kubectl logs -l app=llm
    

    輸出結果會與下列內容相似:

    {"timestamp":"2024-03-09T05:08:14.751646Z","level":"INFO","message":"Warming up model","target":"text_generation_router","filename":"router/src/main.rs","line_number":291}
    {"timestamp":"2024-03-09T05:08:19.961136Z","level":"INFO","message":"Setting max batch total tokens to 133696","target":"text_generation_router","filename":"router/src/main.rs","line_number":328}
    {"timestamp":"2024-03-09T05:08:19.961164Z","level":"INFO","message":"Connected","target":"text_generation_router","filename":"router/src/main.rs","line_number":329}
    {"timestamp":"2024-03-09T05:08:19.961171Z","level":"WARN","message":"Invalid hostname, defaulting to 0.0.0.0","target":"text_generation_router","filename":"router/src/main.rs","line_number":343}
    

建立 ClusterIP 類型的服務

在叢集內部公開 Pod,讓其他應用程式可以探索及存取這些 Pod。

  1. 建立下列 llm-service.yaml 資訊清單:

    apiVersion: v1
    kind: Service
    metadata:
      name: llm-service
    spec:
      selector:
        app: llm
      type: ClusterIP
      ports:
        - protocol: TCP
          port: 80
          targetPort: 8080
    
  2. 套用資訊清單:

    kubectl apply -f llm-service.yaml
    

部署即時通訊介面

使用 Gradio 建構網頁應用程式,與模型互動。Gradio 是 Python 程式庫,內含 ChatInterface 包裝函式,可為聊天機器人建立使用者介面。

Llama 3 70b

  1. 建立名為 gradio.yaml 的檔案:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: gradio
      labels:
        app: gradio
    spec:
      strategy:
        type: Recreate
      replicas: 1
      selector:
        matchLabels:
          app: gradio
      template:
        metadata:
          labels:
            app: gradio
        spec:
          containers:
          - name: gradio
            image: us-docker.pkg.dev/google-samples/containers/gke/gradio-app:v1.0.4
            resources:
              requests:
                cpu: "512m"
                memory: "512Mi"
              limits:
                cpu: "1"
                memory: "512Mi"
            env:
            - name: CONTEXT_PATH
              value: "/generate"
            - name: HOST
              value: "http://llm-service"
            - name: LLM_ENGINE
              value: "tgi"
            - name: MODEL_ID
              value: "meta-llama/Meta-Llama-3-70B-Instruct"
            - name: USER_PROMPT
              value: "<|begin_of_text|><|start_header_id|>user<|end_header_id|> prompt <|eot_id|><|start_header_id|>assistant<|end_header_id|>"
            - name: SYSTEM_PROMPT
              value: "prompt <|eot_id|>"
            ports:
            - containerPort: 7860
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: gradio-service
    spec:
      type: LoadBalancer
      selector:
        app: gradio
      ports:
      - port: 80
        targetPort: 7860
    
  2. 套用資訊清單:

    kubectl apply -f gradio.yaml
    
  3. 找出 Service 的外部 IP 位址:

    kubectl get svc
    

    輸出結果會與下列內容相似:

    NAME             TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
    gradio-service   LoadBalancer   10.24.29.197   34.172.115.35   80:30952/TCP   125m
    
  4. 複製「EXTERNAL-IP」欄中的外部 IP 位址。

  5. 在網路瀏覽器上利用外部 IP 位址與公開的通訊埠來查看模型介面:

    http://EXTERNAL_IP
    

Mixtral 8x7b

  1. 建立名為 gradio.yaml 的檔案:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: gradio
      labels:
        app: gradio
    spec:
      strategy:
        type: Recreate
      replicas: 1
      selector:
        matchLabels:
          app: gradio
      template:
        metadata:
          labels:
            app: gradio
        spec:
          containers:
          - name: gradio
            image: us-docker.pkg.dev/google-samples/containers/gke/gradio-app:v1.0.4
            resources:
              requests:
                cpu: "512m"
                memory: "512Mi"
              limits:
                cpu: "1"
                memory: "512Mi"
            env:
            - name: CONTEXT_PATH
              value: "/generate"
            - name: HOST
              value: "http://llm-service"
            - name: LLM_ENGINE
              value: "tgi"
            - name: MODEL_ID
              value: "mixtral-8x7b"
            - name: USER_PROMPT
              value: "[INST] prompt [/INST]"
            - name: SYSTEM_PROMPT
              value: "prompt"
            ports:
            - containerPort: 7860
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: gradio-service
    spec:
      type: LoadBalancer
      selector:
        app: gradio
      ports:
      - port: 80
        targetPort: 7860
    
  2. 套用資訊清單:

    kubectl apply -f gradio.yaml
    
  3. 找出 Service 的外部 IP 位址:

    kubectl get svc
    

    輸出結果會與下列內容相似:

    NAME             TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
    gradio-service   LoadBalancer   10.24.29.197   34.172.115.35   80:30952/TCP   125m
    
  4. 複製「EXTERNAL-IP」欄中的外部 IP 位址。

  5. 在網路瀏覽器上利用外部 IP 位址與公開的通訊埠來查看模型介面:

    http://EXTERNAL_IP
    

Falcon 40b

  1. 建立名為 gradio.yaml 的檔案:

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: gradio
      labels:
        app: gradio
    spec:
      strategy:
        type: Recreate
      replicas: 1
      selector:
        matchLabels:
          app: gradio
      template:
        metadata:
          labels:
            app: gradio
        spec:
          containers:
          - name: gradio
            image: us-docker.pkg.dev/google-samples/containers/gke/gradio-app:v1.0.4
            resources:
              requests:
                cpu: "512m"
                memory: "512Mi"
              limits:
                cpu: "1"
                memory: "512Mi"
            env:
            - name: CONTEXT_PATH
              value: "/generate"
            - name: HOST
              value: "http://llm-service"
            - name: LLM_ENGINE
              value: "tgi"
            - name: MODEL_ID
              value: "falcon-40b-instruct"
            - name: USER_PROMPT
              value: "User: prompt"
            - name: SYSTEM_PROMPT
              value: "Assistant: prompt"
            ports:
            - containerPort: 7860
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: gradio-service
    spec:
      type: LoadBalancer
      selector:
        app: gradio
      ports:
      - port: 80
        targetPort: 7860
    
  2. 套用資訊清單:

    kubectl apply -f gradio.yaml
    
  3. 找出 Service 的外部 IP 位址:

    kubectl get svc
    

    輸出結果會與下列內容相似:

    NAME             TYPE           CLUSTER-IP     EXTERNAL-IP     PORT(S)        AGE
    gradio-service   LoadBalancer   10.24.29.197   34.172.115.35   80:30952/TCP   125m
    
  4. 複製「EXTERNAL-IP」欄中的外部 IP 位址。

  5. 在網路瀏覽器上利用外部 IP 位址與公開的通訊埠來查看模型介面:

    http://EXTERNAL_IP
    

計算 GPU 數量

GPU 數量取決於 QUANTIZE 標記的值。在本教學課程中,QUANTIZE 會設為 bitsandbytes-nf4,表示模型會以 4 位元載入。

700 億個參數的模型至少需要 40 GB 的 GPU 記憶體,這等於 700 億乘以 4 位元 (700 億 x 4 位元= 35 GB),並考量 5 GB 的額外負荷。在這個情況下,單一 L4 GPU 的記憶體不足。因此,本教學課程中的範例使用兩個 L4 GPU 的記憶體 (2 x 24 = 48 GB)。這個設定足以在 L4 GPU 中執行 Falcon 40b 或 Llama 3 70b。

清除所用資源

如要避免系統向您的 Google Cloud 帳戶收取本教學課程中所用資源的相關費用,請刪除含有該項資源的專案,或者保留專案但刪除個別資源。

刪除叢集

如要避免系統向您的 Google Cloud 帳戶收取本指南中建立資源的費用,請刪除 GKE 叢集:

gcloud container clusters delete l4-demo --region ${REGION}

後續步驟