# vLLM で Llama を本番セルフホストする：高スループット推論サーバ運用記

> Llama を自前GPUで本番運用するための vLLM 実践ガイド。連続バッチとPagedAttentionでスループットを最大化し、FP8量子化・テンソル並列で詰め、OpenAI互換エンドポイントとして提供。ヘルスチェック・可観測性・オートスケール・グレースフルドレイン・Bedrockフォールバック・ネットワーク隔離まで、落ちない推論基盤の作り方を実コードで。

- 公開日: 2026-06-25
- 著者: 友田 陽大
- タグ: Llama, vLLM, 生成AI, GPU, MLOps, セルフホスト, Python
- URL: https://tomodahinata.com/blog/vllm-llama-self-hosting-production-inference-server

## 要点

- vLLM の核心は PagedAttention × 連続バッチ。リクエストを動的に束ねてGPUを遊ばせず、セルフホストの原価を決める『実効スループット』を最大化する
- Scout は FP8＋テンソル並列でサーブ。`--kv-cache-dtype fp8` で使える文脈を実質拡大。OpenAI互換エンドポイントなので本番コードは無改修で差し替え可能
- 本番化はモデルではなく周辺：ヘルス/レディネス・Prometheusメトリクス・GPUオートスケール・グレースフルドレイン・負荷試験が要る
- 回復性はクライアント側にも：タイムアウト＋リトライ＋Bedrockへのフォールバックで、自前ノードの障害を全体障害にしない
- セキュリティは『公開しない』が基本。private VPC・前段に認証ゲートウェイ・データ非送出・レート制限で守る

---

## この記事のゴール

[Llama を本番投入する全体像](/blog/meta-llama-open-weight-llm-production-guide)で「自前運用なら vLLM」と書きました。本稿はその**実装の runbook** です。データを外に出せない・1リクエスト単価を最小化したい・微調整した重みを動かしたい——そんな要件で **Llama を自分のGPUで、落ちずに、速く**動かすための全工程を実コードで示します。

ゴールは、`vllm serve` を叩けることではなく、**ヘルスチェック・可観測性・オートスケール・回復性・セキュリティを備えた“本番の推論基盤”**を組めることです。

