Skip to content
Published on

LLM推論最適化完全ガイド: vLLM、TensorRT-LLM、Speculative Decoding

Authors
  • Name
    Twitter
LLM推論最適化完全ガイド

はじめに

LLM(Large Language Model)を学習させることと同じくらい重要なのが、推論(Inference)の最適化です。どれほど優れたモデルを構築しても、推論コストが過大であったり応答遅延が長かったりすると、実際のサービスに投入するのは困難です。特に70B以上の大規模モデルをプロダクション環境でサービング(提供)するには、GPUメモリ管理、バッチング戦略、デコーディング高速化、量子化など、さまざまな最適化技法を総合的に適用する必要があります。

2025年以降、LLM推論最適化の分野は急速な発展を遂げました。vLLMのPagedAttentionがKVキャッシュのメモリ浪費を4%未満に削減し、TensorRT-LLM 1.0はPyTorchベースのアーキテクチャの安定化とFP8/NVFP4量子化によってNVIDIA GPUで最高性能を実現し、Speculative Decodingは出力品質の損失なしに2〜3倍の速度向上を達成しています。

本記事では、LLM推論の主要なボトルネックを分析し、vLLM、TensorRT-LLM、Speculative Decoding、KV Cache最適化といった主要技術を、実践的なコードとベンチマークを通じて比較分析します。プロダクション環境での運用ノウハウやトラブルシューティング事例も含め、LLM推論最適化の全体像を提供します。

LLM推論パイプラインの理解

PrefillとDecodeステージ

LLM推論は大きく2つのステージに分かれます。

Prefillステージ(プロンプト処理): 入力プロンプトのすべてのトークンを一度に並列処理してKVキャッシュを生成します。このステージは**コンピュートバウンド(compute-bound)**な処理であり、GPU演算能力が鍵となります。

Decodeステージ(トークン生成): 一度に1つのトークンを自己回帰的に生成します。各ステップごとにKVキャッシュ全体を読み出す必要があるため、**メモリバウンド(memory-bound)**な処理です。推論時間の大部分を占めます。

# LLM 추론 파이프라인의 두 단계를 개념적으로 표현
import torch
import time

def llm_inference_pipeline(model, tokenizer, prompt, max_new_tokens=128):
    input_ids = tokenizer.encode(prompt, return_tensors="pt").to(model.device)

    # 1단계: Prefill - 입력 프롬프트 전체를 병렬 처리
    prefill_start = time.time()
    with torch.no_grad():
        outputs = model(input_ids, use_cache=True)
        past_key_values = outputs.past_key_values  # KV Cache 생성
        next_token_logits = outputs.logits[:, -1, :]
    prefill_time = time.time() - prefill_start

    # 2단계: Decode - 토큰을 하나씩 자기회귀적으로 생성
    decode_start = time.time()
    generated_tokens = []
    for step in range(max_new_tokens):
        next_token = torch.argmax(next_token_logits, dim=-1, keepdim=True)
        generated_tokens.append(next_token.item())

        with torch.no_grad():
            outputs = model(
                next_token,
                past_key_values=past_key_values,  # 캐시 재사용
                use_cache=True,
            )
            past_key_values = outputs.past_key_values
            next_token_logits = outputs.logits[:, -1, :]

        if next_token.item() == tokenizer.eos_token_id:
            break

    decode_time = time.time() - decode_start
    tokens_per_sec = len(generated_tokens) / decode_time

    print(f"Prefill 시간: {prefill_time:.3f}s (입력 {input_ids.shape[1]} 토큰)")
    print(f"Decode 시간: {decode_time:.3f}s ({len(generated_tokens)} 토큰 생성)")
    print(f"Decode 속도: {tokens_per_sec:.1f} tokens/s")

    return tokenizer.decode(generated_tokens)

主要パフォーマンス指標

LLM推論の性能を評価する際に必ず考慮すべき指標は以下の通りです。

指標説明影響要因
TTFT (Time to First Token)最初のトークン生成までの遅延Prefill速度、キュー待機時間
TPOT (Time Per Output Token)出力トークン間の間隔Decode速度、バッチサイズ
Throughput (tokens/s)1秒あたりの処理トークン数バッチング、並列化、量子化
GPU Memory UtilizationGPUメモリ使用効率KV Cache管理、量子化
Latency P9999パーセンタイル遅延システム全体の安定性

Static Batching vs Continuous Batching

