0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloud RunのL4 GPUでLLMを動かしてみた

Last updated at Posted at 2025-12-06

3-shake Advent Calendar 2025 Day #7 としての投稿です。

はじめに

3-shake Sreake事業部の荒木です。生成AIのAPIサービスを使うのも便利ですが、Cloud RunにLLMモデルをホストしてみました。

この記事でわかること

  • Cloud RunでLLMをホストする具体的な手順
  • vLLMを使った推論サーバーの構築方法
  • 実際に動かしてみて分かった課題や制約

正直なところ先に結論を言ってしまうと、特に実用レベルのLLMを動かすには、スペック的にかなり厳しいです。また、おそらくこの方法以上に高速にLLMをホストする方法があることも断っておきます。

Cloud Run と GPU

Cloud RunでLLMをホストする上で重要なのがGPUの利用です。2025年6月からCloud Runで誰もがGPUが使えるようになりました。

Cloud Runで利用できるのはNVIDIA L4 GPUのみです。主なスペックは以下の通り:

  • GPU メモリ(VRAM): 24GB
  • インスタンスあたり1つのGPUを構成可能
  • GPU搭載インスタンスの起動は約5秒で完了する
  • gemma3:4bモデルで約19秒で最初のトークンを生成可能とのこと

リソース要件
GPUを使う場合、Cloud Runのインスタンスは以下の要件を満たす必要があります。

  • 最小CPU: 4個(推奨は8個)
  • 最小メモリ: 16GiB(推奨は32GiB)

GPUメモリ24GBは、インスタンスメモリとは別に割り当てられます。

料金体系
GPU利用時はインスタンスベースの課金が必須となります。つまり:

  • リクエスト単位ではなく、インスタンスが起動している時間で課金
  • 未使用時はゼロスケールも可能(最小インスタンス設定次第)
  • GPU は、インスタンスのライフサイクル全体に対して課金されます

利用可能なリージョン
以下のリージョンでGPUが利用可能です:

  • asia-southeast1 (シンガポール)
  • asia-south1 (ムンバイ)
  • europe-west1、europe-west4
  • us-central1、us-east4

残念ながら、asia-northeast1(東京)では現時点で利用できません。

注意点

  • 初回利用時に3つのGPU割り当てが自動付与される
  • 追加が必要な場合は申請が必要

詳細は公式ドキュメントを参照してください。

Cloud Runへのデプロイ

LLMモデルを外部から利用できるようにするために、vllmを使用します。

  1. HuggingFaceのトークンをSecretManagerに保存
  2. Cloud Buildでモデルキャッシュの作成とvllmのコンテナイメージをビルド
  3. Cloud Runにデプロイ

という順番でデプロイを行います。

vllmとは
vLLM(Virtual LLM)は、LLMの推論を高速化するためのオープンソースライブラリです。

  • 高速な推論: PagedAttentionという独自の技術で、メモリ効率を大幅に改善
  • OpenAI API互換: サーバーモードを利用することでOpenAI APIと同じインターフェースでモデルを利用可能
  • バッチ処理: 複数のリクエストを効率的にバッチ処理
  • 多様なモデル対応: Hugging Faceにある多くのLLMモデルをサポート

1. 事前準備

以下のコマンドで依存するコンポーネントのセットアップを行います。

サービス有効化、SecretManagerにトークン保存、権限設定、Artifact Registry作成を行う
gcloud services enable run.googleapis.com \
    cloudbuild.googleapis.com \
    secretmanager.googleapis.com \
    artifactregistry.googleapis.com

HF_TOKEN=<YOUR_HUGGING_FACE_TOKEN> # 一部モデルのダウンロードでこのトークンが必要
PROJECT_ID=<YOUR_PROJECT_ID>

REGION=us-central1
SERVICE_NAME=vllm
AR_REPO_NAME=vllm-repo
SERVICE_ACCOUNT=vllm-sa
SERVICE_ACCOUNT_ADDRESS=$SERVICE_ACCOUNT@$PROJECT_ID.iam.gserviceaccount.com
GCS_BUCKET_NAME=model-gcs

