12
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

vLLMってなんだ? 〜ローカルLLM推論を爆速化するOSSエンジンの全貌と実践ガイド〜

12
Posted at

この記事の対象読者

  • 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. 学習ロードマップ

この記事を読んだ後、次のステップとして以下をおすすめする。

初級者向け(まずはここから)

  1. vLLM公式ドキュメント のQuickstart Guideを実行
  2. Docker Composeで開発環境を構築し、WebブラウザからOpenAI互換APIを叩いてみる
  3. Ollamaとの速度比較をしてみる(同一モデル・同一GPUで)

中級者向け(実践に進む)

  1. AWQ/GPTQ量子化モデルの利用と精度検証
  2. プリフィックスキャッシングの有効化によるRAGパイプラインの高速化
  3. テンソル並列(--tensor-parallel-size)を使ったマルチGPU推論
  4. vLLM Playground でGUIベースのモデル操作

上級者向け(さらに深く)

  1. vLLM論文: Efficient Memory Management for Large Language Model Serving with PagedAttention を読む
  2. Speculative Decodingによる推論速度の更なる向上
  3. vLLMソースコード のPagedAttention実装を読む
  4. vLLMへのコントリビュート(モデルサポートの追加等)

8. まとめ

この記事では、vLLMについて以下を解説した:

  1. vLLMとは何か: PagedAttentionと継続バッチングでLLM推論を爆速化するOSSエンジン
  2. 技術的な仕組み: OSの仮想メモリ管理をGPUに応用し、KVキャッシュの断片化を解消
  3. 実践的な使い方: 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の投資回収は驚くほど早い。


参考文献

12
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?