Skip to content
Published on

vLLM PagedAttentionベースのLLMプロダクションサービング最適化と推論エンジン比較ガイド

Authors
  • Name
    Twitter
vLLM PagedAttention

はじめに

LLMをプロダクション環境でサービングする際、最初にぶつかる壁はGPUメモリだ。Llama 3.1 70BをFP16でロードすると、モデルの重みだけで140GBが必要となり、複数のリクエストを同時に処理するにはKV Cacheがさらに数十~数百GB占有する。実際のプロダクション環境では同時リクエスト数が数十から数百に達するため、KV Cacheのメモリ管理がシステム全体のスループットとレイテンシーを決定づける核心要素となる。

従来のTransformer推論実装では、各リクエストに対して最大シーケンス長分のKV Cacheを事前に確保する。最大4,096トークンを許容するサービスで実際の平均出力が512トークンであれば、割り当てられたメモリの87%が無駄になる。この問題を根本的に解決したのがUC Berkeleyが発表したPagedAttentionアルゴリズムであり、これを実装したオープンソース推論エンジンがvLLMだ。

この記事では、PagedAttentionの動作原理からvLLMのアーキテクチャ、プロダクションデプロイ設定、パフォーマンス最適化手法、KubernetesベースのオートスケーリングSGLang/TensorRT-LLMとの比較、運用モニタリング、そして障害事例と復旧手順まで、プロダクションLLMサービングに必要な全領域を網羅する。

PagedAttentionの動作原理

仮想メモリページングの適用

PagedAttentionは、オペレーティングシステムの仮想メモリ管理からインスピレーションを得ている。OSはプロセスに連続した仮想アドレス空間を提供するが、実際の物理メモリは固定サイズのページ単位で非連続的に割り当てる。PagedAttentionはこの概念をKV Cacheにそのまま適用する。

KV Cacheを固定サイズのブロック(block)に分割し、各ブロックは固定数のトークンに対応するKey-Valueテンソルを格納する。デフォルトのブロックサイズは16トークンだ。リクエストの処理が進み新しいトークンが生成されるたびに、現在のブロックが満杯になると新しい物理ブロックが割り当てられ、ブロックテーブル(block table)にマッピングが追加される。

従来の連続割り当て方式:
Request A: [████████░░░░░░░░░░░░░░░░░░░░░░░░]  実際512、最大2048を割り当て
Request B: [██████████████░░░░░░░░░░░░░░░░░░]  実際896、最大2048を割り当て
Request C: [██░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░]  実際128、最大2048を割り当て
→ 合計6,144スロット割り当て、実際の使用は1,536。無駄率75%

