この記事の対象読者
- Pythonの基本とpip installを理解している方
- OllamaやHugging Faceでローカルにモデルを動かしたことがある、または興味がある方
- 「ローカルLLMの推論速度を上げたい」「APIコストを削減したい」と考えている方
この記事で得られること
- vLLMの全体像: なぜGitHubコントリビューター数で2025年OSSトップに立ったのか
- 技術的な仕組み: PagedAttention・継続バッチングが推論を爆速化する原理
- 実践的なセットアップ: GPU別の環境構築からOpenAI互換APIサーバーの立ち上げまで
この記事で扱わないこと
- Transformersライブラリ自体の使い方(HuggingFaceの基礎)
- モデルのファインチューニング(vLLMは推論特化)
- マルチノードクラスタの本格構築
1. vLLMとの出会い
ローカルLLMを触ったことがある人なら、一度はこう思ったことがあるだろう。
「なんでこんなに遅いの?」
Hugging FaceのTransformersで7Bモデルを動かす。1トークン生成するのに数百ミリ秒。長文を生成させると、コーヒーを淹れに行って戻ってきてもまだ動いている。GPUメモリは16GB中14GBも使っているのに、GPU使用率を見ると30%。
「GPUは遊んでるのに、推論が遅い——これ、ソフトウェアの問題では?」
私がvLLMに出会ったのは、まさにそんなフラストレーションを抱えていたときだった。RTX 5090を手に入れて意気揚々とローカルLLMを動かしたが、期待した速度の半分も出ない。原因を調べていくうちにたどり着いたのが、UC BerkeleyのSky Computing Lab発のOSSプロジェクト vLLM だった。
vLLMは、料理に例えるなら「同じキッチン(GPU)で、注文(リクエスト)をさばく速度を劇的に上げる、超優秀なキッチンマネージャー」だ。食材(モデルの重み)を効率よく冷蔵庫(VRAMメモリ)に配置し、注文を途切れなく流す(継続バッチング)ことで、同じGPUから何倍もの料理を出す。
ここまでで、vLLMがどんなものか、なんとなくイメージできたでしょうか。次は、この記事で使う用語を整理しておきましょう。
2. 前提知識の確認
本題に入る前に、この記事で登場する用語を確認します。
2.1 推論(Inference)とは
学習済みのLLMに入力を与え、出力を生成する処理のこと。「学習(Training)」がモデルを作る工程なら、「推論」はモデルを使う工程だ。vLLMは推論を高速化することに特化している。
2.2 KVキャッシュ(Key-Value Cache)とは
Transformerモデルが文章を生成するとき、過去のトークンの計算結果(Key/Valueベクトル)をキャッシュして再利用する仕組み。これがGPUメモリの最大消費源であり、vLLMはここを最適化する。
2.3 バッチ処理とは
複数のリクエストをまとめて処理すること。従来は「全リクエストが完了するまで次のバッチを処理しない」静的バッチだったが、vLLMは動的に(完了したリクエストから次を投入して)処理する「継続バッチング」を実装している。
2.4 テンソル並列(Tensor Parallelism)とは
1つのモデルを複数GPUに分割して並列推論する技術。vLLMはテンソル並列、パイプライン並列、データ並列、エキスパート並列をすべてサポートしている。
これらの用語が押さえられたら、vLLMの背景を見ていきましょう。
3. vLLMが生まれた背景
3.1 KVキャッシュの「メモリ断片化」問題
LLM推論の最大のボトルネックは、KVキャッシュのメモリ管理だった。
従来のフレームワーク(HuggingFace Transformers等)は、リクエストごとに連続したメモリ領域をKVキャッシュに割り当てる。これは、以下の問題を引き起こす。
| 問題 | 影響 |
|---|---|
| 内部断片化 | 最大長に合わせてメモリを確保するため、短い文では余りが出る |
| 外部断片化 | リクエストが終了・開始を繰り返すとメモリに「穴」ができる |
| 事前確保の無駄 | 生成が始まる前に最大トークン数分を確保してしまう |
これらの断片化により、GPUメモリの60〜80%が無駄になっているケースもあった。
3.2 PagedAttention——OSの仮想メモリ管理をGPUに持ち込む
2023年、UC BerkeleyのKwon et al. がこの問題を根本から解決する論文を発表した。着想はシンプルかつ革命的だ。
「OSがメモリをページ単位で管理するように、KVキャッシュもページ単位で管理すればいいじゃないか」
この発想から生まれたのがPagedAttentionであり、vLLMの中核技術だ。
3.3 2025年: GitHubコントリビューター数トップのOSSプロジェクトに
vLLMはアカデミアの成果にとどまらず、産業界で爆発的に普及した。
| 時期 | 出来事 |
|---|---|
| 2023年6月 | vLLM初版リリース(UC Berkeley) |
| 2024年 | Red HatがメインコーポレートスポンサーとしてvLLMに参画 |
| 2025年 | GitHubコントリビューター数で2025年OSSトップに |
| 2026年2月 | NVIDIA公式コンテナでBlackwell(RTX PRO 6000等)対応 |
背景がわかったところで、基本的な仕組みを見ていきましょう。
4. 基本概念と仕組み
4.1 PagedAttention の仕組み
PagedAttentionは、KVキャッシュを固定サイズの「ブロック」に分割して管理する。
従来方式(連続メモリ割り当て):
┌──────────────────────────────────────┐
│ リクエストA(確保: 2048トークン分) │ ← 実際は500トークンしか使わない
│ [使用中||||][ 未使用の無駄 ]│ → 75%が無駄
└──────────────────────────────────────┘
┌──────────────────────────────────────┐
│ リクエストB(確保: 2048トークン分) │
│ [使用中||||||][ 未使用の無駄 ]│
└──────────────────────────────────────┘
PagedAttention(ページ単位管理):
┌────┐┌────┐┌────┐┌────┐┌────┐┌────┐
│ A1 ││ B1 ││ A2 ││ B2 ││ A3 ││ B3 │ ← 必要な分だけブロック割り当て
└────┘└────┘└────┘└────┘└────┘└────┘ → 無駄がほぼゼロ
この仕組みにより、メモリ無駄がほぼゼロになり、同じGPUで処理できるリクエスト数(バッチサイズ)が大幅に増える。
4.2 継続バッチング(Continuous Batching)
従来の静的バッチングは「全員の料理が出来上がるまで、次の注文を受けない」方式だった。
静的バッチング:
バッチ1: [A(長い), B(短い), C(中間)]
→ Bが先に終わっても、Aが終わるまでGPUが遊ぶ
→ Aが終わってからバッチ2を開始
継続バッチング:
→ Bが終わった瞬間にDを投入
→ Cが終わった瞬間にEを投入
→ GPUが常に働き続ける
これにより、GPUの稼働率が最大化され、スループット(単位時間あたりの処理量)が飛躍的に向上する。
4.3 対応モデルと量子化
vLLMは主要なモデルファミリーのほぼ全てに対応している。
| モデルファミリー | 対応状況 | 備考 |
|---|---|---|
| Llama / Llama 3 | ✅ | Meta製。最も使われるOSSモデル |
| Qwen / Qwen 3 | ✅ | Alibaba製。Qwenファミリーが2025年最もDLされたモデルに |
| DeepSeek V3 / R1 | ✅ | MoEアーキテクチャ対応 |
| Gemma / Gemma 2 | ✅ | Google製 |
| Mistral / Mixtral | ✅ | ROCm対応あり(4096コンテキストまで) |
| GPT-OSS-120B / 20B | ✅ | OpenAI初のOSSモデル |
量子化対応:
| 量子化方式 | メモリ削減 | 速度影響 |
|---|---|---|
| FP16 | 基準 | 基準 |
| FP8 | 約50%削減 | ほぼ同等〜やや向上 |
| NVFP4 | 約75%削減 | Blackwell GPUで大幅向上 |
| AWQ (INT4) | 約75%削減 | 若干の精度低下 |
| GPTQ (INT4) | 約75%削減 | 若干の精度低下 |
基本概念が理解できたところで、実際にコードを書いて動かしてみましょう。
5. 実践:実際に使ってみよう
5.1 環境構築
前提条件
| 項目 | 要件 |
|---|---|
| OS | Linux(Ubuntu 22.04+推奨)、Windows(WSL2経由)、macOS(CPU版のみ) |
| GPU | NVIDIA GPU(Compute Capability 7.0以上、VRAM 8GB+推奨) |
| CUDA | 12.1以上(CUDA 13.0でBlackwell対応) |
| Python | 3.9以上 |
| ディスク容量 | モデルサイズに依存(7Bモデルで約14GB) |
インストール手順
# 方法1: pip(最も簡単)
pip install vllm --break-system-packages
# 方法2: uv(高速)
uv pip install vllm
# 方法3: Docker(環境を汚さない・推奨)
docker pull vllm/vllm-openai:latest
# 方法4: NVIDIA公式コンテナ(Blackwell GPU対応)
docker pull nvcr.io/nvidia/vllm:25.12-py3
5.2 環境別の設定ファイル
vLLMの設定は起動コマンドのオプションまたは環境変数で管理する。以下はDocker Compose形式で3環境分を用意した。
開発環境用(docker-compose.dev.yaml)
# docker-compose.dev.yaml - 開発環境用(このままコピーして使える)
version: "3.8"
services:
vllm:
image: vllm/vllm-openai:latest
container_name: vllm-dev
ports:
- "8000:8000"
volumes:
- ./models:/root/.cache/huggingface # モデルキャッシュを永続化
environment:
- HUGGING_FACE_HUB_TOKEN=${HF_TOKEN}
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1 # 開発は1GPUで十分
capabilities: [gpu]
command: >
--model Qwen/Qwen2.5-7B-Instruct
--max-model-len 4096
--gpu-memory-utilization 0.85
--dtype float16
--trust-remote-code
--api-key dev-test-key
restart: unless-stopped
本番環境用(docker-compose.prod.yaml)
# docker-compose.prod.yaml - 本番環境用
version: "3.8"
services:
vllm:
image: vllm/vllm-openai:latest
container_name: vllm-prod
ports:
- "8000:8000"
volumes:
- /data/models:/root/.cache/huggingface
environment:
- HUGGING_FACE_HUB_TOKEN=${HF_TOKEN}
- VLLM_LOGGING_LEVEL=INFO
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all # 本番は全GPU使用
capabilities: [gpu]
command: >
--model Qwen/Qwen2.5-72B-Instruct-AWQ
--max-model-len 16384
--gpu-memory-utilization 0.92
--dtype auto
--quantization awq
--tensor-parallel-size 2
--trust-remote-code
--api-key ${VLLM_API_KEY}
--max-num-seqs 128
--enable-prefix-caching
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
テスト環境用(docker-compose.test.yaml)
# docker-compose.test.yaml - CI/テスト用
version: "3.8"
services:
vllm:
image: vllm/vllm-openai:latest
container_name: vllm-test
ports:
- "8001:8000" # テスト用ポート
volumes:
- ./test-models:/root/.cache/huggingface
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
command: >
--model Qwen/Qwen2.5-1.5B-Instruct
--max-model-len 2048
--gpu-memory-utilization 0.70
--dtype float16
--trust-remote-code
--api-key test-key
restart: "no"
5.3 基本的な使い方
方法1: OpenAI互換APIサーバーとして使う(推奨)
# サーバー起動
vllm serve Qwen/Qwen2.5-7B-Instruct \
--api-key my-secret-key \
--max-model-len 4096
"""
vLLM OpenAI互換API クライアント
前提: vLLMサーバーが localhost:8000 で起動済みであること
実行方法: python vllm_client.py
"""
from openai import OpenAI
def main():
"""OpenAI互換APIでvLLMに推論リクエストを送る"""
# 1. クライアント設定(エンドポイントをvLLMサーバーに向ける)
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="my-secret-key",
)
# 2. 利用可能なモデル一覧を確認
models = client.models.list()
print("=== 利用可能モデル ===")
for model in models.data:
print(f" - {model.id}")
# 3. チャット補完リクエスト
print("\n=== チャット推論 ===")
response = client.chat.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
messages=[
{
"role": "system",
"content": "あなたは優秀なPythonエンジニアです。"
},
{
"role": "user",
"content": (
"Pythonでフィボナッチ数列を生成する関数を"
"3種類の方法で書いてください。"
)
}
],
temperature=0.7,
max_tokens=1024,
)
print(response.choices[0].message.content)
print(f"\n--- 使用トークン ---")
print(f" 入力: {response.usage.prompt_tokens}")
print(f" 出力: {response.usage.completion_tokens}")
print(f" 合計: {response.usage.total_tokens}")
# 4. ストリーミング出力
print("\n=== ストリーミング推論 ===")
stream = client.chat.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
messages=[
{
"role": "user",
"content": "日本の四季の特徴を簡潔に説明して"
}
],
stream=True,
max_tokens=256,
)
for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
print()
if __name__ == "__main__":
main()
方法2: Python APIで直接使う(バッチ推論向け)
"""
vLLM Python API を使ったバッチ推論
前提: vLLMがpipインストール済みであること
実行方法: python vllm_batch.py
"""
from vllm import LLM, SamplingParams
def main():
"""オフラインバッチ推論"""
# 1. モデルのロード
print("モデルをロード中...")
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
max_model_len=4096,
gpu_memory_utilization=0.85,
)
# 2. サンプリングパラメータの設定
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=256,
)
# 3. バッチ推論(複数プロンプトを一括処理)
prompts = [
"Pythonのリスト内包表記の利点を3つ挙げてください。",
"RESTful APIの設計原則を簡潔に説明してください。",
"Dockerコンテナとは何か、初心者向けに説明してください。",
"Git rebaseとmergeの違いを説明してください。",
]
print(f"\n{len(prompts)}件のプロンプトをバッチ推論中...\n")
outputs = llm.generate(prompts, sampling_params)
# 4. 結果表示
for output in outputs:
prompt = output.prompt
generated = output.outputs[0].text
print(f"Q: {prompt}")
print(f"A: {generated[:200]}...")
print("-" * 60)
if __name__ == "__main__":
main()
5.4 実行結果
OpenAI互換APIクライアントの実行結果:
$ python vllm_client.py
=== 利用可能モデル ===
- Qwen/Qwen2.5-7B-Instruct
=== チャット推論 ===
フィボナッチ数列を生成する3つの方法を紹介します。
**方法1: 再帰(シンプルだが非効率)**
def fib_recursive(n):
if n <= 1:
return n
return fib_recursive(n-1) + fib_recursive(n-2)
**方法2: メモ化再帰(効率的)**
from functools import lru_cache
@lru_cache(maxsize=None)
def fib_memo(n):
if n <= 1:
return n
return fib_memo(n-1) + fib_memo(n-2)
**方法3: イテレーティブ(最も効率的)**
def fib_iter(n):
a, b = 0, 1
for _ in range(n):
a, b = b, a + b
return a
--- 使用トークン ---
入力: 42
出力: 187
合計: 229
5.5 よくあるエラーと対処法
| エラー | 原因 | 対処法 |
|---|---|---|
torch.cuda.OutOfMemoryError: CUDA out of memory |
GPUメモリ不足 |
--gpu-memory-utilization を下げる(0.7等)、または --max-model-len を短くする |
ValueError: The model's max seq len is too large |
モデルのコンテキスト長がGPUメモリを超える |
--max-model-len 4096 で明示的に制限 |
RuntimeError: CUDA error: no kernel image is available |
GPUのCompute Capabilityが非対応 |
nvidia-smi でGPUを確認。CC 7.0以上が必要 |
Connection refused: http://localhost:8000 |
vLLMサーバーが起動していない |
vllm serve でサーバーを起動 |
Loading model takes very long time |
大型モデルの初回ダウンロード中 | 初回はHugging Faceからのダウンロードに時間がかかる。HF_TOKEN を設定してゲートモデルにも対応 |
5.6 環境診断スクリプト
#!/usr/bin/env python3
"""
vLLM 環境診断スクリプト
実行方法: python check_vllm_env.py
"""
import sys
import os
import subprocess
def check_environment():
"""環境をチェックして問題を報告"""
issues = []
warnings = []
# Python バージョン確認
if sys.version_info < (3, 9):
issues.append(
f"Python 3.9以上が必要です(現在: {sys.version})"
)
else:
print(f"✅ Python {sys.version_info.major}.{sys.version_info.minor}")
# PyTorch & CUDA確認
try:
import torch
print(f"✅ PyTorch {torch.__version__}")
if torch.cuda.is_available():
device_name = torch.cuda.get_device_name(0)
vram = torch.cuda.get_device_properties(0).total_mem
vram_gb = vram / (1024 ** 3)
cc = torch.cuda.get_device_capability(0)
print(f"✅ CUDA GPU: {device_name}")
print(f" VRAM: {vram_gb:.1f} GB")
print(f" Compute Capability: {cc[0]}.{cc[1]}")
if cc[0] < 7:
issues.append(
f"Compute Capability 7.0以上が必要です"
f"(現在: {cc[0]}.{cc[1]})"
)
if vram_gb < 8:
warnings.append(
f"VRAM 8GB以上を推奨"
f"(現在: {vram_gb:.1f}GB)。"
f"小型モデル(1.5B〜3B)のみ利用可能"
)
else:
issues.append("CUDAが利用できません(GPU未検出)")
except ImportError:
issues.append("PyTorchがインストールされていません")
# vLLM確認
try:
import vllm
print(f"✅ vLLM がインストール済み")
except ImportError:
issues.append(
"vLLMがインストールされていません。"
"`pip install vllm` を実行してください"
)
# NVIDIA Driver確認
try:
result = subprocess.run(
["nvidia-smi", "--query-gpu=driver_version",
"--format=csv,noheader"],
capture_output=True, text=True, timeout=10
)
if result.returncode == 0:
driver = result.stdout.strip()
print(f"✅ NVIDIA Driver: {driver}")
else:
warnings.append("nvidia-smi の実行に失敗しました")
except FileNotFoundError:
warnings.append(
"nvidia-smi が見つかりません。"
"NVIDIAドライバが正しくインストールされているか確認してください"
)
# Docker確認
try:
result = subprocess.run(
["docker", "--version"],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0:
print(f"✅ Docker: {result.stdout.strip()}")
else:
warnings.append("Docker の実行に失敗しました")
except FileNotFoundError:
warnings.append(
"Docker がインストールされていません"
"(Docker無しでも利用可能ですが推奨します)"
)
# HuggingFaceトークン確認
if os.environ.get("HF_TOKEN") or os.environ.get(
"HUGGING_FACE_HUB_TOKEN"
):
print("✅ HuggingFace Token が設定済み")
else:
warnings.append(
"HuggingFace Token が未設定です。"
"ゲートモデルのダウンロードに必要です"
)
# VRAM別おすすめモデル
try:
import torch
if torch.cuda.is_available():
vram_gb = (
torch.cuda.get_device_properties(0).total_mem
/ (1024 ** 3)
)
print(f"\n📊 VRAM {vram_gb:.0f}GB でのおすすめモデル:")
if vram_gb >= 80:
print(" → 72B量子化モデル (AWQ/GPTQ)")
elif vram_gb >= 24:
print(" → 7B〜14B FP16モデル")
elif vram_gb >= 12:
print(" → 7B量子化モデル (AWQ/GPTQ)")
elif vram_gb >= 8:
print(" → 1.5B〜3B FP16モデル")
else:
print(" → 1.5B以下のモデルを推奨")
except Exception:
pass
# 結果表示
if issues:
print(f"\n❌ {len(issues)}件の問題:")
for issue in issues:
print(f" ✗ {issue}")
if warnings:
print(f"\n⚠️ {len(warnings)}件の警告:")
for w in warnings:
print(f" △ {w}")
if not issues and not warnings:
print("\n🎉 環境は正常です!vLLMを始められます。")
if __name__ == "__main__":
check_environment()
実装方法がわかったので、次は具体的なユースケースを見ていきます。
6. ユースケース別ガイド
6.1 ユースケース1: OpenAI APIの代替としてローカルLLMを使う
想定読者: APIコストを削減したい個人開発者・スタートアップ
推奨構成: vLLM + Qwen2.5-7B + Docker
サンプルコード:
"""
既存のOpenAI APIクライアントコードをvLLMで動かす
- base_urlを変えるだけで、既存コードがそのまま動く
実行方法: python openai_replacement.py
"""
from openai import OpenAI
def create_client(use_local: bool = True) -> OpenAI:
"""ローカルvLLMまたはOpenAI APIのクライアントを作成"""
if use_local:
return OpenAI(
base_url="http://localhost:8000/v1",
api_key="local-key",
)
else:
return OpenAI() # 通常のOpenAI API
def summarize_text(client: OpenAI, text: str) -> str:
"""テキスト要約(APIとローカルで同じコード)"""
response = client.chat.completions.create(
model=client.models.list().data[0].id,
messages=[
{
"role": "system",
"content": "日本語で簡潔に要約してください。"
},
{"role": "user", "content": f"以下を要約:\n{text}"}
],
temperature=0.3,
max_tokens=256,
)
return response.choices[0].message.content
def main():
# ローカルvLLMに切り替え(base_urlだけの変更!)
client = create_client(use_local=True)
sample_text = (
"vLLMは、UC BerkeleyのSky Computing Labで開発された"
"LLM推論エンジンです。PagedAttentionと継続バッチングにより、"
"従来のフレームワークと比較して2〜24倍のスループットを実現します。"
"2025年にはGitHubコントリビューター数でOSSプロジェクト1位を獲得し、"
"Red Hatがメインスポンサーとして参画しています。"
)
print("=== テキスト要約 ===")
summary = summarize_text(client, sample_text)
print(f"要約: {summary}")
if __name__ == "__main__":
main()
6.2 ユースケース2: 大量テキストのバッチ処理
想定読者: データ分析者・ML エンジニア
推奨構成: vLLM + Python API + 大型モデル
サンプルコード:
"""
CSVファイル内のテキストを一括でLLM処理する
- vLLMのバッチ推論でスループットを最大化
実行方法: python batch_processing.py
"""
import csv
import json
import time
from pathlib import Path
from vllm import LLM, SamplingParams
def process_batch(
llm: LLM,
texts: list[str],
system_prompt: str,
) -> list[str]:
"""テキストのバッチを一括処理"""
prompts = []
for text in texts:
prompt = (
f"<|im_start|>system\n{system_prompt}<|im_end|>\n"
f"<|im_start|>user\n{text}<|im_end|>\n"
f"<|im_start|>assistant\n"
)
prompts.append(prompt)
sampling_params = SamplingParams(
temperature=0.0, # 分析タスクは確定的に
max_tokens=128,
)
outputs = llm.generate(prompts, sampling_params)
return [o.outputs[0].text.strip() for o in outputs]
def main():
"""CSVのレビューデータを感情分析"""
# サンプルデータ(実際はCSVから読み込む)
reviews = [
"この商品は最高です!毎日使っています。",
"期待はずれでした。品質が悪い。",
"普通。可もなく不可もなく。",
"配送が早くて助かりました。商品も良い。",
"返品したい。壊れていました。",
]
print("モデルをロード中...")
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
max_model_len=2048,
gpu_memory_utilization=0.85,
)
system_prompt = (
"以下のレビューの感情を分析し、"
"「ポジティブ」「ネガティブ」「ニュートラル」"
"のいずれか1つだけを回答してください。"
)
print(f"\n{len(reviews)}件のレビューをバッチ処理中...")
start = time.time()
results = process_batch(llm, reviews, system_prompt)
elapsed = time.time() - start
for review, sentiment in zip(reviews, results):
print(f" [{sentiment}] {review[:30]}...")
print(f"\n処理時間: {elapsed:.2f}秒")
print(f"スループット: {len(reviews)/elapsed:.1f} 件/秒")
if __name__ == "__main__":
main()
6.3 ユースケース3: GPU別パフォーマンス比較
想定読者: GPU購入を検討しているエンジニア、ハードウェア最適化に興味がある方
推奨構成: vLLM + ベンチマークスクリプト
サンプルコード:
"""
vLLMベンチマーク: トークン生成速度の測定
- 異なる設定でのスループットを比較
実行方法: python vllm_benchmark.py
"""
import time
from vllm import LLM, SamplingParams
def run_benchmark(
model_name: str,
num_prompts: int = 10,
max_tokens: int = 256,
gpu_memory_utilization: float = 0.85,
) -> dict:
"""ベンチマーク実行"""
print(f"\nベンチマーク開始: {model_name}")
print(f" プロンプト数: {num_prompts}")
print(f" 最大トークン: {max_tokens}")
llm = LLM(
model=model_name,
max_model_len=4096,
gpu_memory_utilization=gpu_memory_utilization,
)
prompts = [
f"技術的なトピックについて{i+1}番目の質問です。"
f"Pythonのベストプラクティスを教えてください。"
for i in range(num_prompts)
]
sampling_params = SamplingParams(
temperature=0.7,
max_tokens=max_tokens,
)
start = time.time()
outputs = llm.generate(prompts, sampling_params)
elapsed = time.time() - start
total_tokens = sum(
len(o.outputs[0].token_ids) for o in outputs
)
result = {
"model": model_name,
"num_prompts": num_prompts,
"total_tokens": total_tokens,
"elapsed_seconds": round(elapsed, 2),
"tokens_per_second": round(total_tokens / elapsed, 1),
"requests_per_second": round(num_prompts / elapsed, 2),
}
print(f" 生成トークン数: {total_tokens}")
print(f" 所要時間: {elapsed:.2f}秒")
print(f" スループット: {result['tokens_per_second']} tok/s")
print(f" リクエスト速度: {result['requests_per_second']} req/s")
return result
def main():
"""複数設定でベンチマーク"""
results = []
# ベンチマーク1: 7Bモデル
results.append(run_benchmark(
model_name="Qwen/Qwen2.5-7B-Instruct",
num_prompts=10,
))
print("\n" + "=" * 60)
print("ベンチマーク結果サマリ")
print("=" * 60)
for r in results:
print(
f" {r['model']}: "
f"{r['tokens_per_second']} tok/s "
f"({r['elapsed_seconds']}s)"
)
if __name__ == "__main__":
main()
参考: GPU別の推定スループット(7Bモデル FP16)
| GPU | VRAM | 推定 tok/s | 備考 |
|---|---|---|---|
| RTX 4060 | 8GB | 30〜50 | 入門用 |
| RTX 4070 Ti | 12GB | 60〜90 | コスパ良好 |
| RTX 4090 | 24GB | 120〜180 | ハイエンド |
| RTX 5090 | 32GB | 200〜300 | NVFP4対応で更に高速 |
| A100 | 80GB | 200〜300 | データセンター向け |
| H100 | 80GB | 400〜600 | FP8対応 |
ユースケースを把握できたところで、この先の学習パスを確認しましょう。
7. 学習ロードマップ
この記事を読んだ後、次のステップとして以下をおすすめする。
初級者向け(まずはここから)
- vLLM公式ドキュメント のQuickstart Guideを実行
- Docker Composeで開発環境を構築し、WebブラウザからOpenAI互換APIを叩いてみる
- Ollamaとの速度比較をしてみる(同一モデル・同一GPUで)
中級者向け(実践に進む)
- AWQ/GPTQ量子化モデルの利用と精度検証
- プリフィックスキャッシングの有効化によるRAGパイプラインの高速化
- テンソル並列(
--tensor-parallel-size)を使ったマルチGPU推論 - vLLM Playground でGUIベースのモデル操作
上級者向け(さらに深く)
- vLLM論文: Efficient Memory Management for Large Language Model Serving with PagedAttention を読む
- Speculative Decodingによる推論速度の更なる向上
- vLLMソースコード のPagedAttention実装を読む
- vLLMへのコントリビュート(モデルサポートの追加等)
8. まとめ
この記事では、vLLMについて以下を解説した:
- vLLMとは何か: PagedAttentionと継続バッチングでLLM推論を爆速化するOSSエンジン
- 技術的な仕組み: OSの仮想メモリ管理をGPUに応用し、KVキャッシュの断片化を解消
- 実践的な使い方: OpenAI互換APIサーバー、バッチ処理、GPU別ベンチマーク
私の所感
正直に言うと、vLLMを導入する前は「ローカルLLMなんてAPI叩いた方が早い」と思っていた。しかし、実際にvLLMを動かしてみると、「GPUを持っている人にとって、ローカル推論は十分に実用的」だと認識が変わった。
特に印象的だったのは、RTX 5090 + vLLM + Qwen2.5-7B の組み合わせで、OpenAI APIと遜色ないレスポンス速度が出たこと。APIコスト(月数万円〜数十万円)を考えると、GPU購入費は1年以内に回収できる計算だ。
一方で、最初のセットアップは正直面倒だった。CUDAバージョンの互換性、モデルのダウンロード時間、Docker環境の構築——これらの「初期コスト」を乗り越える価値があるかどうかは、利用頻度次第だ。月に数回しかLLMを使わないなら、APIで十分。毎日ヘビーに使うなら、vLLM + ローカルGPUの投資回収は驚くほど早い。