- Published on
LLM推論最適化完全ガイド2025:vLLM、TensorRT-LLM、KV Cache、Speculative Decoding
- Authors

- Name
- Youngju Kim
- @fjvbn20031
目次(もくじ)
1. LLM推論(すいろん)のボトルネック理解:Compute-Bound vs Memory-Bound
LLM推論最適化を議論する前に、まずボトルネックがどこで発生するかを正確に理解する必要があります。
1.1 Arithmetic IntensityとRoofline Model
GPU演算の性能は二つのリソースによって決定されます。
| リソース | 単位 | A100 80GB | H100 80GB | H200 141GB |
|---|---|---|---|---|
| 演算能力(FP16) | TFLOPS | 312 | 989 | 989 |
| メモリ帯域幅(たいいきはば) | TB/s | 2.0 | 3.35 | 4.8 |
| Arithmetic Intensity境界 | FLOP/byte | 156 | 295 | 206 |
Arithmetic Intensity = 総演算量(FLOPs) / 総メモリ転送量(Bytes)
- Compute-Bound:Arithmetic Intensityが境界値より高い場合。行列積が代表的
- Memory-Bound:Arithmetic Intensityが境界値より低い場合。Attention、Decodingが代表的
1.2 Prefill vs Decode段階(だんかい)
LLM推論は大きく二つの段階に分かれます。
┌──────────────────────────────────────────────────────┐
│ LLM推論パイプライン │
├──────────────────┬───────────────────────────────────┤
│ Prefill段階 │ Decode段階 │
│ (プロンプト処理) │ (トークン生成) │
├──────────────────┼───────────────────────────────────┤
│ - 入力トークン並列 │ - トークン1つずつ順次生成 │
│ - Compute-Bound │ - Memory-Bound │
│ - 高いGPU活用率 │ - 低いGPU活用率(通常5-15%) │
│ - 一回実行 │ - 出力長分だけ繰り返し │
│ - KV Cache生成 │ - KV Cache読み取り+追加 │
└──────────────────┴───────────────────────────────────┘
Prefill段階:プロンプト全体を一度に処理します。行列-行列積(GEMM)が主となりcompute-boundです。
Decode段階:トークンを一つずつ生成します。行列-ベクトル積(GEMV)が主となりmemory-boundです。毎ステップでモデル全体の重みを読み込む必要がありますが、実際の演算量は少ないです。
1.3 なぜDecodeが遅(おそ)いのか
Llama-2 70Bモデル基準:
- モデル重み:約140GB(FP16)
- Decode1ステップあたり:140GBをメモリから読み込む必要
- A100帯域幅2TB/s基準:140GB / 2TB/s = 70ms per token
- 実際の演算に必要な時間:約1ms
メモリ読み込みが70倍長くかかります。 これがLLM推論最適化の核心的な動機です。
2. KV Cache:LLM推論の核心データ構造
2.1 KV Cacheとは何(なに)か
TransformerのSelf-Attentionはすべての過去トークンのKey(K)とValue(V)を必要とします。KV Cacheは既に計算されたK、Vテンソルを保存して再計算を防止します。
# KV Cacheなしの場合(毎ステップ全再計算)
# トークンn個生成時の総演算: O(n^2 * d)
# KV Cacheありの場合(以前の結果を再利用)
# トークンn個生成時の総演算: O(n * d)
# ただし、KV Cacheメモリ: O(n * d) 追加必要
2.2 KV Cacheメモリ計算
KV Cacheサイズ = 2 * num_layers * num_kv_heads * head_dim * seq_len * batch_size * dtype_size
例: Llama-2 70B, seq_len=4096, batch_size=1, FP16
= 2 * 80 * 8 * 128 * 4096 * 1 * 2 bytes
= 1.34 GB (シーケンス1つに!)
batch_size=32の場合: 1.34 * 32 = 42.9 GB
| モデル | パラメータ | KV Cache/token (FP16) | 4Kシーケンスx1 | 4Kシーケンスx32 |
|---|---|---|---|---|
| Llama-2 7B | 7B | 800 KB | 3.2 GB | 102 GB |
| Llama-2 70B | 70B | 320 KB | 1.34 GB | 42.9 GB |
| Mixtral 8x7B | 46.7B | 640 KB | 2.56 GB | 81.9 GB |
| Llama-3 405B | 405B | 1.6 MB | 6.4 GB | 204 GB |
2.3 PagedAttention(vLLMのコア技術)
従来方式の問題:シーケンスごとに最大長分の連続メモリを事前確保。実際には60-80%が無駄になります。
┌─────────────────────────────────────────────┐
│ 従来のKV Cache割り当て方式 │
│ │
│ Request 1: [████████░░░░░░░░░░░░] 40% 使用│
│ Request 2: [████████████░░░░░░░░] 60% 使用│
│ Request 3: [██░░░░░░░░░░░░░░░░░░] 10% 使用│
│ ^^^^^^^^ 無駄なメモリ │
└─────────────────────────────────────────────┘
┌─────────────────────────────────────────────┐
│ PagedAttention KV Cache割り当て │
│ │
│ 物理ブロック: [B0][B1][B2][B3][B4][B5] │
│ │
│ Request 1 → ページテーブル: [B0, B3, B5] │
│ Request 2 → ページテーブル: [B1, B4, B6] │
│ Request 3 → ページテーブル: [B2] │
│ │
│ 内部フラグメンテーションほぼゼロ │
│ 非連続メモリブロック活用 │
│ Copy-on-Writeでプロンプト共有 │
└─────────────────────────────────────────────┘
PagedAttentionの核心アイデア:
- KV Cacheを固定サイズの**ブロック(ページ)**に分割
- OSの仮想メモリのようにページテーブルで非連続ブロックを論理的に接続
- 必要な時だけブロックを割り当て、内部フラグメンテーションを除去
- Copy-on-Write:同じプロンプトを共有するリクエストがKV Cacheを共有
2.4 Prefix Caching
繰り返されるシステムプロンプトや共通プレフィックスのKV Cacheを再利用します。
# vLLMでPrefix Cachingを有効化
from vllm import LLM
llm = LLM(
model="meta-llama/Llama-3.1-70B-Instruct",
enable_prefix_caching=True,
max_model_len=8192,
)
# 同じシステムプロンプトを使用するリクエストは
# システムプロンプト部分のKV Cacheを共有します
3. Attention最適化:FlashAttentionとMQA/GQA
3.1 FlashAttention:IO-Aware Attention
標準Attentionの問題点:
- Q、K、V行列をHBM(High Bandwidth Memory)から読み込む
- S = Q @ K^Tを計算してHBMに書き込む
- P = softmax(S)を計算してHBMに書き込む
- O = P @ Vを計算してHBMに書き込む
合計4回のHBM読み書き — これがボトルネックです。
┌──────────────────────────────────────────────┐
│ FlashAttention核心アイデア │
│ │
│ GPUメモリ階層: │
│ ┌─────────┐ 19 TB/s ┌─────────────────┐ │
│ │ SRAM │◄──────────►│ Compute Units │ │
│ │ (20 MB) │ └─────────────────┘ │
│ └────┬────┘ │
│ │ 2-4.8 TB/s │
│ ┌────▼────────────────┐ │
│ │ HBM (80-141 GB) │ │
│ └─────────────────────┘ │
│ │
│ 戦略: Q,K,Vをタイル(ブロック)に分割して │
│ SRAMですべての計算を実行し │
│ 最終結果のみHBMに記録 │
└──────────────────────────────────────────────┘
3.2 FlashAttentionバージョン比較
| 特性 | FlashAttention-1 | FlashAttention-2 | FlashAttention-3 |
|---|---|---|---|
| リリース | 2022 | 2023 | 2024 |
| 速度向上 | 2-4倍 | 追加2倍 | 追加1.5-2倍 |
| GPU対応 | A100 | A100, H100 | H100(Hopper最適化) |
| 主要最適化 | タイリング、再計算 | 並列化改善、warp分割 | FP8、非同期コピー、パイプライニング |
| MHA比FLOPS | 50-70% | 70-80% | 最大740 TFLOPS(75%) |
3.3 Multi-Query Attention (MQA) vs Grouped-Query Attention (GQA)
KV Cacheサイズを削減するアーキテクチャレベルの最適化です。
┌─────────────────────────────────────────────────────┐
│ Multi-Head Attention (MHA) │
│ Q heads: [H1][H2][H3][H4][H5][H6][H7][H8] │
│ K heads: [H1][H2][H3][H4][H5][H6][H7][H8] │
│ V heads: [H1][H2][H3][H4][H5][H6][H7][H8] │
│ KV Cache: 8x │
├─────────────────────────────────────────────────────┤
│ Multi-Query Attention (MQA) │
│ Q heads: [H1][H2][H3][H4][H5][H6][H7][H8] │
│ K heads: [ H_shared ] │
│ V heads: [ H_shared ] │
│ KV Cache: 1x (8倍削減) │
├─────────────────────────────────────────────────────┤
│ Grouped-Query Attention (GQA, 2グループ) │
│ Q heads: [H1][H2][H3][H4] | [H5][H6][H7][H8] │
│ K heads: [ K_group1 ] | [ K_group2 ] │
│ V heads: [ V_group1 ] | [ V_group2 ] │
│ KV Cache: 2x (4倍削減) │
└─────────────────────────────────────────────────────┘
| モデル | Attentionタイプ | KV Heads | Q Heads | KV Cache削減 |
|---|---|---|---|---|
| GPT-J 6B | MHA | 16 | 16 | 1x |
| Falcon-40B | MQA | 1 | 64 | 64x |
| Llama-2 70B | GQA | 8 | 64 | 8x |
| Llama-3 70B | GQA | 8 | 64 | 8x |
| Mistral 7B | GQA | 8 | 32 | 4x |
4. Batching戦略:Static vs Continuous
4.1 Static Batchingの限界(げんかい)
Static Batching(従来方式):
時間 ──────────────────────────────────►
Req 1: [████████████████████████████████] (長い応答)
Req 2: [████████░░░░░░░░░░░░░░░░░░░░░░] (短い応答)
Req 3: [██████████████░░░░░░░░░░░░░░░░] (中程度の応答)
Req 4: [WAIT WAIT WAIT WAIT WAIT WAIT ] (待機中)
░ = GPUアイドル(パディング), WAIT = バッチ完了まで待機
バッチ全体が終わるまで次のバッチ開始不可 → スループット非常に低い
4.2 Continuous Batching (In-Flight Batching)
Continuous Batching:
時間 ──────────────────────────────────►
Req 1: [████████████████████████████████]
Req 2: [████████]
Req 3: [██████████████]
Req 4: [████████████████]
Req 5: [████████]
完了したリクエストを即座に除去 → 新しいリクエストを即座に投入
GPUアイドル時間を最小化 → スループット10-20倍向上
Continuous Batchingの核心原理:
- 毎イテレーションで完了したリクエストをバッチから除去
- 待機中のリクエストを即座にバッチに追加
- GPUが常に最大負荷で動作
- 個々のリクエストのレイテンシも改善(待機時間削減)
4.3 Chunked Prefill
長いプロンプトのPrefill段階がDecodeリクエストをブロッキングする問題を解決します。
# vLLM chunked prefill設定
from vllm import LLM, SamplingParams
llm = LLM(
model="meta-llama/Llama-3.1-8B-Instruct",
enable_chunked_prefill=True,
max_num_batched_tokens=2048, # 一度に処理する最大トークン数
)
# 長いプロンプト(例:32Kトークン)を2048トークンチャンクに分割処理
# チャンク間にDecodeリクエストも処理可能
# TTFTは若干増加するが、全体のスループットとITLが改善
5. Speculative Decoding:推論速度のゲームチェンジャー
5.1 核心アイデア
小さな**ドラフトモデル(Draft Model)が複数トークンを高速に予測し、大きなターゲットモデル(Target Model)**が1回のforward passですべて検証します。
┌────────────────────────────────────────────────────┐
│ Speculative Decodingフロー │
│ │
│ Step 1: Draft Model(小さく高速なモデル) │
│ "The capital of France is" → [Paris][,][a][city] │
│ 4トークンを非常に高速に予測(4ms) │
│ │
│ Step 2: Target Model(大きく正確なモデル) │
│ 1回のforward passで4トークンを同時検証 │
│ [Paris OK] [, OK] [a NG→"known"] [city NG] │
│ │
│ 結果: "Paris, known" (2個受理 + 1個修正) │
│ 従来: 3回のforward pass必要 → 今は1回 │
│ 速度向上: 約2-3倍 │
└────────────────────────────────────────────────────┘
5.2 数学的保証:出力品質の維持
Speculative Decodingの核心的な利点は、ターゲットモデルの出力分布を正確に維持することです。
受理/棄却確率:
- ドラフトトークンxに対し、受理確率 = min(1, p_target(x) / p_draft(x))
- 棄却時:(p_target(x) - p_draft(x))分布から再サンプリング
この過程を通じて、最終出力はターゲットモデルのみを使用した場合と数学的に同一の分布を持ちます。
5.3 Speculative Decodingのバリエーション
# 1. 別途Draft Modelを使用
from vllm import LLM, SamplingParams
llm = LLM(
model="meta-llama/Llama-3.1-70B-Instruct",
speculative_model="meta-llama/Llama-3.1-8B-Instruct",
num_speculative_tokens=5,
use_v2_block_manager=True,
)
# 2. Medusa Heads(追加MLPヘッドで複数位置を同時予測)
# Draftモデル不要 - ターゲットモデル自体に軽量ヘッドを追加
# 学習が必要だがメモリオーバーヘッド最小
# 3. EAGLE (Extrapolation Algorithm for Greater Language-model Efficiency)
# ドラフトモデルがターゲットモデルのhidden stateを再利用
# 別途ドラフトモデルより高い受理率
5.4 Tree Attention
複数の候補シーケンスをツリー構造で同時に検証します。
トークン位置: 1 2 3
┌── Paris ─┬── is ── ...
The ──────────┤ └── was ── ...
├── Lyon ── is ── ...
└── capital ── of ── ...
ツリーのすべてのパスを1回のforward passで検証
→ 受理確率最大化、スループット向上
6. 量子化(りょうしか)で推論を加速
6.1 データ型(かた)別比較
| データ型 | ビット数 | 範囲 | メモリ削減 | 品質影響 |
|---|---|---|---|---|
| FP32 | 32 | 非常に広い | 基準 | 基準 |
| FP16 | 16 | 広い | 2倍 | 無視可能 |
| BF16 | 16 | FP32と同等 | 2倍 | 無視可能 |
| FP8 (E4M3) | 8 | 中程度 | 4倍 | 非常に小さい |
| INT8 | 8 | -128から127 | 4倍 | 小さい |
| INT4 | 4 | -8から7 | 8倍 | 中程度 |
| NF4 | 4 | 正規分布最適化 | 8倍 | INT4より小さい |
6.2 量子化技法の比較
┌───────────────────────────────────────────────────────┐
│ 量子化技法の分類 │
├─────────────────────┬─────────────────────────────────┤
│ Post-Training │ Training-Aware │
│ Quantization(PTQ) │ Quantization │
├─────────────────────┼─────────────────────────────────┤
│ - GPTQ (INT4) │ - QLoRA + Merge │
│ - AWQ (INT4) │ - QAT (Quantization-Aware │
│ - GGUF (各種) │ Training) │
│ - bitsandbytes │ │
│ - SmoothQuant │ │
│ - FP8 Dynamic │ │
└─────────────────────┴─────────────────────────────────┘
6.3 主要量子化フォーマットの詳細
# GPTQ: レイヤー別最適量子化(OBQベース)
# 利点: INT4でも良好な品質、GPU推論最適化
# 欠点: キャリブレーションデータ必要、量子化時間が長い
from transformers import AutoModelForCausalLM, GPTQConfig
gptq_config = GPTQConfig(
bits=4,
group_size=128,
dataset="c4",
desc_act=True,
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-70B-Instruct",
quantization_config=gptq_config,
device_map="auto",
)
# AWQ: Activation-aware Weight Quantization
# 核心: 重要な重みチャネルを発見し保護(活性化の大きさ基準)
# GPTQより高速な量子化、同等の品質
from awq import AutoAWQForCausalLM
model = AutoAWQForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-70B-Instruct"
)
quant_config = {
"zero_point": True,
"q_group_size": 128,
"w_bit": 4,
"version": "GEMM"
}
model.quantize(tokenizer, quant_config=quant_config)
# bitsandbytes: 簡単なINT8/NF4量子化
from transformers import BitsAndBytesConfig
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype="bfloat16",
bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B-Instruct",
quantization_config=bnb_config,
)
6.4 GGUF:CPU/Metal推論用フォーマット
llama.cppで使用される量子化フォーマットです。多様な量子化レベルをサポートします。
| GGUF量子化 | ビット | 方法 | 品質 | 速度 |
|---|---|---|---|---|
| Q2_K | 2-3 | K-quant混合 | 低い | 非常に高速 |
| Q4_K_M | 4-5 | K-quant中間 | 良好 | 高速 |
| Q5_K_M | 5-6 | K-quant中間 | 非常に良好 | 中程度 |
| Q6_K | 6 | K-quant | ほぼ原本 | 遅い |
| Q8_0 | 8 | 均一量子化 | 原本と同等 | 遅い |
| F16 | 16 | 量子化なし | 原本 | 最も遅い |
7. サービングフレームワーク比較:vLLM vs TensorRT-LLM vs TGI
7.1 総合比較表
| 機能 | vLLM | TensorRT-LLM | TGI | Ollama | llama.cpp |
|---|---|---|---|---|---|
| 開発元 | UC Berkeley | NVIDIA | Hugging Face | Ollama | ggerganov |
| 言語 | Python/C++ | C++/Python | Rust/Python | Go | C/C++ |
| PagedAttention | O | O | O | X | X |
| Continuous Batching | O | O | O | X | X |
| Tensor Parallelism | O | O | O | X | X |
| FP8サポート | O | O(最適) | O | X | X |
| Speculative Decoding | O | O | 限定的 | X | O |
| LoRAサービング | O(複数) | O | O | O | O |
| Visionモデル | O | O | O | O | O(一部) |
| CPU推論 | 限定的 | X | X | O | O(最適) |
| Metal (Apple) | X | X | X | O | O |
| インストール難易度 | 簡単 | 困難 | 簡単 | 非常に簡単 | 中程度 |
| 本番適合度 | 高い | 高い | 高い | 低い | 中程度 |
7.2 スループットベンチマーク (Llama-3.1 8B, A100 80GB)
| フレームワーク | スループット (tok/s) | TTFT (ms) | ITL (ms) | メモリ使用量 |
|---|---|---|---|---|
| vLLM (FP16) | 4,200 | 45 | 12 | 18 GB |
| vLLM (AWQ-4bit) | 6,800 | 32 | 8 | 7 GB |
| TensorRT-LLM (FP16) | 4,800 | 38 | 10 | 17 GB |
| TensorRT-LLM (FP8) | 7,500 | 28 | 7 | 10 GB |
| TGI (FP16) | 3,600 | 52 | 14 | 18 GB |
| llama.cpp (Q4_K_M) | 120 | 200 | 35 | 5 GB |
8. vLLM深堀り:アーキテクチャからLoRAサービングまで
8.1 vLLMアーキテクチャ
┌──────────────────────────────────────────┐
│ vLLMアーキテクチャ │
│ │
│ ┌─────────┐ ┌──────────────────┐ │
│ │ FastAPI │────►│ LLM Engine │ │
│ │ Server │ │ │ │
│ └─────────┘ │ ┌────────────┐ │ │
│ │ │ Scheduler │ │ │
│ ┌─────────┐ │ │ (バッチング) │ │ │
│ │ OpenAI │────►│ └─────┬──────┘ │ │
│ │ compat │ │ │ │ │
│ └─────────┘ │ ┌─────▼──────┐ │ │
│ │ │ Block Mgr │ │ │
│ │ │ (PagedAttn) │ │ │
│ │ └─────┬──────┘ │ │
│ │ │ │ │
│ │ ┌─────▼──────┐ │ │
│ │ │ Worker(s) │ │ │
│ │ │ (GPU実行) │ │ │
│ │ └────────────┘ │ │
│ └──────────────────┘ │
└──────────────────────────────────────────┘
8.2 vLLM本番デプロイ
# vLLMサーバー起動(OpenAI API互換)
# vllm serve meta-llama/Llama-3.1-70B-Instruct \
# --tensor-parallel-size 4 \
# --max-model-len 32768 \
# --gpu-memory-utilization 0.90 \
# --enable-prefix-caching \
# --enable-chunked-prefill \
# --max-num-batched-tokens 4096 \
# --port 8000
# PythonでAPI呼び出し
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="token-abc123",
)
response = client.chat.completions.create(
model="meta-llama/Llama-3.1-70B-Instruct",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain quantum computing"},
],
max_tokens=512,
temperature=0.7,
)
8.3 vLLMマルチLoRAサービング
一つのベースモデルで複数のLoRAアダプターを同時にサービングできます。
# vllm serve meta-llama/Llama-3.1-8B-Instruct \
# --enable-lora \
# --lora-modules \
# sql-lora=./adapters/sql-lora \
# code-lora=./adapters/code-lora \
# chat-lora=./adapters/chat-lora \
# --max-loras 3 \
# --max-lora-rank 64
# API呼び出し時にモデル名でLoRAアダプターを選択
response = client.chat.completions.create(
model="sql-lora", # LoRAアダプター名
messages=[{"role": "user", "content": "SELECT ..."}],
)
8.4 vLLM Visionモデルサービング
# マルチモーダルモデルサービング
# vllm serve Qwen/Qwen2-VL-7B-Instruct \
# --max-model-len 8192 \
# --limit-mm-per-prompt image=4
from openai import OpenAI
import base64
client = OpenAI(base_url="http://localhost:8000/v1", api_key="key")
response = client.chat.completions.create(
model="Qwen/Qwen2-VL-7B-Instruct",
messages=[{
"role": "user",
"content": [
{"type": "text", "text": "What is in this image?"},
{"type": "image_url", "image_url": {"url": "data:image/png;base64,..."}}
]
}],
)
9. TensorRT-LLM深堀り:最高性能のための選択
9.1 TensorRT-LLMビルドパイプライン
┌────────┐ ┌───────────┐ ┌──────────┐ ┌──────────┐
│HFモデル │────►│チェックポイント│────►│ TRT-LLM │────►│ Triton │
│(元モデル)│ │ 変換 │ │ Engine │ │サービング │
└────────┘ └───────────┘ └──────────┘ └──────────┘
量子化適用 コンパイル最適化 APIサーバー
# Step 1: チェックポイント変換 + FP8量子化
python convert_checkpoint.py \
--model_dir meta-llama/Llama-3.1-70B-Instruct \
--output_dir ./checkpoint_fp8 \
--dtype bfloat16 \
--tp_size 4 \
--pp_size 1 \
--use_fp8
# Step 2: TensorRTエンジンビルド
trtllm-build \
--checkpoint_dir ./checkpoint_fp8 \
--output_dir ./engine_fp8 \
--gemm_plugin auto \
--max_batch_size 64 \
--max_input_len 4096 \
--max_seq_len 8192 \
--paged_kv_cache enable \
--use_paged_context_fmha enable \
--workers 4
9.2 TensorRT-LLM FP8最適化
H100 GPUのFP8 Tensor Coreを最大限活用します。
| 設定 | スループット (Llama-3.1 70B, 4xH100) | レイテンシ |
|---|---|---|
| FP16, TP=4 | 2,400 tok/s | 16ms ITL |
| FP8, TP=4 | 4,200 tok/s | 9ms ITL |
| FP8 + Speculative | 5,800 tok/s | 6ms ITL |
| INT4 AWQ, TP=2 | 3,800 tok/s | 11ms ITL |
9.3 Inflight Batching (TensorRT-LLM)
TensorRT-LLMのContinuous Batching実装です。
# Triton Inference Server + TensorRT-LLMバックエンド
# model_config.pbtxt設定
"""
backend: "tensorrtllm"
max_batch_size: 64
model_transaction_policy {
decoupled: True # ストリーミングレスポンスサポート
}
parameters: {
key: "batching_type"
value: {string_value: "inflight"} # Inflight Batching有効化
}
parameters: {
key: "max_tokens_in_paged_kv_cache"
value: {string_value: "131072"} # KV Cacheトークン数制限
}
"""
10. モデル並列化:マルチGPU戦略
10.1 Tensor Parallelism (TP)
一つのレイヤーを複数のGPUに分割します。
Tensor Parallelism (TP=4):
レイヤーNの重み行列W
┌──────┬──────┬──────┬──────┐
│ W_1 │ W_2 │ W_3 │ W_4 │
│GPU 0 │GPU 1 │GPU 2 │GPU 3 │
└──┬───┴──┬───┴──┬───┴──┬───┘
│ │ │ │
▼ ▼ ▼ ▼
[部分1] [部分2] [部分3] [部分4]
│ │ │ │
└──────┴──────┴──────┘
All-Reduce
(結果集約)
利点: レイテンシ削減(全GPU同時計算)
欠点: GPU間通信が必要(NVLink推奨)
適用: 同一ノード内GPU(低レイテンシ必要)
10.2 Pipeline Parallelism (PP)
レイヤーを順次複数のGPUに分配します。
Pipeline Parallelism (PP=4, 80 layers):
GPU 0: [Layer 0-19] → GPU 1: [Layer 20-39]
→ GPU 2: [Layer 40-59]
→ GPU 3: [Layer 60-79]
利点: GPU間通信最小(一方向)
欠点: パイプラインバブル(GPUアイドル時間)
適用: ノード間分散(高レイテンシ許容)
10.3 Expert Parallelism (EP) - MoEモデル用
Mixture of Expertsモデルでエキスパートを分散します。
Expert Parallelism (Mixtral 8x7B, EP=4):
GPU 0: Expert 0, 1 + Shared Layers
GPU 1: Expert 2, 3 + Shared Layers
GPU 2: Expert 4, 5 + Shared Layers
GPU 3: Expert 6, 7 + Shared Layers
トークンルーティング: 各トークンはTop-2 Expertに送信
→ GPU間All-to-All通信が必要
10.4 実践的な並列化の組み合わせ
# Llama-3.1 405Bサービング (8x H100 80GB)
# モデルサイズ: 約810 GB (FP16) → FP8で約405 GB
# オプション1: TP=8(全GPUに全レイヤーを分割)
vllm serve meta-llama/Llama-3.1-405B-Instruct-FP8 \
--tensor-parallel-size 8 \
--max-model-len 16384
# オプション2: TP=4, PP=2(4 GPUずつ2パイプラインステージ)
vllm serve meta-llama/Llama-3.1-405B-Instruct-FP8 \
--tensor-parallel-size 4 \
--pipeline-parallel-size 2 \
--max-model-len 16384
11. GPUメモリ最適化の深堀り
11.1 KV Cache量子化
# vLLMでKV Cache FP8量子化
# vllm serve meta-llama/Llama-3.1-70B-Instruct \
# --tensor-parallel-size 4 \
# --kv-cache-dtype fp8 \
# --quantization fp8
# KV Cacheメモリ削減効果
# FP16 KV Cache: 1.34 GB / シーケンス (Llama-2 70B, 4K)
# FP8 KV Cache: 0.67 GB / シーケンス (50%削減)
# 同じGPUで2倍多くの同時リクエストを処理可能
11.2 メモリ割り当て戦略
GPUメモリ配分 (A100 80GB, Llama-3.1 70B FP16):
┌─────────────────────────────────┐
│ モデル重み: 約35 GB (TP=2) │ 43.75%
├─────────────────────────────────┤
│ KV Cache: 約35 GB │ 43.75%
│ (gpu_memory_utilization=0.90) │
├─────────────────────────────────┤
│ 活性化メモリ: 約2 GB │ 2.5%
├─────────────────────────────────┤
│ システム予約: 約8 GB │ 10%
└─────────────────────────────────┘
KV Cacheが処理可能な最大同時リクエスト数を決定します。
11.3 メモリ不足(ぶそく)時の対応戦略
| 戦略 | 実装 | 効果 | 副作用 |
|---|---|---|---|
| 量子化 | FP16→INT4 | 重み4倍削減 | 微小な品質低下 |
| KV Cache量子化 | FP16→FP8 | KV Cache 2倍削減 | 無視できるレベル |
| max_model_len縮小 | 32K→8K | 該当比率分KV Cache削減 | 長いコンテキスト不可 |
| TP増加 | TP=2→TP=4 | GPU当たりメモリ半減 | GPU追加コスト |
| Prefix Caching | システムプロンプト共有 | 繰り返しリクエスト時大きな削減 | ユニークなリクエストには効果なし |
12. コスト分析:プラットフォーム別tokens/dollar
12.1 セルフホスティングコスト比較
| GPU | クラウド時間当たりコスト | Llama-3.1 70Bスループット | tokens/dollar |
|---|---|---|---|
| A100 80GB x1 | 約3.0 USD | 800 tok/s (FP16) | 960K |
| A100 80GB x4 (TP=4) | 約12.0 USD | 2,800 tok/s | 840K |
| H100 80GB x1 | 約4.5 USD | 1,500 tok/s (FP8) | 1,200K |
| H100 80GB x4 (TP=4) | 約18.0 USD | 5,000 tok/s (FP8) | 1,000K |
| L40S x1 | 約1.5 USD | 600 tok/s (INT4) | 1,440K |
| 4090 x1(自前サーバー) | 約0.3 USD(電気代) | 400 tok/s (INT4) | 4,800K |
12.2 API vs セルフホスティング損益分岐点
月間トークン使用量別コスト比較(Llama-3.1 70B級):
┌─────────────────────────────────────────────────┐
│ コスト │
│ ($) │
│ 5000│ /API │
│ │ / │
│ 3000│ ────────── Self-Hosted │
│ │ / (H100x4 月額固定費) │
│ 1000│ / API │
│ │ / │
│ 0├──┬────┬────┬────┬────┬────┬────► │
│ 0 2B 5B 10B 20B 50B 100B token/月 │
│ │
│ 損益分岐点: 約10Bトークン/月 │
└─────────────────────────────────────────────────┘
13. ベンチマーキング:正しい測定方法
13.1 コアメトリクス
| メトリクス | 定義 | 重要な理由 |
|---|---|---|
| TTFT (Time To First Token) | 最初のトークン生成までの時間 | ユーザー体感の応答開始時間 |
| ITL (Inter-Token Latency) | トークン間の生成時間 | ストリーミング時の体感速度 |
| E2E Latency | リクエスト全体の完了時間 | 総待機時間 |
| Throughput | 秒当たり生成トークン数 | システム全体の処理能力 |
| TPS/User | ユーザー当たり秒当たりトークン | 個人の体感速度 |
13.2 ベンチマーキングツールと方法
# vLLM内蔵ベンチマーク(推奨)
# python -m vllm.entrypoints.openai.api_server 実行後:
# python benchmarks/benchmark_serving.py \
# --backend vllm \
# --model meta-llama/Llama-3.1-8B-Instruct \
# --dataset-name sharegpt \
# --dataset-path ShareGPT_V3_unfiltered.json \
# --num-prompts 1000 \
# --request-rate 10 \
# --endpoint /v1/completions
# 結果例:
# Successful requests: 1000
# Benchmark duration (s): 105.23
# Total input tokens: 215000
# Total generated tokens: 180000
# Request throughput (req/s): 9.50
# Output token throughput (tok/s): 1710.5
# Mean TTFT (ms): 48.2
# Median TTFT (ms): 42.1
# P99 TTFT (ms): 125.3
# Mean ITL (ms): 11.8
# Median ITL (ms): 10.2
# P99 ITL (ms): 35.7
13.3 負荷(ふか)別の性能特性
スループットとレイテンシの関係(concurrency増加時):
スループット レイテンシ
(tok/s) (ms)
│ ┌────────── │ /
│ / │ /
│ / │ /
│ / │ /
│ / │ /
│ / │ /
│ / │ /
│ / │/
├──────────────► concurrency ├──────────────► concurrency
最適運用ポイント: スループットが飽和する直前(Knee point)
通常GPU活用率70-80%の地点
14. 本番デプロイアーキテクチャ
14.1 プロダクションサービングアーキテクチャ
┌──────────────────────────────────────────────────┐
│ 本番アーキテクチャ │
│ │
│ Client → Load Balancer → API Gateway │
│ │ │
│ ┌─────────┼─────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────┐┌────────┐┌────────┐ │
│ │ vLLM ││ vLLM ││ vLLM │ │
│ │ Pod 1 ││ Pod 2 ││ Pod 3 │ │
│ │ (4xH100)││(4xH100)││(4xH100)│ │
│ └────┬────┘└───┬────┘└───┬────┘ │
│ │ │ │ │
│ ┌────▼─────────▼─────────▼────┐ │
│ │ Prometheus + Grafana │ │
│ │ (メトリクス収集/可視化) │ │
│ └─────────────────────────────┘ │
│ │
│ オートスケーリング: キュー長/GPU活用率基準 │
│ ヘルスチェック: /healthエンドポイント │
│ Graceful Shutdown: 進行中リクエスト完了後に終了 │
└──────────────────────────────────────────────────┘
14.2 Kubernetesデプロイ例
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-llama3-70b
spec:
replicas: 3
selector:
matchLabels:
app: vllm-llama3
template:
metadata:
labels:
app: vllm-llama3
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
command: ["python", "-m", "vllm.entrypoints.openai.api_server"]
args:
- "--model=meta-llama/Llama-3.1-70B-Instruct"
- "--tensor-parallel-size=4"
- "--max-model-len=16384"
- "--gpu-memory-utilization=0.90"
- "--enable-prefix-caching"
- "--enable-chunked-prefill"
resources:
limits:
nvidia.com/gpu: "4"
memory: "64Gi"
requests:
nvidia.com/gpu: "4"
memory: "32Gi"
ports:
- containerPort: 8000
readinessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 120
periodSeconds: 10
livenessProbe:
httpGet:
path: /health
port: 8000
initialDelaySeconds: 180
periodSeconds: 30
nodeSelector:
gpu-type: h100
tolerations:
- key: nvidia.com/gpu
operator: Exists
effect: NoSchedule
15. クイズ
Q1. vLLMのPagedAttentionが解決する核心的な問題は何ですか?
正解:KV Cacheのメモリフラグメンテーション問題を解決します。
従来方式はシーケンスごとに最大長分の連続メモリを事前確保し、60-80%が無駄になっていました。PagedAttentionはOSの仮想メモリのようにKV Cacheを固定サイズブロック(ページ)に分割し、ページテーブルで非連続ブロックを論理的に接続します:
- 内部フラグメンテーションほぼゼロ
- 非連続メモリブロック活用可能
- Copy-on-Writeで共通プレフィックスのKV Cache共有
結果として同じGPUメモリで2-4倍多くの同時リクエストを処理できます。
Q2. Continuous BatchingがStatic Batchingよりスループットが高い理由は?
正解:Static Batchingはバッチ内のすべてのリクエストが完了するまで待機してから次のバッチを開始します。短い応答が先に終わってもGPUはアイドル状態で待機します。
Continuous Batchingは:
- 毎イテレーションで完了したリクエストを即座に除去
- 待機キューの新しいリクエストを即座に投入
- GPUが常に最大負荷で動作
これによりStatic Batching比10-20倍高いスループットを達成します。個々のリクエストのレイテンシも待機時間削減により改善されます。
Q3. Speculative Decodingが出力品質を低下させない理由は?
正解:数学的にターゲットモデルの出力分布を正確に保存するからです。
ドラフトモデルが予測したトークンxに対して:
- 受理確率 = min(1, p_target(x) / p_draft(x))
- 棄却時:(p_target - p_draft)分布から再サンプリング
この過程を通じて最終出力はターゲットモデルのみを使用した場合と数学的に同一の分布を持ちます。速度のみ向上し品質損失はゼロです。
Q4. LLM Decode段階がMemory-Boundである理由は?
正解:Decode段階では一度に1トークンのみ生成します。この時モデル全体の重みをメモリから読み込む必要がありますが(行列-ベクトル積)、実際の演算量は非常に少ないです。
Llama-2 70Bの例:
- モデル重み140GBを毎ステップ読み込む必要
- A100帯域幅2TB/s基準:70ms(メモリ読み込み)
- 実際の演算時間:約1ms
メモリ帯域幅がボトルネックであるためMemory-Boundです。これが量子化(重みサイズ縮小)とバッチング(重み読み込み1回で複数リクエスト処理)が効果的な理由です。
Q5. FP8量子化がINT8よりLLM推論に適している理由は?
正解:FP8は浮動小数点形式であり、広いダイナミックレンジを持ちます。LLMの重みと活性化は非常に多様な大きさを持つため、固定小数点のINT8よりFP8が適しています。
具体的には:
- FP8 E4M3:指数部4ビット、仮数部3ビット → 広い範囲、適度な精度
- INT8:-128から127の固定範囲 → 外れ値に脆弱
- H100 GPUはFP8専用Tensor Coreを搭載し、FP16比2倍の演算性能
- FP8は別途キャリブレーションなしで動的量子化可能
結果としてFP8はINT4に近い性能向上を提供しつつ、FP16に近い品質を維持します。
16. 参考資料(さんこうしりょう)
- vLLM: Easy, Fast, and Cheap LLM Serving with PagedAttention - Kwon et al., 2023
- FlashAttention: Fast and Memory-Efficient Exact Attention - Dao et al., 2022
- FlashAttention-2: Faster Attention with Better Parallelism - Dao, 2023
- Efficient Memory Management for Large Language Model Serving with PagedAttention - Kwon et al., 2023
- Fast Inference from Transformers via Speculative Decoding - Leviathan et al., 2023
- GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers - Frantar et al., 2023
- AWQ: Activation-aware Weight Quantization - Lin et al., 2024
- TensorRT-LLM - NVIDIA公式ドキュメント
- Orca: A Distributed Serving System for Transformer-Based Generative Models - Yu et al., 2022
- GQA: Training Generalized Multi-Query Transformer Models - Ainslie et al., 2023
- Medusa: Simple LLM Inference Acceleration Framework - Cai et al., 2024
- EAGLE: Speculative Sampling Requires Rethinking Feature Uncertainty - Li et al., 2024
- SmoothQuant: Accurate and Efficient Post-Training Quantization - Xiao et al., 2023