PagedAttentionブロック方式(ブロックサイズ = 16:
Request A: [B0][B1]...[B31]32ブロックのみ割り当て(512トークン)
Request B: [B0][B1]...[B55]56ブロックのみ割り当て(896トークン)
Request C: [B0]...[B7]8ブロックのみ割り当て(128トークン)
→ 最後のブロックの内部フラグメンテーションのみ存在。平均無駄率4%以下

Copy-on-WriteとPrefix Sharing

PagedAttentionのもう一つの強みは、Copy-on-Write(CoW)メカニズムだ。Beam searchやparallel samplingのように同一プロンプトから複数のシーケンスを生成する場合、共通プレフィックス(prefix)に対応するKV Cacheブロックを物理的に共有できる。分岐が発生する時点でのみ新しいブロックを割り当てるため、beam searchのメモリ使用量が最大55%削減される。

KV Cacheサイズの計算

プロダクション環境でGPUメモリを正確に見積もるには、KV Cacheサイズを予測する必要がある。以下の関数でモデル別のKV Cacheサイズを計算できる。

def estimate_kv_cache_memory(
    num_layers: int,
    num_kv_heads: int,
    head_dim: int,
    max_seq_len: int,
    max_batch_size: int,
    dtype_bytes: int = 2,  # FP16
    block_size: int = 16,
) -> dict:
    """
    vLLM PagedAttentionベースのKV Cacheメモリ推定。
    GQAモデルではnum_kv_headsがnum_attention_headsより小さい。
    """
    # トークン1つあたりのKV Cacheバイト数
    per_token_bytes = 2 * num_layers * num_kv_heads * head_dim * dtype_bytes
    # ブロック1つのバイト数
    per_block_bytes = per_token_bytes * block_size
    # 最大同時処理時に必要な総ブロック数
    total_tokens = max_seq_len * max_batch_size
    total_blocks = (total_tokens + block_size - 1) // block_size
    total_bytes = total_blocks * per_block_bytes
    total_gb = total_bytes / (1024 ** 3)

    return {
        "per_token_kv_bytes": per_token_bytes,
        "per_block_kv_bytes": per_block_bytes,
        "total_blocks": total_blocks,
        "total_kv_cache_gb": round(total_gb, 2),
    }

# Llama 3.1 70B (GQA: 8 KV heads, 80 layers, head_dim=128)
result = estimate_kv_cache_memory(
    num_layers=80,
    num_kv_heads=8,
    head_dim=128,
    max_seq_len=4096,
    max_batch_size=64,
    dtype_bytes=2,
)
print(result)
# {'per_token_kv_bytes': 327680, 'per_block_kv_bytes': 5242880,
#  'total_blocks': 16384, 'total_kv_cache_gb': 80.0}
# → 64同時リクエスト × 4096トークンでKV Cacheだけで80GB必要

この計算結果を見ると、70Bモデルを64同時リクエストでサービングするには、モデルの重み140GB + KV Cache 80GB = 220GB以上のVRAMが必要だ。A100 80GBが3枚以上必要な規模である。PagedAttentionはこのメモリを実際の使用量ベースで動的に割り当てることで効率を最大化する。

vLLMアーキテクチャ

V1エンジンとコアコンポーネント

vLLM 0.7.x以降に導入されたV1エンジンは、以前のバージョンと比較してアーキテクチャを大幅に改善した。コアコンポーネントは以下の通りだ。

Scheduler: 待機中のリクエストの優先順位を決定し、GPUメモリの状況に応じてどのリクエストを実行するかを決定する。Preemption(プリエンプション)メカニズムにより、メモリが不足すると低優先度リクエストのKV CacheをCPUメモリにスワップまたは再計算する。

Block Manager: PagedAttentionの核心であり、物理ブロックの割り当て・解放・共有を管理する。ブロックテーブルを維持し、論理ブロックと物理ブロック間のマッピングを追跡する。

Worker: 実際にGPU上でモデル推論を実行するプロセスだ。Tensor Parallelismが有効な場合、複数のWorkerが協力して1つのリクエストを処理する。

Model Runner: モデルのforward passを実行し、FlashAttentionやFlashInferなどの最適化されたアテンションカーネルを呼び出す。

Continuous Batching

従来のスタティックバッチング(static batching)では、バッチ内のすべてのリクエストが完了するまで新しいリクエストを追加できない。短い応答を生成するリクエストが長い応答を待たなければならず、GPU利用率が低下する。

vLLMのContinuous Batchingは、各イテレーションで完了したリクエストをバッチから除去し、待機中の新しいリクエストを即座に追加する。これによりGPU利用率を90%以上に維持しながら、平均レイテンシーを大幅に削減する。

スタティックバッチング:
時間 →  ████████████████████████████████████
Req 1:  [■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■]  生成完了
Req 2:  [■■■■■■■■■■░░░░░░░░░░░░░░░░░░░░░░]  早期完了、待機中
Req 3:  [■■■■░░░░░░░░░░░░░░░░░░░░░░░░░░░░]  早期完了、待機中
Req 2, 3の枠に新しいリクエストを投入できない

Continuous Batching:
時間 →  ████████████████████████████████████
Req 1:  [■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■]
Req 2:  [■■■■■■■■■■] ← 完了後即座にスロット返却
Req 4:             [■■■■■■■■■■■■■■■■■■■■■■]  ← 即座に投入
Req 3:  [■■■■] ← 完了
Req 5:       [■■■■■■■■■■■■■■] ← 即座に投入
GPUが常に最大負荷で動作

vLLMのインストールと基本的な使い方

インストール

vLLMはpipで簡単にインストールできる。CUDA 12.1以上が必要で、2026年3月現在の最新安定版は0.7.x系列だ。

# 基本インストール(CUDA 12.4基準)
pip install vllm

# 特定バージョンのインストール
pip install vllm==0.7.2

# FlashInferバックエンド使用時(推奨)
pip install flashinfer-python
pip install vllm

# ソースからビルド(カスタムCUDAバージョン)
git clone https://github.com/vllm-project/vllm.git
cd vllm
pip install -e .

# インストール確認
python -c "import vllm; print(vllm.__version__)"

OpenAI互換APIサーバーの実行

vLLMの最大の利点の一つは、OpenAI APIと完全互換のサーバーを提供することだ。既存のOpenAIクライアントコードを修正なしで使用できる。

# 基本サーバー起動
vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --host 0.0.0.0 \
  --port 8000 \
  --tensor-parallel-size 1 \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.90 \
  --enable-prefix-caching \
  --dtype auto

# 70Bモデルを4-GPU Tensor Parallelで実行
vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --tensor-parallel-size 4 \
  --max-model-len 4096 \
  --gpu-memory-utilization 0.92 \
  --enable-prefix-caching \
  --max-num-seqs 256 \
  --disable-log-requests

Python SDKによるオフライン推論

サーバーなしでPythonコードから直接vLLMエンジンを使用することもできる。バッチ処理やベンチマーク時に便利だ。

from vllm import LLM, SamplingParams

# モデルのロード(自動的にPagedAttentionが適用される)
llm = LLM(
    model="meta-llama/Llama-3.1-8B-Instruct",
    tensor_parallel_size=1,
    max_model_len=4096,
    gpu_memory_utilization=0.90,
    enable_prefix_caching=True,
)

# サンプリングパラメータの設定
sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=1024,
    repetition_penalty=1.1,
    stop=["<|eot_id|>"],
)