従来のスタティックバッチング(Static Batching)では、バッチ内で最も長いシーケンスが完了するまですべてのリクエストが待機する必要がありました。これはGPUリソースの深刻な無駄を引き起こします。

コンティニュアスバッチング(Continuous Batching、またはIteration-level Batching)は、各デコーディングステップごとに完了したリクエストを即座に取り出し、新しいリクエストを追加します。これによりGPU活用率を大幅に向上させることができ、vLLM、TGI、TensorRT-LLMなどの最新サービングエンジンはすべてコンティニュアスバッチングを標準でサポートしています。

KV Cache最適化: PagedAttentionとFlashAttention

KV Cacheのメモリ問題

Transformerモデルのアテンションメカニズムは、以前のトークンのKeyとValueベクトルを保存するKVキャッシュを必要とします。このキャッシュのサイズはシーケンス長に比例して増加し、大規模モデルではGPUメモリのかなりの部分を占めます。

例えばLlama 3.1 70Bモデルの場合、FP16での単一リクエストのKVキャッシュだけで数GBのメモリを消費する可能性があります。従来の方式では各リクエストに対して最大シーケンス長分のメモリを事前に割り当てるため、実際の使用量に対して60〜80%のメモリが無駄になっていました。

PagedAttention: 仮想メモリから着想を得たイノベーション

vLLMが導入したPagedAttentionは、OSの仮想メモリページング技法をKVキャッシュ管理に適用したものです。核心的なアイデアは以下の通りです。

  1. ブロック単位管理: KVキャッシュを固定サイズのブロックに分割し、非連続的な物理メモリに格納します。
  2. ブロックテーブル: 論理ブロックと物理ブロックのマッピングをブロックテーブルで管理します。
  3. 動的割り当て: 実際に必要な時にのみブロックを割り当てるため、メモリの無駄が4%未満に削減されます。
  4. メモリ共有: ビームサーチや並列サンプリング時に、同一プロンプトのKVキャッシュブロックをCopy-on-Write方式で共有できます。

FlashAttention: IO最適化アテンション

FlashAttentionは、GPUのメモリ階層構造を考慮してアテンション演算を最適化します。

  • タイリング(Tiling): アテンション行列を小さなブロックに分割してSRAMで処理
  • カーネルフュージョン: Softmaxと行列積を1つのCUDAカーネルに統合
  • 再計算(Recomputation): 中間結果を保存せず、必要に応じて再計算してHBMアクセスを最小化

FlashAttention-2は元のバージョンに比べて約2倍の性能向上を達成し、FlashAttention-3はHopperアーキテクチャ(H100)でFP8サポートと非同期実行を追加しました。

# FlashAttention과 기본 어텐션의 메모리 사용량 비교
import torch
from flash_attn import flash_attn_func

# 설정: batch=4, heads=32, seq_len=4096, head_dim=128
batch_size, num_heads, seq_len, head_dim = 4, 32, 4096, 128

q = torch.randn(batch_size, seq_len, num_heads, head_dim, dtype=torch.float16, device="cuda")
k = torch.randn(batch_size, seq_len, num_heads, head_dim, dtype=torch.float16, device="cuda")
v = torch.randn(batch_size, seq_len, num_heads, head_dim, dtype=torch.float16, device="cuda")

# 기본 어텐션: O(N^2) 메모리 필요 (어텐션 행렬 전체 저장)
# 4096 * 4096 * 32 * 4 * 2 bytes = ~4 GB

# FlashAttention: O(N) 메모리만 필요 (타일링으로 분할 처리)
output = flash_attn_func(q, k, v, causal=True)
# 메모리 사용량이 시퀀스 길이에 선형적으로 증가
print(f"FlashAttention 출력 shape: {output.shape}")

vLLM: 高性能LLMサービングエンジン

vLLMの概要とアーキテクチャ

vLLMはUCバークレーで開発された高性能LLM推論・サービングエンジンです。2025年時点の最新バージョンはv0.17.xで、PagedAttentionを核にさまざまな最適化技術を統合しています。

主な特徴は以下の通りです。

  • PagedAttentionベースのKVキャッシュ管理
  • Continuous Batchingによる高いGPU活用率
  • テンソル並列処理(Tensor Parallelism)およびパイプライン並列処理のサポート
  • OpenAI互換APIサーバー内蔵
  • AWQ、GPTQ、FP8など多様な量子化フォーマットのサポート
  • Prefix Cachingによる共通プロンプトの最適化

