GKE で複数の GPU を使用して LLM を提供する


このチュートリアルでは、Google Kubernetes Engine(GKE)で GPU を使用して大規模言語モデル(LLM)を提供する方法について説明します。このチュートリアルでは、複数の L4 GPU を使用する GKE クラスタを作成し、次のいずれかのモデルを処理する GKE インフラストラクチャを準備します。

GPU の数はモデルのデータ形式によって異なります。このチュートリアルでは、各モデルで 2 つの L4 GPU を使用します。詳細については、GPU の量の計算をご覧ください。

GKE でこのチュートリアルを始める前に、GKE での GPU についてをご覧ください。

目標

このチュートリアルは、LLM を提供するために GKE オーケストレーション機能を使用する MLOps または DevOps エンジニア、またはプラットフォーム管理者を対象としています。

このチュートリアルでは、次の手順について説明します。

  1. クラスタとノードプールを作成する。
  2. ワークロードを準備する。
  3. ワークロードをデプロイする。
  4. LLM インターフェースを操作する。

始める前に

作業を始める前に、次のことを確認してください。

  • Google Kubernetes Engine API を有効にします。
  • Google Kubernetes Engine API の有効化
  • このタスクに Google Cloud CLI を使用する場合は、gcloud CLI をインストールして初期化します。すでに gcloud CLI をインストールしている場合は、gcloud components update を実行して最新のバージョンを取得します。
  • Llama 2 70b モデルを使用する場合は、次のものが揃っていることを確認してください。

環境を準備する

  1. Google Cloud コンソールで、Cloud Shell インスタンスを起動します。
    Cloud Shell を開く

  2. デフォルトの環境変数を設定します。

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

    PROJECT_ID は、実際の Google Cloud プロジェクト ID に置き換えます。

GKE クラスタとノードプールを作成する

GKE Autopilot クラスタまたは GKE Standard クラスタの GPU で LLM を提供できます。フルマネージドの Kubernetes エクスペリエンスを実現するには、Autopilot クラスタを使用することをおすすめします。ワークロードに最適な 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}
    

Standard

  1. Cloud Shell で次のコマンドを実行して、GKE 用 Workload Identity 連携を使用する 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 エディションの一般公開クラスタ。
    • 0 ノードにスケールダウンされた g2-standard-24 マシンタイプのノードプール。GPU をリクエストする Pod を起動するまで、GPU の料金は発生しません。このノードプールは Spot VM をプロビジョニングします。Spot VM はデフォルトの標準の Compute Engine VM よりも低価格ですが、可用性は保証されません。オンデマンド VM を使用するには、このコマンドから --spot フラグと text-generation-inference.yaml 構成の cloud.google.com/gke-spot ノードセレクタを削除します。
  3. クラスタと通信を行うように kubectl を構成します。

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

ワークロードを準備する

次のセクションでは、使用するモデルに応じてワークロードを設定する方法について説明します。

