生成AI入門 2026.04.19

量子化とは?AIモデルを1/4サイズにする仕組みとPythonコード【初心者向け】

タグ:AI / 機械学習 / 技術解説 / 初心者向け

量子化とは:数値精度を下げてサイズを圧縮する技術

32bit浮動小数点(fp32)の値:
  3.14159265358979...  ← 4バイト/数値

8bit整数(int8)に量子化後:
  127  ← 1バイト/数値(75%削減)

4bit量子化(Q4)後:
  8  ← 0.5バイト/数値(87.5%削減)

精度は落ちますが、大半のテキスト生成タスクでは実用上問題ありません。

精度とサイズのトレードオフ

精度バイト/数値7Bモデルのサイズ精度低下
fp324 byte~28GBなし(基準)
fp16 / bf162 byte~14GBほぼなし
int81 byte~7GB1〜2%
Q4 (GGUF)0.5 byte~4GB2〜5%
Q2 (GGUF)0.25 byte~2GB5〜15%+

Pythonでの量子化の仕組みを理解する

# quantization_demo.py
# 量子化の数学的な仕組みを示すデモ

import numpy as np

def quantize_to_int8(weights: np.ndarray) -> tuple[np.ndarray, float, float]:
    """fp32の重みをint8に量子化する"""
    # スケールとゼロ点を計算
    w_min, w_max = weights.min(), weights.max()
    scale = (w_max - w_min) / 255  # int8は-128〜127(256段階)
    zero_point = -round(w_min / scale)
    
    # 量子化
    quantized = np.round(weights / scale + zero_point).astype(np.int8)
    
    return quantized, scale, zero_point

def dequantize_from_int8(quantized: np.ndarray, scale: float, zero_point: float) -> np.ndarray:
    """int8から元の値を復元(誤差あり)"""
    return (quantized.astype(np.float32) - zero_point) * scale

# 例:fp32の重みをint8に変換
original_weights = np.array([0.1234, -0.5678, 0.9012, -0.3456, 0.7890], dtype=np.float32)
print(f"元の重み: {original_weights}")
print(f"元のサイズ: {original_weights.nbytes} bytes")

quantized, scale, zp = quantize_to_int8(original_weights)
print(f"\n量子化後(int8): {quantized}")
print(f"サイズ: {quantized.nbytes} bytes({quantized.nbytes/original_weights.nbytes:.0%})")

# 復元して誤差を確認
restored = dequantize_from_int8(quantized, scale, zp)
error = np.abs(original_weights - restored)
print(f"\n復元値: {restored}")
print(f"誤差:   {error}")
print(f"最大誤差: {error.max():.6f}")

実際のモデルをGGUF Q4に変換する(llama.cpp)

# llama.cppをビルド(Mac M1/M2の場合)
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make -j4 LLAMA_METAL=1  # Metal対応

# Hugging Faceからモデルをダウンロード
pip install huggingface_hub
python3 -c "
from huggingface_hub import snapshot_download
snapshot_download('meta-llama/Llama-3.2-1B-Instruct', local_dir='./llama-1b')
"

# fp16に変換(中間ステップ)
python3 convert_hf_to_gguf.py ./llama-1b \
  --outfile ./llama-1b-f16.gguf \
  --outtype f16

# Q4_K_M に量子化(推論精度と速度のバランスが良い設定)
./llama-quantize ./llama-1b-f16.gguf ./llama-1b-Q4_K_M.gguf Q4_K_M

# ファイルサイズ確認
ls -lh llama-1b-f16.gguf llama-1b-Q4_K_M.gguf
# -rw-r--r--  2.5G llama-1b-f16.gguf
# -rw-r--r--  770M llama-1b-Q4_K_M.gguf  ← 70%削減

bitsandbytesで4bit量子化モデルをPythonからロード

# load_quantized.py
# pip install transformers bitsandbytes accelerate torch

import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig

# 4bit量子化設定
quantization_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.bfloat16,  # 計算はbf16で高精度に
    bnb_4bit_quant_type="nf4",              # Normal Float 4(精度が良い)
    bnb_4bit_use_double_quant=True,         # 二重量子化でさらに圧縮
)

model_id = "meta-llama/Llama-3.2-3B-Instruct"

print("モデルをロード中(4bit量子化)...")
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    quantization_config=quantization_config,
    device_map="auto",  # 利用可能なGPUを自動選択
)
tokenizer = AutoTokenizer.from_pretrained(model_id)

# メモリ使用量を確認
if torch.cuda.is_available():
    mem_gb = torch.cuda.memory_allocated() / 1024**3
    print(f"GPU メモリ使用量: {mem_gb:.1f} GB")

# テキスト生成
inputs = tokenizer("Pythonのデコレータとは何ですか?", return_tensors="pt")
outputs = model.generate(**inputs, max_new_tokens=200)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))

Ollamaで量子化モデルを即座に実行

# Ollamaをインストール(最も簡単な方法)
brew install ollama  # Mac

# Q4量子化済みのLlama 3.2をダウンロードして実行
ollama pull llama3.2:3b  # 約2GB(Q4_K_M)
ollama run llama3.2:3b "量子化について200字で説明して"

# サイズ比較
ollama list
# NAME            ID        SIZE    MODIFIED
# llama3.2:3b     ...       2.0 GB  ...  ← Q4量子化済み
# (fp16なら ~6GB になるところを70%削減)

量子化レベルの選び方

目的: テキスト生成・チャット(精度より速度)
  → Q4_K_M(バランス最良・推奨)

目的: 高品質な文章・分析タスク
  → Q5_K_M または Q6_K

目的: 精度を最大化(VRAMに余裕あり)
  → Q8_0 または fp16

目的: とにかく軽く(2〜4GB GPUしかない)
  → Q2_K(精度低下を許容する場合のみ)

あわせて読みたい

参考ソース