vLLMのインストールと基本的な使い方

# vLLM 설치 (CUDA 12.1 이상 필요)
pip install vllm

# OpenAI 호환 API 서버 실행
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-3.1-70B-Instruct \
    --tensor-parallel-size 4 \
    --gpu-memory-utilization 0.90 \
    --max-model-len 8192 \
    --enable-prefix-caching \
    --dtype auto \
    --port 8000

# 요청 테스트
curl http://localhost:8000/v1/completions \
    -H "Content-Type: application/json" \
    -d '{
        "model": "meta-llama/Llama-3.1-70B-Instruct",
        "prompt": "LLM 추론 최적화의 핵심은",
        "max_tokens": 256,
        "temperature": 0.7
    }'

vLLM Python APIの活用

from vllm import LLM, SamplingParams

# 모델 로드 (자동으로 PagedAttention 적용)
llm = LLM(
    model="meta-llama/Llama-3.1-8B-Instruct",
    tensor_parallel_size=1,
    gpu_memory_utilization=0.90,
    max_model_len=4096,
    enable_prefix_caching=True,
    quantization="awq",            # AWQ 양자화 모델 사용 시
    # enforce_eager=True,           # CUDA Graph 비활성화 (디버깅용)
)

# 샘플링 파라미터 설정
sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=512,
    repetition_penalty=1.1,
)

# 배치 추론 - 여러 프롬프트를 동시에 처리
prompts = [
    "Kubernetes에서 GPU 노드 스케줄링을 최적화하는 방법을 설명해주세요.",
    "Python에서 비동기 프로그래밍의 장단점은 무엇인가요?",
    "마이크로서비스 아키텍처의 장단점을 비교해주세요.",
]

outputs = llm.generate(prompts, sampling_params)

for output in outputs:
    prompt = output.prompt
    generated = output.outputs[0].text
    tokens_count = len(output.outputs[0].token_ids)
    print(f"프롬프트: {prompt[:50]}...")
    print(f"생성 토큰 수: {tokens_count}")
    print(f"응답: {generated[:200]}...\n")

vLLMプロダクション設定ガイド

# vllm-deployment.yaml - Kubernetes 배포 예시
apiVersion: apps/v1
kind: Deployment
metadata:
  name: vllm-llama-70b
  labels:
    app: vllm-server
spec:
  replicas: 1
  selector:
    matchLabels:
      app: vllm-server
  template:
    metadata:
      labels:
        app: vllm-server
    spec:
      containers:
        - name: vllm
          image: vllm/vllm-openai:v0.17.1
          command:
            - python
            - -m
            - vllm.entrypoints.openai.api_server
          args:
            - --model
            - meta-llama/Llama-3.1-70B-Instruct
            - --tensor-parallel-size
            - '4'
            - --gpu-memory-utilization
            - '0.90'
            - --max-model-len
            - '8192'
            - --enable-prefix-caching
            - --max-num-seqs
            - '256'
          ports:
            - containerPort: 8000
          resources:
            limits:
              nvidia.com/gpu: '4'
            requests:
              nvidia.com/gpu: '4'
              memory: '64Gi'
              cpu: '16'
          readinessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 120
            periodSeconds: 10
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 180
            periodSeconds: 30
      nodeSelector:
        nvidia.com/gpu.product: A100-SXM4-80GB
      tolerations:
        - key: nvidia.com/gpu
          operator: Exists
          effect: NoSchedule
---
apiVersion: v1
kind: Service
metadata:
  name: vllm-service
spec:
  selector:
    app: vllm-server
  ports:
    - port: 80
      targetPort: 8000
  type: ClusterIP

TensorRT-LLM: NVIDIA最適化推論

TensorRT-LLM 1.0の主要な変更点

TensorRT-LLM 1.0は2つの重要な変更をもたらしました。第一に、PyTorchベースのアーキテクチャが安定化してデフォルトの体験となり、第二にLLM APIが安定化しました。以前のバージョンで必要だった複雑なエンジンビルドプロセスが大幅に簡素化されました。

主な最適化機能は以下の通りです。

  • FP8およびNVFP4量子化のサポート
  • Disaggregated Serving(分離型サービング)
  • Wide Expert Parallelism(EP)などの並列化手法
  • EAGLE-3およびMulti-Token PredictionベースのSpeculative Decoding
  • DeepSeek V3/R1モデルのサポート