# バッチ推論
prompts = [
    "KubernetesでGPUノードを管理するベストプラクティスを説明してください。",
    "Python asyncioのイベントループの動作原理を教えてください。",
    "PostgreSQLパーティショニング戦略の長所と短所を比較してください。",
]

outputs = llm.generate(prompts, sampling_params)
for output in outputs:
    prompt = output.prompt
    generated_text = output.outputs[0].text
    print(f"Prompt: {prompt[:50]}...")
    print(f"Generated: {generated_text[:200]}...")
    print(f"Tokens: {len(output.outputs[0].token_ids)}")
    print("---")

プロダクションデプロイ設定

Dockerベースのデプロイ

プロダクション環境ではDockerコンテナでのデプロイが標準だ。vLLM公式イメージをベースに、モデルキャッシュとGPU設定を適切に構成する必要がある。

# 公式イメージを使用したプロダクションデプロイ
docker run -d \
  --name vllm-server \
  --gpus '"device=0,1,2,3"' \
  --shm-size=16g \
  -p 8000:8000 \
  -v /data/models:/root/.cache/huggingface \
  -e HUGGING_FACE_HUB_TOKEN=${HF_TOKEN} \
  -e VLLM_ATTENTION_BACKEND=FLASHINFER \
  --restart unless-stopped \
  vllm/vllm-openai:v0.7.2 \
  --model meta-llama/Llama-3.1-70B-Instruct \
  --tensor-parallel-size 4 \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.92 \
  --enable-prefix-caching \
  --max-num-seqs 256 \
  --served-model-name llama-70b \
  --disable-log-requests \
  --uvicorn-log-level warning

注意: --shm-sizeを十分に設定しないと、Tensor Parallelモードでの NCCL通信エラーが発生する。最低8GB以上、可能であれば16GBを推奨する。

GPUメモリ設定戦略

--gpu-memory-utilizationパラメータは、vLLMが使用するGPUメモリの割合を決定する。この値を高く設定しすぎるとCUDA OOMが発生し、低く設定しすぎると同時処理量が減少する。

環境gpu-memory-utilization理由
開発/テスト0.80他のプロセスとGPUを共有する可能性
プロダクション(専用GPU)0.90~0.92最大スループット確保、若干の余裕
プロダクション(監視付き)0.88~0.90Prometheus exporterなどがVRAMを消費
不安定なワークロード0.85突発的な長いシーケンスへの備え

モデルロードの最適化

大規模モデルのロード時間は数分かかることがある。Safetensorsフォーマットとローカルキャッシュを活用すれば、ロード速度を大幅に改善できる。

# モデルを事前ダウンロードしてロード時間を短縮
huggingface-cli download meta-llama/Llama-3.1-70B-Instruct \
  --local-dir /data/models/llama-3.1-70b \
  --local-dir-use-symlinks False

