問題の概要:vLLMのDockerデプロイで遭遇する典型的なエラーと課題
vLLMは、大規模言語モデル(LLM)の推論を高速化するためのオープンソースライブラリです。その高いパフォーマンスから、多くの開発者がDockerコンテナを用いて本番環境にデプロイしようと試みます。しかし、Docker環境でのデプロイ時には、ホスト環境とは異なる特有のエラーに遭遇することが少なくありません。具体的には、以下のような問題が頻発します。
- GPUリソースがコンテナ内で認識されない(
CUDA error: no kernel image is available for execution on the device) - モデルのダウンロードがタイムアウトする、または権限エラーが発生する
- メモリ不足(OOM)エラーが頻発する(特にマルチGPU環境)
- vLLM固有の
AsyncLLMEngineやOpenAI-compatible serverが起動しない - 本番環境でのスケーリング時にパフォーマンスが期待通りに出ない
これらの問題は、単にDockerコマンドを実行するだけでは解決が難しく、vLLMのアーキテクチャとDocker環境の両方に対する理解が必要です。
原因の解説:なぜDockerデプロイは難しいのか?
vLLMのDockerデプロイが複雑になる主な原因は、以下の3点に集約されます。
1. CUDA/GPUドライバの互換性問題
vLLMはPyTorchをベースとしており、CUDAカーネルを高度に最適化しています。ホストマシンのNVIDIAドライバのバージョンと、Dockerイメージ内のCUDA Toolkit、PyTorchのバージョンの組み合わせが適切でない場合、カーネルの実行に失敗します。特に、Dockerfileでnvidia/cuda:xx.x-baseのような軽量イメージを使用すると、必要なライブラリが欠落していることがあります。
2. モデルファイルの管理とアクセス権
LLMのモデルファイルは非常に大きく(数十GB)、コンテナ内に直接ダウンロードするとビルド時間が膨大になります。そのため、ホストのボリュームをマウントして共有するのが一般的ですが、コンテナ内のユーザー(多くの場合、非rootユーザー)とホストのファイル権限が不一致を起こし、読み取りエラーが発生します。
3. 本番環境向けリソース管理の複雑さ
開発環境では単一のGPUで動作していたvLLMインスタンスも、本番環境では複数GPUへの分散推論や、リクエストのバッチ処理、自動スケーリングが必要になります。Docker ComposeやKubernetes上でこれらのリソースを適切に割り当て、vLLMの設定パラメータ(tensor_parallel_size, gpu_memory_utilizationなど)と連動させるには、慎重な構成が求められます。
解決方法:ステップバイステップのトラブルシューティングと最適構成
ステップ1:互換性のあるDockerイメージの作成とGPU認識確認
まず、ホストのNVIDIAドライババージョンに合ったCUDAベースイメージを選択します。以下のDockerfileは、依存関係を明示的にインストールする安定した例です。
# Dockerfile
FROM nvidia/cuda:12.1.1-cudnn8-runtime-ubuntu22.04
# システム依存関係のインストール
RUN apt-get update && apt-get install -y
python3-pip
python3-dev
git
&& rm -rf /var/lib/apt/lists/*
# 非rootユーザーの作成(セキュリティ向上)
RUN useradd -m -u 1000 -s /bin/bash vllm-user
WORKDIR /app
RUN chown vllm-user:vllm-user /app
USER vllm-user
# Python依存関係のインストール
COPY --chown=vllm-user requirements.txt .
RUN pip3 install --no-cache-dir -r requirements.txt
# vLLMのインストール(特定バージョンを推奨)
RUN pip3 install --no-cache-dir vllm==0.3.3
# アプリケーションコードのコピー
COPY --chown=vllm-user . .
EXPOSE 8000
CMD ["python3", "-m", "vllm.entrypoints.openai.api_server",
"--host", "0.0.0.0",
"--model", "mistralai/Mistral-7B-Instruct-v0.2"]
ビルド後、以下のコマンドでGPUが認識されているか確認します。
# コンテナを起動してnvidia-smiを実行
docker run --gpus all --rm your-vllm-image nvidia-smi
# コンテナ内でPythonインタラクティブシェルを起動し、vLLMがGPUを認識するか確認
docker run --gpus all -it --rm your-vllm-image python3 -c "import torch; print(torch.cuda.is_available()); import vllm; print('vLLM import OK')"
ステップ2:モデルファイルの効率的な管理と権限問題の解決
モデルファイルはホスト側にキャッシュし、コンテナにボリュームマウントします。vLLMは~/.cache/huggingfaceをデフォルトのキャッシュディレクトリとして使用します。
# ホスト側でキャッシュディレクトリを作成(適切な権限を付与)
mkdir -p /hf_cache
chmod 777 /hf_cache # または、コンテナユーザーUIDに合わせたchown
# Docker runコマンドでマウント。環境変数でキャッシュ場所を指定。
docker run --gpus all
-p 8000:8000
-v /hf_cache:/home/vllm-user/.cache/huggingface
-e HF_HOME=/home/vllm-user/.cache/huggingface
your-vllm-image
権限エラーが発生する場合は、Dockerfile内のUSER命令で指定したユーザーのUID(例:1000)と、ホスト側のキャッシュディレクトリの所有者を一致させるのが確実です。
ステップ3:本番環境向けの最適化とスケーリング構成
本番環境では、単一コンテナではなく、複数インスタンスとロードバランサの構成が一般的です。以下はdocker-compose.ymlを使ったマルチGPU、マルチインスタンス構成の例です。
# docker-compose.yml
version: '3.8'
services:
vllm-server-1:
build: .
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
ports:
- "8001:8000"
command: >
python3 -m vllm.entrypoints.openai.api_server
--host 0.0.0.0
--port 8000
--model mistralai/Mistral-7B-Instruct-v0.2
--tensor-parallel-size 1
--gpu-memory-utilization 0.9
--max-num-batched-tokens 4096
volumes:
- /hf_cache:/home/vllm-user/.cache/huggingface
environment:
- HF_HOME=/home/vllm-user/.cache/huggingface
vllm-server-2:
build: .
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
ports:
- "8002:8000"
command: >
python3 -m vllm.entrypoints.openai.api_server
--host 0.0.0.0
--port 8000
--model mistralai/Mistral-7B-Instruct-v0.2
--tensor-parallel-size 1
--gpu-memory-utilization 0.9
--max-num-batched-tokens 4096
volumes:
- /hf_cache:/home/vllm-user/.cache/huggingface
environment:
- HF_HOME=/home/vllm-user/.cache/huggingface
nginx-lb:
image: nginx:alpine
ports:
- "8000:80"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- vllm-server-1
- vllm-server-2
対応するNginx設定(nginx.conf)で、ロードバランシングを設定します。
ステップ4:よくあるエラーとその対処法
エラー1: RuntimeError: CUDA error: no kernel image is available for execution on the device
解決策: ホストのGPUアーキテクチャ(例: Ampere)に対応したPyTorch/CUDAバージョンを使用しているか確認。Dockerfileのベースイメージをnvidia/cuda:12.1.1のように具体的なバージョンに固定し、pip install torch==2.1.2 torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121のようにPyTorchも明示的にインストールします。
エラー2: OutOfMemoryError: CUDA out of memory.
解決策: vLLMサーバー起動時の引数を調整します。--gpu-memory-utilizationを0.8など下げる、--max-num-batched-tokensを小さくする、あるいは--tensor-parallel-sizeを増やして複数GPUにモデルを分散させます。
エラー3: モデルダウンロードがPermission deniedまたはタイムアウト。
解決策: 環境変数HF_HOMEを設定し、書き込み権限のあるパスを指定。企業環境ではプロキシ設定(HTTP_PROXY, HTTPS_PROXY)も必要になる場合があります。
コード例・コマンド例:健全性チェックとベンチマーク
デプロイ後、以下のようなスクリプトでAPIサーバーの動作を確認します。
# health_check.py
import openai
import time
client = openai.OpenAI(
api_key="token-abc123", # vLLMサーバーは任意のキーを受け入れる
base_url="http://localhost:8000/v1"
)
def health_check():
try:
start = time.time()
response = client.chat.completions.create(
model="mistralai/Mistral-7B-Instruct-v0.2",
messages=[{"role": "user", "content": "Hello, how are you?"}],
max_tokens=50
)
elapsed = time.time() - start
print(f"✅ Health check PASSED. Response: {response.choices[0].message.content[:50]}...")
print(f" Time taken: {elapsed:.2f} seconds")
return True
except Exception as e:
print(f"❌ Health check FAILED: {e}")
return False
if __name__ == "__main__":
health_check()
実行コマンド:
python3 health_check.py
まとめ・補足情報
vLLMをDockerで本番環境にデプロイする際の成功の鍵は、「環境の再現性」、「リソースの明示的管理」、「段階的な検証」の3点です。CUDAとPyTorchのバージョン互換性を最初に固め、モデルキャッシュの権限問題を解決した上で、メモリやGPUのパラメータを本番負荷に合わせてチューニングします。
さらに本番運用では、以下の点にも留意すると良いでしょう。
- 監視: Prometheus + Grafanaを用いて、vLLMのメトリクス(リクエストレイテンシ、GPU使用率、キャッシュヒット率など)を可視化する。
- ログ: コンテナの標準出力/エラー出力をFluentdやLokiなどに集約し、エラートレースを容易にする。
- セキュリティ: 本番環境では、
--api-keyオプションを必ず使用し、不正なAPIアクセスを防ぐ。また、ネットワークポリシーで必要最小限の通信のみを許可する。 - モデルバージョン管理: 使用するモデルは特定のリビジョン(例:
mistralai/Mistral-7B-Instruct-v0.2)で固定し、意図しない更新を防ぐ。
このガイドで紹介した構成とトラブルシューティング手順を参考にすれば、vLLMの高い推論性能を、安定したDocker環境で本番サービスに活かすことができるでしょう。