gcloud iam service-accounts create $SERVICE_ACCOUNT \
  --display-name="Cloud Run vllm SA to access secret manager"

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member serviceAccount:$SERVICE_ACCOUNT_ADDRESS \
  --role=roles/secretmanager.secretAccessor

printf $HF_TOKEN | gcloud secrets create HF_TOKEN --data-file=-
PROJECT_NUMBER=$(gcloud projects describe $PROJECT_ID --format="value(projectNumber)")

gcloud projects add-iam-policy-binding $PROJECT_ID \
  --member="serviceAccount:$PROJECT_NUMBER-compute@developer.gserviceaccount.com" \
  --role="roles/secretmanager.secretAccessor"

gcloud artifacts repositories create $AR_REPO_NAME \
  --repository-format docker \
  --location us-central1

HF_TOKEN はHuggingFaceのアクセストークンです。モデルによっては、利用に際し規約に同意する必要があります。HuggingFaceのウェブサイトで該当モデルのページにアクセスし、利用規約に同意します。これにより、同意したユーザーのトークンでのみモデルをダウンロードできるようになります。

image.png

2. コンテナイメージ(Dockerfile)の作成

LLMモデルをコンテナに含めるとコンテナサイズが大きくなってしまいます。そこで、事前にCloud Storageにモデルのキャッシュを作成、Cloud Runのコンテナ起動時にマウントする方法を取ります。これによって、コンテナサイズを削減、起動の高速化を試みます。

モデルキャッシュ用のGCSバケットを作成
gsutil mb -p $PROJECT_ID gs://$GCS_BUCKET_NAME
バケットへの権限付与
gcloud storage buckets add-iam-policy-binding gs://$GCS_BUCKET_NAME \
  --member="serviceAccount:$SERVICE_ACCOUNT_ADDRESS" \
  --role="roles/storage.objectAdmin"

今回使用するコンテナイメージとしては以下のようなDockerfileで定義されます。

FROM vllm/vllm-openai:v0.11.0

ENV HF_HOME=/model-cache
RUN --mount=type=secret,id=HF_TOKEN HF_TOKEN=$(cat /run/secrets/HF_TOKEN) \
    huggingface-cli download google/gemma-2-2b-it

ENV HF_HUB_OFFLINE=1

ENTRYPOINT python3 -m vllm.entrypoints.openai.api_server \
    --port ${PORT:-8000} \
    --model ${MODEL_NAME:-google/gemma-2-2b-it} \
    --gpu-memory-utilization 0.85 \
    --max-num-seqs 256 \
    --max-model-len 4096

vllmには、LLMモデルごとにHuggingFace形式の重みをvLLMで推論するための重み形式に変換するロジックが実装されています。

そのため、最新のLLMモデルはvllmで利用できない、もしくは、vllm/vllm-openai:latestが新しいモデルに追従できていない場合があります。

そのようなときは、nightly-*のタグが付いたベースイメージを利用することで利用可能になることがあります。

DeepSeekOCRのデプロイを試したときのコンテナイメージの例
FROM vllm/vllm-openai:nightly-380ba6816d4646be99d9b6d207ba7bc7fce8290e

RUN pip install addict matplotlib

ENV HF_HOME=/model-cache

ENV HF_HUB_OFFLINE=1

ENTRYPOINT python3 -m vllm.entrypoints.openai.api_server \
    --port ${PORT:-8000} \
    --model ${MODEL_NAME:-deepseek-ai/DeepSeek-OCR} \
    --logits_processors vllm.model_executor.models.deepseek_ocr:NGramPerReqLogitsProcessor \
    --no-enable-prefix-caching --mm-processor-cache-gb 0 \
    ${MAX_MODEL_LEN:+--max-model-len "$MAX_MODEL_LEN"}

追加で必要なライブラリもインストールしている

今回ベースイメージとして用いている、vllm/vllm-openaiは、ベースイメージの時点でそれなりのサイズを要します。

image.png

3. モデルキャッシュの取得とコンテナイメージをビルドするCloud Buildを作成