# ローカルパスから直接ロード(ダウンロード不要)
vllm serve /data/models/llama-3.1-70b \
  --tensor-parallel-size 4 \
  --load-format safetensors \
  --max-model-len 8192

パフォーマンス最適化手法

Prefix Caching(Automatic Prefix Caching)

多くのプロダクションワークロードでは、システムプロンプトがすべてのリクエストに共通で含まれる。Prefix Cachingはこの共通プレフィックスのKV Cacheをキャッシュして再利用する。システムプロンプトが2,000トークンで毎秒100リクエストが来る場合、Prefix Cachingなしでは毎リクエストごとに2,000トークンのKV Cacheを再計算しなければならない。Prefix Cachingを有効にすれば、最初のリクエストでのみ計算し、以降のリクエストはキャッシュから即座に読み取る。

# Prefix Cachingの有効化(vLLM 0.7.xではデフォルト無効)
vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --enable-prefix-caching \
  --max-model-len 8192

ベンチマークでは、共通システムプロンプトがあるワークロードでPrefix CachingがTTFT(Time-To-First-Token)を最大8倍改善することが観測されている。ただし、プロンプトが毎回完全に異なるワークロードではキャッシュヒット率が低く、効果は限定的だ。

Speculative Decoding

Speculative Decodingは、小さなDraftモデルで複数のトークンを事前に予測し、元のモデル(Target)が1回のforward passで検証する手法だ。vLLMはDraftモデル方式とngram方式の両方をサポートする。

# Draftモデルを使用したSpeculative Decoding
vllm serve meta-llama/Llama-3.1-70B-Instruct \
  --speculative-model meta-llama/Llama-3.1-8B-Instruct \
  --num-speculative-tokens 5 \
  --speculative-disable-mqa-scorer \
  --tensor-parallel-size 4

# ngramベースのSpeculative Decoding(追加モデル不要)
vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --speculative-model "[ngram]" \
  --num-speculative-tokens 5 \
  --ngram-prompt-lookup-max 4

Speculative Decodingはgreedy decodingや低いtemperatureで最も効果的だ。高いtemperatureではDraftモデルの予測受け入れ率が低下し、むしろオーバーヘッドになる可能性がある。

量子化(Quantization)とサービング

GPTQ、AWQ、FP8量子化モデルをvLLMで直接サービングできる。量子化はモデルサイズを削減して少ないGPUでサービングしたり、同時処理量を向上させるために活用される。

量子化手法ビット70B VRAM相対品質vLLMサポート
FP16(基準)16~140GB100%デフォルト
FP8(W8A8)8~70GB99.5%サポート
AWQ(W4)4~35GB98.5%サポート
GPTQ(W4)4~35GB98.0%サポート
GGUF(Q4_K_M)4~35GB97.5%限定的
# AWQ量子化モデルのサービング
vllm serve TheBloke/Llama-3.1-70B-Instruct-AWQ \
  --quantization awq \
  --tensor-parallel-size 2 \
  --max-model-len 8192 \
  --gpu-memory-utilization 0.90

# FP8量子化モデルのサービング(H100/L40SなどFP8対応GPU)
vllm serve neuralmagic/Llama-3.1-70B-Instruct-FP8 \
  --quantization fp8 \
  --tensor-parallel-size 4 \
  --max-model-len 8192

注意: AWQ/GPTQモデルをTensor Parallelでサービングする際は、量子化モデルの分割互換性を必ず確認する必要がある。一部の量子化モデルは特定のTPサイズでのみ正常に動作する。

Kubernetesデプロイパターン

基本Deployment構成

KubernetesでvLLMをデプロイする際は、GPUリソースリクエスト、ヘルスチェック、Graceful Shutdownを適切に構成する必要がある。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-llama-70b
  namespace: llm-serving
  labels:
    app: vllm
    model: llama-70b
