Skip to content
Published on

GPUメモリ管理とLLM推論最適化: vLLM、PagedAttention、GPTQ、TensorRT-LLMまで

Authors

はじめに

LLM(大規模言語モデル)を本番環境にデプロイする際、最大の課題はGPUメモリ管理と推論の効率化です。GPT-4規模のモデルは数百GBのメモリを必要とし、リアルタイム応答のためには毎秒数十トークンの生成速度が求められます。

このガイドでは、LLM推論最適化のすべての重要な要素を解説します。GPUメモリ階層の理解からKVキャッシュ最適化、GPTQ/AWQ量子化、PagedAttention、continuous batching、マルチGPU推論まで、本番エンジニアが必ず知っておくべき内容をステップバイステップで説明します。


1. GPUメモリ階層

HBM(High Bandwidth Memory)

現代のAI GPUの中核はHBMです。HBMは複数のDRAMダイを垂直に積層したメモリで、従来のGDDR6よりはるかに広いメモリバスを提供します。

GPUメモリHBMタイプ帯域幅バス幅
A100 80G80 GBHBM2e2.0 TB/s5120-bit
H100 SXM80 GBHBM33.35 TB/s5120-bit
H200 SXM141 GBHBM3e4.8 TB/s5120-bit
B200 SXM192 GBHBM3e8.0 TB/s8192-bit
MI300X192 GBHBM35.3 TB/s8192-bit

L2キャッシュとSRAM

GPUメモリ階層は大きく3段階で構成されています。

  1. HBM(グローバルメモリ): 数十〜数百GB、帯域幅は数TB/s、レイテンシは数百ns程度
  2. L2キャッシュ: 数十〜数百MB(H100: 50 MB)、全SMが共有
  3. L1キャッシュ / SRAM(シェアードメモリ): SM当たり128〜256 KB、帯域幅は数十TB/s、レイテンシは数ns程度

各SM(Streaming Multiprocessor)内のSRAMはレジスタファイルに次ぐ高速メモリです。Flash Attentionなどの最適化アルゴリズムはこのSRAMを積極的に活用し、HBMへのアクセス回数を削減します。

Rooflineモデル:性能限界の分析

Rooflineモデルは、特定の演算がcompute-boundかmemory-boundかを判定する分析ツールです。

演算強度 (AI) = FLOP/ メモリアクセス量 (bytes)

性能上限 = min(Peak FLOPS, Peak Memory BW × AI)
  • AIが低い場合(memory-bound): メモリ帯域幅がボトルネック。LLMのdecodeフェーズが典型例
  • AIが高い場合(compute-bound): 演算速度がボトルネック。LLMのprefillフェーズや大バッチ処理

H100の場合:

  • Peak FP16 FLOPS: 989 TFLOPS
  • Peak HBM帯域幅: 3.35 TB/s
  • Ridge point(均衡点): 989 / 3.35 ≈ 295 FLOP/byte

1トークンを生成する際、70BモデルのAI(FP16)は約1〜2 FLOP/byteと極めてmemory-boundです。


2. LLMメモリ計算

パラメータメモリ

LLMのメモリ使用量を正確に計算することは、デプロイ計画の基盤です。

def calc_model_memory_gb(
    num_params: int,       # パラメータ数(例: 70e9)
    dtype_bytes: int = 2,  # FP16=2, FP32=4, INT8=1, INT4=0.5
) -> float:
    """モデル重みのメモリを計算"""
    return (num_params * dtype_bytes) / (1024 ** 3)

# 主要モデルのメモリ(FP16基準)
models = {
    "Llama-3.1-8B":   {"params": 8e9,   "bytes": 2},
    "Llama-3.1-70B":  {"params": 70e9,  "bytes": 2},
    "Llama-3.1-405B": {"params": 405e9, "bytes": 2},
    "Mistral-7B":     {"params": 7e9,   "bytes": 2},
    "Qwen2-72B":      {"params": 72e9,  "bytes": 2},
}

for name, cfg in models.items():
    mem_gb = calc_model_memory_gb(cfg["params"], cfg["bytes"])
    print(f"{name}: {mem_gb:.1f} GB")
