1. LLM推論の基礎
推論はLLMが価値を提供する場面です:テキスト生成、質問応答、アプリケーションの駆動など。しかし、推論には訓練とは異なる独自の課題があります。訓練は数時間から数日かけてスループットを最適化しますが、推論はミリ秒から秒単位で応答を返す必要があり、しばしば厳しいコスト制約の下で動作します。
1.1 推論パイプライン
2フェーズ推論
Prefillフェーズ(プロンプト処理):
- すべての入力トークンを並列処理
- 計算バウンド:GPUの並列性を活用
- 後続生成用のKVキャッシュを生成
- 時間はプロンプト長に比例
Decodeフェーズ(トークン生成):
- トークンを1つずつ生成(自己回帰的)
- メモリバウンド:KVキャッシュアクセスが制限要因
- 各ステップでKVキャッシュ全体を読み込む必要
- 時間は出力長に比例
1.2 メモリ要件
def calculate_inference_memory(
params_billions: float,
context_length: int,
batch_size: int,
num_layers: int,
hidden_dim: int,
num_kv_heads: int,
precision: str = "fp16"
) -> dict:
"""
LLM推論のメモリ要件を計算
Args:
params_billions: モデルサイズ(10億パラメータ単位)
context_length: 最大シーケンス長
batch_size: 同時リクエスト数
num_layers: Transformerレイヤー数
hidden_dim: モデル隠れ次元
num_kv_heads: KVアテンションヘッド数
precision: 重みの精度
Returns:
メモリ内訳(GB単位)
"""
bytes_per_param = {
"fp32": 4, "fp16": 2, "bf16": 2,
"int8": 1, "int4": 0.5, "fp8": 1
}
# モデル重み
weight_bytes = params_billions * 1e9 * bytes_per_param[precision]
weight_gb = weight_bytes / (1024**3)
# KVキャッシュ: 2(KとV)× レイヤー × バッチ × シーケンス × ヘッド × ヘッド次元
head_dim = hidden_dim // num_kv_heads
kv_cache_bytes = (
2 * num_layers * batch_size * context_length *
num_kv_heads * head_dim * 2 # KVキャッシュはfp16
)
kv_cache_gb = kv_cache_bytes / (1024**3)
# 活性化メモリ(概算)
activation_gb = batch_size * context_length * hidden_dim * 4 / (1024**3)
return {
"重み(GB)": round(weight_gb, 2),
"KVキャッシュ(GB)": round(kv_cache_gb, 2),
"活性化(GB)": round(activation_gb, 2),
"合計(GB)": round(weight_gb + kv_cache_gb + activation_gb, 2)
}
# 例: 128Kコンテキストを持つLlama-3-70B
memory = calculate_inference_memory(
params_billions=70,
context_length=128000,
batch_size=1,
num_layers=80,
hidden_dim=8192,
num_kv_heads=8, # GQA
precision="int4"
)
print(f"メモリ内訳: {memory}")
1.3 レイテンシ構成要素
| 構成要素 | 説明 | 最適化対象 |
|---|---|---|
| TTFT(最初のトークンまでの時間) | 最初のトークンが出現するまでの遅延 | Prefill最適化、プロンプトキャッシュ |
| TPOT(出力トークンあたりの時間) | 生成トークンあたりの遅延 | Decode最適化、バッチング |
| エンドツーエンドレイテンシ | TTFT + (トークン数 × TPOT) | 投機的デコーディング |
| スループット | 全リクエストでのトークン/秒 | 継続的バッチング |
2. モデル量子化
量子化は、モデルの精度を16ビットまたは32ビット浮動小数点から低ビット幅に削減し、メモリ使用量を大幅に削減し推論を高速化します。最新の量子化技術は、メモリを4-8倍削減しながらほぼ無損失の品質を達成します。
2.1 量子化の基礎
import torch
import torch.nn.functional as F
from typing import Tuple
class Quantizer:
"""量子化の基本理解のためのユーティリティクラス"""
@staticmethod
def absmax_quantize(
tensor: torch.Tensor,
bits: int = 8
) -> Tuple[torch.Tensor, float]:
"""
Absmax(対称)量子化
値を[-2^(bits-1), 2^(bits-1)-1]にマップ
"""
qmax = 2 ** (bits - 1) - 1
scale = tensor.abs().max() / qmax
quantized = torch.round(tensor / scale).to(torch.int8)
return quantized, scale
@staticmethod
def block_quantize(
tensor: torch.Tensor,
block_size: int = 128,
bits: int = 4
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
ブロック単位量子化(GPTQ、AWQで使用)
各ブロックは独自のスケールファクターを持つ
"""
original_shape = tensor.shape
tensor_flat = tensor.view(-1)
# block_sizeの倍数にパディング
pad_len = (block_size - len(tensor_flat) % block_size) % block_size
if pad_len:
tensor_flat = F.pad(tensor_flat, (0, pad_len))
blocks = tensor_flat.view(-1, block_size)
# 各ブロックを独立して量子化
qmax = 2 ** (bits - 1) - 1
scales = blocks.abs().max(dim=1, keepdim=True).values / qmax
scales = scales.clamp(min=1e-8)
quantized = torch.round(blocks / scales).to(torch.int8)
return quantized, scales.squeeze()
# デモンストレーション
tensor = torch.randn(1024, 1024)
int8_quant, int8_scale = Quantizer.absmax_quantize(tensor, bits=8)
int4_quant, int4_scales = Quantizer.block_quantize(tensor, bits=4)
print(f"元のサイズ: {tensor.numel() * 4 / 1024:.1f} KB")
print(f"INT8サイズ: {int8_quant.numel() * 1 / 1024:.1f} KB(4倍削減)")
print(f"INT4サイズ: {int4_quant.numel() * 0.5 / 1024:.1f} KB(8倍削減)")
2.2 量子化手法の比較
量子化手法比較(Llama-3-70B)
| 手法 | ビット | メモリ(GB) | パープレキシティ | FP16比速度 |
|---|---|---|---|---|
| FP16(ベースライン) | 16 | 140 | 5.42 | 1.0x |
| FP8 | 8 | 70 | 5.44 | 1.8x |
| GPTQ | 4 | 35 | 5.58 | 2.2x |
| AWQ | 4 | 35 | 5.52 | 2.4x |
3. 高性能推論エンジン
最新の推論エンジンは、バッチング、メモリ管理、カーネル最適化、分散実行を含むサービングスタック全体を最適化します。2025-2026年の主要なソリューションはvLLMとTensorRT-LLMです。
3.1 vLLM: PagedAttention
vLLMはPagedAttentionを導入し、KVキャッシュを仮想メモリのページのように管理します。これによりメモリ断片化が解消され、効率的な継続的バッチングが可能になり、2-24倍のスループット向上を達成します。
from vllm import LLM, SamplingParams
from typing import List
def setup_vllm_server(
model: str,
tensor_parallel_size: int = 1,
gpu_memory_utilization: float = 0.9,
max_model_len: int = 32768,
quantization: str = None
) -> LLM:
"""
高スループット推論用にvLLMを設定
Args:
model: モデル名またはパス
tensor_parallel_size: テンソル並列のGPU数
gpu_memory_utilization: 使用するGPUメモリの割合
max_model_len: 最大シーケンス長
quantization: "awq", "gptq", "fp8", またはNone
"""
llm = LLM(
model=model,
tensor_parallel_size=tensor_parallel_size,
gpu_memory_utilization=gpu_memory_utilization,
max_model_len=max_model_len,
quantization=quantization,
# PagedAttention設定
block_size=16,
swap_space=4, # CPUスワップ領域(GB)
# 最適化フラグ
enforce_eager=False, # CUDAグラフを使用
enable_prefix_caching=True, # 共通プレフィックスをキャッシュ
)
return llm
def batch_inference(
llm: LLM,
prompts: List[str],
max_tokens: int = 512,
temperature: float = 0.7
) -> List[str]:
"""
vLLMでバッチ推論を実行
vLLMは自動的に以下を処理:
- 継続的バッチング(動的なバッチ構成)
- PagedAttention(効率的なKVキャッシュ管理)
- プレフィックスキャッシュ(共通プレフィックスの再利用)
"""
sampling_params = SamplingParams(
max_tokens=max_tokens,
temperature=temperature,
top_p=0.95,
stop=["", "[/INST]"],
)
outputs = llm.generate(prompts, sampling_params)
return [output.outputs[0].text for output in outputs]
# 使用例
llm = setup_vllm_server(
model="meta-llama/Llama-3.1-70B-Instruct",
tensor_parallel_size=4,
quantization="awq"
)
prompts = [
"量子コンピューティングを簡単に説明してください。",
"2つのソート済みリストをマージするPython関数を書いてください。",
]
results = batch_inference(llm, prompts)
3.2 推論エンジン比較
| 機能 | vLLM | TensorRT-LLM | llama.cpp |
|---|---|---|---|
| 最適用途 | 汎用サービング | 最大スループット | ローカル/エッジ展開 |
| ハードウェア | NVIDIA, AMD, Intel | NVIDIAのみ | CPU, GPU, Apple Silicon |
| セットアップ難易度 | 低 | 高 | 非常に低 |
| 相対スループット | 1.0x | 1.2-1.5x | 0.3-0.5x |
4. 長コンテキスト処理
コンテキストウィンドウは2023年の4Kトークンから2026年には100万トークン以上に劇的に拡大しました。これらの長いコンテキストを効率的に管理することは、最新の推論システムにおける重要な課題です。
4.1 コンテキスト長の進化
| モデル | コンテキスト長 | リリース時期 |
|---|---|---|
| GPT-4(初期) | 8K / 32K | 2023年3月 |
| Claude 2 | 100K | 2023年7月 |
| Gemini 1.5 Pro | 1M → 2M | 2024年2月 |
| Llama 4 Scout | 10M | 2025年4月 |
4.2 KVキャッシュ最適化技術
import torch
from typing import Tuple
class StreamingLLM:
"""
StreamingLLM: アテンションシンクによる効率的な無限コンテキスト
重要な洞察: 最初の数トークン(「アテンションシンク」)が
グローバル情報を捕捉する。これらとスライディングウィンドウを
保持することで無限ストリーミングが可能。
"""
def __init__(
self,
num_sink_tokens: int = 4,
window_size: int = 4096
):
self.num_sink_tokens = num_sink_tokens
self.window_size = window_size
def evict_kv_cache(
self,
k_cache: torch.Tensor,
v_cache: torch.Tensor,
current_len: int
) -> Tuple[torch.Tensor, torch.Tensor]:
"""
シンクと最近のウィンドウを保持しながら中間トークンを削除
キャッシュレイアウト: [シンクトークン | 削除対象 | ウィンドウトークン]
"""
max_len = self.num_sink_tokens + self.window_size
if current_len <= max_len:
return k_cache, v_cache
# シンクトークンと最近のウィンドウを保持
sink_k = k_cache[:, :, :self.num_sink_tokens, :]
sink_v = v_cache[:, :, :self.num_sink_tokens, :]
window_k = k_cache[:, :, -self.window_size:, :]
window_v = v_cache[:, :, -self.window_size:, :]
new_k = torch.cat([sink_k, window_k], dim=2)
new_v = torch.cat([sink_v, window_v], dim=2)
return new_k, new_v
class GroupedQueryAttention:
"""
GQA: 複数のQueryヘッドで1つのKVヘッドを共有
KVキャッシュサイズを大幅に削減
"""
@staticmethod
def calculate_kv_cache_size(
batch_size: int,
seq_len: int,
num_layers: int,
hidden_dim: int,
num_q_heads: int,
num_kv_heads: int,
dtype_bytes: int = 2
) -> dict:
"""異なるAttention変種のKVキャッシュサイズを比較"""
head_dim = hidden_dim // num_q_heads
# MHA: num_kv_heads == num_q_heads
mha_size = 2 * batch_size * seq_len * num_layers * num_q_heads * head_dim * dtype_bytes
# GQA: num_kv_heads < num_q_heads
gqa_size = 2 * batch_size * seq_len * num_layers * num_kv_heads * head_dim * dtype_bytes
return {
"MHA": f"{mha_size / 1e9:.2f} GB",
"GQA": f"{gqa_size / 1e9:.2f} GB",
"GQA削減率": f"{mha_size / gqa_size:.1f}x"
}
# 例: Llama-3-70BのKVキャッシュ比較
kv_comparison = GroupedQueryAttention.calculate_kv_cache_size(
batch_size=1,
seq_len=128000,
num_layers=80,
hidden_dim=8192,
num_q_heads=64,
num_kv_heads=8 # 8 KVヘッドのGQA
)
print("KVキャッシュ比較:")
for k, v in kv_comparison.items():
print(f" {k}: {v}")
5. スループット最適化
5.1 継続的バッチング
従来の静的バッチングは、バッチ内のすべてのリクエストが完了するまで新しいリクエストを受け付けません。継続的(またはイン・フライト)バッチングは、リクエストの到着と完了に応じて動的に追加・削除し、GPU使用率を最大化します。
5.2 投機的デコーディング
投機的デコーディングは、小さな「ドラフト」モデルを使用して複数のトークンを提案し、大きな「ターゲット」モデルが並列で検証します。これにより自己回帰生成の2-3倍の高速化が可能です。
5.3 本番デプロイメントチェックリスト
推論最適化チェックリスト
メモリ最適化:
- 量子化を使用(4ビットはAWQ/GPTQ、最小品質損失はFP8)
- KVキャッシュ削減のためGQA/MQAを有効化
- 用途に応じた適切なmax_model_lenを設定
- メモリオーバーフロー用にスワップ領域を設定
スループット最適化:
- 継続的バッチングを有効化(vLLMまたはTensorRT-LLM)
- 大規模モデルにはテンソル並列を使用
- カーネル起動オーバーヘッド削減のためCUDAグラフを有効化
- レイテンシ重視アプリには投機的デコーディングを検討
レイテンシ最適化:
- 繰り返しプロンプト用にプレフィックスキャッシュを有効化
- リアルタイムアプリにはストリーミングを使用
- 投機的デコーディングと小規模モデルを検討
- ワークロードに応じてTTFT vs TPOTをプロファイル
まとめ
第4章の重要ポイント
- 推論フェーズ: Prefill(計算バウンド)とDecode(メモリバウンド)は異なる最適化が必要
- 量子化: 4ビットはAWQとGPTQ、ほぼ無損失8ビットはFP8;2-8倍のメモリ削減
- 推論エンジン: 汎用サービングはvLLM、NVIDIA最大性能はTensorRT-LLM
- 長コンテキスト: スライディングウィンドウ、StreamingLLM、GQAで100万トークン以上を効率的に処理
- スループット: 継続的バッチングと投機的デコーディングでGPU使用率を最大化