spec:
  replicas: 2
  selector:
    matchLabels:
      app: vllm
      model: llama-70b
  template:
    metadata:
      labels:
        app: vllm
        model: llama-70b
      annotations:
        prometheus.io/scrape: 'true'
        prometheus.io/port: '8000'
        prometheus.io/path: '/metrics'
    spec:
      terminationGracePeriodSeconds: 120
      containers:
        - name: vllm
          image: vllm/vllm-openai:v0.7.2
          args:
            - '--model'
            - 'meta-llama/Llama-3.1-70B-Instruct'
            - '--tensor-parallel-size'
            - '4'
            - '--max-model-len'
            - '8192'
            - '--gpu-memory-utilization'
            - '0.90'
            - '--enable-prefix-caching'
            - '--max-num-seqs'
            - '256'
            - '--served-model-name'
            - 'llama-70b'
            - '--disable-log-requests'
          ports:
            - containerPort: 8000
              name: http
          env:
            - name: HUGGING_FACE_HUB_TOKEN
              valueFrom:
                secretKeyRef:
                  name: hf-secret
                  key: token
            - name: VLLM_ATTENTION_BACKEND
              value: 'FLASHINFER'
          resources:
            requests:
              cpu: '8'
              memory: '32Gi'
              nvidia.com/gpu: '4'
            limits:
              cpu: '16'
              memory: '64Gi'
              nvidia.com/gpu: '4'
          volumeMounts:
            - name: model-cache
              mountPath: /root/.cache/huggingface
            - name: shm
              mountPath: /dev/shm
          startupProbe:
            httpGet:
              path: /health
              port: http
            initialDelaySeconds: 30
            periodSeconds: 10
            failureThreshold: 60 # モデルロードに最大10分を許容
          readinessProbe:
            httpGet:
              path: /health
              port: http
            periodSeconds: 5
            failureThreshold: 3
          livenessProbe:
            httpGet:
              path: /health
              port: http
            periodSeconds: 15
            failureThreshold: 5
      volumes:
        - name: model-cache
          persistentVolumeClaim:
            claimName: model-cache-pvc
        - name: shm
          emptyDir:
            medium: Memory
            sizeLimit: 16Gi
      nodeSelector:
        nvidia.com/gpu.product: 'NVIDIA-A100-SXM4-80GB'
      tolerations:
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
---
apiVersion: v1
kind: Service
metadata:
  name: vllm-llama-70b
  namespace: llm-serving
spec:
  selector:
    app: vllm
    model: llama-70b
  ports:
    - port: 8000
      targetPort: http
      name: http
  type: ClusterIP

KEDAベースのオートスケーリング

LLMサービングはトラフィック変動が大きいため、オートスケーリングが必須だ。KEDA(Kubernetes Event-Driven Autoscaling)を活用すれば、Prometheusメトリクスベースでポッドを自動スケーリングできる。

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: vllm-scaler
  namespace: llm-serving
spec:
  scaleTargetRef:
    name: vllm-llama-70b
  minReplicaCount: 1
  maxReplicaCount: 8
  pollingInterval: 15
  cooldownPeriod: 300 # スケールダウン前に5分待機
  triggers:
    - type: prometheus
      metadata:
        serverAddress: http://prometheus.monitoring.svc:9090
        metricName: vllm_pending_requests
        query: |
          avg(vllm:num_requests_waiting{model_name="llama-70b"})
        threshold: '10'
    - type: prometheus
      metadata:
        serverAddress: http://prometheus.monitoring.svc:9090
        metricName: vllm_gpu_cache_usage
        query: |
          avg(vllm:gpu_cache_usage_perc{model_name="llama-70b"})
        threshold: '85'

注意: GPUポッドのスケールアップは通常のポッドよりはるかに遅い。モデルロードに2~10分かかるため、cooldownPeriodを十分に長く設定し、minReplicaCountを最低1以上に維持する必要がある。スケールアップが必要な時点と実際にトラフィック処理が可能になる時点の時間差を必ず考慮すること。

推論エンジン比較

vLLM vs SGLang vs TensorRT-LLM vs LMDeploy

2026年現在の主要LLM推論エンジンを複数の観点から比較する。ベンチマーク数値はLlama 3.1 8B、A100 80GB、入力1024トークン/出力512トークン基準だ。

項目vLLM (0.7.x)SGLang (0.4.x)TensorRT-LLMLMDeploy
スループット (req/s)~42~48~55~40
TTFT (ms)~85~72~60~90
ITL (ms/token)~12~11~9~13
モデルサポート範囲非常に広い広い中程度広い
インストール難易度簡単簡単難しい中程度
OpenAI API互換完全サポート完全サポート部分サポート完全サポート
マルチモーダルサポートサポート限定的サポート
FP8量子化サポートサポートネイティブサポート
Prefix CachingサポートRadixAttentionサポートサポート
LoRAサービングサポートサポート限定的サポート
Speculative Decodingサポートサポートサポート限定的
コミュニティ活性度非常に高い高い中程度中程度
プロダクション成熟度高い高い非常に高い中程度
ライセンスApache 2.0Apache 2.0Apache 2.0Apache 2.0