TensorRT-LLMのインストールと推論

# TensorRT-LLM 설치 (Docker 권장)
docker pull nvcr.io/nvidia/tensorrt-llm:latest

# 또는 pip 설치
pip install tensorrt-llm

# Llama 모델 체크포인트 변환 및 엔진 빌드
# 1단계: HuggingFace 모델을 TensorRT-LLM 형식으로 변환
python convert_checkpoint.py \
    --model_dir /models/Llama-3.1-70B-Instruct \
    --output_dir /engines/llama-70b-ckpt \
    --dtype float16 \
    --tp_size 4

# 2단계: TensorRT 엔진 빌드
trtllm-build \
    --checkpoint_dir /engines/llama-70b-ckpt \
    --output_dir /engines/llama-70b-engine \
    --gemm_plugin float16 \
    --max_batch_size 64 \
    --max_input_len 4096 \
    --max_seq_len 8192 \
    --paged_kv_cache enable \
    --use_paged_context_fmha enable \
    --multiple_profiles enable

# 3단계: Triton Inference Server로 서빙
docker run --gpus all -p 8000:8000 \
    -v /engines:/engines \
    nvcr.io/nvidia/tritonserver:latest \
    tritonserver --model-repository=/engines/model_repo

TensorRT-LLM Python APIの使用

import tensorrt_llm
from tensorrt_llm import LLM, SamplingParams, KvCacheConfig

# KV Cache 설정
kv_cache_config = KvCacheConfig(
    free_gpu_memory_fraction=0.85,
    enable_block_reuse=True,
)

# 모델 로드 (HuggingFace 모델에서 직접 빌드 가능)
llm = LLM(
    model="meta-llama/Llama-3.1-8B-Instruct",
    tensor_parallel_size=1,
    kv_cache_config=kv_cache_config,
)

# 추론 실행
sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=512,
)

prompts = [
    "LLM 추론 최적화에서 가장 중요한 요소를 설명해주세요.",
    "TensorRT-LLM의 장점과 한계를 비교해주세요.",
]

outputs = llm.generate(prompts, sampling_params=sampling_params)

for output in outputs:
    print(f"생성 결과: {output.outputs[0].text[:200]}")

Speculative Decoding: ドラフト・検証ベースの高速化

動作原理

Speculative Decoding(投機的デコーディング)は、2022年にGoogleの論文で提案された手法で、出力品質の損失なしに推論速度を向上させる画期的な方法です。

核心的なアイデアは以下の通りです。

  1. ドラフトモデル(Draft Model): 小さくて高速なモデルがK個のトークンを事前に生成(推測)します。
  2. ターゲットモデル(Target Model): 大きくて高精度なモデルがK個のトークンを1回のforward passで並列検証します。
  3. 受理/棄却判定: ターゲットモデルの確率分布と比較して、一致するトークンは受理し、不一致の地点からターゲットモデルが正しいトークンを生成します。

この方式の核心は、検証は生成よりも高速であるという点です。K個のトークンの検証はターゲットモデルの単一のforward passで実行されるため、受理率が高いほど速度向上が大きくなります。

vLLMでのSpeculative Decoding設定

# vLLM에서 Speculative Decoding 활성화
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-3.1-70B-Instruct \
    --tensor-parallel-size 4 \
    --speculative-model meta-llama/Llama-3.1-8B-Instruct \
    --num-speculative-tokens 5 \
    --speculative-max-model-len 4096 \
    --use-v2-block-manager \
    --port 8000
# Speculative Decoding 효과 측정 스크립트
import time
import requests
import json
import statistics

API_URL = "http://localhost:8000/v1/completions"

def measure_latency(prompt, max_tokens=256, n_requests=10):
    """여러 요청의 지연 시간과 처리량 측정"""
    latencies = []
    total_tokens = []

    for i in range(n_requests):
        payload = {
            "model": "meta-llama/Llama-3.1-70B-Instruct",
            "prompt": prompt,
            "max_tokens": max_tokens,
            "temperature": 0.7,
        }

        start = time.time()
        response = requests.post(API_URL, json=payload)
        elapsed = time.time() - start

        result = response.json()
        completion_tokens = result["usage"]["completion_tokens"]

        latencies.append(elapsed)
        total_tokens.append(completion_tokens)

    avg_latency = statistics.mean(latencies)
    p99_latency = sorted(latencies)[int(0.99 * len(latencies))]
    avg_throughput = statistics.mean(total_tokens) / avg_latency

    print(f"평균 지연: {avg_latency:.3f}s")
    print(f"P99 지연: {p99_latency:.3f}s")
    print(f"평균 처리량: {avg_throughput:.1f} tokens/s")
    return avg_latency, p99_latency, avg_throughput