モデルパラメータFP32FP16/BF16INT8INT4
Llama-3.1-8B8B32 GB16 GB8 GB4 GB
Llama-3.1-70B70B280 GB140 GB70 GB35 GB
Llama-3.1-405B405B1620 GB810 GB405 GB202 GB
Mistral-7B7B28 GB14 GB7 GB3.5 GB

KVキャッシュメモリ計算

KVキャッシュは推論中で最も動的に変化するメモリ使用量です。シーケンス長とバッチサイズに比例します。

def calc_kv_cache_memory_gb(
    num_layers: int,
    num_heads: int,
    head_dim: int,
    seq_len: int,
    batch_size: int,
    dtype_bytes: int = 2,  # FP16
) -> float:
    """
    KVキャッシュメモリを計算
    各レイヤー: 2 (K, V) × num_heads × head_dim × seq_len × batch_size
    """
    kv_per_layer = 2 * num_heads * head_dim * seq_len * batch_size
    total_bytes = kv_per_layer * num_layers * dtype_bytes
    return total_bytes / (1024 ** 3)

# Llama-3.1-70B の例
# layers=80, heads=64(GQA: kv_heads=8), head_dim=128
kv_mem = calc_kv_cache_memory_gb(
    num_layers=80,
    num_heads=8,      # GQAの場合はkv_headsを使用
    head_dim=128,
    seq_len=4096,
    batch_size=1,
    dtype_bytes=2,
)
print(f"KVキャッシュ (seq=4096, bs=1): {kv_mem:.2f} GB")
# 出力: KVキャッシュ (seq=4096, bs=1): 0.50 GB

# バッチサイズ別のKVキャッシュ
for bs in [1, 4, 8, 16, 32]:
    mem = calc_kv_cache_memory_gb(80, 8, 128, 4096, bs, 2)
    print(f"  batch_size={bs:2d}: {mem:.2f} GB")

KVキャッシュメモリ(Llama-3.1-70B、seq_len=4096、FP16)

バッチサイズKVキャッシュモデル重み合計
10.5 GB140 GB140.5 GB
42.0 GB140 GB142.0 GB
84.0 GB140 GB144.0 GB
168.0 GB140 GB148.0 GB
3216.0 GB140 GB156.0 GB

活性化メモリ

推論時の活性化メモリはバッチサイズ、シーケンス長、隠れ層サイズの積に比例します。学習と異なり、推論ではグラジエントを保存しないため比較的小さくなります。

def calc_activation_memory_gb(
    hidden_size: int,
    seq_len: int,
    batch_size: int,
    num_layers: int,
    dtype_bytes: int = 2,
) -> float:
    """推論時の活性化メモリを近似計算"""
    # 各レイヤー: attention + FFN の活性化
    # 近似値: 2 × hidden_size × seq_len × batch_size per layer
    bytes_per_layer = 2 * hidden_size * seq_len * batch_size * dtype_bytes
    return (bytes_per_layer * num_layers) / (1024 ** 3)

3. KVキャッシュ最適化: PagedAttention

従来のKVキャッシュの問題点

従来のLLMサービングシステムでは、KVキャッシュを連続したメモリブロックとして割り当てます。これは深刻な問題を引き起こします。

  1. 内部フラグメンテーション: 最大シーケンス長に合わせて事前割り当てすると、実際に使用されない空間が無駄になります
  2. 外部フラグメンテーション: リクエスト終了ごとに異なるサイズの空き領域が生まれ、新しいリクエストの割り当てが困難になります
  3. メモリ効率: 実際のシステムではKVキャッシュの60〜80%が無駄になっています

PagedAttention: OSページングの原理を応用

vLLMのPagedAttentionは、オペレーティングシステムの仮想メモリページング概念をKVキャッシュ管理に適用します。

OS仮想メモリ       → PagedAttention
────────────────────────────────────
仮想ページ         → 論理ブロック(logical block)
物理フレーム        → 物理ブロック(physical block)
ページテーブル      → ブロックテーブル(block table)
ページフォルト      → ブロック割り当て

核心的なアイデア:

  • KVキャッシュを固定サイズのブロック(例: 16トークン)に分割
  • シーケンスのKVは論理ブロックでアクセスし、物理ブロックは必要時に割り当て
  • 異なるシーケンスが共通プロンプトを共有する場合、Copy-on-Writeで物理ブロックを共有