エンジン選択ガイド

vLLMを選択すべき場合: 最も幅広いモデル互換性が必要な時、素早くプロトタイピングしてプロダクションまで繋げたい時、コミュニティエコシステムとプラグインが重要な時に適している。OpenAI互換APIがスムーズで、新しいモデルアーキテクチャのサポートが最も速い。

SGLangを選択すべき場合: RadixAttentionベースの高度なプロンプトキャッシングが必要な時、構造化出力(structured output)が多い時、TTFT最適化が重要なインタラクティブワークロードに適している。SGLangのRadixTreeベースのキャッシングは、ツリー構造のマルチターン会話においてvLLMのPrefix Cachingより効率的だ。

TensorRT-LLMを選択すべき場合: 最大スループットと最小レイテンシーが絶対的に重要な時、NVIDIA GPUを専用で使用しエンジンビルドプロセスの複雑さを許容できる時に適している。モデルごとにTensorRTエンジンを事前にビルドする必要があるため、デプロイパイプラインが複雑になる。

LMDeployを選択すべき場合: TurboMindエンジンの高いパフォーマンスと共にPyTorchエコシステムとの深い統合が必要な時、特にInternLM系列のモデルをサービングする際に効果的だ。

バッチサイズ別スループット比較

同時リクエスト数vLLM (tok/s)SGLang (tok/s)TensorRT-LLM (tok/s)
18592105
8620680750
322,1002,3502,600
643,8004,2004,500
1285,2005,8006,100

これらの数値はLlama 3.1 8B、A100 80GB、FP16基準であり、ワークロードの特性によって大きく変わる可能性がある。実際のプロダクション環境では必ず独自のベンチマークを実施すべきだ。

運用上の注意点とモニタリング

Prometheusメトリクス収集

vLLMは/metricsエンドポイントを通じてPrometheus形式のメトリクスを公開する。重要なモニタリング指標は以下の通りだ。

メトリクス説明アラート基準
vllm:num_requests_running現在実行中のリクエスト数max_num_seqsの90%超過
vllm:num_requests_waitingキュー内のリクエスト数継続的に50以上
vllm:gpu_cache_usage_percGPU KV Cache使用率95%超過で警告
vllm:cpu_cache_usage_percCPUスワップキャッシュ使用率50%超過でスワップ頻発
vllm:avg_prompt_throughput_toks_per_sプロンプト処理トークン/秒基準値の50%以下
vllm:avg_generation_throughput_toks_per_s生成トークン/秒基準値の50%以下
vllm:e2e_request_latency_secondsリクエストごとの全体レイテンシーp99がSLA超過
vllm:time_to_first_token_seconds最初のトークンまでの時間p99が2秒超過

GPUメモリモニタリング

GPUレベルのメモリモニタリングはDCGM(Data Center GPU Manager)Exporterを通じて実行する。vLLM自体のメトリクスとGPUハードウェアメトリクスを併せてモニタリングしなければ全体像を把握できない。

# Prometheusクエリ例: Grafanaダッシュボード用

# 1. GPU KV Cache使用率(vLLM内部)
# vllm:gpu_cache_usage_perc{model_name="llama-70b"}

# 2. 実際のGPUメモリ使用量(DCGM)
# DCGM_FI_DEV_FB_USED{gpu="0"} / DCGM_FI_DEV_FB_TOTAL{gpu="0"} * 100

# 3. 待機リクエスト数の推移
# rate(vllm:num_requests_waiting{model_name="llama-70b"}[5m])

# 4. p99 TTFTモニタリング
# histogram_quantile(0.99, rate(vllm:time_to_first_token_seconds_bucket[5m]))

# 5. 毎秒生成トークン数
# rate(vllm:avg_generation_throughput_toks_per_s[1m])

# AlertManagerルール例
# ALERT VLLMHighCacheUsage
#   IF vllm:gpu_cache_usage_perc > 95
#   FOR 5m
#   LABELS { severity = "warning" }
#   ANNOTATIONS {
#     summary = "vLLM KV Cache使用率が95%を超過",
#     description = "{{ $labels.model_name }}モデルのKV Cache使用率が
#       {{ $value }}%です。スケールアップを検討してください。"
#   }