# 테스트 프롬프트
test_prompt = "다음 주제에 대해 상세히 설명해주세요: 마이크로서비스 아키텍처"

print("=== Speculative Decoding 벤치마크 ===")
measure_latency(test_prompt)

最新のSpeculative Decoding手法

2025〜2026年にはSpeculative Decodingの分野でさまざまな進展がありました。

  • EAGLE-3: TensorRT-LLMに統合された高度な推測手法で、ドラフトモデルを使わずにターゲットモデル自体の隠れ状態を活用してトークンを予測します。別途ドラフトモデル用のメモリが不要です。
  • Multi-Token Prediction(MTP): 1ステップで複数のトークンを同時に予測する方式で、DeepSeek V3で採用された手法です。
  • TurboSpec: ランタイムで推測パラメータを動的に調整するクローズドループ制御システムで、ワークロードとハードウェアに適応します。
  • 異種語彙(Heterogeneous Vocabulary)サポート: ドラフトモデルとターゲットモデルが同じ語彙を共有しなくてもよいアルゴリズムが開発され、ドラフトモデル選択の幅が広がりました。経験的に最大2.8倍の速度向上が報告されています。

推論エンジン比較分析(vLLM vs TGI vs TensorRT-LLM)

総合比較表

項目vLLM (v0.17.x)TGI (v3.x)TensorRT-LLM (v1.0)
開発元UC Berkeley / vLLMコミュニティHugging FaceNVIDIA
ライセンスApache 2.0Apache 2.0Apache 2.0
コア技術PagedAttentionFlashAttention-2/3, FlashInferTensorRTエンジン最適化
スループット(req/s)120-160100-140180-220
TTFT50-80ms60-90ms35-50ms
インストール難易度低(pip install)低(Docker)高(エンジンビルド必要)
Continuous Batchingサポートサポートサポート
量子化AWQ, GPTQ, FP8AWQ, GPTQ, BitsAndBytesFP8, NVFP4, INT8, INT4
Speculative Decodingサポートサポート(限定的)EAGLE-3, MTPサポート
Tensor Parallelismサポートサポートサポート
Pipeline Parallelismサポート未サポートサポート
Prefix Cachingサポート(自動)サポートサポート
長文コンテキスト普通優秀(TGI v3基準13倍高速)優秀
モデル互換性非常に広い広いNVIDIA GPU専用
API互換性OpenAI互換独自API + OpenAI互換Tritonベース
コミュニティ活性度非常に高い高い高い

利用シナリオ別の推奨エンジン

vLLMを選択すべきケース:

  • 素早くプロトタイピングしたり、開発環境でテストする場合
  • 多様なモデルをサポートする必要がある場合(HuggingFaceモデルの直接ロード)
  • 高い同時接続数で安定したレイテンシが必要な場合
  • OpenAI互換APIが必要な場合

TGIを選択すべきケース:

  • Hugging Faceエコシステムとの緊密な統合が必要な場合
  • 200Kトークン以上の超長文コンテキストを処理する必要がある場合(TGI v3の13倍速度向上)
  • Dockerベースの簡単なデプロイを希望する場合

TensorRT-LLMを選択すべきケース:

  • NVIDIA GPUで絶対的な最高性能が必要な場合
  • TTFT(最初のトークン時間)が極めて重要なリアルタイムサービスの場合
  • NVIDIA Triton Inference Serverと統合されたエンタープライズ環境の場合
  • FP8/NVFP4など最新の量子化手法を活用したい場合

量子化と推論最適化(AWQ、GPTQ、FP8)

量子化手法の比較

量子化は、モデル重みの精度を下げることでメモリ使用量を削減し、推論速度を向上させる手法です。メモリバウンド環境(小さなバッチ)で特に効果的です。