リクエストA: [Block 0][Block 1][Block 2]
                                           ↕ 物理ブロック共有(共通プロンプト)
リクエストB: [Block 0][Block 1][Block 3]

vLLMサーバー起動例

# vLLMのインストール
pip install vllm

# サーバー起動(シングルGPU)
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-3.1-8B-Instruct \
    --dtype bfloat16 \
    --max-model-len 8192 \
    --gpu-memory-utilization 0.90

# OpenAI互換APIの呼び出し
curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "meta-llama/Llama-3.1-8B-Instruct",
    "messages": [{"role": "user", "content": "GPUメモリ最適化について説明して"}],
    "max_tokens": 512,
    "temperature": 0.7
  }'
# PythonクライアントからvLLM APIを呼び出す
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="dummy",
)

response = client.chat.completions.create(
    model="meta-llama/Llama-3.1-8B-Instruct",
    messages=[
        {"role": "system", "content": "あなたはGPU最適化の専門家です。"},
        {"role": "user", "content": "PagedAttentionの動作原理を説明してください。"},
    ],
    max_tokens=1024,
    stream=True,
)

for chunk in response:
    if chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="", flush=True)

4. 量子化: GPTQ、AWQ、GGUF、bitsandbytes

量子化手法の比較

手法精度メモリ削減速度品質劣化特徴
FP16/BF1616-bit基準基準なしデフォルト
GPTQ4-bit約75%速い低いPTQ、GPU専用
AWQ4-bit約75%速い非常に低い活性化認識型
GGUF2〜8-bit可変CPU可可変llama.cpp
bitsandbytes NF44-bit約75%中程度低いQLoRA学習
bitsandbytes INT88-bit約50%中程度非常に低いLLM.int8()

bitsandbytes 4ビット量子化ロード

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig

# 4ビットNF4量子化の設定
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.bfloat16,  # 計算はBF16で実行
    bnb_4bit_quant_type="nf4",              # NormalFloat4量子化
    bnb_4bit_use_double_quant=True,         # ダブル量子化で追加圧縮
)

model_id = "meta-llama/Llama-3.1-70B-Instruct"

model = AutoModelForCausalLM.from_pretrained(
    model_id,
    quantization_config=bnb_config,
    device_map="auto",      # 自動マルチGPU分散
    trust_remote_code=True,
)

tokenizer = AutoTokenizer.from_pretrained(model_id)

# メモリ使用量の確認
print(f"GPUメモリ: {torch.cuda.memory_allocated() / 1e9:.2f} GB")

GPTQ量子化(auto-gptq)

from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
from transformers import AutoTokenizer
import torch

model_id = "meta-llama/Llama-3.1-8B-Instruct"

quantize_config = BaseQuantizeConfig(
    bits=4,              # 4ビット量子化
    group_size=128,      # グループサイズ(小さいほど精度高いがメモリ増加)
    desc_act=False,      # 活性化順序の記述
    damp_percent=0.01,   # Hessianダンピング係数
)

tokenizer = AutoTokenizer.from_pretrained(model_id)

# キャリブレーションデータの準備(代表的なテキストサンプル)
calibration_data = [
    tokenizer("The GPU accelerates machine learning by...", return_tensors="pt").input_ids,
    tokenizer("Quantization reduces model size while...", return_tensors="pt").input_ids,
    # 実際には1024サンプル以上を推奨
]

# モデルのロードと量子化
model = AutoGPTQForCausalLM.from_pretrained(
    model_id,
    quantize_config=quantize_config,
    torch_dtype=torch.float16,
)

model.quantize(calibration_data)
model.save_quantized("llama-3.1-8b-gptq-4bit")
print("GPTQ量子化完了!")

# 量子化モデルのロード
quantized_model = AutoGPTQForCausalLM.from_quantized(
    "llama-3.1-8b-gptq-4bit",
    use_safetensors=True,
    device="cuda:0",
)

AWQ: 活性化認識型重み量子化

AWQはすべての重みを同等に量子化しません。活性化値が大きいチャンネル(重要な重み)はより高い精度で保護します。

from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

model_id = "meta-llama/Llama-3.1-8B-Instruct"
quant_path = "llama-3.1-8b-awq"

# AWQ量子化の設定
quant_config = {
    "zero_point": True,   # ゼロポイント量子化
    "q_group_size": 128,  # グループサイズ
    "w_bit": 4,           # 4ビット
    "version": "GEMM",    # 行列乗算カーネル
}