重要な運用指針

  1. ログレベル管理: プロダクションでは必ず--disable-log-requestsを使用する。リクエストごとにプロンプトをログに記録すると、ディスクI/Oボトルネックと個人情報漏洩のリスクが生じる。

  2. Graceful Shutdown: SIGTERM受信時に進行中のリクエストを完了させる時間を十分に確保する。KubernetesのterminationGracePeriodSecondsを120秒以上に設定すること。

  3. ヘルスチェックの分離: startupProbereadinessProbeを分離する。モデルのロードには数分かかるため、startupProbefailureThresholdを十分に高く設定する。readinessProbeは実際の推論可否を確認する。

  4. 共有メモリ設定: Tensor Parallelモードでは、NCCLが/dev/shmを通じてGPU間通信を行う。Dockerでは--shm-size=16g、KubernetesではemptyDirmedium: Memoryを必ず設定する必要がある。

  5. モデルキャッシュの永続化: PVC(PersistentVolumeClaim)を使用してHuggingFaceモデルキャッシュを永続化する。ポッドの再起動のたびに数十GBのモデルを再ダウンロードするのは、コストと時間の両面で非効率的だ。

障害事例と復旧手順

Case 1: CUDA Out of Memory(OOM)

症状: サーバー起動後、一定時間が経過するとtorch.cuda.OutOfMemoryErrorが発生し、すべてのリクエストが失敗する。

原因: --gpu-memory-utilizationを高く設定しすぎたか、--max-model-lenが実際のワークロードに対して過度に大きい場合に発生する。KV Cache割り当てが物理GPUメモリを超過する。

復旧手順:

  1. --gpu-memory-utilizationを0.85に下げる。
  2. --max-model-lenを実際に必要な最大長に制限する。
  3. --max-num-seqsを減らして同時リクエスト数を制限する。
  4. 量子化(AWQ/FP8)を適用してモデルメモリを削減する。
  5. Tensor Parallel数を増やしてGPUあたりの負荷を分散する。

予防策: プロダクションデプロイ前に必ず想定最大負荷の120%でストレステストを実施する。

# OOMデバッグのためのメモリプロファイリング
VLLM_LOGGING_LEVEL=DEBUG vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --gpu-memory-utilization 0.85 \
  --max-model-len 4096 \
  --max-num-seqs 64 \
  --enforce-eager  # CUDA Graphを無効化してメモリ使用量を削減

Case 2: モデルロード失敗

症状: サーバー起動時にValueError: The model's max seq len is larger than the maximum number of tokens that can be stored in KV cacheまたはNot enough memoryエラーが発生する。

原因: 指定した--max-model-lenに必要なKV Cacheが利用可能なGPUメモリを超過する。モデルの重みをロードした後、残りのメモリが最小限のKV Cacheすら割り当てるのに不足している。

復旧手順:

  1. --max-model-lenを減らす(例: 8192から4096に)。
  2. --tensor-parallel-sizeを増やす。
  3. 量子化モデルを使用する。
  4. --enforce-eagerフラグを追加してCUDA Graphが占有するメモリを解放する。

Case 3: NCCLタイムアウト(Tensor Parallel)

症状: マルチGPU環境でRuntimeError: NCCL communicator was abortedまたはWatchdog caught collective operation timeoutが発生する。

原因: GPU間通信(NVLink/PCIe)の帯域幅不足、/dev/shmのサイズ不足、または異なる仕様のGPUの混在使用が原因だ。

復旧手順:

  1. --shm-sizeを16GB以上に設定する。
  2. 同一仕様のGPUのみを使用するようnodeSelectorを設定する。
  3. NCCL_DEBUG=INFO環境変数で詳細ログを確認する。
  4. NVLink接続のないGPU組み合わせの場合、PCIe帯域幅の限界を考慮してTensor Parallel数を減らす。

Case 4: 応答品質の低下

症状: 量子化モデルのサービング後、応答品質が明らかに低下する。繰り返しの文章、文脈のない応答、コード生成精度の低下などが観測される。

原因: 過度な量子化(INT4)がモデルの重要な重みを損傷した。特にコーディングや数学タスクでINT4量子化の品質低下が顕著だ。