> **信頼性の開示**：GPU を本番で回す難しさ（スループット・障害・コスト）は、[動画AIローカライズ基盤](/case-studies/ai-video-localization-lipsync)で実際に踏んだ領域です。本稿のコマンド・フラグは公式（[vLLM Blog: Llama 4](https://blog.vllm.ai/2025/04/05/llama4.html)・[vLLM Recipes](https://docs.vllm.ai/projects/recipes/en/latest/Llama/Llama4-Scout.html)）に基づきます。スループットの実数は**環境依存・要ベンチ**です。

---

## なぜ vLLM なのか

自前サーブの勝敗は「**GPU をどれだけ遊ばせないか（実効スループット）**」で決まります。vLLM はここに最適化されています。

- **PagedAttention**：KVキャッシュを OS の仮想メモリのように**ページ管理**し、断片化を抑えて**より多くの同時リクエスト**を載せる。
- **連続バッチ（continuous batching）**：到着したリクエストを**動的に束ねて**処理。1件が終わる前に次を詰めるので、GPU が空転しない。これが[セルフホストの損益分岐](/blog/llama-inference-cost-optimization-self-host-vs-api)を直接動かす。
- **OpenAI 互換サーバ**：`:8000/v1` を提供。**本番コードを一切変えず**に向け先だけ差し替えられる。

---

## サーブする：Scout を FP8 × テンソル並列で

公式 recipe に沿った最小の本番コマンドです。FP8 量子化で VRAM を圧縮し、テンソル並列で複数 GPU に分散します。

```bash
# Llama 4 Scout（FP8）を 2GPU にテンソル並列、KVキャッシュもFP8で文脈を稼ぐ
vllm serve meta-llama/Llama-4-Scout-17B-16E-Instruct-FP8 \
  --tensor-parallel-size 2 \
  --kv-cache-dtype fp8 \
  --max-model-len 500000 \
  --gpu-memory-utilization 0.90 \
  --port 8000
```

- `--tensor-parallel-size 2`：重みとKVを2GPUに分割。台数は VRAM とモデルで決める。
- `--kv-cache-dtype fp8`：KVキャッシュをFP8化。**使える文脈長が実質的に伸び**、スループットも改善（精度劣化は実用上小さい）。
- `--max-model-len`：実効上限。Scout は本来 10M だが、**VRAM とレイテンシの現実**で絞る。青天井にしない。
- `--gpu-memory-utilization`：確保率。上げすぎると OOM、低すぎると同時実行が減る。

> 🔧 **ハマりどころ**：FP8 は **H100/H200/Ada/Blackwell** など対応GPUが前提。非対応GPUだと起動失敗かフォールバックで遅くなる。`nvidia-smi` で世代を、起動ログで実際の dtype を必ず確認。

立ち上がれば、**そのまま OpenAI クライアントで叩けます**。

```ts
import OpenAI from "openai";
// 本番コードは無改修。baseURL を自前エンドポイントに向けるだけ。
const llm = new OpenAI({ baseURL: "http://llama-internal:8000/v1", apiKey: "internal" });
const r = await llm.chat.completions.create({
  model: "meta-llama/Llama-4-Scout-17B-16E-Instruct-FP8",
  messages: [{ role: "user", content: "RAGの再ランキングを2行で" }],
});
```

---

## コンテナ化：再現可能なイメージにする

本番は手作業の `pip install` で再現性を失います。**イメージに固める**のが原則です。

```dockerfile
# Dockerfile — vLLM の公式イメージを基盤に、モデルは実行時にマウント/取得
FROM vllm/vllm-openai:latest

# モデルIDと並列数は環境変数で外出し（イメージは1つ、構成は差し替え）
ENV MODEL_ID=meta-llama/Llama-4-Scout-17B-16E-Instruct-FP8 \
    TP_SIZE=2

# ヘルスチェック：/health が 200 を返すまでトラフィックを入れない
HEALTHCHECK --interval=15s --timeout=5s --start-period=600s --retries=10 \
  CMD curl -fsS http://localhost:8000/health || exit 1

ENTRYPOINT ["sh", "-c", \
  "vllm serve $MODEL_ID --tensor-parallel-size $TP_SIZE --kv-cache-dtype fp8 --port 8000"]
```

`--start-period` を長め（モデルロードは数分かかる）に取るのが地味だが重要。ロード中に unhealthy 判定で**再起動ループ**に陥る事故を防ぎます。

---

## 可観測性：vLLM は最初からメトリクスを出す

vLLM は **Prometheus 形式のメトリクス**を `/metrics` に出します。これを取り込めば、**スループット・待ち行列・GPUキャッシュ使用率・TTFT/TPOT**が可視化でき、「**いつスケールすべきか**」を数字で判断できます。

```bash
# 監視で見るべき主要メトリクス（例）
# vllm:num_requests_running     … 実行中リクエスト数（飽和の指標）
# vllm:num_requests_waiting     … 待ち行列（増え続けるならスケール）
# vllm:gpu_cache_usage_perc     … KVキャッシュ使用率（高止まりは限界）
# vllm:time_to_first_token_*    … 体感レイテンシ
curl -s http://localhost:8000/metrics | grep -E "num_requests|gpu_cache_usage"
```

`num_requests_waiting` が**継続的に積み上がる**なら容量不足のサイン。ここをトリガに**GPUノードをオートスケール**します。可観測性の設計思想は[OpenTelemetry の記事](/blog/opentelemetry-observability-production-tracing-metrics-logs)に揃えてください（メトリクス・ログ・トレースの相関、PII非出力）。

---

## オートスケールとグレースフルドレイン

GPU は高価なので、**需要に追従**させます。同時に、スケールインや更新で**処理中のリクエストを切らない**ことが品質です。

- **スケールアウト判断**：`num_requests_waiting` / キュー滞留時間をトリガに、GPUノードを増やす。コールドスタート（モデルロード数分）を見込み**先回り**で増やす。
- **グレースフルドレイン**：終了シグナルを受けたら**新規受付を止め、処理中を完走させてから**落とす。ロードバランサのレディネスを先に落とし、`preStop` で待つ。

```yaml
# k8s（抜粋）：レディネスで安全に出し入れし、preStop で在庫を捌いてから落とす
readinessProbe:
  httpGet: { path: /health, port: 8000 }
  periodSeconds: 10
lifecycle:
  preStop:
    exec: { command: ["sh", "-c", "sleep 30"] } # 在庫リクエストの完走待ち
terminationGracePeriodSeconds: 120
```

---

## 回復性：自前ノードの障害を全体障害にしない

セルフホストは**落ちる前提**で設計します。クライアント側に**タイムアウト・リトライ・フォールバック**を持たせ、自前ノードが死んでも[Bedrock](/blog/meta-llama-open-weight-llm-production-guide#使い方baws-bedrock本番フルマネージド)に逃がして**止めない**。

```ts
// lib/llama-client.ts — 自前vLLM優先、ダメならBedrockへフォールバック（止めない）
import OpenAI from "openai";

const selfHosted = new OpenAI({ baseURL: process.env.VLLM_URL, apiKey: "internal", timeout: 30_000 });

export async function generate(prompt: string): Promise<{ text: string; via: "self" | "bedrock" }> {
  try {
    const r = await selfHosted.chat.completions.create({
      model: "meta-llama/Llama-4-Scout-17B-16E-Instruct-FP8",
      messages: [{ role: "user", content: prompt }],
    });
    return { text: r.choices[0]?.message.content ?? "", via: "self" };
  } catch (err) {
    // タイムアウト/接続不可/5xx は“正常系”として吸収し、マネージドへ退避する。
    const text = await viaBedrock(prompt); // Converse API 実装は別関数に分離（SRP）
    return { text, via: "bedrock" };
  }
}
```

連鎖障害を防ぐには、フォールバック側にも**サーキットブレーカー**を入れて殺到を止めます（[リトライ/バックオフ/サーキットブレーカー](/blog/retry-backoff-circuit-breaker-resilience-patterns-guide)）。

---

## セキュリティ：推論エンドポイントは「公開しない」

vLLM の OpenAI 互換サーバは**それ自体に強い認証がありません**。**インターネットに直接晒さない**のが大前提です。

- **ネットワーク隔離**：private subnet / VPC に置き、外部から直接到達できないようにする。
- **前段に認証ゲートウェイ**：API Gateway / リバースプロキシで**認証・レート制限・監査ログ**を担う。アプリは内部DNSで叩く。
- **データ非送出**：そもそもセルフホストの動機は「外に出さない」こと。ログにも**プロンプト本文（PII）を残さない**——メタデータだけ記録する。
- **入力検証**：プロンプトも外部入力。長さ上限・スキーマ検証を境界で行う（[型安全の規律](/blog/typescript-type-safety-discipline-zod-nevererror-no-any)）。

---

## 負荷試験：本番前に「どこで折れるか」を知る

スループットとレイテンシは**推測せず計測**します。同時実行を段階的に上げ、`num_requests_waiting` が跳ね TTFT が劣化する点＝**飽和点**を見つけ、そこから**1ノードあたりの安全な同時実行数**を決めます。

```bash
# vLLM 同梱のベンチで実効スループットを測る（数値は環境依存・要実測）
python -m vllm.entrypoints.benchmarks.benchmark_serving \
  --backend openai --base-url http://localhost:8000 \
  --model meta-llama/Llama-4-Scout-17B-16E-Instruct-FP8 \
  --num-prompts 500 --request-rate 20
```

ここで得た**実効スループット**を、[コスト記事の式](/blog/llama-inference-cost-optimization-self-host-vs-api#検証可能なコスト計算機純粋関数)に入れて初めて、セルフホストの損益分岐が**数字で**出ます。

---

## よくある質問（FAQ）

**Q. Ollama と vLLM はどう違う？**
A. **Ollama はローカル開発・単機・お手軽**、**vLLM は本番の高スループット**。連続バッチで多数同時を捌くのは vLLM。開発は Ollama、本番は vLLM、と使い分けます（[ピラー記事のセルフホスト章](/blog/meta-llama-open-weight-llm-production-guide#使い方cセルフホスト-ローカル所有して動かす)）。

**Q. 何台のGPUが要る？**
A. モデル・量子化・文脈長次第。Scout(FP8) は少数GPUのテンソル並列が現実的。**まず1ノードで負荷試験**し、`num_requests_waiting` が飽和する点から逆算して台数を決めます。

**Q. 文脈長 10M を設定していい？**
A. できますが**VRAMとレイテンシが破綻**します。`--max-model-len` は**実際に必要な長さ**に絞るのが本番の鉄則。長文脈は `--kv-cache-dtype fp8` と併せて現実的な範囲で。

**Q. ファインチューニングした重みも動かせる？**
A. はい。[LoRAをマージした重み](/blog/llama-fine-tuning-lora-qlora-production-guide#デプロイとライセンス命名規約に注意)を `vllm serve` の対象にすれば、特化モデルをそのまま本番提供できます。

**Q. マネージド（Bedrock）と比べて運用は重い？**
A. 重いです。スケール・障害・更新・監視を自前で持つ分、**定常大量でコストが見合うとき**に選ぶべき。少量・変動は[Bedrock](/blog/meta-llama-open-weight-llm-production-guide#使い方baws-bedrock本番フルマネージド)が正解です。

---

## まとめ

vLLM の `serve` は1行ですが、**本番の推論基盤**はその外側にあります。

1. **連続バッチ × FP8 × テンソル並列**でスループットを最大化する。
2. **ヘルス/メトリクス/オートスケール/ドレイン**で落ちない・切らない。
3. **クライアント側のフォールバック**で自前障害を全体障害にしない。
4. **隔離＋認証ゲート＋データ非送出**で守る。
5. **負荷試験の実数**を[コスト式](/blog/llama-inference-cost-optimization-self-host-vs-api)に入れて損益分岐を出す。

> プライベートで可観測・回復性のある Llama 推論基盤を、負荷試験とコスト試算まで含めて構築します。GPU 本番運用の[実績](/case-studies/ai-video-localization-lipsync)をご覧のうえご相談ください。**一人 × 生成AI**で、速く・安く・安全に。

### 出典・公式リソース

- [vLLM Blog: Llama 4 day-0 support](https://blog.vllm.ai/2025/04/05/llama4.html)
- [vLLM Recipes: Llama 4 Scout](https://docs.vllm.ai/projects/recipes/en/latest/Llama/Llama4-Scout.html)
- [vLLM 公式ドキュメント](https://docs.vllm.ai/) — サーバ・メトリクス・ベンチ
- [Hugging Face: meta-llama](https://huggingface.co/meta-llama) — 重みと FP8 量子化版

※ フラグ・スループット・GPU要件は更新・実測依存です。実装前に一次情報と自社ベンチで確認してください。