model = AutoAWQForCausalLM.from_pretrained(
    model_id,
    low_cpu_mem_usage=True,
    use_cache=False,
)
tokenizer = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)

model.quantize(tokenizer, quant_config=quant_config)
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)
print("AWQ量子化完了!")

量子化別パフォーマンスベンチマーク(Llama-3.1-8B)

手法メモリスループット(tok/s)Perplexity備考
FP1616 GB100(基準)7.2基準値
BF1616 GB1007.2FP16と同等
INT88 GB757.3わずかな品質劣化
GPTQ-4bit4.5 GB1207.6メモリ節約と速度向上
AWQ-4bit4.5 GB1257.4GPTQより高品質
GGUF-Q4_K_M4.8 GB80(CPU)7.5CPU推論可能

5. バッチング戦略: Continuous Batching

Static Batchingの限界

従来のstatic batchingは、バッチ内の全リクエストが同時に開始し、すべてが完了するまで待機します。これはGPU利用率の深刻な低下を招きます。

Static Batching(batch_size=3:

時間 →
[リクエストA: ████████████░░░░░░░░]  (12トークン生成)
[リクエストB: ████░░░░░░░░░░░░░░░░]  (4トークン生成)
[リクエストC: ████████░░░░░░░░░░░░]  (8トークン生成)
                └─ BCAの完了を待つ必要あり(GPU浪費)

Continuous Batching(イテレーションレベルスケジューリング)

vLLM、TensorRT-LLMなどの現代のLLMサービングシステムはcontinuous batchingを使用し、各推論ステップ(イテレーション)ごとにバッチを動的に再構成します。

Continuous Batching:

Step 1: [A1][B1][C1]3件を同時処理
Step 2: [A2][B2][C2]
Step 3: [A3][B3][C3]B完了、新規リクエストDを追加
Step 4: [A4][C4][D1]  ← 空きスロットを即座に埋める
Step 5: [A5][C5][D2]
Step 6: [A6][C6][D3]C完了、新規リクエストEを追加
...

GPU利用率はstatic batchingと比較して2〜5倍向上します。

PrefillとDecodeの分離

LLM推論は2つのフェーズに分かれます。

  • Prefill: プロンプト全体を一度に処理。compute-bound(バッチ処理に類似)
  • Decode: トークンを1つずつ自己回帰的に生成。memory-bound

この2つのフェーズは異なるGPU特性を必要とします。Disaggregated Prefillは、prefill専用GPUとdecode専用GPUを分離するアーキテクチャです。


6. LLM推論フレームワーク比較

フレームワーク開発元特徴最適な用途
vLLMUC BerkeleyPagedAttention、OpenAI互換API高スループットサービング
TensorRT-LLMNVIDIA最適化CUDAカーネル、FP8対応最低レイテンシ
OllamaOllama Inc簡単なローカル実行開発/テスト
llama.cppggmlCPU推論、GGUF形式エッジ/ローカル
SGLangLM-Sys構造化生成、RadixAttention複雑なパイプライン

vLLMテンソル並列推論

from vllm import LLM, SamplingParams

# テンソル並列で4つのGPUに分散
llm = LLM(
    model="meta-llama/Llama-3.1-70B-Instruct",
    tensor_parallel_size=4,        # 4 GPUにテンソル並列分散
    dtype="bfloat16",
    max_model_len=8192,
    gpu_memory_utilization=0.90,
    enforce_eager=False,           # CUDAグラフ最適化を使用
)

sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.95,
    max_tokens=512,
    stop=["</s>", "[INST]"],
)

prompts = [
    "GPUメモリ階層を説明して",
    "PagedAttentionの利点は?",
    "量子化手法を比較して",
]

outputs = llm.generate(prompts, sampling_params)

for output in outputs:
    prompt = output.prompt
    generated = output.outputs[0].text
    print(f"プロンプト: {prompt[:50]}...")
    print(f"生成: {generated[:100]}...")
    print()

7. マルチGPU推論: テンソル並列とパイプライン並列

テンソル並列(Tensor Parallelism)

テンソル並列は個々の行列演算を複数のGPUに分散します。各Transformerレイヤーを水平方向に分割します。