復旧手順:

  1. FP8量子化に切り替える(品質損失の最小化)。
  2. AWQの代わりにGPTQを試す、またはその逆を試す。
  3. 量子化なしでTensor Parallel数を増やしてFP16サービングを検討する。
  4. 量子化モデルのベンチマークを主要タスク別に実施して品質基準を設定する。

Case 5: Kubernetesポッドの無限再起動

症状: vLLMポッドがCrashLoopBackOff状態に陥る。モデルロードが完了する前にlivenessProbeが失敗し、kubeletがコンテナを強制終了する。

原因: startupProbeが設定されていないか、failureThresholdが低すぎてモデルロード時間をカバーできていない。

復旧手順:

  1. startupProbeを追加し、failureThresholdを60以上に設定する(10秒間隔であれば10分)。
  2. livenessProbeinitialDelaySecondsを十分に長く設定する。
  3. モデルをPVCに事前ダウンロードしてロード時間を短縮する。

チェックリスト

プロダクションvLLMデプロイ前に必ず確認すべき項目だ。

インフラ準備

  • GPUドライバーとCUDAバージョンがvLLM要件と互換性があるか確認
  • GPUメモリがモデルの重み + KV Cacheに十分か計算完了
  • NVLink/NVSwitch接続状態の確認(Tensor Parallel使用時)
  • /dev/shmサイズを16GB以上に設定(Tensor Parallel使用時)
  • モデルファイルがローカルPVCに事前ダウンロード済みか確認

サービング設定

  • --gpu-memory-utilization値をワークロードに合わせて調整
  • --max-model-lenを実際に必要な最大シーケンス長に設定
  • --max-num-seqsを負荷テスト結果に基づいて設定
  • --enable-prefix-caching有効化の判断(システムプロンプト共有時は必須)
  • --disable-log-requestsをプロダクションで有効化
  • --dtype autoまたは明示的なdtype設定
  • アテンションバックエンド選択(FLASHINFER推奨)

Kubernetesデプロイ

  • startupProbe設定およびfailureThresholdを十分に高く設定
  • readinessProbelivenessProbeの分離
  • terminationGracePeriodSecondsを120秒以上に設定
  • GPU nodeSelectorまたはnodeAffinityの設定
  • PVCでモデルキャッシュを永続化
  • emptyDir Medium: Memoryで/dev/shmをマウント
  • KEDAまたはHPAオートスケーリングの構成
  • minReplicaCountを1以上に設定(コールドスタート防止)

モニタリングとアラート

  • Prometheusメトリクス収集パスの設定(/metrics
  • Grafanaダッシュボードの構成(KV Cache使用率、TTFT、スループット)
  • OOM発生時のアラート設定
  • 待機リクエスト数しきい値アラートの設定
  • GPU温度と電力のモニタリング(DCGM)
  • 応答品質モニタリングパイプラインの構築(サンプリングベース)

パフォーマンス検証

  • 想定最大負荷の120%でストレステスト完了
  • TTFTとITLのp50/p95/p99測定およびSLA適合確認
  • Speculative Decoding適用時の受け入れ率確認(60%以上推奨)
  • 量子化適用時の主要タスク別品質ベンチマーク完了
  • 長時間(24時間以上)安定性テスト完了

参考資料

  1. Efficient Memory Management for Large Language Model Serving with PagedAttention - PagedAttentionの原論文で、KV Cacheの仮想メモリページング手法を詳細に解説している。
  2. vLLM公式ドキュメント - インストールから高度な設定までvLLMの公式リファレンスドキュメント。
  3. vLLM GitHubリポジトリ - ソースコード、Issueトラッカー、リリースノートを確認できる。
  4. DistServe: Disaggregating Prefill and Decoding for Goodput-optimized Large Language Model Serving - PrefillとDecodingステージを分離してサービング効率を最適化する手法に関する論文。
  5. vLLM vs SGLang vs LMDeploy: Fastest LLM Inference Engine in 2026 - 2026年時点の主要推論エンジンのパフォーマンスベンチマーク比較分析。
  6. SGLang: Efficient Execution of Structured Language Model Programs - SGLangのRadixAttentionと構造化出力最適化手法に関する論文。
  7. NVIDIA TensorRT-LLM Documentation - TensorRT-LLMの公式ドキュメントで、エンジンビルドと最適化ガイドを含む。