以下のビルドフローで、次の処理を行います。

  1. モデルをダウンロードして、GCSに保存
  2. Dockerfileをもとにコンテナイメージをbuild, Artifact Registryにpush

を行います。

steps:
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    id: download-model
    entrypoint: 'bash'
    secretEnv: ['HF_TOKEN']
    args:
      - -c
      - |
        apt update && apt install python3.11 python3.11-venv -y
        curl -LsSf https://hf.co/cli/install.sh | bash
        export HF_TOKEN=$$HF_TOKEN

        export HF_HOME=/workspace/hf-home
        echo "Downloading model ${_MODEL_NAME} from Hugging Face..."
        ~/.local/bin/hf download ${_MODEL_NAME} --token $$HF_TOKEN
        echo "Uploading..."

        gsutil -m cp -r /workspace/hf-home/hub gs://${_GCS_BUCKET_NAME}/

  - name: 'gcr.io/cloud-builders/docker'
    id: build
    entrypoint: 'bash'
    secretEnv: ['HF_TOKEN']
    args:
      - -c
      - |
        SECRET_TOKEN="$$HF_TOKEN"
        docker buildx build --tag=${_IMAGE} --secret id=HF_TOKEN .

availableSecrets:
  secretManager:
    - versionName: 'projects/${PROJECT_ID}/secrets/HF_TOKEN/versions/latest'
      env: 'HF_TOKEN'

images: ["${_IMAGE}"]

substitutions:
  _IMAGE: 'us-central1-docker.pkg.dev/${PROJECT_ID}/vllm-repo/vllm'
  _GCS_BUCKET_NAME: 'model-gcs'
  _MODEL_NAME: 'google/gemma-2-2b-it'

options:
  dynamicSubstitutions: true
  machineType: "E2_HIGHCPU_8"
gcloud builds submit --config=cloudbuild.yaml

今回はベースモデルから大きく変更は加えていないため、ビルド時間は15分程度で完了しました。ベースイメージのダウンロードとArtifact Registryへのプッシュに主に時間がかかるため、マシンタイプはそこまで高スペックにする必要はないかもしれません。

4. Cloud Runでデプロイ

vllmは環境変数で指定された、HF_HOMEにモデルキャッシュが存在することを期待しています。このディレクトリにモデルが存在しない場合、コンテナ起動時にモデルのダウンロードを試みます。

以下のコマンドでは、Cloud StorageのバケットをHF_HOMEとして指定された/model-cacheにマウントしています。

#!/bin/bash
gcloud beta run deploy $SERVICE_NAME \
  --image=$REGION-docker.pkg.dev/$PROJECT_ID/$AR_REPO_NAME/$SERVICE_NAME \
  --service-account $SERVICE_ACCOUNT_ADDRESS \
  --execution-environment gen2 \
  --add-volume name=model-dir,type=cloud-storage,bucket=$GCS_BUCKET_NAME \
  --add-volume-mount volume=model-dir,mount-path=/model-cache \
  --cpu=8 \
  --memory=16Gi \
  --gpu=1 \
  --port=8000 \
  --gpu-type=nvidia-l4 \
  --region $REGION \
  --no-allow-unauthenticated \
  --min-instances=1 \
  --max-instances=2 \
  --no-cpu-throttling \
  --no-gpu-zonal-redundancy \
  --startup-probe tcpSocket.port=8000,initialDelaySeconds=240,failureThreshold=1,timeoutSeconds=240,periodSeconds=240
2025-11-20 02:30:52.068 JST Cloud RunCreateServicevllm...
...
2025-11-20 02:39:22.283 JST STARTUP TCP probe succeeded after 1 attempt for container "vllm-1" on port 8000.

起動には10分近くかかっています。

こちらの記事にて、

最初のトークンまでの時間が gemma3:4b モデルで約 19 秒という驚異的な数値を達成しました

とありますので、もっと最適なデプロイ方法があるのかもしれません。

Cloud Runの強みとしては、リクエストがない場合はゼロスケールにでき、コストを抑えられる点にあります。