Attentionヘッドの分散(4-wayテンソル並列):

GPU 0: Head 015
GPU 1: Head 1631
GPU 2: Head 3247
GPU 3: Head 4863

GPUが独立して計算し、AllReduceで結果を集約
  • 長所: レイテンシ削減、単一GPUに収まらない大規模レイヤーを処理可能
  • 短所: レイヤーごとにAllReduce通信が必要 → 高帯域幅NVLink接続が必須
  • 適合: ノード内NVLink接続GPU、レイテンシ重視のアプリケーション

パイプライン並列(Pipeline Parallelism)

パイプライン並列はレイヤーをグループに分けて各GPUに割り当てます。

Llama-3.1-70B(80レイヤー)→ 4-wayパイプライン:

GPU 0: Layer 019
GPU 1: Layer 2039
GPU 2: Layer 4059
GPU 3: Layer 6079

レイヤー順に処理し、GPU間でactivationを転送
  • 長所: ノード間の低速接続でも効率的、通信量が少ない
  • 短所: パイプラインバブル(前段GPUが計算中に後段GPUが待機)、レイテンシ増加
  • 適合: マルチノード分散推論、超大規模モデル

メモリプロファイリング

import torch

def profile_gpu_memory(func, *args, **kwargs):
    """GPUメモリ使用量をプロファイリング"""
    torch.cuda.reset_peak_memory_stats()
    torch.cuda.synchronize()

    before = torch.cuda.memory_allocated()
    result = func(*args, **kwargs)
    torch.cuda.synchronize()

    after = torch.cuda.memory_allocated()
    peak = torch.cuda.max_memory_allocated()

    print(f"メモリ増加: {(after - before) / 1e9:.3f} GB")
    print(f"ピークメモリ: {peak / 1e9:.3f} GB")
    print()
    print(torch.cuda.memory_summary())
    return result

# メモリ統計出力の例
def load_and_infer():
    from transformers import pipeline
    pipe = pipeline(
        "text-generation",
        model="microsoft/phi-2",
        torch_dtype=torch.float16,
        device_map="auto",
    )
    return pipe("GPU memory management is", max_new_tokens=50)

profile_gpu_memory(load_and_infer)

8. 実践的な最適化チェックリスト

GPUメモリ最適化戦略

  1. 量子化の適用: INT4/INT8量子化でメモリを50〜75%削減
  2. KVキャッシュの最適化: max_model_lenの制限、GQAモデルの選択
  3. Flash Attention 2: SRAMを活用してメモリをO(n²)からO(n)に削減
  4. モデルシャーディング: テンソル並列またはパイプライン並列でマルチGPUを活用
  5. Continuous batching: GPU利用率の最大化

推論速度の最適化

# 最適化されたvLLMサーバー設定例
vllm_config = {
    "model": "meta-llama/Llama-3.1-8B-Instruct",
    "dtype": "bfloat16",
    "tensor_parallel_size": 1,
    "gpu_memory_utilization": 0.90,    # GPUメモリの90%を使用
    "max_model_len": 8192,
    "max_num_batched_tokens": 8192,    # バッチあたりの最大トークン数
    "max_num_seqs": 256,               # 同時処理シーケンス数
    "enable_chunked_prefill": True,    # Chunked prefillを有効化
    "block_size": 16,                  # KVキャッシュブロックサイズ(PagedAttention)
    "swap_space": 4,                   # CPUスワップ領域(GB)
    "enforce_eager": False,            # CUDAグラフを使用
    "disable_log_stats": False,
}

クイズ: 理解度チェック

Q1. LLM推論におけるprefillフェーズとdecodeフェーズのcompute特性が異なる理由は?

答え: Prefillはcompute-bound、Decodeはmemory-bound

解説: Prefillフェーズでは、プロンプト内のすべてのトークンを並列処理します。バッチ処理に類似し、演算強度が高くGPUの演算ユニットを最大限に活用します(compute-bound)。一方、Decodeフェーズでは、前回生成したすべてのトークンのKVキャッシュを読み込みながら1トークンを生成します。毎ステップ、モデル重み全体とKVキャッシュをメモリから読み込む必要があるため、演算強度が極めて低くmemory-boundになります。H100のridge pointが約295 FLOP/byteなのに対し、decodeフェーズのAIはわずか1〜2 FLOP/byteです。

