Python LLM推論高速化実践:100msから10msレイテンシへの6つのプロダクションパターン
Python LLM推論高速化実践:100msから10msレイテンシへの6つのプロダクションパターン
LLMのAPIレスポンスに3秒かかっている?ユーザーはページを閉じてしまう?GPU費用が毎月数万円?これは個別の問題ではありません。2026年、ほとんどのチームがLLMデプロイ後に直面する最初のボトルネックは推論レイテンシとスループットです。モデル学習は全体の20%に過ぎず、推論最適化こそが本番運用の真の課題です。
本記事では、Python最新推論フレームワーク(vLLM 0.6+、TensorRT-LLM、llama.cpp)を用いた6つのプロダクション対応推論高速化パターンを紹介します。PagedAttentionから量子化デプロイまで、各パターンに完全な実行可能なPythonコードを付属します。
主要な学び
- vLLM PagedAttentionの原理とプロダクション対応デプロイメント
- TensorRT-LLMグラフ最適化とKernel Fusionの完全なワークフロー
- GPTQ/AWQ/GGUF 3つの量子化手法の選定とデプロイ
- KV Cache動的管理とPrefix Caching戦略の構築
- Continuous BatchingによるGPU利用率3-5倍向上
- 5つの最も一般的な推論デプロイの落とし穴を回避
目次
- LLM推論高速化アーキテクチャ概要
- パターン1:vLLM + PagedAttention効率推論
- パターン2:TensorRT-LLMグラフ最適化とKernel Fusion
- パターン3:量子化デプロイ(GPTQ/AWQ/GGUF)
- パターン4:KV Cache動的管理とPrefix Caching
- パターン5:Continuous Batchingと動的スケジューリング
- パターン6:本番環境デプロイとモニタリング
- 5つのよくある落とし穴と解決策
- 10のよくあるエラートラブルシューティング
- 高度な最適化テクニック
- 比較分析:4つの推論フレームワーク
- オンラインツールおすすめ
- まとめ
LLM推論高速化アーキテクチャ概要
LLM推論高速化は単一の技術ではなく、アルゴリズムからハードウェアまでの完全な最適化体系です:
┌─────────────────────────────────────────────────────────────────────┐
│ LLM 推論高速化アーキテクチャ (2026) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ アルゴリズム│ │ │ │ │ │ │ │
│ │ レイヤー │ │ │ │ │ │ │ │
│ │ 量子化 │───▶│ KV Cache │───▶│ バッチ │───▶│ 推論 │ │
│ │ GPTQ │ │ PagedAtt │ │ スケジュ │ │ エンジン │ │
│ │ AWQ │ │ Prefix │ │ Continuous│ │ vLLM │ │
│ │ GGUF │ │ SlidingW │ │ DynaBatch│ │ TRT-LLM │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ システム │ │ │ │ │ │ │ │
│ │ レイヤー │ │ │ │ │ │ │ │
│ │ モデル │───▶│ 負荷分散 │───▶│ GPU │───▶│ オブザー │ │
│ │ サービング│ │ Nginx LB │ │ スケジュ │ │ バビリティ│ │
│ │ FastAPI │ │ Router │ │ MIG │ │ Prometheus│ │
│ │ Triton │ │ │ │ MultiGPU │ │ Grafana │ │
│ │ vLLM Srv │ │ │ │ TensorPar│ │ OTel │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────────────┘
推論レイテンシの主要ボトルネック
| ボトルネック段階 | 割合 | 説明 | 最適化方向 |
|---|---|---|---|
| Prefill | 30-40% | 入力プロンプト処理、KV Cache計算 | Flash Attention、Tensor並列 |
| Decode | 50-60% | トークンごと生成、メモリ帯域制約 | 量子化、KV Cache最適化 |
| スケジュールオーバーヘッド | 5-10% | リクエストキューイング、バッチ再編成 | Continuous Batching |
| ネットワーク転送 | 5-15% | APIリクエスト/レスポンスシリアライズ | ストリーミング出力、圧縮 |
推論高速化技術ロードマップ
| 技術 | レイテンシ改善 | スループット改善 | メモリ節約 | 実装難易度 |
|---|---|---|---|---|
| vLLM PagedAttention | 1.2x | 2-4x | 40-55% | 低 |
| TensorRT-LLM | 2-3x | 3-5x | 20-30% | 高 |
| INT4量子化 | 1.5-2x | 1.5-2x | 60-75% | 中 |
| KV Cache最適化 | 1.3-1.5x | 1.5-2x | 30-50% | 中 |
| Continuous Batching | 1.1x | 3-5x | 10-20% | 中 |
| Speculative Decoding | 2-3x | 0.8-1.2x | -10% | 高 |
💡 JSONフォーマッターツールを使って、推論APIのリクエスト/レスポンスJSON構造を素早く確認できます。
パターン1:vLLM + PagedAttention効率推論
vLLMのPagedAttentionは、2024-2026年のLLM推論分野における最も重要なイノベーションの一つです。従来の推論エンジンは各リクエストに固定サイズのKV Cacheを事前割り当てし、深刻なメモリ断片化と無駄を引き起こしていました。PagedAttentionはOS仮想メモリのページング機構を参考に、KV Cacheを固定サイズのBlockに分割し、オンデマンドで割り当て、メモリ利用率を40%から90%以上に向上させます。
1.1 PagedAttentionの原理
従来のKV Cache(事前割り当て):
リクエスト1: [████████████░░░░░░░░] 2KB割り当て、1KB使用、50%無駄
リクエスト2: [████░░░░░░░░░░░░░░░░] 2KB割り当て、0.5KB使用、75%無駄
リクエスト3: [████████████████░░░░] 2KB割り当て、1.5KB使用、25%無駄
断片: ████████ 利用不可能な断片空間
PagedAttention(ページ管理):
Block Pool: [B0][B1][B2][B3][B4][B5][B6][B7][B8][B9]
リクエスト1: B0→B1→B3 (3 Blockをオンデマンド割り当て)
リクエスト2: B2→B5 (2 Blockをオンデマンド割り当て)
リクエスト3: B4→B6→B7→B8 (4 Blockをオンデマンド割り当て)
残り: B9 (新リクエストに利用可能)
→ ゼロ断片、メモリ利用率90%+
1.2 vLLMクイックデプロイ
# vLLMインストール(CUDA 12.4+)
pip install vllm==0.6.6
# インストール確認
python -c "import vllm; print(vllm.__version__)"
1.3 基本推論サービス
from vllm import LLM, SamplingParams
def basic_inference():
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
tensor_parallel_size=1,
gpu_memory_utilization=0.90,
max_model_len=8192,
enforce_eager=True,
)
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=1024,
repetition_penalty=1.05,
)
prompts = [
"PythonでO(1)のgetとput操作をサポートする効率的なLRUキャッシュを実装してください",
"TransformerのMulti-Head Attentionの計算フローを説明してください",
"RAGとFine-tuningの知識更新シナリオでの長所と短所を比較してください",
]
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
prompt = output.prompt
generated_text = output.outputs[0].text
print(f"Prompt: {prompt[:50]}...")
print(f"Generated: {generated_text[:200]}...")
print(f"Tokens: {len(output.outputs[0].token_ids)}")
print("---")
if __name__ == "__main__":
basic_inference()
1.4 OpenAI互換APIサーバー
# start_vllm_server.py
import subprocess
import os
def start_vllm_server():
cmd = [
"python", "-m", "vllm.entrypoints.openai.api_server",
"--model", "Qwen/Qwen2.5-7B-Instruct",
"--host", "0.0.0.0",
"--port", "8000",
"--tensor-parallel-size", "1",
"--gpu-memory-utilization", "0.90",
"--max-model-len", "8192",
"--enable-prefix-caching",
"--enable-chunked-prefill",
"--max-num-seqs", "256",
"--max-num-batched-tokens", "32768",
]
env = os.environ.copy()
env["CUDA_VISIBLE_DEVICES"] = "0"
subprocess.run(cmd, env=env)
if __name__ == "__main__":
start_vllm_server()
1.5 クライアント呼び出し
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="not-needed",
)
response = client.chat.completions.create(
model="Qwen/Qwen2.5-7B-Instruct",
messages=[
{"role": "system", "content": "あなたはPythonの専門家です。簡潔かつ正確に回答してください。"},
{"role": "user", "content": "Pythonで非同期イテレータを実装する方法は?"},
],
temperature=0.7,
max_tokens=1024,
stream=True,
)
for chunk in response:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
print()
パターン2:TensorRT-LLMグラフ最適化とKernel Fusion
TensorRT-LLMはNVIDIAの高性能推論エンジンで、計算グラフ最適化とKernel Fusionにより複数のGPU操作を単一カーネルに統合し、メモリアクセス回数とカーネル起動オーバーヘッドを大幅に削減します。
2.1 TensorRT-LLM最適化の原理
標準PyTorch推論(マルチカーネル):
MatMul → GPU→CPU → LayerNorm → CPU→GPU → MatMul → GPU→CPU → Softmax
↑ 各カーネル起動約5-10μs、頻繁な切り替えでGPUアイドル発生
TensorRT-LLM(Kernel Fusion):
[MatMul + LayerNorm + MatMul + Softmax] → 単一Fused Kernel
↑ 1回の起動で全計算完了、メモリアクセス90%削減
2.2 モデル変換とビルド
import tensorrt_llm
from tensorrt_llm import LLM, BuildConfig
def build_trt_engine():
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
tensor_parallel_size=1,
build_config=BuildConfig(
max_input_len=2048,
max_output_len=1024,
max_batch_size=32,
gpu_memory_utilization=0.90,
),
)
sampling_params = {
"temperature": 0.7,
"top_p": 0.9,
"max_tokens": 1024,
}
output = llm.generate(
prompts=["GPU Kernel Fusionの原理を説明してください"],
sampling_params=sampling_params,
)
print(output)
if __name__ == "__main__":
build_trt_engine()
2.3 FP8量子化高速化
from tensorrt_llm import LLM, BuildConfig
from tensorrt_llm.quantization import QuantConfig
def build_fp8_engine():
quant_config = QuantConfig(
quant_algo="FP8",
calib_size=512,
)
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
tensor_parallel_size=1,
quant_config=quant_config,
build_config=BuildConfig(
max_input_len=2048,
max_output_len=1024,
max_batch_size=32,
),
)
output = llm.generate(
prompts=["FP8量子化がモデル精度に与える影響はどの程度ですか?"],
sampling_params={"temperature": 0.7, "max_tokens": 512},
)
print(output)
if __name__ == "__main__":
build_fp8_engine()
パターン3:量子化デプロイ(GPTQ/AWQ/GGUF)
量子化は推論コスト削減の最も直接的な手段です。モデル重みをFP16(16bit)からINT4(4bit)に圧縮すると、メモリ要件が75%削減され、推論速度が1.5-2倍向上します。
3.1 3つの量子化手法の比較
| 手法 | 精度損失 | 推論速度 | メモリ節約 | 適用シナリオ |
|---|---|---|---|---|
| GPTQ | 小 | 速い | 75% | GPUデプロイ、精度重視 |
| AWQ | 極小 | 最速 | 75% | GPUデプロイ、速度重視 |
| GGUF | 調整可能 | 中程度 | 50-75% | CPU/ハイブリッドデプロイ、柔軟 |
3.2 GPTQ量子化デプロイ
from vllm import LLM, SamplingParams
def gptq_inference():
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct-GPTQ-Int4",
tensor_parallel_size=1,
gpu_memory_utilization=0.85,
quantization="gptq",
)
sampling_params = SamplingParams(
temperature=0.7,
max_tokens=512,
)
outputs = llm.generate(
["GPTQ量子化の原理は何ですか?"],
sampling_params,
)
for output in outputs:
print(output.outputs[0].text)
if __name__ == "__main__":
gptq_inference()
3.3 AWQ量子化デプロイ
from vllm import LLM, SamplingParams
def awq_inference():
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct-AWQ",
tensor_parallel_size=1,
gpu_memory_utilization=0.85,
quantization="awq",
)
sampling_params = SamplingParams(
temperature=0.7,
max_tokens=512,
)
outputs = llm.generate(
["AWQ量子化はGPTQに比べてどんな利点がありますか?"],
sampling_params,
)
for output in outputs:
print(output.outputs[0].text)
if __name__ == "__main__":
awq_inference()
3.4 GGUF + llama.cppデプロイ(CPU/ハイブリッド推論)
import subprocess
import requests
import json
def start_llamacpp_server():
cmd = [
"./llama-server",
"-m", "qwen2.5-7b-instruct-q4_k_m.gguf",
"--host", "0.0.0.0",
"--port", "8080",
"-ngl", "32",
"-c", "8192",
"--parallel", "4",
"-tb", "512",
]
subprocess.Popen(cmd)
def query_llamacpp():
response = requests.post(
"http://localhost:8080/v1/chat/completions",
json={
"model": "qwen2.5-7b-instruct-q4_k_m.gguf",
"messages": [
{"role": "user", "content": "GGUF形式はどのようなデプロイシナリオに適していますか?"}
],
"temperature": 0.7,
"max_tokens": 512,
"stream": True,
},
stream=True,
)
for line in response.iter_lines():
if line:
data = json.loads(line.decode("utf-8").removeprefix("data: "))
if "choices" in data and data["choices"][0].get("delta", {}).get("content"):
print(data["choices"][0]["delta"]["content"], end="", flush=True)
print()
if __name__ == "__main__":
start_llamacpp_server()
import time
time.sleep(10)
query_llamacpp()
パターン4:KV Cache動的管理とPrefix Caching
KV CacheはLLM推論の最大メモリ消費者です。7Bモデルが8Kコンテキストを処理する場合、KV Cacheだけで4-6GBを消費します。適切なKV Cache管理がスループット向上の鍵です。
4.1 KV Cacheメモリ計算
def calculate_kv_cache_memory(
num_layers: int,
num_heads: int,
head_dim: int,
seq_len: int,
batch_size: int,
dtype_bytes: int = 2,
) -> int:
kv_cache_per_token = num_layers * 2 * num_heads * head_dim * dtype_bytes
total_memory = kv_cache_per_token * seq_len * batch_size
return total_memory
qwen25_7b_kv = calculate_kv_cache_memory(
num_layers=28,
num_heads=28,
head_dim=128,
seq_len=8192,
batch_size=32,
dtype_bytes=2,
)
print(f"Qwen2.5-7B KV Cache (8K×32batch): {qwen25_7b_kv / 1024**3:.2f} GB")
qwen25_72b_kv = calculate_kv_cache_memory(
num_layers=80,
num_heads=64,
head_dim=128,
seq_len=8192,
batch_size=32,
dtype_bytes=2,
)
print(f"Qwen2.5-72B KV Cache (8K×32batch): {qwen25_72b_kv / 1024**3:.2f} GB")
4.2 Prefix Caching(システムプロンプトキャッシュ)
from vllm import LLM, SamplingParams
from vllm.prefix import Prefix
def prefix_caching_example():
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
enable_prefix_caching=True,
gpu_memory_utilization=0.90,
)
system_prompt = """あなたはプロのPythonコードレビュー専門家です。あなたの責任は:
1. コード内の潜在的なバグとセキュリティ脆弱性をチェックする
2. コードの可読性と保守性を評価する
3. 具体的な最適化提案を行う
4. 修正後のコード例を提供する
上記4つの観点から厳格にレビューしてください。"""
prefix = Prefix(llm, system_prompt)
sampling_params = SamplingParams(temperature=0.7, max_tokens=1024)
prompts = [
prefix + "\n\n以下のコードをレビューしてください:\n```python\ndef add(a, b): return a + b\n```",
prefix + "\n\n以下のコードをレビューしてください:\n```python\ndef divide(a, b): return a / b\n```",
prefix + "\n\n以下のコードをレビューしてください:\n```python\ndef factorial(n): return 1 if n <= 1 else n * factorial(n-1)\n```",
]
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
print(output.outputs[0].text[:200])
print("---")
if __name__ == "__main__":
prefix_caching_example()
4.3 Sliding Window Attention
from vllm import LLM, SamplingParams
def sliding_window_inference():
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
gpu_memory_utilization=0.90,
max_model_len=32768,
sliding_window=4096,
)
sampling_params = SamplingParams(
temperature=0.7,
max_tokens=512,
)
long_prompt = "これは非常に長いテキストです..." * 2000
outputs = llm.generate(
[f"以下のテキストの要点をまとめてください:\n{long_prompt}"],
sampling_params,
)
for output in outputs:
print(output.outputs[0].text[:200])
if __name__ == "__main__":
sliding_window_inference()
パターン5:Continuous Batchingと動的スケジューリング
従来のStatic Batchingは最も遅いリクエストの完了を待ってから次のバッチを処理するため、GPU利用率は通常30-50%に留まります。Continuous Batchingは新リクエストの随時参加と完了リクエストの随時退出を可能にし、GPU利用率を90%以上に向上させます。
5.1 Static vs Continuous Batching
Static Batching(最も遅いものを待つ):
時間→ t1 t2 t3 t4 t5 t6
リクエスト1: [████████████████] ← 16トークン生成
リクエスト2: [████] ← 4トークン生成、その後待機!
リクエスト3: [████████] ← 8トークン生成、その後待機!
GPU利用率: ~50%
Continuous Batching(動的スケジューリング):
時間→ t1 t2 t3 t4 t5 t6
リクエスト1: [████████████████]
リクエスト2: [████]──リクエスト4: [████████]
リクエスト3: [████████]──リクエスト5: [████]
GPU利用率: ~90%
5.2 vLLM Continuous Batching設定
from vllm import LLM, SamplingParams
import asyncio
import time
from typing import List
async def continuous_batching_benchmark():
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
tensor_parallel_size=1,
gpu_memory_utilization=0.90,
max_num_seqs=256,
max_num_batched_tokens=32768,
enable_chunked_prefill=True,
)
prompts_short = [
f"{i}を一文で説明してください。"
for i in ["再帰", "クロージャ", "コルーチン", "デコレータ", "ジェネレータ"]
]
prompts_long = [
f"{i}の原理、応用例、コード例を500字以上で説明してください。"
for i in ["Transformerアーキテクチャ", "分散合意", "コンパイラ最適化"]
]
all_prompts = prompts_short + prompts_long
sampling_params = SamplingParams(
temperature=0.7,
max_tokens=512,
)
start_time = time.time()
outputs = llm.generate(all_prompts, sampling_params)
elapsed = time.time() - start_time
total_tokens = sum(len(o.outputs[0].token_ids) for o in outputs)
throughput = total_tokens / elapsed
print(f"Total prompts: {len(all_prompts)}")
print(f"Total tokens: {total_tokens}")
print(f"Elapsed: {elapsed:.2f}s")
print(f"Throughput: {throughput:.1f} tokens/s")
if __name__ == "__main__":
asyncio.run(continuous_batching_benchmark())
5.3 動的バッチスケジューラ
import asyncio
import time
from dataclasses import dataclass, field
from typing import List, Optional
from collections import deque
@dataclass
class InferenceRequest:
request_id: str
prompt: str
max_tokens: int = 512
temperature: float = 0.7
arrival_time: float = field(default_factory=time.time)
completed: bool = False
class DynamicBatchScheduler:
def __init__(
self,
max_batch_size: int = 32,
max_waiting_time: float = 0.1,
max_batch_tokens: int = 32768,
):
self.max_batch_size = max_batch_size
self.max_waiting_time = max_waiting_time
self.max_batch_tokens = max_batch_tokens
self.pending_queue: deque[InferenceRequest] = deque()
self.running_batch: List[InferenceRequest] = []
def add_request(self, request: InferenceRequest):
self.pending_queue.append(request)
def get_next_batch(self) -> List[InferenceRequest]:
if not self.pending_queue:
return []
batch = []
current_tokens = 0
oldest_arrival = self.pending_queue[0].arrival_time
while self.pending_queue and len(batch) < self.max_batch_size:
wait_time = time.time() - oldest_arrival
if wait_time >= self.max_waiting_time and batch:
break
request = self.pending_queue[0]
estimated_tokens = len(request.prompt.split()) + request.max_tokens
if current_tokens + estimated_tokens > self.max_batch_tokens:
if batch:
break
estimated_tokens = self.max_batch_tokens
self.pending_queue.popleft()
batch.append(request)
current_tokens += estimated_tokens
return batch
@property
def queue_size(self) -> int:
return len(self.pending_queue)
scheduler = DynamicBatchScheduler(max_batch_size=32, max_waiting_time=0.1)
for i in range(50):
scheduler.add_request(InferenceRequest(
request_id=f"req-{i}",
prompt=f"概念{i}を説明してください",
))
batch = scheduler.get_next_batch()
print(f"Batch size: {len(batch)}")
print(f"Remaining in queue: {scheduler.queue_size}")
パターン6:本番環境デプロイとモニタリング
6.1 Dockerデプロイ for vLLM
FROM vllm/vllm-openai:v0.6.6
ENV MODEL_NAME=Qwen/Qwen2.5-7B-Instruct
ENV TENSOR_PARALLEL_SIZE=1
ENV GPU_MEMORY_UTILIZATION=0.90
ENV MAX_MODEL_LEN=8192
EXPOSE 8000
ENTRYPOINT ["python", "-m", "vllm.entrypoints.openai.api_server"]
CMD ["--model", "${MODEL_NAME}", \
"--host", "0.0.0.0", \
"--port", "8000", \
"--tensor-parallel-size", "${TENSOR_PARALLEL_SIZE}", \
"--gpu-memory-utilization", "${GPU_MEMORY_UTILIZATION}", \
"--max-model-len", "${MAX_MODEL_LEN}", \
"--enable-prefix-caching", \
"--enable-chunked-prefill"]
# docker-compose.yml
version: "3.8"
services:
vllm-server:
build: .
ports:
- "8000:8000"
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
environment:
- MODEL_NAME=Qwen/Qwen2.5-7B-Instruct
- TENSOR_PARALLEL_SIZE=1
- GPU_MEMORY_UTILIZATION=0.90
volumes:
- ~/.cache/huggingface:/root/.cache/huggingface
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 30s
timeout: 10s
retries: 3
prometheus:
image: prom/prometheus:v2.52.0
ports:
- "9090:9090"
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
grafana:
image: grafana/grafana:11.0.0
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
6.2 Prometheus モニタリング設定
# prometheus.yml
global:
scrape_interval: 15s
scrape_configs:
- job_name: "vllm"
static_configs:
- targets: ["vllm-server:8000"]
metrics_path: /metrics
scheme: http
6.3 推論パフォーマンスモニタリングメトリクス
import requests
import time
from dataclasses import dataclass
from typing import List
@dataclass
class InferenceMetrics:
prompt_tokens: int
completion_tokens: int
total_tokens: int
latency_ms: float
tokens_per_second: float
time_to_first_token_ms: float
def benchmark_inference(
base_url: str = "http://localhost:8000",
prompt: str = "Transformerアーキテクチャの原理を詳しく説明してください",
max_tokens: int = 512,
num_requests: int = 10,
) -> List[InferenceMetrics]:
metrics_list = []
for i in range(num_requests):
start_time = time.time()
ttft = None
response = requests.post(
f"{base_url}/v1/chat/completions",
json={
"model": "Qwen/Qwen2.5-7B-Instruct",
"messages": [{"role": "user", "content": prompt}],
"max_tokens": max_tokens,
"temperature": 0.7,
"stream": True,
},
stream=True,
)
full_content = ""
for line in response.iter_lines():
if not line:
continue
data = line.decode("utf-8")
if data.startswith("data: "):
data = data[6:]
if data == "[DONE]":
break
import json
chunk = json.loads(data)
if chunk.get("choices") and chunk["choices"][0].get("delta", {}).get("content"):
if ttft is None:
ttft = (time.time() - start_time) * 1000
full_content += chunk["choices"][0]["delta"]["content"]
elapsed_ms = (time.time() - start_time) * 1000
completion_tokens = len(full_content)
prompt_tokens = len(prompt.split()) * 2
metrics = InferenceMetrics(
prompt_tokens=prompt_tokens,
completion_tokens=completion_tokens,
total_tokens=prompt_tokens + completion_tokens,
latency_ms=elapsed_ms,
tokens_per_second=completion_tokens / (elapsed_ms / 1000) if elapsed_ms > 0 else 0,
time_to_first_token_ms=ttft or 0,
)
metrics_list.append(metrics)
avg_tps = sum(m.tokens_per_second for m in metrics_list) / len(metrics_list)
avg_ttft = sum(m.time_to_first_token_ms for m in metrics_list) / len(metrics_list)
avg_latency = sum(m.latency_ms for m in metrics_list) / len(metrics_list)
print(f"Avg Throughput: {avg_tps:.1f} tokens/s")
print(f"Avg TTFT: {avg_ttft:.0f} ms")
print(f"Avg Latency: {avg_latency:.0f} ms")
return metrics_list
if __name__ == "__main__":
benchmark_inference()
5つのよくある落とし穴と解決策
落とし穴1:vLLM起動時OOM(メモリ不足)
現象:torch.cuda.OutOfMemoryError: CUDA out of memory
原因:gpu_memory_utilizationが高すぎる、またはmax_model_lenが大きすぎる
解決策:
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
gpu_memory_utilization=0.85,
max_model_len=4096,
enforce_eager=True,
)
落とし穴2:量子化後の精度著しく低下
現象:INT4量子化後の出力が文字化けや論理的混乱
原因:GPTQキャリブレーションデータセットが実際の使用シーンと不一致
解決策:GPTQの代わりにAWQを使用、または一致するデータセットで再キャリブレーション
落とし穴3:KV Cacheメモリリーク
現象:長時間実行後のメモリ使用量継続増加
原因:リクエスト異常中断時のKV Cache未適切解放
解決策:
# vLLMサーバー設定
cmd = [
"--block-size", "16",
"--swap-space", "4",
"--disable-log-requests",
]
落とし穴4:TensorRT-LLMエンジンビルドに時間がかかりすぎる
現象:初回エンジンビルドに30分以上
原因:ビルド済みエンジンがディスクに保存されていない、毎回再コンパイル
解決策:エンジンをディスクに保存し、後続実行で直接ロード
落とし穴5:ストリーミング出力の高レイテンシ
現象:SSEストリーミング出力のチャンク間隔が500msを超える
原因:Chunked Prefillが無効、PrefillがDecodeをブロック
解決策:
--enable-chunked-prefill \
--max-num-batched-tokens 32768
10のよくあるエラートラブルシューティング
| エラーメッセージ | 原因 | 解決方法 |
|---|---|---|
CUDA out of memory |
GPU メモリ不足 | gpu_memory_utilizationまたはmax_model_lenを下げる |
RuntimeError: Expected all tensors on the same device |
モデルとデータが異なるGPUに | CUDA_VISIBLE_DEVICES設定を確認 |
ValueError: Token id out of range |
トークナイザとモデルの不一致 | 同じモデルのトークナイザを使用 |
ConnectionRefusedError: [Errno 111] |
vLLMサービス未起動 | サービスプロセスとポートを確認 |
KeyError: 'model' |
リクエスト形式エラー | OpenAI APIリクエスト形式を確認 |
AssertionError: block_size must be power of 2 |
block_sizeパラメータエラー | 8/16/32に設定 |
RuntimeError: CUDA driver version is insufficient |
CUDAドライバが古い | CUDA 12.4+にアップグレード |
OSError: Model path not found |
モデルパスエラー | HuggingFaceキャッシュまたはローカルパスを確認 |
TypeError: __init__() got an unexpected keyword argument |
vLLMバージョン不一致 | vLLMバージョンとパラメータ互換性を確認 |
json.decoder.JSONDecodeError |
ストリーミングレスポンス解析エラー | SSEデータ形式処理ロジックを確認 |
高度な最適化テクニック
テクニック1:Speculative Decoding(投機的デコーディング)
from vllm import LLM, SamplingParams
def speculative_decoding_example():
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
speculative_model="Qwen/Qwen2.5-0.5B-Instruct",
num_speculative_tokens=5,
gpu_memory_utilization=0.90,
)
sampling_params = SamplingParams(temperature=0.0, max_tokens=512)
outputs = llm.generate(
["量子コンピューティングの基本原理を説明してください"],
sampling_params,
)
for output in outputs:
print(f"Tokens: {len(output.outputs[0].token_ids)}")
print(output.outputs[0].text[:200])
if __name__ == "__main__":
speculative_decoding_example()
テクニック2:マルチGPU Tensor並列
from vllm import LLM, SamplingParams
def tensor_parallel_inference():
llm = LLM(
model="Qwen/Qwen2.5-72B-Instruct-AWQ",
tensor_parallel_size=4,
gpu_memory_utilization=0.90,
max_model_len=8192,
)
sampling_params = SamplingParams(temperature=0.7, max_tokens=1024)
outputs = llm.generate(
["大規模言語モデルの学習パイプラインを説明してください"],
sampling_params,
)
for output in outputs:
print(output.outputs[0].text[:200])
if __name__ == "__main__":
tensor_parallel_inference()
テクニック3:LoRA動的ロード
from vllm import LLM, SamplingParams
def lora_inference():
llm = LLM(
model="Qwen/Qwen2.5-7B-Instruct",
enable_lora=True,
max_loras=4,
max_lora_rank=16,
gpu_memory_utilization=0.90,
)
sampling_params = SamplingParams(temperature=0.7, max_tokens=512)
outputs = llm.generate(
[
{"prompt": "法的用語で契約の効力を説明してください", "lora_request": ("legal-lora", "/path/to/legal-lora", 16)},
{"prompt": "医学用語で症状を説明してください", "lora_request": ("medical-lora", "/path/to/medical-lora", 16)},
],
sampling_params,
)
for output in outputs:
print(output.outputs[0].text[:200])
if __name__ == "__main__":
lora_inference()
テクニック4:マルチモーダル推論高速化
from vllm import LLM, SamplingParams
def multimodal_inference():
llm = LLM(
model="Qwen/Qwen2.5-VL-7B-Instruct",
limit_mm_per_prompt={"image": 1},
gpu_memory_utilization=0.90,
)
sampling_params = SamplingParams(temperature=0.7, max_tokens=512)
from vllm.inputs import PromptInputs
inputs: PromptInputs = {
"prompt": "<|image_pad|>この画像の内容を説明してください",
"multi_modal_data": {"image": "https://example.com/photo.jpg"},
}
outputs = llm.generate([inputs], sampling_params)
for output in outputs:
print(output.outputs[0].text[:200])
if __name__ == "__main__":
multimodal_inference()
テクニック5:推論結果キャッシュ
import hashlib
import json
import redis
from typing import Optional
class InferenceCache:
def __init__(self, redis_url: str = "redis://localhost:6379", ttl: int = 3600):
self.client = redis.from_url(redis_url)
self.ttl = ttl
def _cache_key(self, prompt: str, model: str, params: dict) -> str:
raw = json.dumps({"prompt": prompt, "model": model, "params": params}, sort_keys=True)
return f"llm:cache:{hashlib.sha256(raw.encode()).hexdigest()}"
def get(self, prompt: str, model: str, params: dict) -> Optional[str]:
key = self._cache_key(prompt, model, params)
result = self.client.get(key)
return result.decode("utf-8") if result else None
def set(self, prompt: str, model: str, params: dict, response: str):
key = self._cache_key(prompt, model, params)
self.client.setex(key, self.ttl, response)
cache = InferenceCache()
cached = cache.get("Python GILを説明", "qwen2.5-7b", {"temperature": 0.7})
if cached:
print(f"Cache hit: {cached[:100]}")
else:
print("Cache miss, running inference...")
cache.set("Python GILを説明", "qwen2.5-7b", {"temperature": 0.7}, "GILはグローバルインタプリタロック...")
比較分析:4つの推論フレームワーク
| 次元 | vLLM | TensorRT-LLM | llama.cpp | LMDeploy |
|---|---|---|---|---|
| 初回トークンレイテンシ | 中 | 最低 | 高 | 低 |
| スループット | 高 | 最高 | 低 | 高 |
| メモリ効率 | 最高 | 高 | 中 | 高 |
| 量子化サポート | GPTQ/AWQ/GGUF | FP8/INT8 | GGUF/Q4/Q5/Q8 | AWQ/INT4 |
| デプロイ難易度 | 低 | 高 | 低 | 中 |
| コミュニティ | 最も活発 | NVIDIA公式 | 最も広範 | 商用サポート |
| マルチGPU | ✅ Tensor並列 | ✅ Pipeline+Tensor | ❌ | ✅ |
| ストリーミング出力 | ✅ | ✅ | ✅ | ✅ |
| LoRA | ✅ 動的ロード | ❌ | ❌ | ✅ |
| 適用シナリオ | 汎用GPU推論 | 極限パフォーマンス | CPU/エッジ | 国産GPU |
選定の推奨
- 迅速なデプロイ:vLLM、すぐに使える、活発なコミュニティ
- 最大パフォーマンス:TensorRT-LLM、NVIDIA GPU最適解
- CPU/エッジデプロイ:llama.cpp + GGUF、GPU依存なし
- 国産GPU対応:LMDeploy、Huawei Ascend等をサポート
💡 Base64エンコードツールを使って、推論APIでのバイナリデータ転送を処理できます。
オンラインツールおすすめ
- JSONフォーマッター — 推論APIのリクエスト/レスポンスJSONをフォーマット
- Base64エンコード — マルチモーダル推論での画像Base64エンコードを処理
- cURL to Code — cURLテストコマンドをPython/Goコードに変換
- ハッシュ計算 — 推論キャッシュキーのハッシュ値を計算
まとめ
LLM推論高速化はシステム工学であり、アルゴリズム、システム、ハードウェアの3つのレイヤーで協調的な最適化が必要です。2026年の6つの主要プロダクションパターン:
- vLLM PagedAttention:メモリ利用率40%→90%+、推論高速化のインフラ
- TensorRT-LLM:Kernel Fusion + FP8量子化、極限パフォーマンスの選択
- 量子化デプロイ:GPU用GPTQ/AWQ、CPU/エッジ用GGUF、メモリ75%削減
- KV Cache管理:Prefix Cachingでシステムプロンプト再利用、Sliding Windowで長文脈処理
- Continuous Batching:動的バッチ処理、GPU利用率50%→90%
- プロダクションモニタリング:Prometheus + Grafanaフルスタックオブザーバビリティ、TTFT/TPSデュアルメトリクス駆動
将来のトレンド:Speculative Decodingは小モデル支援からモデル自己推測へ進化、FP8/INT4がデフォルト精度に、エッジ推論によりすべての開発者がローカルで大規模モデルを実行可能に。
LLM推論高速化で他の問題に遭遇した場合は、コメント欄で議論してください。役に立ったら、ブックマークとシェアをお忘れなく!
関連記事:
ブラウザローカルツールを無料で試す →