Llama 2 70b

  1. デフォルトの環境変数を設定します。

    export HF_TOKEN=HUGGING_FACE_TOKEN
    

    HUGGING_FACE_TOKEN は、HuggingFace トークンに置き換えます。

  2. HuggingFace トークンの Kubernetes Secret を作成します。

    kubectl create secret generic l4-demo \
        --from-literal=HUGGING_FACE_TOKEN=${HF_TOKEN} \
        --dry-run=client -o yaml > hf-secret.yaml
    
  3. 次のようにマニフェストを適用します。

    kubectl apply -f hf-secret.yaml
    
  4. 次の text-generation-inference.yaml マニフェストを作成します。

    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: ghcr.io/huggingface/text-generation-inference:1.4.3
            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/Llama-2-70b-chat-hf
            - 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: /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: 150Gi
          nodeSelector:
            cloud.google.com/gke-accelerator: "nvidia-l4"
            cloud.google.com/gke-spot: "true"

    このマニフェストの内容:

    • このモデルには 2 つの NVIDIA L4 GPU が必要なため、NUM_SHARD2 にする必要があります。
    • QUANTIZEbitsandbytes-nf4 に設定されているため、モデルは 32 ビットではなく 4 ビットで読み込まれます。これにより、GKE は必要な GPU メモリの量を減らし、推論速度を向上させることができます。ただし、モデルの精度は低下する可能性があります。リクエストする GPU を計算する方法については、GPU の量の計算をご覧ください。
  5. 次のようにマニフェストを適用します。

    kubectl apply -f text-generation-inference.yaml
    

    出力は次のようになります。

    deployment.apps/llm created
    
  6. モデルのステータスを確認します。

    kubectl get deploy
    

    出力は次のようになります。

    NAME          READY   UP-TO-DATE   AVAILABLE   AGE
    llm           1/1     1            1           20m
    
  7. 実行中のデプロイのログを表示します。

    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. 次の text-generation-inference.yaml マニフェストを作成します。

    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: ghcr.io/huggingface/text-generation-inference:1.4.3
            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
            volumeMounts:
              - mountPath: /dev/shm
                name: dshm
              - 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: 100Gi
          nodeSelector:
            cloud.google.com/gke-accelerator: "nvidia-l4"
            cloud.google.com/gke-spot: "true"

    このマニフェストの内容:

    • このモデルには 2 つの NVIDIA L4 GPU が必要なため、NUM_SHARD2 にする必要があります。
    • QUANTIZEbitsandbytes-nf4 に設定されているため、モデルは 32 ビットではなく 4 ビットで読み込まれます。これにより、GKE は必要な GPU メモリの量を減らし、推論速度を向上させることができます。ただし、これによりモデルの精度が低下する可能性があります。リクエストする GPU を計算する方法については、GPU の量の計算をご覧ください。
  2. 次のようにマニフェストを適用します。

    kubectl apply -f text-generation-inference.yaml
    

    出力は次のようになります。

    deployment.apps/llm created
    
  3. モデルのステータスを確認します。

    watch kubectl get deploy
    

    デプロイの準備が整うと、出力は次のようになります。監視を終了するには、「CTRL + C」と入力します。

    NAME          READY   UP-TO-DATE   AVAILABLE   AGE
    llm           1/1     1            1           10m
    
  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}
    

Falcon 40b

  1. 次の text-generation-inference.yaml マニフェストを作成します。

    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: ghcr.io/huggingface/text-generation-inference:1.4.3
            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: /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"

    このマニフェストの内容:

    • このモデルには 2 つの NVIDIA L4 GPU が必要なため、NUM_SHARD2 にする必要があります。
    • QUANTIZEbitsandbytes-nf4 に設定されているため、モデルは 32 ビットではなく 4 ビットで読み込まれます。これにより、GKE は必要な GPU メモリの量を減らし、推論速度を向上させることができます。ただし、モデルの精度は低下する可能性があります。リクエストする GPU を計算する方法については、GPU の量の計算をご覧ください。
  2. 次のようにマニフェストを適用します。

    kubectl apply -f text-generation-inference.yaml
    

    出力は次のようになります。

    deployment.apps/llm created
    
  3. モデルのステータスを確認します。

    watch kubectl get deploy
    

    デプロイの準備が整うと、出力は次のようになります。監視を終了するには、「CTRL + C」と入力します。

    NAME          READY   UP-TO-DATE   AVAILABLE   AGE
    llm           1/1     1            1           10m
    
  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 タイプの Service を作成する

  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 は、chatbot のユーザー インターフェースを作成する ChatInterface ラッパーを含む Python ライブラリです。

Llama 2 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.0
            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: "llama-2-70b"
            - 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
    

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.0
            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.0
            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 フラグの値によって異なります。このチュートリアルでは、QUANTIZEbitsandbytes-nf4 に設定されています。これは、モデルが 4 ビットで読み込まれることを意味します。

700 億のパラメータ モデルでは、最低 40 GB の GPU メモリが必要です。これは 700 億 × 4 ビット(700 億 x 4 ビット = 35 GB)に相当し、5 GB のオーバーヘッドを考慮します。この場合、1 つの L4 GPU ではメモリが不足します。したがって、このチュートリアルの例では、2 つの L4 GPU メモリ(2 × 24 = 48 GB)を使用します。 この構成は、L4 GPU で Falcon 40b または Llama 2 70b を実行するのに十分です。

クリーンアップ

このチュートリアルで使用したリソースについて、Google Cloud アカウントに課金されないようにするには、リソースを含むプロジェクトを削除するか、プロジェクトを維持して個々のリソースを削除します。

クラスタを削除する

このガイドで作成したリソースについて、Google Cloud アカウントに課金されないようにするには、GKE クラスタを削除します。

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

次のステップ