Q2. PagedAttentionが従来のKVキャッシュ管理よりメモリ効率が高い理由は?

答え: 非連続物理ブロック割り当てと動的割り当てによるフラグメンテーションの排除

解説: 従来の方式では、各リクエストに最大シーケンス長分の連続メモリを事前予約します。実際には短く終わるリクエストも長いメモリを占有する内部フラグメンテーション、そしてさまざまなサイズのリクエストが終了するたびに生じる外部フラグメンテーションが深刻です。実際のシステムでKVキャッシュの60〜80%が無駄になっています。PagedAttentionはOSのページングと同様にKVキャッシュを固定サイズのブロックに分割し、必要な時にのみ割り当てます。非連続物理メモリを論理ブロックで抽象化するためフラグメンテーションがほぼなく、共通プロンプトを持つリクエストはCopy-on-WriteでKVブロックを共有できます。

Q3. AWQがGPTQより重要な重みをうまく保護できる方法は?

答え: 活性化値の大きさに基づいてチャンネルごとにスケーリングし、重要な重みを保護

解説: GPTQは2次近似(Hessian)を使って量子化誤差を最小化しますが、すべての重みをほぼ同等に扱います。AWQ(Activation-aware Weight Quantization)は活性化値の分布を分析し、大きな活性化値を持つチャンネル(顕著なチャンネル)がモデル性能に不均衡に寄与するという観察に基づいています。これらの重要なチャンネルの重みにはスケールファクターを乗算して量子化前に値を拡大し、推論時には対応する活性化に逆数を乗算して補償します。重要な重みを保護しながらハードウェアに優しい均一量子化を維持できるため、同じビット幅でGPTQよりperplexityが低くなります。

Q4. Continuous batchingがstatic batchingよりGPU利用率を高める方法は?

答え: イテレーションレベルスケジューリングにより、完了したシーケンスのスロットを即座に再利用

解説: Static batchingはバッチ内の全リクエストが完了するまでGPUを待機させます。最も長いシーケンスが完了するまで、短く終わったリクエストのGPUスロットが無駄になります。Continuous batching(イテレーションレベルスケジューリング)は毎推論ステップでバッチを再構成します。あるシーケンスがEOSトークンを生成するかmax_tokensに達すると、そのスロットに即座に新しい待機リクエストを追加します。結果として、GPUは常に最大バッチサイズで動作し、実験ではstatic batchingと比べてスループットが2〜5倍向上します。vLLMの論文では、Hugging Faceの静的サービングと比較して最大24倍のスループット向上が報告されています。

Q5. Tensor ParallelismとPipeline Parallelismの通信パターンの違いは?

答え: テンソル並列はレイヤーごとにAllReduce、パイプライン並列はレイヤー境界でのP2P転送

解説: Tensor Parallelismは各Transformerレイヤーの重み行列を複数のGPUに分割します。各レイヤーの演算後、すべてのGPUがAllReduce集合通信で部分結果を合算する必要があります。レイヤーが80個あれば80回のAllReduceが必要で、通信レイテンシが累積します。NVLinkのような高帯域幅インターコネクトが必須です。一方、Pipeline Parallelismはレイヤーグループの境界でのみactivationを次のGPUに転送します。通信回数は少ないですが、パイプラインバブル(上流GPUが計算中に下流GPUが待機)が発生します。ノード内NVLink環境にはテンソル並列、ノード間InfiniBand環境にはパイプライン並列が適しています。


おわりに

LLM推論最適化は、ハードウェアの物理的限界をソフトウェアで乗り越える挑戦です。GPUメモリ階層を理解し、KVキャッシュを効率的に管理し、適切な量子化とバッチング戦略を組み合わせることで、同じハードウェアでも格段に優れたパフォーマンスを達成できます。

重要なまとめ:

  • メモリ節約: AWQ/GPTQ 4ビット量子化により、70BモデルをA100 80G 1枚で実行可能
  • スループット向上: vLLMのPagedAttention + continuous batchingで静的サービング比最大24倍のスループット
  • レイテンシ削減: TensorRT-LLMによるCUDAカーネル最適化、FP8活用
  • スケールアウト: テンソル/パイプライン並列で単一GPU限界を超えてマルチGPUクラスターを活用