LabHub

ブログ

LLM量子化(Quantization)実践ガイド:GPTQ・AWQ・GGUFフォーマット比較と精度-性能トレードオフ

한국어English日本語

LLM 量子化 実践ガイド

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 数値表現と精度

ディープラーニングモデルで使う数値表現の種類と特性を理解することが、量子化の出発点である。

データ型ビット数範囲用途
FP3232約 1.18e-38 ~ 3.4e+38学習の既定値
FP1616約 5.96e-8 ~ 65504混合精度学習
BF1616FP32 と同じ範囲、低い精度大規模モデル学習
INT88-128 ~ 127量子化推論
INT44-8 ~ 7積極的な量子化
NF44正規分布に最適化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)

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 の主な特徴は次のとおりである。

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 の中心的な原理は次のとおりである。

  1. 活性化の考慮: 入力活性化の大きさが大きいチャネルの重みほど重要
  2. チャネル別スケーリング: 重要なチャネルの重みをスケールアップして量子化誤差を減らす
  3. 等価変換: スケーリング係数を次のレイヤーへ吸収させ、追加の演算コストをなくす
  4. 重みのみ量子化: 活性化は FP16 のまま維持する

4.2 AWQ vs GPTQ の中心的な違い

特性GPTQAWQ
量子化の方式ヘッセ行列ベースの誤差補償活性化を考慮したスケーリング
キャリブレーション速度遅い (15-30 分)速い (5-10 分)
4bit の精度良い非常に良い
推論速度速いさらに速い
メモリ効率良い良い
カーネル最適化ExLlama v2AWQ 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 フォーマットを置き換えるもので、次のような特徴を持つ。

5.2 GGUF 量子化タイプの比較

量子化タイプビット方式品質速度用途
Q2_K2.6K-quant mixed低い非常に速い極限の圧縮
Q3_K_S3.4K-quant small普通以下速いメモリ制約時
Q3_K_M3.9K-quant medium普通速いバランス
Q4_04.5Legacy普通速いレガシー
Q4_K_S4.6K-quant small良い速い推奨
Q4_K_M4.8K-quant medium良い普通推奨 (既定)
Q5_K_S5.5K-quant small非常に良い普通品質重視
Q5_K_M5.7K-quant medium非常に良い普通品質重視
Q6_K6.6K-quant優秀遅いほぼ原本水準
Q8_08.5Round-to-nearest最上遅い最高品質
F1616量子化なし原本最も遅い基準値

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 ビット量子化である。

中心的な特徴は次のとおりである。

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 メモリ使用量の比較

モデルサイズFP16GPTQ 4bitAWQ 4bitGGUF Q4_K_MNF4
7B14 GB4.2 GB4.1 GB4.4 GB4.5 GB
13B26 GB7.8 GB7.6 GB8.2 GB8.4 GB
34B68 GB20 GB19.5 GB21 GB21.5 GB
70B140 GB40 GB39 GB42 GB43 GB

7.2 Perplexity の比較 (WikiText-2)

モデルFP16GPTQ 4bitAWQ 4bitGGUF Q4_K_MGGUF Q5_K_M
Llama 3 8B6.146.486.326.416.22
Llama 3 70B3.323.553.423.513.38
Mistral 7B5.255.585.415.495.32
Qwen2.5 72B3.183.413.293.373.24

7.3 推論速度の比較 (tokens/sec, A100 80GB)

モデルFP16GPTQ 4bitAWQ 4bitGGUF Q4_K_M (GPU)
Llama 3 8B (bs=1)45829578
Llama 3 8B (bs=16)58011501320N/A
Llama 3 70B (bs=1)12283224
Llama 3 70B (bs=16)142320365N/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 + MetalApple Silicon 最適化
ファインチューニングNF4 + QLoRAメモリ効率の良い学習
エッジデバイスGGUF Q3_K_M最小メモリ
高品質サービングAWQ 4bitperplexity の劣化が小さい
マルチ 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 性能最適化チェックリスト

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 年の量子化技術トレンド

  1. AWQ の標準化: vLLM や TensorRT-LLM など主要なサービングフレームワークが AWQ を既定の量子化フォーマットとして採用
  2. FP8 量子化: H100/H200 GPU の FP8 ネイティブ対応により、8 ビット量子化が性能劣化なしに可能
  3. GGUF エコシステムの拡大: llama.cpp、ollama、LM Studio などローカル推論ツールの急成長
  4. 混合精度量子化: レイヤーごとに異なるビット数を適用して精度と効率を最適化
  5. 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. 参考資料

  1. GPTQ: Accurate Post-Training Quantization for Generative Pre-trained Transformers - Frantar et al., 2022
  2. AWQ: Activation-aware Weight Quantization for LLM Compression and Acceleration - Lin et al., 2023
  3. QLoRA: Efficient Finetuning of Quantized LLMs - Dettmers et al., 2023
  4. llama.cpp GitHub Repository - GGUF フォーマットと量子化の実装
  5. AutoGPTQ GitHub Repository - GPTQ 自動量子化ツール
  6. AutoAWQ GitHub Repository - AWQ 自動量子化ツール
  7. vLLM Quantization Documentation - vLLM 量子化ガイド
  8. bitsandbytes GitHub Repository - NF4 および INT8 量子化
  9. The Era of 1-bit LLMs (BitNet) - Ma et al., 2024
  10. SmoothQuant: Accurate and Efficient Post-Training Quantization - Xiao et al., 2022

コメント

まだコメントはありません。

ログインするとコメントできます