しかしながら、このvllmのホスティングでは、起動に非常に時間がかかります。
今回の例では、リクエストが来てからインスタンスが起動するまでに10分近くかかるため、レスポンスを待つことができません。

そのため、--min-instancesで常に1インスタンスを起動しておくことを推奨します。もちろん、その分コストはかかります。

動作確認

以下のコマンドでCloud Runのエンドポイントにプロキシ接続します。

ローカルのターミナルなどで実行
export PROJECT_ID=<YOUR_PROJECT_ID>
gcloud run services proxy vllm --region us-central1 --project=$PROJECT_ID

これにより、認証などをバイパスして、ローカルホスト経由でCloud Runのエンドポイントにアクセスできます。

このようなcurlコマンドで動作確認が可能になる
curl -X POST http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
    "model": "google/gemma-2-2b-it",
    "messages": [
      {
        "role": "user",
        "content": [
          {
            "type": "text",
            "text": "Hello, world!"
          }
        ]
      }
    ]
  }'

Cloud RunでLLMを動かすとどのくらい費用がかかるか?

社内の検証にて、1時間程度Cloud RunでLLMをホストしてみました。11/19のことです。

image.png

当初、 E2_HIGHCPU_32でビルドを行っていたため、合計コストが高くなっています。

Cloud Runには253円かかっており、図には載っていないのですがL4 GPUに対して発生している課金は130円程度でした。1時間で253円なら、GPUを使う分には普通といった印象です。

あくまで私の場合の数値ですので、利用状況によって変わります。詳細は以下のドキュメントを参照してください。

課題と他プロバイダーとの比較

このL4 GPUは、メモリが24GBしかないため、実用的なLLMモデルを動かすにはかなり厳しいです。私はvllmの設定に関して精通しているわけではないため、最適化の余地はあるかもしれませんが、その他のモデルも試した感じ、LLMに限って言うと~ 4Bクラスのモデルのホスティングが実用性を考えた上では限界ではないかと感じました。高機密データを扱う場合など、セルフホストでLLMを運用したい場合には利用できるかもしれませんが、難しいところだと思います。

その他GPUプロバイダを利用する
Cloud RunのL4 GPUに対して発生している課金は130円程度ということでしたが、他のGPUプロバイダを利用することで、より高性能なGPUをより低コストで利用できる可能性があります。例えば、さくらのクラウドでV100 GPU(32GB)を使う場合は、2025/11/30現在、1時間あたり 57.6円でした。

さくらのクラウドは噂にも聞いておりましたが、やはり安いですね。

その他、GPUを利用する手段としてはNvidiaのGPUクラウドサービスであるNvidia Brevが上げられます。こちらは1時間あたり $0.85 ~ (約133円) が利用可能です。このサービスの良いところは、非常に多くの種類のGPUを利用できる点です。A100やH100をはじめとして、グラフィック用途のRTX PRO 6000 なども利用可能です。

おわりに

本記事では、Cloud RunでLLMモデルをホストする方法について、vLLMを用いた具体的な実装手順と実際に検証して分かった課題をまとめました。

現時点では、Cloud RunのメモリやGPUの制約、コスト面から考えると、実運用での採用は慎重に検討すべきだと感じています。

しかしながら、この検証を通じて以下のような知見を得ることができました。

  • vLLMを使った推論サーバーの構築方法
  • Cloud Storageをボリュームマウントしたモデルキャッシュの運用
  • GPUを搭載したCloud Runの起動特性と課題

何よりもこのサービスの強みは、サーバーレスでGPUを利用でき、スケール速度が非常に速い点にあります。画像処理や非同期的なMLのバッチ処理などにおいて、非常に有用な選択肢になると感じました。

また、おそらくこのデプロイ方法はL4 GPUを利用するCloud Runにとって最適なものではなく、これ以上に高速にモデルをホストする方法があるはずです。今後も、調査と検証を続けていきたいと思います。

最後まで読んでいただきありがとうございました。本記事が、vllmを利用したことがある方や、LLMのセルフホスティングを検討されている方の参考になれば幸いです。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?