科学と神々株式会社アドベントカレンダー 2025
LLM量子化 Day 24: パフォーマンス最適化
大規模モデルの現実
LLMの量子化は、計算リソースとの戦いです:
モデルサイズと必要リソース:
├── 7Bモデル
│ ├── FP16重み: 14GB
│ ├── 量子化時のピークメモリ: 20GB以上
│ └── 処理時間: 10〜30分
│
├── 13Bモデル
│ ├── FP16重み: 26GB
│ ├── 量子化時のピークメモリ: 35GB以上
│ └── 処理時間: 30分〜1時間
│
└── 70Bモデル
├── FP16重み: 140GB
├── 量子化時のピークメモリ: 180GB以上
└── 処理時間: 数時間
一般的なPCのメモリ(16〜64GB)では、大きなモデルを扱うのが難しいことがわかります。
メモリ効率化の戦略
戦略1: レイヤー単位の処理
モデル全体をメモリに載せるのではなく、レイヤーごとに処理します:
全体をメモリに載せる方式:
├── 全レイヤーを読み込み: 14GB
├── 量子化処理: +4GB
└── 合計: 18GB必要
レイヤー単位の方式:
├── 1レイヤーを読み込み: 0.5GB
├── 量子化処理: +0.2GB
├── 保存してメモリ解放
└── 次のレイヤーへ
ピーク: 0.7GB程度
メモリ使用量を大幅に削減できます。
戦略2: 明示的なメモリ解放
Pythonのガベージコレクションに頼らず、明示的にメモリを解放します:
for layer_idx in range(total_layers):
# レイヤーを読み込み
layer_weights = load_layer(model, layer_idx)
# 量子化
quantized = quantize_weights(layer_weights)
# 保存
save_quantized_layer(quantized, layer_idx)
# 明示的にメモリ解放
del layer_weights
del quantized
gc.collect() # ガベージコレクション強制実行
戦略3: 一時ファイルの活用
メモリに収まらないデータは、ディスクに退避します:
with tempfile.TemporaryDirectory() as tmpdir:
# 中間結果をディスクに保存
intermediate_path = Path(tmpdir) / "intermediate.bin"
save_to_disk(data, intermediate_path)
# メモリを解放
del data
gc.collect()
# 必要になったら読み込み
data = load_from_disk(intermediate_path)
# withブロック終了で自動クリーンアップ
出力サイズの事前推定
処理前に必要なリソースを見積もることで、失敗を防ぎます:
$ llm-quantize info meta-llama/Llama-2-7b-hf
Model: meta-llama/Llama-2-7b-hf
Architecture: LlamaForCausalLM
Parameters: 7,000,000,000
Original size: 14 GB
Estimated quantized sizes:
├── Q2_K: 2.1 GB (85% reduction)
├── Q4_K_M: 4.0 GB (71% reduction)
├── Q5_K_M: 5.0 GB (64% reduction)
└── Q8_0: 7.5 GB (46% reduction)
ディスク容量が足りるか、事前に確認できます。
推定式の仕組み
推定サイズ = パラメータ数 × (ビット数 / 16) × オーバーヘッド係数
例: 7Bモデル、Q4_K_M
= 7,000,000,000 × (4 / 16) × 1.1
= 7,000,000,000 × 0.25 × 1.1
= 1,925,000,000 bytes
≈ 1.8 GB (理論値)
実測: 約4.0 GB(k-quantのメタデータ込み)
k-quantは追加のメタデータを持つため、単純な計算より大きくなります。
スループット最適化
処理速度を上げるための工夫:
1. I/Oのバッファリング
非効率: 小さな単位で頻繁にI/O
├── write(100bytes)
├── write(100bytes)
└── ...1000回
効率的: バッファリングしてまとめてI/O
├── buffer.append(100bytes)
├── buffer.append(100bytes)
└── buffer.flush(100KB) # まとめて書き込み
2. 並列処理の検討
シーケンシャル処理:
Layer 1 → Layer 2 → Layer 3 → ...
|────────────────────────────────|
総時間: T × N
並列処理(検討中):
Layer 1 ──┐
Layer 2 ──┼── → 結合
Layer 3 ──┘
|─────────|
総時間: T + α
ただし、LLMの量子化はメモリ制約が厳しいため、並列化のメリットは限定的です。
3. キャッシュの活用
同じモデルを複数形式に変換する場合:
非効率:
├── モデル読み込み → GGUF変換
├── モデル読み込み → AWQ変換 ← 再読み込み
└── モデル読み込み → GPTQ変換 ← 再読み込み
効率的:
├── モデル読み込み
├── → GGUF変換
├── → AWQ変換 ← キャッシュから
└── → GPTQ変換 ← キャッシュから
メモリ監視
処理中のメモリ使用量を監視することで、問題を早期に検出できます:
Layer 10/32 [████████████] 31% Memory: 12.5GB / 16GB
↑
危険水準に近い
メモリが上限に近づいたら、警告を出すこともできます。
パフォーマンスのボトルネック
量子化処理のボトルネックは状況によって異なります:
ボトルネックの特定:
CPU bound(計算量がボトルネック):
├── CPU使用率: 100%
├── メモリ使用率: 低い
└── 対策: より速いCPU、または並列化
Memory bound(メモリ帯域がボトルネック):
├── CPU使用率: 中程度
├── メモリ使用率: 高い
└── 対策: より高速なメモリ、またはレイヤー単位処理
I/O bound(ディスクがボトルネック):
├── CPU使用率: 低い
├── ディスク使用率: 100%
└── 対策: SSD、またはバッファリング
Tips: パフォーマンス改善のコツ
1. 計測してから最適化
# 時間計測
time llm-quantize quantize model gguf -q Q4_K_M
# メモリ計測
/usr/bin/time -v llm-quantize quantize model gguf -q Q4_K_M
推測ではなく、実測に基づいて最適化しましょう。
2. 適切なハードウェアを選ぶ
推奨環境:
├── RAM: 32GB以上(7Bモデル)、64GB以上(13Bモデル)
├── ストレージ: SSD(HDDは10倍以上遅い)
├── GPU: AWQ/GPTQ量子化には必須
└── CPU: マルチコアが有利
3. 不要なプロセスを停止
# バックグラウンドプロセスを確認
htop
# 大きなメモリを使っているプロセスを特定
ps aux --sort=-%mem | head
4. スワップを避ける
# スワップ使用状況を確認
free -h
# スワップが使われ始めたら危険信号
# → モデルサイズを下げるか、メモリを増やす
5. 段階的にサイズを上げる
# まず小さいモデルで動作確認
llm-quantize quantize TinyLlama/TinyLlama-1.1B-Chat-v1.0 gguf -q Q4_K_M
# 問題なければ7B
llm-quantize quantize meta-llama/Llama-2-7b-hf gguf -q Q4_K_M
# さらに13B...
次回予告
Day 25では「まとめと展望」として、25日間で学んだことの振り返りと、LLM量子化の未来について解説します。
パフォーマンス最適化は「芸術」です。万能の解決策はなく、状況に応じた判断が求められます。計測し、ボトルネックを特定し、適切な対策を講じる——この繰り返しが最適化の本質です。