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


このチュートリアルでは、効率的でスケーラブルな推論を行うために、GKE で複数の GPU を使用して大規模言語モデル(LLM)をデプロイして提供する方法について説明します。複数の L4 GPU を使用する GKE クラスタを作成し、次のいずれかのモデルを処理するインフラストラクチャを準備します。

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

このチュートリアルは、LLM の提供に Kubernetes コンテナ オーケストレーション機能を使用する ML エンジニア、プラットフォーム管理者、オペレーター、データおよび AI スペシャリストを対象としています。 Google Cloudのコンテンツで使用されている一般的なロールとタスクの例の詳細については、一般的な GKE Enterprise ユーザーロールとタスクをご覧ください。

このページを読む前に、次のことをよく理解しておいてください。

目標

このチュートリアルの内容は次のとおりです。

  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 クラスタまたは 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} 

ワークロードを準備する

このセクションでは、使用するモデルに応じてワークロードを設定する方法について説明します。このチュートリアルでは、Kubernetes Deployment を使用してモデルをデプロイします。Deployment は、クラスタ内のノードに分散された Pod の複数のレプリカを実行できる Kubernetes API オブジェクトです。

Llama 3 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 | 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"

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

    • このモデルには 2 つの NVIDIA L4 GPU が必要なため、NUM_SHARD2 にする必要があります。
    • QUANTIZEbitsandbytes-nf4 に設定されているため、モデルは 32 ビットではなく 4 ビットで読み込まれます。これにより、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 Secret を作成します。

    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"

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

    • このモデルには 2 つの NVIDIA L4 GPU が必要なため、NUM_SHARD2 にする必要があります。
    • QUANTIZEbitsandbytes-nf4 に設定されているため、モデルは 32 ビットではなく 4 ビットで読み込まれます。これにより、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"

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

    • このモデルには 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 

    デプロイの準備ができている場合、出力は次のようになります。

    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 タイプの Service を作成する

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

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 フラグの値によって異なります。このチュートリアルでは、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 3 70b を実行するのに十分です。

クリーンアップ

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

クラスタの削除

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

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

次のステップ