手法ビット数メモリ削減品質損失速度向上特徴
FP16(基準)16ビット---ベースライン
FP8 (W8A8)8ビット約50%-2.7%(長文)1.5-2xH100ネイティブサポート、学習不要
AWQ (INT4)4ビット約75%-0.2%(長文)2-3x活性化認識、高速量子化
GPTQ (INT4)4ビット約75%-1.8%(長文)2-3xHessianベース最適化、データ必要
NVFP44ビット約75%低い2-3xTensorRT-LLM専用、Blackwell最適

量子化の実践コード

# AWQ 양자화 모델을 vLLM에서 사용하는 예시
from vllm import LLM, SamplingParams

# AWQ 양자화 모델 로드 (HuggingFace에서 AWQ 모델 직접 사용)
llm_awq = LLM(
    model="TheBloke/Llama-2-70B-Chat-AWQ",
    quantization="awq",
    tensor_parallel_size=2,        # 4비트이므로 GPU 2장이면 충분
    gpu_memory_utilization=0.90,
    max_model_len=4096,
)

# GPTQ 양자화 모델 로드
llm_gptq = LLM(
    model="TheBloke/Llama-2-70B-Chat-GPTQ",
    quantization="gptq",
    tensor_parallel_size=2,
    gpu_memory_utilization=0.90,
)

# FP8 양자화 (H100 이상 권장)
llm_fp8 = LLM(
    model="meta-llama/Llama-3.1-70B-Instruct",
    quantization="fp8",
    tensor_parallel_size=4,
    gpu_memory_utilization=0.90,
)

# 성능 비교 벤치마크
import time

sampling_params = SamplingParams(temperature=0.7, max_tokens=512)
test_prompts = ["마이크로서비스 아키텍처의 장단점을 설명해주세요."] * 10

for name, model in [("AWQ", llm_awq), ("GPTQ", llm_gptq), ("FP8", llm_fp8)]:
    start = time.time()
    outputs = model.generate(test_prompts, sampling_params)
    elapsed = time.time() - start
    total_tokens = sum(len(o.outputs[0].token_ids) for o in outputs)
    print(f"{name}: {elapsed:.2f}s, {total_tokens/elapsed:.1f} tokens/s")

量子化の選択ガイド

実務で量子化手法を選択する際の推奨事項は以下の通りです。

  • FP8から始める: H100以上のGPUを使用しているなら、FP8は学習データなしで適用可能で品質損失も少ないです。
  • メモリが不足する場合はAWQ: INT4量子化の中でAWQが品質損失が最も少なく(-0.2%)、量子化速度も高速です。
  • バッチサイズによる効果の違いに注意: 小さなバッチではメモリバウンドのため量子化の効果が大きいですが、大きなバッチではコンピュートバウンドとなり、INT4-to-FP16逆量子化のオーバーヘッドにより効果が減少します。

運用時の注意事項とトラブルシューティング

GPUメモリ管理

プロダクション環境で最もよくある問題はGPU OOM(Out of Memory)です。以下の点を確認する必要があります。

  • gpu-memory-utilizationの値を0.90以上に設定すると、一時的なメモリスパイクに脆弱になります。0.85〜0.90が安全な範囲です。
  • max-model-lenを必要以上に大きく設定すると、KVキャッシュが過剰に割り当てられます。実際の使用パターンに合わせて調整してください。
  • テンソル並列時のGPU間通信バッファもメモリを占有します。NVLink接続状態を確認しましょう。

モニタリングの重要指標

# GPU 모니터링 - nvidia-smi 활용
watch -n 1 nvidia-smi

# vLLM 메트릭 확인 (Prometheus 포맷)
curl http://localhost:8000/metrics | grep -E "vllm_(num_requests|gpu_cache|avg_generation)"

# 핵심 모니터링 대상:
# - vllm:num_requests_running: 현재 처리 중인 요청 수
# - vllm:num_requests_waiting: 대기 중인 요청 수
# - vllm:gpu_cache_usage_perc: GPU KV 캐시 사용률
# - vllm:avg_generation_throughput_toks_per_s: 평균 토큰 생성 처리량

CUDA Graphとメモリのトレードオフ

vLLMはCUDA Graphを使用してカーネル実行のオーバーヘッドを削減します。しかし、CUDA Graphは追加のGPUメモリを消費します。メモリが不足する場合は--enforce-eagerオプションで無効化できますが、スループットが低下します。

リクエストタイムアウトとキュー管理

