この記事のゴール
Llama を本番投入する全体像で「自前運用なら vLLM」と書きました。本稿はその実装の runbook です。データを外に出せない・1リクエスト単価を最小化したい・微調整した重みを動かしたい——そんな要件で Llama を自分のGPUで、落ちずに、速く動かすための全工程を実コードで示します。
ゴールは、vllm serve を叩けることではなく、**ヘルスチェック・可観測性・オートスケール・回復性・セキュリティを備えた“本番の推論基盤”**を組めることです。
信頼性の開示:GPU を本番で回す難しさ(スループット・障害・コスト)は、動画AIローカライズ基盤で実際に踏んだ領域です。本稿のコマンド・フラグは公式(vLLM Blog: Llama 4・vLLM Recipes)に基づきます。スループットの実数は環境依存・要ベンチです。
なぜ vLLM なのか
自前サーブの勝敗は「GPU をどれだけ遊ばせないか(実効スループット)」で決まります。vLLM はここに最適化されています。
- PagedAttention:KVキャッシュを OS の仮想メモリのようにページ管理し、断片化を抑えてより多くの同時リクエストを載せる。
- 連続バッチ(continuous batching):到着したリクエストを動的に束ねて処理。1件が終わる前に次を詰めるので、GPU が空転しない。これがセルフホストの損益分岐を直接動かす。
- OpenAI 互換サーバ:
:8000/v1を提供。本番コードを一切変えずに向け先だけ差し替えられる。
サーブする:Scout を FP8 × テンソル並列で
公式 recipe に沿った最小の本番コマンドです。FP8 量子化で VRAM を圧縮し、テンソル並列で複数 GPU に分散します。
# 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 クライアントで叩けます。
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 — 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が可視化でき、「いつスケールすべきか」を数字で判断できます。
# 監視で見るべき主要メトリクス(例)
# 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 の記事に揃えてください(メトリクス・ログ・トレースの相関、PII非出力)。
オートスケールとグレースフルドレイン
GPU は高価なので、需要に追従させます。同時に、スケールインや更新で処理中のリクエストを切らないことが品質です。
- スケールアウト判断:
num_requests_waiting/ キュー滞留時間をトリガに、GPUノードを増やす。コールドスタート(モデルロード数分)を見込み先回りで増やす。 - グレースフルドレイン:終了シグナルを受けたら新規受付を止め、処理中を完走させてから落とす。ロードバランサのレディネスを先に落とし、
preStopで待つ。
# k8s(抜粋):レディネスで安全に出し入れし、preStop で在庫を捌いてから落とす
readinessProbe:
httpGet: { path: /health, port: 8000 }
periodSeconds: 10
lifecycle:
preStop:
exec: { command: ["sh", "-c", "sleep 30"] } # 在庫リクエストの完走待ち
terminationGracePeriodSeconds: 120
回復性:自前ノードの障害を全体障害にしない
セルフホストは落ちる前提で設計します。クライアント側にタイムアウト・リトライ・フォールバックを持たせ、自前ノードが死んでもBedrockに逃がして止めない。
// 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" };
}
}
連鎖障害を防ぐには、フォールバック側にもサーキットブレーカーを入れて殺到を止めます(リトライ/バックオフ/サーキットブレーカー)。
セキュリティ:推論エンドポイントは「公開しない」
vLLM の OpenAI 互換サーバはそれ自体に強い認証がありません。インターネットに直接晒さないのが大前提です。
- ネットワーク隔離:private subnet / VPC に置き、外部から直接到達できないようにする。
- 前段に認証ゲートウェイ:API Gateway / リバースプロキシで認証・レート制限・監査ログを担う。アプリは内部DNSで叩く。
- データ非送出:そもそもセルフホストの動機は「外に出さない」こと。ログにもプロンプト本文(PII)を残さない——メタデータだけ記録する。
- 入力検証:プロンプトも外部入力。長さ上限・スキーマ検証を境界で行う(型安全の規律)。
負荷試験:本番前に「どこで折れるか」を知る
スループットとレイテンシは推測せず計測します。同時実行を段階的に上げ、num_requests_waiting が跳ね TTFT が劣化する点=飽和点を見つけ、そこから1ノードあたりの安全な同時実行数を決めます。
# 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
ここで得た実効スループットを、コスト記事の式に入れて初めて、セルフホストの損益分岐が数字で出ます。
よくある質問(FAQ)
Q. Ollama と vLLM はどう違う? A. Ollama はローカル開発・単機・お手軽、vLLM は本番の高スループット。連続バッチで多数同時を捌くのは vLLM。開発は Ollama、本番は vLLM、と使い分けます(ピラー記事のセルフホスト章)。
Q. 何台のGPUが要る?
A. モデル・量子化・文脈長次第。Scout(FP8) は少数GPUのテンソル並列が現実的。まず1ノードで負荷試験し、num_requests_waiting が飽和する点から逆算して台数を決めます。
Q. 文脈長 10M を設定していい?
A. できますがVRAMとレイテンシが破綻します。--max-model-len は実際に必要な長さに絞るのが本番の鉄則。長文脈は --kv-cache-dtype fp8 と併せて現実的な範囲で。
Q. ファインチューニングした重みも動かせる?
A. はい。LoRAをマージした重みを vllm serve の対象にすれば、特化モデルをそのまま本番提供できます。
Q. マネージド(Bedrock)と比べて運用は重い? A. 重いです。スケール・障害・更新・監視を自前で持つ分、定常大量でコストが見合うときに選ぶべき。少量・変動はBedrockが正解です。
まとめ
vLLM の serve は1行ですが、本番の推論基盤はその外側にあります。
- 連続バッチ × FP8 × テンソル並列でスループットを最大化する。
- ヘルス/メトリクス/オートスケール/ドレインで落ちない・切らない。
- クライアント側のフォールバックで自前障害を全体障害にしない。
- 隔離+認証ゲート+データ非送出で守る。
- 負荷試験の実数をコスト式に入れて損益分岐を出す。
プライベートで可観測・回復性のある Llama 推論基盤を、負荷試験とコスト試算まで含めて構築します。GPU 本番運用の実績をご覧のうえご相談ください。一人 × 生成AIで、速く・安く・安全に。
出典・公式リソース
- vLLM Blog: Llama 4 day-0 support
- vLLM Recipes: Llama 4 Scout
- vLLM 公式ドキュメント — サーバ・メトリクス・ベンチ
- Hugging Face: meta-llama — 重みと FP8 量子化版
※ フラグ・スループット・GPU要件は更新・実測依存です。実装前に一次情報と自社ベンチで確認してください。