- 1. はじめに: なぜ量子化なのか
- 2. 量子化の基礎理論
- 3. GPTQ: 初期の革新
- 4. AWQ: 活性化を考慮した量子化
- 5. GGUF: エッジデバイスの標準
- 6. bitsandbytes NF4: QLoRA の中核
- 7. 総合比較とベンチマーク
- 8. vLLM での量子化モデルのサービング
- 9. 量子化フォーマット選択ガイド
- 10. トラブルシューティング
- 11. 実践的なベンチマークスクリプト
- 12. 最新動向と展望
- 13. 参考資料

1. はじめに: なぜ量子化なのか
LLM をプロダクションへデプロイするとき、最大のボトルネックは GPU メモリと推論コストである。Llama 3 70B モデルは FP16 基準で約 140GB の GPU メモリを必要とし、これは A100 80GB GPU 2 枚が最低要件になることを意味する。量子化(Quantization)は、モデル重みの精度を落としてメモリ使用量と演算量を大幅に削減する技術である。
2025-2026 年現在、量子化技術は単なるコスト削減手段を超えて、プロダクション配備の必須要素として定着した。AWQ は GPTQ に比べて精度と速度の両方で優位を示し、llama.cpp の GGUF フォーマットはエッジデバイス配備の事実上の標準になった。
本記事では、量子化の基礎理論から GPTQ、AWQ、GGUF、bitsandbytes NF4 までの主要手法を比較分析し、実践的なコードとベンチマークを通じて最適な量子化戦略を提示する。
2. 量子化の基礎理論
2.1 数値表現と精度
ディープラーニングモデルで使う数値表現の種類と特性を理解することが、量子化の出発点である。
| データ型 | ビット数 | 範囲 | 用途 |
|---|---|---|---|
| FP32 | 32 | 約 1.18e-38 ~ 3.4e+38 | 学習の既定値 |
| FP16 | 16 | 約 5.96e-8 ~ 65504 | 混合精度学習 |
| BF16 | 16 | FP32 と同じ範囲、低い精度 | 大規模モデル学習 |
| INT8 | 8 | -128 ~ 127 | 量子化推論 |
| INT4 | 4 | -8 ~ 7 | 積極的な量子化 |
| NF4 | 4 | 正規分布に最適化 | QLoRA/bitsandbytes |
2.2 量子化の数学的原理
量子化は、連続的な浮動小数点の値を離散的な整数値へ写像する過程である。
import numpy as np
def symmetric_quantize(weights, n_bits=8):
"""対称量子化: zero point が 0"""
qmax = 2**(n_bits - 1) - 1
qmin = -2**(n_bits - 1)
# スケールの計算
abs_max = np.max(np.abs(weights))
scale = abs_max / qmax
# 量子化
quantized = np.clip(np.round(weights / scale), qmin, qmax).astype(np.int8)
return quantized, scale
def asymmetric_quantize(weights, n_bits=8):
"""非対称量子化: zero point を使用"""
qmax = 2**n_bits - 1
qmin = 0
w_min = np.min(weights)
w_max = np.max(weights)
scale = (w_max - w_min) / (qmax - qmin)
zero_point = int(np.round(-w_min / scale))
quantized = np.clip(
np.round(weights / scale) + zero_point, qmin, qmax
).astype(np.uint8)
return quantized, scale, zero_point
def dequantize(quantized, scale, zero_point=0):
"""逆量子化: 整数を再び浮動小数点へ"""
return scale * (quantized.astype(np.float32) - zero_point)
# 例
weights = np.random.randn(1000).astype(np.float32)
q_sym, scale_sym = symmetric_quantize(weights, n_bits=4)
q_asym, scale_asym, zp = asymmetric_quantize(weights, n_bits=4)
print(f"元のメモリ: {weights.nbytes} bytes")
print(f"INT4 メモリ: {q_sym.nbytes // 2} bytes (理論値)")
print(f"圧縮率: {weights.nbytes / (q_sym.nbytes // 2):.1f}x")
2.3 PTQ vs QAT
量子化手法は大きく二つに分かれる。
Post-Training Quantization (PTQ)
- すでに学習済みのモデルへ量子化を適用する
- 追加学習が不要なので速く手軽
- GPTQ、AWQ、GGUF がこの方式
- キャリブレーションデータのみ必要 (通常 128~512 サンプル)
Quantization-Aware Training (QAT)
- 学習過程で量子化の効果をシミュレートする
- モデルが量子化ノイズに適応し、より高い精度が得られる
- 学習全体のコストが発生する
- 小規模モデルで主に使われる
# PTQ vs QAT の比較パイプライン
class QuantizationPipeline:
"""量子化パイプラインの比較"""
@staticmethod
def ptq_pipeline(model, calibration_data, method="awq"):
"""PTQ パイプライン"""
# 1. キャリブレーションデータで統計を収集
# 2. 量子化パラメータの決定 (scale, zero_point)
# 3. 重みの量子化を実行
# 4. 量子化されたモデルを保存
steps = [
"Load pretrained model",
"Run calibration (128 samples)",
"Compute quantization parameters",
"Quantize weights",
"Save quantized model",
]
return steps
@staticmethod
def qat_pipeline(model, training_data):
"""QAT パイプライン"""
# 1. Fake quantization ノードの挿入
# 2. 全体または部分的な再学習
# 3. 実際の量子化を適用
# 4. 量子化されたモデルを保存
steps = [
"Insert fake quantization nodes",
"Fine-tune with quantization simulation",
"Convert to actual quantized model",
"Evaluate and save",
]
return steps
2.4 グループ量子化 (Group Quantization)
現代の量子化手法のポイントはグループ量子化である。テンソル全体に一つの scale/zero_point を使う代わりに、一定サイズのグループ単位で別々の量子化パラメータを使う。
def group_quantize(weights, n_bits=4, group_size=128):
"""グループ単位の量子化"""
qmax = 2**(n_bits - 1) - 1
qmin = -2**(n_bits - 1)
# グループ単位で分割
num_groups = weights.shape[-1] // group_size
w_grouped = weights.reshape(-1, num_groups, group_size)
scales = []
zeros = []
quantized_groups = []
for i in range(num_groups):
group = w_grouped[:, i, :]
abs_max = np.max(np.abs(group), axis=-1, keepdims=True)
scale = abs_max / qmax
q = np.clip(np.round(group / (scale + 1e-10)), qmin, qmax)
quantized_groups.append(q)
scales.append(scale)
return quantized_groups, scales
# group_size が小さいほど精度は高いがオーバーヘッドが増える
# 一般に group_size=128 が最適な均衡点
3. GPTQ: 初期の革新
3.1 GPTQ アルゴリズムの原理
GPTQ(Generative Pre-trained Transformer Quantization)は 2022 年に Frantar et al. が提案した PTQ アルゴリズムで、Optimal Brain Quantization(OBQ)を改善したものである。中心的なアイデアは、量子化誤差を最小化するためにヘッセ行列(Hessian)の情報を活用することにある。
GPTQ の主な特徴は次のとおりである。
- 列(column)単位で逐次的に量子化を行う
- ある列の量子化誤差を、以降の列の重み更新で補償する
- Cholesky 分解を活用した効率的なヘッセ行列の逆行列計算
- キャリブレーションデータは 128 個で十分
3.2 AutoGPTQ を用いた量子化
from transformers import AutoTokenizer
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
import torch
# 1. モデルとトークナイザのロード
model_name = "meta-llama/Llama-3.1-8B"
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 2. 量子化の設定
quantize_config = BaseQuantizeConfig(
bits=4, # 4 ビット量子化
group_size=128, # グループサイズ
desc_act=True, # Activation order (精度の向上)
damp_percent=0.01, # ダンピング比率
sym=True, # 対称量子化
true_sequential=True, # 逐次的な量子化 (精度の向上)
model_seqlen=4096, # シーケンス長
)
# 3. モデルのロード
model = AutoGPTQForCausalLM.from_pretrained(
model_name,
quantize_config=quantize_config,
torch_dtype=torch.float16,
)
# 4. キャリブレーションデータの準備
from datasets import load_dataset
dataset = load_dataset("wikitext", "wikitext-2-raw-v1", split="train")
calibration_texts = [text for text in dataset["text"] if len(text) > 100][:128]
calibration_data = []
for text in calibration_texts:
tokens = tokenizer(
text,
return_tensors="pt",
max_length=2048,
truncation=True,
padding=False,
)
calibration_data.append(tokens.input_ids)
# 5. 量子化の実行 (7B モデル基準で約 15-30 分)
model.quantize(calibration_data)
# 6. 量子化されたモデルの保存
output_dir = "./llama3-8b-gptq-4bit"
model.save_quantized(output_dir)
tokenizer.save_pretrained(output_dir)
print(f"量子化が完了しました! 保存先: {output_dir}")
3.3 GPTQ モデルの推論
from auto_gptq import AutoGPTQForCausalLM
from transformers import AutoTokenizer
# 量子化されたモデルのロード
model = AutoGPTQForCausalLM.from_quantized(
"./llama3-8b-gptq-4bit",
device_map="auto",
use_safetensors=True,
inject_fused_attention=True, # Fused attention を使用
inject_fused_mlp=True, # Fused MLP を使用
use_triton=False, # CUDA カーネルを使用
disable_exllama=False, # ExLlama v2 カーネルを有効化
exllama_config={"version": 2}, # ExLlama v2 を使用
)
tokenizer = AutoTokenizer.from_pretrained("./llama3-8b-gptq-4bit")
# 推論
prompt = "Explain quantum computing in simple terms:"
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=256,
temperature=0.7,
do_sample=True,
)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
4. AWQ: 活性化を考慮した量子化
4.1 AWQ アルゴリズムの原理
AWQ(Activation-aware Weight Quantization)は MIT Han Lab が 2023 年に提案した量子化手法で、GPTQ の限界を克服する。中心的な洞察は「すべての重みが等しく重要なわけではない」という点にある。
AWQ の中心的な原理は次のとおりである。
- 活性化の考慮: 入力活性化の大きさが大きいチャネルの重みほど重要
- チャネル別スケーリング: 重要なチャネルの重みをスケールアップして量子化誤差を減らす
- 等価変換: スケーリング係数を次のレイヤーへ吸収させ、追加の演算コストをなくす
- 重みのみ量子化: 活性化は FP16 のまま維持する
4.2 AWQ vs GPTQ の中心的な違い
| 特性 | GPTQ | AWQ |
|---|---|---|
| 量子化の方式 | ヘッセ行列ベースの誤差補償 | 活性化を考慮したスケーリング |
| キャリブレーション速度 | 遅い (15-30 分) | 速い (5-10 分) |
| 4bit の精度 | 良い | 非常に良い |
| 推論速度 | 速い | さらに速い |
| メモリ効率 | 良い | 良い |
| カーネル最適化 | ExLlama v2 | AWQ GEMM カーネル |
| vLLM 対応 | 対応 | 優先的に対応 |
| 大型モデル (70B+) | 精度低下の可能性 | 安定 |
4.3 AutoAWQ を用いた量子化
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
# 1. モデルのロード
model_name = "meta-llama/Llama-3.1-8B"
model = AutoAWQForCausalLM.from_pretrained(model_name)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 2. 量子化の設定
quant_config = {
"zero_point": True, # 非対称量子化 (精度の向上)
"q_group_size": 128, # グループサイズ
"w_bit": 4, # 4 ビット量子化
"version": "GEMM", # GEMM カーネルを使用 (バッチ推論に最適)
}
# 3. 量子化の実行 (7B モデル基準で約 5-10 分)
model.quantize(tokenizer, quant_config=quant_config)
# 4. 保存
output_dir = "./llama3-8b-awq-4bit"
model.save_quantized(output_dir)
tokenizer.save_pretrained(output_dir)
print(f"AWQ 量子化が完了しました! 保存先: {output_dir}")
4.4 AWQ GEMM vs GEMV カーネル
AWQ は二種類のカーネル実装を提供する。
# GEMM カーネル: バッチ推論に最適化
quant_config_gemm = {
"zero_point": True,
"q_group_size": 128,
"w_bit": 4,
"version": "GEMM", # バッチサイズ > 1 のとき最適
}
# GEMV カーネル: 単一リクエストの推論に最適化
quant_config_gemv = {
"zero_point": True,
"q_group_size": 128,
"w_bit": 4,
"version": "GEMV", # バッチサイズ = 1 のとき最適
}
# 選択ガイド:
# - サービング (複数ユーザーの同時処理) -> GEMM
# - ローカル推論 (単一ユーザー) -> GEMV
# - vLLM 統合 -> GEMM (必須)
4.5 AWQ の高度な設定
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
model_name = "meta-llama/Llama-3.1-70B"
# 大型モデルの量子化設定
model = AutoAWQForCausalLM.from_pretrained(
model_name,
safetensors=True,
device_map="auto", # 複数 GPU へ自動分配
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 70B モデルの最適設定
quant_config = {
"zero_point": True,
"q_group_size": 128,
"w_bit": 4,
"version": "GEMM",
}
# カスタムキャリブレーションデータの使用
from datasets import load_dataset
# ドメイン特化のキャリブレーションデータ
calib_data = load_dataset(
"HuggingFaceH4/ultrachat_200k",
split="train_sft",
)
# 量子化の実行 (70B モデル基準で約 1-2 時間、A100 80GB x2)
model.quantize(
tokenizer,
quant_config=quant_config,
calib_data=calib_data,
n_samples=128,
seqlen=2048,
)
model.save_quantized("./llama3-70b-awq-4bit")
tokenizer.save_pretrained("./llama3-70b-awq-4bit")
5. GGUF: エッジデバイスの標準
5.1 GGUF フォーマットの概要
GGUF(GPT-Generated Unified Format)は llama.cpp プロジェクトが開発したモデルフォーマットで、CPU およびエッジデバイスでの推論に最適化されている。従来の GGML フォーマットを置き換えるもので、次のような特徴を持つ。
- 単一ファイルにモデル重みとメタデータを含む
- 多様な量子化レベルに対応 (Q2_K ~ Q8_0)
- CPU、Metal (Apple)、CUDA、Vulkan など多様なバックエンドに対応
- メモリマップ(mmap)ベースの高速なロード
- llama.cpp、ollama、LM Studio などで利用される
5.2 GGUF 量子化タイプの比較
| 量子化タイプ | ビット | 方式 | 品質 | 速度 | 用途 |
|---|---|---|---|---|---|
| Q2_K | 2.6 | K-quant mixed | 低い | 非常に速い | 極限の圧縮 |
| Q3_K_S | 3.4 | K-quant small | 普通以下 | 速い | メモリ制約時 |
| Q3_K_M | 3.9 | K-quant medium | 普通 | 速い | バランス |
| Q4_0 | 4.5 | Legacy | 普通 | 速い | レガシー |
| Q4_K_S | 4.6 | K-quant small | 良い | 速い | 推奨 |
| Q4_K_M | 4.8 | K-quant medium | 良い | 普通 | 推奨 (既定) |
| Q5_K_S | 5.5 | K-quant small | 非常に良い | 普通 | 品質重視 |
| Q5_K_M | 5.7 | K-quant medium | 非常に良い | 普通 | 品質重視 |
| Q6_K | 6.6 | K-quant | 優秀 | 遅い | ほぼ原本水準 |
| Q8_0 | 8.5 | Round-to-nearest | 最上 | 遅い | 最高品質 |
| F16 | 16 | 量子化なし | 原本 | 最も遅い | 基準値 |
5.3 llama.cpp を用いた GGUF 変換
# 1. llama.cpp のビルド
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make -j$(nproc) LLAMA_CUDA=1 # CUDA 対応ビルド
# 2. HuggingFace モデルを GGUF へ変換
python convert_hf_to_gguf.py \
../meta-llama/Llama-3.1-8B \
--outtype f16 \
--outfile llama3-8b-f16.gguf
# 3. 量子化の実行
./llama-quantize \
llama3-8b-f16.gguf \
llama3-8b-Q4_K_M.gguf \
Q4_K_M
# 4. さまざまな量子化レベルへ変換
for quant in Q3_K_M Q4_K_S Q4_K_M Q5_K_M Q6_K Q8_0; do
./llama-quantize \
llama3-8b-f16.gguf \
"llama3-8b-${quant}.gguf" \
"$quant"
echo "Completed: $quant"
done
# 5. Importance Matrix を活用した高品質な量子化
./llama-imatrix \
-m llama3-8b-f16.gguf \
-f calibration_data.txt \
--output-frequency 10 \
-o imatrix.dat
./llama-quantize \
--imatrix imatrix.dat \
llama3-8b-f16.gguf \
llama3-8b-IQ4_XS.gguf \
IQ4_XS
5.4 GGUF モデルの推論
# llama.cpp CLI で推論
./llama-cli \
-m llama3-8b-Q4_K_M.gguf \
-p "Explain the theory of relativity:" \
-n 256 \
--temp 0.7 \
--top-p 0.9 \
-ngl 35 # GPU へオフロードするレイヤー数
# サーバモードで実行
./llama-server \
-m llama3-8b-Q4_K_M.gguf \
--host 0.0.0.0 \
--port 8080 \
-ngl 35 \
--ctx-size 4096 \
--parallel 4 # 同時リクエスト数
# Python から llama-cpp-python で使う
from llama_cpp import Llama
# モデルのロード
llm = Llama(
model_path="./llama3-8b-Q4_K_M.gguf",
n_gpu_layers=35, # GPU オフロード
n_ctx=4096, # コンテキストサイズ
n_batch=512, # バッチサイズ
verbose=False,
)
# 推論
output = llm(
"Explain quantum computing in simple terms:",
max_tokens=256,
temperature=0.7,
top_p=0.9,
stop=["###", "\n\n\n"],
)
print(output["choices"][0]["text"])
# OpenAI 互換 API として使う
output = llm.create_chat_completion(
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is quantum computing?"},
],
max_tokens=256,
temperature=0.7,
)
print(output["choices"][0]["message"]["content"])
6. bitsandbytes NF4: QLoRA の中核
6.1 NormalFloat4 (NF4) の理論
bitsandbytes の NF4(NormalFloat4)は QLoRA 論文で紹介されたデータ型で、正規分布に従う重みに最適化された 4 ビット量子化である。
中心的な特徴は次のとおりである。
- 重みの分布が正規分布に従うという仮定のもとで、最適な量子化レベルを決める
- 通常の INT4 に比べて情報理論的に最適
- 二重量子化(Double Quantization)により、量子化定数のメモリも節約する
- 推論よりもファインチューニング(QLoRA)で主に使われる
6.2 bitsandbytes の 4 ビットロード
import torch
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
)
# NF4 量子化の設定
bnb_config = BitsAndBytesConfig(
load_in_4bit=True, # 4 ビットでロード
bnb_4bit_quant_type="nf4", # NF4 タイプ
bnb_4bit_compute_dtype=torch.bfloat16, # 演算は BF16
bnb_4bit_use_double_quant=True, # 二重量子化
bnb_4bit_quant_storage=torch.uint8, # 保存タイプ
)
# モデルのロード
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B",
quantization_config=bnb_config,
device_map="auto",
torch_dtype=torch.bfloat16,
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B")
# メモリ使用量の確認
print(f"モデルメモリ: {model.get_memory_footprint() / 1024**3:.2f} GB")
# INT8 量子化も可能
bnb_config_8bit = BitsAndBytesConfig(
load_in_8bit=True,
llm_int8_threshold=6.0, # 外れ値の閾値
llm_int8_has_fp16_weight=False,
)
6.3 bitsandbytes + QLoRA のファインチューニング
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from transformers import TrainingArguments, Trainer
# 4 ビットモデルをファインチューニング可能な状態に準備
model = prepare_model_for_kbit_training(model)
# LoRA の設定
lora_config = LoraConfig(
r=16, # LoRA ランク
lora_alpha=32, # スケーリング係数
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",
],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
# 学習可能なパラメータの確認
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
total_params = sum(p.numel() for p in model.parameters())
print(f"学習可能: {trainable_params:,} / 全体: {total_params:,}")
print(f"比率: {100 * trainable_params / total_params:.2f}%")
7. 総合比較とベンチマーク
7.1 メモリ使用量の比較
| モデルサイズ | FP16 | GPTQ 4bit | AWQ 4bit | GGUF Q4_K_M | NF4 |
|---|---|---|---|---|---|
| 7B | 14 GB | 4.2 GB | 4.1 GB | 4.4 GB | 4.5 GB |
| 13B | 26 GB | 7.8 GB | 7.6 GB | 8.2 GB | 8.4 GB |
| 34B | 68 GB | 20 GB | 19.5 GB | 21 GB | 21.5 GB |
| 70B | 140 GB | 40 GB | 39 GB | 42 GB | 43 GB |
7.2 Perplexity の比較 (WikiText-2)
| モデル | FP16 | GPTQ 4bit | AWQ 4bit | GGUF Q4_K_M | GGUF Q5_K_M |
|---|---|---|---|---|---|
| Llama 3 8B | 6.14 | 6.48 | 6.32 | 6.41 | 6.22 |
| Llama 3 70B | 3.32 | 3.55 | 3.42 | 3.51 | 3.38 |
| Mistral 7B | 5.25 | 5.58 | 5.41 | 5.49 | 5.32 |
| Qwen2.5 72B | 3.18 | 3.41 | 3.29 | 3.37 | 3.24 |
7.3 推論速度の比較 (tokens/sec, A100 80GB)
| モデル | FP16 | GPTQ 4bit | AWQ 4bit | GGUF Q4_K_M (GPU) |
|---|---|---|---|---|
| Llama 3 8B (bs=1) | 45 | 82 | 95 | 78 |
| Llama 3 8B (bs=16) | 580 | 1150 | 1320 | N/A |
| Llama 3 70B (bs=1) | 12 | 28 | 32 | 24 |
| Llama 3 70B (bs=16) | 142 | 320 | 365 | N/A |
7.4 メモリ削減量の計算
def calculate_memory_savings(
model_params_billions: float,
original_bits: int = 16,
target_bits: int = 4,
group_size: int = 128,
):
"""量子化によるメモリ削減量の計算機"""
# 元のメモリ
original_bytes = model_params_billions * 1e9 * (original_bits / 8)
original_gb = original_bytes / (1024**3)
# 量子化後の重みメモリ
quantized_bytes = model_params_billions * 1e9 * (target_bits / 8)
# 量子化メタデータ (scale + zero_point per group)
num_groups = model_params_billions * 1e9 / group_size
# 各グループごとに scale(FP16=2bytes) + zero_point(FP16=2bytes)
metadata_bytes = num_groups * 4
total_quantized_bytes = quantized_bytes + metadata_bytes
quantized_gb = total_quantized_bytes / (1024**3)
# KV キャッシュのメモリ (FP16、別枠)
# 概算: 2 * num_layers * 2 * hidden_dim * seq_len * 2bytes
# この部分は量子化とは無関係
savings_pct = (1 - quantized_gb / original_gb) * 100
return {
"original_gb": round(original_gb, 2),
"quantized_gb": round(quantized_gb, 2),
"savings_gb": round(original_gb - quantized_gb, 2),
"savings_pct": round(savings_pct, 1),
"compression_ratio": round(original_gb / quantized_gb, 2),
}
# 各モデルサイズごとの削減効果
for params in [7, 13, 34, 70]:
result = calculate_memory_savings(params)
print(f"\n{params}B モデル:")
print(f" FP16: {result['original_gb']} GB")
print(f" INT4: {result['quantized_gb']} GB")
print(f" 削減: {result['savings_gb']} GB ({result['savings_pct']}%)")
print(f" 圧縮率: {result['compression_ratio']}x")
8. vLLM での量子化モデルのサービング
8.1 AWQ モデルのサービング
from vllm import LLM, SamplingParams
# AWQ モデルのロード (自動検出)
llm = LLM(
model="./llama3-8b-awq-4bit",
quantization="awq",
dtype="half",
gpu_memory_utilization=0.90,
max_model_len=8192,
tensor_parallel_size=1,
)
# サービング
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=256,
)
prompts = [
"What is machine learning?",
"Explain neural networks:",
"How does gradient descent work?",
]
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
prompt = output.prompt
generated = output.outputs[0].text
print(f"Prompt: {prompt}")
print(f"Output: {generated}\n")
8.2 GPTQ モデルのサービング
from vllm import LLM, SamplingParams
# GPTQ モデルのサービング
llm = LLM(
model="./llama3-8b-gptq-4bit",
quantization="gptq",
dtype="half",
gpu_memory_utilization=0.90,
max_model_len=8192,
)
# vLLM の OpenAI 互換サーバとして実行
# python -m vllm.entrypoints.openai.api_server \
# --model ./llama3-8b-gptq-4bit \
# --quantization gptq \
# --dtype half \
# --port 8000 \
# --gpu-memory-utilization 0.9
8.3 vLLM 量子化モデルの性能最適化
# vLLM サーバの実行 (プロダクション設定)
python -m vllm.entrypoints.openai.api_server \
--model ./llama3-70b-awq-4bit \
--quantization awq \
--dtype half \
--tensor-parallel-size 2 \
--gpu-memory-utilization 0.92 \
--max-model-len 8192 \
--max-num-batched-tokens 32768 \
--max-num-seqs 256 \
--enable-chunked-prefill \
--port 8000
# ベンチマークの実行
python -m vllm.entrypoints.openai.api_server \
--model ./llama3-8b-awq-4bit \
--quantization awq &
# 負荷テスト
python benchmarks/benchmark_serving.py \
--model ./llama3-8b-awq-4bit \
--num-prompts 1000 \
--request-rate 10 \
--endpoint /v1/completions
9. 量子化フォーマット選択ガイド
9.1 意思決定のフロー
量子化フォーマット選択のフロー:
1. 目的は何か?
- ファインチューニング -> bitsandbytes NF4 + QLoRA
- プロダクションサービング -> 2 番へ
- ローカル/エッジ配備 -> GGUF
2. サービングフレームワークは?
- vLLM -> AWQ (最適) または GPTQ
- TensorRT-LLM -> AWQ (推奨)
- 自前実装 -> AWQ
3. ハードウェアは?
- NVIDIA GPU -> AWQ/GPTQ
- Apple Silicon -> GGUF (Metal)
- CPU のみ -> GGUF
- AMD GPU -> GGUF (ROCm/Vulkan)
4. 品質要求の水準は?
- 最大品質 -> Q5_K_M または Q6_K (GGUF)
- バランス -> Q4_K_M (GGUF) または AWQ 4bit
- 最小メモリ -> Q3_K_M または Q2_K (GGUF)
9.2 シナリオ別の推奨
| シナリオ | 推奨フォーマット | 理由 |
|---|---|---|
| プロダクション API サービング | AWQ 4bit + vLLM | 最高の throughput |
| ローカル開発/テスト | GGUF Q4_K_M | 汎用的な互換性 |
| MacBook でのローカル推論 | GGUF Q4_K_M + Metal | Apple Silicon 最適化 |
| ファインチューニング | NF4 + QLoRA | メモリ効率の良い学習 |
| エッジデバイス | GGUF Q3_K_M | 最小メモリ |
| 高品質サービング | AWQ 4bit | perplexity の劣化が小さい |
| マルチ GPU サービング | AWQ + TP | スケールアウト対応 |
| バッチ処理 | GPTQ + ExLlama v2 | 安定したバッチ性能 |
10. トラブルシューティング
10.1 よくある問題と解決策
CUDA OOM (Out of Memory)
# 問題: 量子化中に GPU メモリが不足する
# 解決 1: max_memory の設定
model = AutoAWQForCausalLM.from_pretrained(
model_name,
max_memory={0: "20GiB", 1: "20GiB", "cpu": "30GiB"},
)
# 解決 2: CPU オフロード
model = AutoAWQForCausalLM.from_pretrained(
model_name,
device_map="auto",
offload_folder="./offload",
)
量子化後の品質低下
# 問題: 量子化後に出力品質が急激に低下する
# 解決 1: グループサイズを小さくする
quant_config = {"q_group_size": 64, "w_bit": 4} # 128 -> 64
# 解決 2: 5 ビットを使う
quant_config = {"q_group_size": 128, "w_bit": 5} # 4bit -> 5bit (GPTQ only)
# 解決 3: ドメイン特化のキャリブレーションデータを使う
# 医療/法律など特殊ドメインは、当該ドメインのテキストでキャリブレーションする
vLLM で量子化モデルのロードに失敗する
# 問題: vLLM が量子化モデルを認識しない
# 解決: config.json の quantization_config を確認する
cat model_dir/config.json | python -m json.tool | grep -A 10 quant
# GPTQ config の例 (config.json に含まれている必要がある):
# "quantization_config": {
# "bits": 4,
# "group_size": 128,
# "quant_method": "gptq"
# }
# AWQ config の例:
# "quantization_config": {
# "bits": 4,
# "group_size": 128,
# "quant_method": "awq",
# "version": "gemm",
# "zero_point": true
# }
10.2 性能最適化チェックリスト
- AWQ GEMM カーネルの使用 (バッチサービング時)
- ExLlama v2 カーネルの有効化 (GPTQ 使用時)
- GPU レイヤーオフロード数の最適化 (GGUF)
- キャリブレーションデータのドメイン一致の確認
- group_size 128 の使用 (既定の推奨)
- vLLM の gpu_memory_utilization を 0.9 以上に設定
- Tensor Parallelism による大型モデルの分散
- KV キャッシュサイズの確認と調整
- バッチサイズに応じたカーネルの選択 (GEMM vs GEMV)
- 量子化後の perplexity 測定による品質検証
11. 実践的なベンチマークスクリプト
import time
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
def benchmark_model(model, tokenizer, prompts, max_tokens=128):
"""モデル推論のベンチマーク"""
results = []
for prompt in prompts:
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
# Warmup
with torch.no_grad():
model.generate(**inputs, max_new_tokens=1)
# 測定
torch.cuda.synchronize()
start = time.perf_counter()
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=max_tokens,
do_sample=False,
)
torch.cuda.synchronize()
elapsed = time.perf_counter() - start
num_tokens = outputs.shape[1] - inputs.input_ids.shape[1]
tokens_per_sec = num_tokens / elapsed
results.append({
"prompt_len": inputs.input_ids.shape[1],
"output_len": num_tokens,
"time_sec": round(elapsed, 3),
"tokens_per_sec": round(tokens_per_sec, 1),
})
# GPU メモリ使用量
memory_gb = torch.cuda.max_memory_allocated() / (1024**3)
avg_tps = sum(r["tokens_per_sec"] for r in results) / len(results)
return {
"avg_tokens_per_sec": round(avg_tps, 1),
"gpu_memory_gb": round(memory_gb, 2),
"results": results,
}
# ベンチマーク用プロンプト
test_prompts = [
"Explain the concept of quantum entanglement in detail:",
"Write a Python function to implement binary search:",
"Describe the architecture of a modern web application:",
"What are the key differences between SQL and NoSQL databases?",
"Explain how neural networks learn from data:",
]
# FP16 ベンチマーク
print("=== FP16 Benchmark ===")
model_fp16 = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B",
torch_dtype=torch.float16,
device_map="auto",
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B")
result_fp16 = benchmark_model(model_fp16, tokenizer, test_prompts)
print(f"FP16 - Speed: {result_fp16['avg_tokens_per_sec']} tok/s, "
f"Memory: {result_fp16['gpu_memory_gb']} GB")
del model_fp16
torch.cuda.empty_cache()
# AWQ 4bit ベンチマーク
print("\n=== AWQ 4bit Benchmark ===")
from awq import AutoAWQForCausalLM
model_awq = AutoAWQForCausalLM.from_quantized(
"./llama3-8b-awq-4bit",
fuse_layers=True,
)
result_awq = benchmark_model(model_awq.model, tokenizer, test_prompts)
print(f"AWQ - Speed: {result_awq['avg_tokens_per_sec']} tok/s, "
f"Memory: {result_awq['gpu_memory_gb']} GB")
12. 最新動向と展望
12.1 2026 年の量子化技術トレンド
- AWQ の標準化: vLLM や TensorRT-LLM など主要なサービングフレームワークが AWQ を既定の量子化フォーマットとして採用
- FP8 量子化: H100/H200 GPU の FP8 ネイティブ対応により、8 ビット量子化が性能劣化なしに可能
- GGUF エコシステムの拡大: llama.cpp、ollama、LM Studio などローカル推論ツールの急成長
- 混合精度量子化: レイヤーごとに異なるビット数を適用して精度と効率を最適化
- 1-bit LLM (BitNet): Microsoft の BitNet b1.58 の研究により、極端な量子化の可能性が示された
12.2 FP8 量子化 (次世代の標準)
# vLLM での FP8 量子化 (H100/H200 専用)
from vllm import LLM
llm = LLM(
model="meta-llama/Llama-3.1-70B",
quantization="fp8",
dtype="auto",
tensor_parallel_size=2,
gpu_memory_utilization=0.92,
)
# FP8 は INT4 に比べて:
# - ほぼ無損失 (perplexity の差は 0.1% 未満)
# - メモリ 50% 削減 (FP16 比)
# - H100 の FP8 テンソルコア活用により高い throughput
# - キャリブレーション不要 (動的量子化)
13. 参考資料
- GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers - Frantar et al., 2022
- AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration - Lin et al., 2023
- QLoRA: Efficient Finetuning of Quantized LLMs - Dettmers et al., 2023
- llama.cpp GitHub Repository - GGUF フォーマットと量子化の実装
- AutoGPTQ GitHub Repository - GPTQ 自動量子化ツール
- AutoAWQ GitHub Repository - AWQ 自動量子化ツール
- vLLM Quantization Documentation - vLLM 量子化ガイド
- bitsandbytes GitHub Repository - NF4 および INT8 量子化
- The Era of 1-bit LLMs (BitNet) - Ma et al., 2024
- SmoothQuant: Accurate and Efficient Post-Training Quantization - Xiao et al., 2022