長時間かかるリクエストがシステム全体をブロッキングする状況を防止する必要があります。--max-num-seqsオプションで同時処理リクエスト数を制限し、プロキシレベルでタイムアウトを設定することをお勧めします。

障害事例とリカバリ手順

事例1: KV Cacheメモリ不足によるリクエスト拒否

症状: gpu_cache_usage_percが100%に近づき、新しいリクエストがキューで無限に待機するか拒否されます。

原因: 長文入力が集中したり、同時リクエスト数が急増してKVキャッシュの空間が不足した場合です。

リカバリ手順:

  1. max-num-seqsの値を下げて同時リクエスト数を制限します。
  2. max-model-lenを実際の使用パターンに合わせて縮小します。
  3. 必要に応じて量子化を適用し、モデル重みのメモリ占有を削減します。
  4. Prefix Cachingを有効にして、共通システムプロンプトのKVキャッシュを共有します。

事例2: TensorRT-LLMエンジンビルドの失敗

症状: trtllm-buildプロセスでOOMエラーまたは互換性エラーが発生します。

原因: ビルド時にもかなりのGPUメモリが必要であり、ビルドパラメータがハードウェア仕様と合っていない場合です。

リカバリ手順:

  1. --max_batch_size--max_input_lenの値を下げて再ビルドを試みます。
  2. --workersオプションでビルドの並列度を下げます。
  3. GPUドライバー、CUDA、TensorRTバージョンの互換性を確認します。
  4. Dockerイメージを使用して環境依存性の問題を解消します。

事例3: Speculative Decoding受理率の低下

症状: Speculative Decodingを適用したのに、かえってレイテンシが増加します。

原因: ドラフトモデルの予測精度が低く、ほとんどのトークンが棄却されると、ドラフトモデルの追加演算が純粋なオーバーヘッドになります。

リカバリ手順:

  1. num-speculative-tokensの値を下げます(5以下から3以下へ)。
  2. ドラフトモデルをターゲットモデルと同じファミリーの小型モデルに変更します(例: 70Bターゲットなら8Bドラフト)。
  3. 受理率メトリクスを監視し、60%以下であればドラフトモデルを変更するかSpeculative Decodingを無効化します。
  4. EAGLE方式に切り替えてドラフトモデルのメモリ負担を解消することを検討します。

事例4: ロードバランシングの不均衡

症状: 複数のvLLMインスタンスのうち、特定のインスタンスにのみ負荷が集中します。

原因: 単純なラウンドロビンロードバランシングがリクエスト長の差異を考慮していないためです。

リカバリ手順:

  1. Least-ConnectionまたはWeighted Round Robin方式のロードバランサーを使用します。
  2. 各インスタンスのnum_requests_runningメトリクスに基づいてリクエストを分配します。
  3. 長文リクエスト専用インスタンスと短文リクエスト専用インスタンスを分離することを検討します。

おわりに

LLM推論最適化は単一の技術ではなく、複数レイヤーの最適化を組み合わせる総合エンジニアリングです。KV Cache管理(PagedAttention)、演算最適化(FlashAttention)、デコーディング高速化(Speculative Decoding)、精度最適化(量子化)、システム最適化(Continuous Batching、Tensor Parallelism)が有機的に結合してこそ、最高の性能を達成できます。

実践における重要な教訓をまとめると以下の通りです。

  1. まずはvLLMから始めましょう: インストールが簡単で、コミュニティが大きく、大半のシナリオで十分な性能を提供します。
  2. TTFTが最優先ならTensorRT-LLMを検討しましょう: 設定は複雑ですが、NVIDIA GPUで最高のレイテンシ性能を達成します。
  3. 量子化はFP8から始めましょう: 品質損失が少なく設定が簡単です。メモリが不足する場合のみINT4(AWQ)に下げましょう。
  4. Speculative Decodingは受理率を監視しましょう: 60%以下では効果がありません。ドラフトモデルの選択が鍵です。
  5. モニタリングなき最適化は盲目的です: GPUキャッシュ使用率、TTFT、スループット、P99レイテンシを必ず追跡しましょう。

LLM推論最適化技術は急速に進化しています。vLLMはv2アーキテクチャを準備中であり、TensorRT-LLMは次世代Blackwell GPU最適化を強化しており、Speculative Decodingは異種語彙サポートと適応型制御システムへと発展しています。こうした潮流を継続的に追跡しながら、自身のワークロードに最適な組み合わせを見つけていくことが重要です。

参考資料