モデル量子化: INT8、INT4、GPTQ、AWQ 以降
完全精度の GPT-4 モデルは数百ギガバイトを占有します。 FP16 の Llama-3 70B には 140 GB の VRAM が必要です。 これらの数値により、消費者向けハードウェアで大規模なモデルを実行することは不可能になります。 を適用します 量子化。 INT4 量子化では、同じ Llama-3 70B が 35 GB に低下します。 2 つの RTX 4090 または 64 GB の RAM を搭載したシステムに収まります。すべて精度が失われる 多くの場合は 1% 未満です。
モデルの量子化は、最新の LLM エコシステムで最も重要な技術の 1 つになりました。 これはもはやメモリを節約するためのトリックではありません。これはモデルにアクセスできるようにするための鍵です。 エッジデバイスに導入可能で、消費者向けハードウェアで実行可能で、レイテンシの点で競争力があります。 のようなアルゴリズム GPTQ, AWQ, スムーズクアント そしてフォーマット GGUF の llama.cpp が LLM へのアクセスを民主化しました。
このガイドでは、基本的な数学から方法の選択まで、量子化をゼロから探求します。 各テクニックの実用的なコード例を使用して、ユースケースに適したものを提供します。
何を学ぶか
- 量子化が現代の AI の基礎となる理由
- PTQ (トレーニング後の量子化) と QAT (量子化を意識したトレーニング) の違い
- bitsandbytes と SmoothQuant による INT8 量子化
- NF4、FP4、QLoRA を使用した INT4 量子化
- GPTQ アルゴリズム: その仕組みといつ使用するか
- AWQ (Activation-Aware Weight Quantization): 異種ハードウェアの利点
- GGUF 形式と llama.cpp による量子化
- ベンチマークの精度 vs 速度 vs メモリ
- PyTorch を使用した量子化を意識したトレーニング
- ベストプラクティスと実際の使用例
なぜ量子化するのか? VRAM の問題
入力パラメータ FP32 4バイトを占めます。入力パラメータ FP16/BF16 占める 2バイト。 8 ビット量子化では、各パラメータは 1 バイトを占有します。 INT4 では、わずか 0.5 バイトです。 次の表は、さまざまな精度での Llama-3 70B のメモリ消費量を示しています。
| 精度 | パラメータあたりのバイト数 | メモリ(70B) | GPUが必要 |
|---|---|---|---|
| FP32 | 4バイト | 280GB | 不可能な消費者 |
| BF16/FP16 | 2バイト | 140GB | 2x A100 80GB |
| INT8 | 1バイト | 70GB | 1x A100 80GB |
| INT4 / NF4 | 0.5バイト | 35GB | 2x RTX 4090 (24GB) |
| INT3 / Q3_K_M | 0.375バイト | ~26GB | RTX 3090 + RAM オフロード |
VRAM に加えて、量子化は次の点で利点をもたらします。 スループット (トークン/秒)、 レイテンシ 推論e クラウドコスト。 Apple M シリーズプロセッサなどのハードウェア上 Raspberry Pi では、量子化が 1B パラメーターを超えてモデルを実行する唯一の方法です。
2026 年の市場データ
Gartner は、2027 年までに、 小規模言語モデル (SLM) 量子化された 使用頻度ではクラウド LLM を 3 倍上回ります。オンデバイス AI による削減 の運営コスト クラウドと比較して 70%、ネットワーク遅延を排除します。 API のコスト。量子化市場はエッジ AI、モバイルにとって重要になっています 導入システムと組み込みシステム。
量子化の数学的基礎
量子化は、連続浮動小数点値を離散整数にマップします。プロセス は 2 つの操作に分かれています。 量子化 e 逆量子化.
重み行列が与えられた場合 W FP16 では、INT8 量子化は次のように発生します。
# Quantizzazione uniforme (schema base)
# W_quantized = round(W / scale) + zero_point
# W_dequantized = (W_quantized - zero_point) * scale
import torch
import numpy as np
def quantize_tensor_int8(tensor: torch.Tensor, symmetric: bool = True):
"""
Quantizzazione INT8 uniforme.
symmetric=True: zero_point=0, range [-127, 127]
symmetric=False: range asimmetrico con zero_point
"""
if symmetric:
# Scale basato sul valore assoluto massimo
max_val = tensor.abs().max().item()
scale = max_val / 127.0
zero_point = 0
else:
# Scale basato su min/max
min_val = tensor.min().item()
max_val = tensor.max().item()
scale = (max_val - min_val) / 255.0
zero_point = round(-min_val / scale)
zero_point = max(0, min(255, zero_point))
# Quantizzazione: FP16 -> INT8
quantized = torch.clamp(
torch.round(tensor / scale) + zero_point,
-128, 127
).to(torch.int8)
# De-quantizzazione: INT8 -> FP16 (per verifica errore)
dequantized = (quantized.float() - zero_point) * scale
# Errore di quantizzazione
error = (tensor - dequantized).abs().mean().item()
return quantized, scale, zero_point, error
# Esempio pratico
W = torch.randn(1024, 1024, dtype=torch.float16)
q, scale, zp, err = quantize_tensor_int8(W, symmetric=True)
print(f"Originale: {W.dtype}, {W.element_size() * W.numel() / 1024:.1f} KB")
print(f"Quantizzato: {q.dtype}, {q.element_size() * q.numel() / 1024:.1f} KB")
print(f"Riduzione memoria: {(1 - q.element_size()/W.element_size()) * 100:.0f}%")
print(f"Errore medio assoluto: {err:.6f}")
# Output tipico:
# Originale: torch.float16, 2048.0 KB
# Quantizzato: torch.int8, 1024.0 KB
# Riduzione memoria: 50%
# Errore medio assoluto: 0.000394
PTQ と QAT: 適切なアプローチの選択
モデルの量子化には 2 つの基本的なパラダイムがあります。
- PTQ (トレーニング後の量子化): トレーニング後にモデルに適用されます すでに訓練を受けています。必要なのは小さなキャリブレーション データセットだけです。速くて実用的ですが、 小さいモデルまたは非常に低い精度のモデル (INT2、INT3) では精度が低下する可能性があります。
- QAT (量子化対応トレーニング): トレーニング中に量子化をシミュレートします。 これにより、モデルが精度の低下に合わせて重みを調整できるようになります。それは結果を生み出します より優れていますが、完全な微調整に匹敵する計算リソースが必要です。
PTQ と QAT: 実際の比較
| 待ってます | PTQ | QAT |
|---|---|---|
| 時間 | 分-時間 | 時間-日 |
| キャリブレーションデータセット | 小規模 (512 ~ 2048 サンプル) | 完全なデータセット |
| 量子化用VRAM | 低 (フォワードパスのみ) | 高 (完全な後方パス) |
| 精度INT8 | 優れた (<0.5% 損失) | 素晴らしい |
| INT4の精度 | 良好 (1 ~ 3% の損失) | 非常に良い (<1% 損失) |
| 使用事例 | 大型モデル、迅速な生産 | 小型モデル、最高の品質 |
最新の LLM (7B パラメータ以上) の場合、通常は PTQ で十分です。 パラメータは、INT4 量子化がモデルの機能をほぼ完全に保持することを意味します。 3B パラメータ未満のモデルの場合、精度が重要な場合は QAT をお勧めします。
INT8 とビットサンドバイト: 最も簡単な方法
ビットサンドバイト 実用的な LLM 量子化に最もよく使用されるライブラリです。 もともと Tim Dettmers によって開発され、INT8 と INT4 (NF4、FP4) の両方をサポートし、統合されています。 ハグフェイストランスフォーマーではネイティブです。大きな利点: キャリブレーションデータセットがありません、 モデル読み込み時のオンザフライ量子化。
# Installazione
# pip install bitsandbytes transformers accelerate
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch
# === CONFIGURAZIONE INT8 ===
config_int8 = BitsAndBytesConfig(
load_in_8bit=True,
llm_int8_threshold=6.0, # Outlier threshold (default ottimale)
llm_int8_has_fp16_weight=False
)
# === CONFIGURAZIONE INT4 NF4 (QLoRA style) ===
config_int4 = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NF4: ottimale per pesi normalmente distribuiti
bnb_4bit_compute_dtype=torch.bfloat16, # Compute in BF16 (non INT4!)
bnb_4bit_use_double_quant=True, # Double quantization: -0.4 bit/param extra
bnb_4bit_quant_storage=torch.uint8 # Storage format
)
model_name = "meta-llama/Llama-3.1-8B-Instruct"
# Caricamento con INT4 NF4
model = AutoModelForCausalLM.from_pretrained(
model_name,
quantization_config=config_int4,
device_map="auto", # Distribuisce automaticamente su GPU/CPU
torch_dtype=torch.bfloat16,
trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# Verifica memoria usata
mem_gb = model.get_memory_footprint() / 1024**3
print(f"Memoria modello (INT4): {mem_gb:.2f} GB")
# Llama-3.1-8B: ~4.5 GB vs 16 GB in BF16
# Inferenza standard (identica al modello full-precision)
inputs = tokenizer("Spiegami la quantizzazione dei modelli:", return_tensors="pt").to("cuda")
with torch.no_grad():
outputs = model.generate(
**inputs,
max_new_tokens=200,
temperature=0.7,
do_sample=True
)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
ビットサンドバイトの制限
- 量子化はロード時に行われます。 量子化されたモデルは保存されません 直接(そのためには GPTQ または AWQ を使用します)
- bitsandbytes の INT8 は、8 ビット量子化重みという混合技術を使用しますが、外れ値のアクティブ化は FP16 (LLM.int8()) で処理されます。
- 計算は常に INT4 ではなく BF16/FP16 で行われます。量子化によりメモリは削減されますが、GPTQ/AWQ ほど計算は高速化されません。
- CUDA GPU のない CPU およびシステムでは、パフォーマンスが低下する可能性があります
GPTQ アルゴリズム: レイヤーごとの量子化
GPTQ (Generative Pre-Trained Transformer Quantization、Frantar et al. 2022) および 各レイヤーを個別に量子化し、再構成エラーを最小限に抑える高度な PTQ アルゴリズム。 を使用します。 ヘシアン行列 (校正データによる近似値) を決定する どの重みが量子化の影響を最も受けやすいか、また残留誤差をどのように補正するか。
GPTQ プロセスは、各重み行列の列ごとに量子化し、列を更新します。 導入されたエラーを補うために残ります。これにより、GPTQ はプレーンよりもはるかに正確になります 特に INT4 と INT3 での均一な量子化。
# Installazione AutoGPTQ
# pip install auto-gptq optimum
from transformers import AutoTokenizer
from auto_gptq import AutoGPTQForCausalLM, BaseQuantizeConfig
model_name = "meta-llama/Llama-3.1-8B-Instruct"
output_dir = "./llama-3.1-8b-gptq-int4"
# === DATASET DI CALIBRAZIONE ===
# GPTQ richiede un piccolo dataset (128-512 sample) per calibrare
# la quantizzazione. Più rappresentativo = migliore accuratezza.
from datasets import load_dataset
tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=True)
# Dataset Wikitext per calibrazione (standard per LLM)
calibration_data = load_dataset("wikitext", "wikitext-2-raw-v1", split="train")
calibration_texts = [
text for text in calibration_data["text"]
if len(text.strip()) > 100
][:128] # 128 sample di calibrazione
# Tokenizza i testi di calibrazione
calibration_tokens = [
tokenizer(text, return_tensors="pt", truncation=True, max_length=2048)
for text in calibration_texts
]
# === CONFIGURAZIONE QUANTIZZAZIONE ===
quantize_config = BaseQuantizeConfig(
bits=4, # INT4 quantization
group_size=128, # Dimensione gruppo (più piccolo = più accurato, più lento)
damp_percent=0.01, # Damping factor per stabilità numerica
desc_act=True, # Activation ordering (migliora qualità, opzionale)
sym=True, # Quantizzazione simmetrica
)
# === CARICAMENTO E QUANTIZZAZIONE ===
print("Caricamento modello FP16...")
model = AutoGPTQForCausalLM.from_pretrained(
model_name,
quantize_config=quantize_config,
torch_dtype="auto"
)
print("Avvio quantizzazione GPTQ (richiede ~30-60 min su A100)...")
model.quantize(
calibration_tokens,
use_triton=False, # True per inferenza ottimizzata con triton
batch_size=1,
cache_examples_on_gpu=True
)
# === SALVATAGGIO ===
model.save_quantized(output_dir, use_safetensors=True)
tokenizer.save_pretrained(output_dir)
print(f"Modello GPTQ salvato in: {output_dir}")
# === CARICAMENTO MODELLO GPTQ GIA QUANTIZZATO ===
print("Caricamento modello GPTQ pre-quantizzato...")
model_gptq = AutoGPTQForCausalLM.from_quantized(
output_dir,
use_triton=False,
device_map="auto",
inject_fused_attention=True, # Ottimizzazione memoria attention
inject_fused_mlp=True # Ottimizzazione memoria MLP
)
# Inferenza
inputs = tokenizer("La quantizzazione GPTQ funziona:", return_tensors="pt").to("cuda")
with torch.no_grad():
outputs = model_gptq.generate(**inputs, max_new_tokens=100)
print(tokenizer.decode(outputs[0], skip_special_tokens=True))
GPTQ は、保存可能で展開可能な量子化モデルを生成します。ハグフェイスハブに多数のモデルが登場
接尾辞付き -GPTQ o -4bit このアルゴリズムで量子化されました。
量子化には時間がかかりますが(A100 の 13B モデルでは通常 30 ~ 90 分)、それは起こります。
1 回のみ: 量子化されたモデルは再量子化せずに再利用できます。
AWQ: アクティベーションを意識した重み量子化
AWQ (Lin et al. 2023) は、観察から始まる GPTQ の代替手段です。 異なります。すべての重みが同じように重要であるわけではありません。重量の小さな割合 (約 1%) は大規模なアクティベーションに対応し、不釣り合いに寄与します モデルの予測に。これらの「顕著な重み」がより正確に保存されれば、 全体的な量子化誤差が大幅に減少します。
AWQ scales important weights before quantization, reducing error on critical channels. 量子化プロセスにより、GPTQ と同等以上の品質が得られます。 多くの場合、異種ハードウェア (CPU、Mac M シリーズ、モバイル) 上でより高速でパフォーマンスが向上します。
# pip install autoawq
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer
model_name = "meta-llama/Llama-3.1-8B-Instruct"
output_dir = "./llama-3.1-8b-awq-int4"
# Caricamento tokenizer
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
# Caricamento modello da quantizzare
print("Caricamento modello per quantizzazione AWQ...")
model = AutoAWQForCausalLM.from_pretrained(
model_name,
safetensors=True,
device_map="cuda",
trust_remote_code=True
)
# Configurazione AWQ
quant_config = {
"zero_point": True, # Quantizzazione asimmetrica (migliore per LLM)
"q_group_size": 128, # Dimensione gruppo
"w_bit": 4, # 4-bit quantization
"version": "GEMM" # GEMM: bilanciamento velocità/qualità
# GEMV: ottimizzato per batch size 1 (chatbot)
}
# Dataset di calibrazione personalizzato
calib_data = [
"La quantizzazione AWQ permette di eseguire modelli grandi su hardware limitato.",
"I Transformer hanno rivoluzionato il natural language processing con il meccanismo di attention.",
"Il fine-tuning con LoRA riduce significativamente il numero di parametri addestrabili.",
# ... aggiungere 128-256 esempi rappresentativi del task target
]
print("Avvio quantizzazione AWQ...")
model.quantize(
tokenizer,
quant_config=quant_config,
calib_data=calib_data
)
# Salvataggio
model.save_quantized(output_dir)
tokenizer.save_pretrained(output_dir)
print(f"Modello AWQ salvato: {output_dir}")
# === CARICAMENTO E INFERENZA MODELLO AWQ ===
model_awq = AutoAWQForCausalLM.from_quantized(
output_dir,
fuse_layers=True, # Ottimizzazione kernel fused
trust_remote_code=True,
safetensors=True
)
from transformers import pipeline
pipe = pipeline(
"text-generation",
model=model_awq,
tokenizer=tokenizer,
device_map="auto"
)
result = pipe("Spiegami AWQ in un paragrafo:", max_new_tokens=150)
print(result[0]["generated_text"])
GPTQ と AWQ: どちらを選択すべきですか?
- GPTQ: CUDA を搭載した NVIDIA GPU に最適です。 Triton カーネルによる高速推論。 GPU導入の事実上の標準。バッチ処理に最適です。
- AWQ: 異種ハードウェア (CPU、Mac、モバイル) に最適です。クオンタイズが速くなります。チャットボット アプリケーションに推奨されます (バッチ=1)。シングルトークン GEMV カーネルで有効です。
- 経験則: 専用 GPU サーバー用の GPTQ、マルチプラットフォームおよびエッジ展開用の AWQ。
GGUF と llama.cpp: CPU とエッジの量子化
フォーマット GGUF (GGML 統一フォーマット) は、llama.cpp プロジェクトによって作成されました。 Metal (Apple) によるオプションの GPU サポートを使用して、CPU 上で LLM 推論を有効にします。 CUDA または OpenCL。 GGUF は GGML を継承し、バージョン間の上位互換性の問題を解決します。
GGUF の命名法は、正確なパターンに従っています。 Q[bits]_[variant] ここでバリアント
量子化の種類を示します。最も一般的なものは次のとおりです。
| 形式 | 平均ビット数 | 品質 | 推奨用途 |
|---|---|---|---|
| Q8_0 | 8.0ビット | ほぼロスレス | 強力な CPU で最高の品質を実現 |
| Q6_K | 6.6ビット | 素晴らしい | 品質とサイズのバランス |
| Q5_K_M | 5.7ビット | とても良い | 16 GB 以上の RAM を搭載したデスクトップ |
| Q4_K_M | 4.8ビット | 良い (95%) | 推奨されるデフォルト、8 GB 以上のラップトップ |
| Q3_K_M | 3.9ビット | 許容できる | 非常に限られたハードウェア |
| Q2_K | 2.6ビット | 劣化した | 極限テスト専用 |
接尾語 _K_M 「中」サイズを示します 「K量子化」:高度な技術 最も重要なレイヤーに対して、より高精度のスケール係数を使用したブロック量子化を使用します。 その結果、均一な量子化よりも優れた品質が得られます。
# Conversione e quantizzazione con llama.cpp
# Prima: installare llama.cpp
# git clone https://github.com/ggerganov/llama.cpp
# cd llama.cpp && make -j4
# 1. Convertire modello HuggingFace -> GGUF FP16
python convert_hf_to_gguf.py \
meta-llama/Llama-3.1-8B-Instruct \
--outfile llama-3.1-8b-f16.gguf \
--outtype f16
# 2. Quantizzare in vari formati
./llama-quantize llama-3.1-8b-f16.gguf llama-3.1-8b-q4_k_m.gguf Q4_K_M
./llama-quantize llama-3.1-8b-f16.gguf llama-3.1-8b-q8_0.gguf Q8_0
./llama-quantize llama-3.1-8b-f16.gguf llama-3.1-8b-q5_k_m.gguf Q5_K_M
# 3. Benchmark performance con llama.cpp
./llama-bench \
-m llama-3.1-8b-q4_k_m.gguf \
-p 512 \ # Token prompt
-n 128 \ # Token da generare
-t 8 # Thread CPU
# Output tipico su M2 Pro (16 GB):
# Q4_K_M: prompt 45.2 t/s, generate 28.1 t/s
# Q8_0: prompt 24.1 t/s, generate 15.8 t/s
# === UTILIZZO CON PYTHON via llama-cpp-python ===
# pip install llama-cpp-python
from llama_cpp import Llama
llm = Llama(
model_path="./llama-3.1-8b-q4_k_m.gguf",
n_ctx=4096, # Context window
n_threads=8, # Thread CPU
n_gpu_layers=35, # Offload 35 layer su GPU (0 = solo CPU)
verbose=False
)
response = llm(
"Q: Come funziona la quantizzazione dei modelli? A:",
max_tokens=256,
stop=["Q:", "\n\n"],
echo=True
)
print(response["choices"][0]["text"])
# === GGUF CON OLLAMA (più semplice) ===
# Crea Modelfile per Ollama
modelfile_content = """
FROM ./llama-3.1-8b-q4_k_m.gguf
PARAMETER temperature 0.7
PARAMETER num_ctx 4096
SYSTEM "Sei un assistente tecnico esperto di deep learning."
"""
with open("Modelfile", "w") as f:
f.write(modelfile_content)
# ollama create mio-llama -f Modelfile
# ollama run mio-llama
ベンチマーク: 精度、速度、メモリ
量子化モデルの品質を評価するために最も使用されるメトリクスは次のとおりです。 困惑 標準データセット (Wikitext-2) に関する推論ベンチマーク (HellaSwag、MMLU) およびアプリケーション ドメイン固有のタスク。
# Benchmark automatico con lm-evaluation-harness
# pip install lm-eval
# Valutazione modello BF16 (baseline)
lm_eval --model hf \
--model_args "pretrained=meta-llama/Llama-3.1-8B-Instruct" \
--tasks hellaswag,mmlu \
--batch_size 4 \
--output_path results_bf16/
# Valutazione modello quantizzato GPTQ
lm_eval --model hf \
--model_args "pretrained=./llama-3.1-8b-gptq-int4,use_auto_gptq=True" \
--tasks hellaswag,mmlu \
--batch_size 4 \
--output_path results_gptq_int4/
# === SCRIPT BENCHMARK MEMORIA E VELOCITA ===
import torch
import time
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
def benchmark_model(model_name_or_path, quant_config=None, n_tokens=100, n_runs=5):
"""Benchmark completo: memoria, latenza, throughput."""
tokenizer = AutoTokenizer.from_pretrained(model_name_or_path)
model = AutoModelForCausalLM.from_pretrained(
model_name_or_path,
quantization_config=quant_config,
device_map="cuda",
torch_dtype=torch.bfloat16 if quant_config is None else None
)
# Memoria usata
mem_gb = model.get_memory_footprint() / 1024**3
# Prompt di test
prompt = "Explain the transformer architecture in detail:" * 3
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
# Warm-up
with torch.no_grad():
model.generate(**inputs, max_new_tokens=10)
# Benchmark
torch.cuda.synchronize()
latencies = []
for _ in range(n_runs):
start = time.perf_counter()
with torch.no_grad():
out = model.generate(**inputs, max_new_tokens=n_tokens)
torch.cuda.synchronize()
elapsed = time.perf_counter() - start
latencies.append(elapsed)
avg_latency = sum(latencies) / len(latencies)
throughput = n_tokens / avg_latency
return {
"memoria_gb": round(mem_gb, 2),
"latenza_sec": round(avg_latency, 3),
"throughput_tps": round(throughput, 1)
}
# Confronto BF16 vs INT8 vs INT4
results = {}
results["BF16"] = benchmark_model("meta-llama/Llama-3.1-8B-Instruct")
config_8bit = BitsAndBytesConfig(load_in_8bit=True)
results["INT8"] = benchmark_model("meta-llama/Llama-3.1-8B-Instruct", config_8bit)
config_4bit = BitsAndBytesConfig(
load_in_4bit=True, bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True
)
results["INT4-NF4"] = benchmark_model("meta-llama/Llama-3.1-8B-Instruct", config_4bit)
for name, r in results.items():
print(f"{name:10} | Mem: {r['memoria_gb']:5.2f} GB | "
f"Latenza: {r['latenza_sec']:.3f}s | "
f"Throughput: {r['throughput_tps']:.1f} t/s")
# Risultati tipici Llama-3.1-8B su RTX 3090:
# BF16 | Mem: 16.02 GB | Latenza: 3.821s | Throughput: 26.2 t/s
# INT8 | Mem: 8.51 GB | Latenza: 4.103s | Throughput: 24.4 t/s
# INT4-NF4 | Mem: 4.89 GB | Latenza: 3.412s | Throughput: 29.3 t/s
ベンチマーク結果の指標 (RTX 4090 上の Llama-3.1-8B)
| 方法 | メモリ | スループット | ヘラスワッグ | 困惑 |
|---|---|---|---|---|
| BF16 (ベースライン) | 16.0GB | 38トン/秒 | 82.1% | 6.14 |
| INT8 (ビットサンドバイト) | 8.5GB | 35トン/秒 | 81.8% | 6.21 |
| INT4 NF4 (bnb) | 4.9GB | 42トン/秒 | 81.2% | 6.47 |
| GPTQ INT4 | 4.8GB | 55トン/秒 | 81.5% | 6.39 |
| AWQ INT4 | 4.7GB | 52トン/秒 | 81.6% | 6.35 |
| Q4_K_M (GGUF、CPU) | 4.9GB | 18トン/秒 | 81.3% | 6.42 |
注: 参考値は、ハードウェア、特定のモデル、バッチ サイズによって異なります。
PyTorch を使用した量子化を意識したトレーニング
PTQ が十分ではないシナリオの場合 - 通常は 3B パラメーターの下の小規模なモデル または 2 ~ 3 ビット量子化 — la QAT 大幅な回復が可能になります 精度。 PyTorch には、バージョン 2.0 以降の QAT のネイティブ モジュールが含まれています。 静的および動的 INT8 のサポート。
import torch
import torch.nn as nn
from torch.quantization import (
prepare_qat, convert, get_default_qat_qconfig,
QConfigMapping
)
from torch.ao.quantization import get_default_qat_qconfig_mapping
# === DEFINIZIONE MODELLO SEMPLICE ===
class SimpleTransformerBlock(nn.Module):
def __init__(self, d_model=256, nhead=4, ff_dim=1024):
super().__init__()
self.attn = nn.MultiheadAttention(d_model, nhead, batch_first=True)
self.norm1 = nn.LayerNorm(d_model)
self.ff = nn.Sequential(
nn.Linear(d_model, ff_dim),
nn.ReLU(),
nn.Linear(ff_dim, d_model)
)
self.norm2 = nn.LayerNorm(d_model)
def forward(self, x):
attn_out, _ = self.attn(x, x, x)
x = self.norm1(x + attn_out)
ff_out = self.ff(x)
return self.norm2(x + ff_out)
class SimpleModel(nn.Module):
def __init__(self, vocab_size=1000, d_model=256, n_layers=4):
super().__init__()
self.embed = nn.Embedding(vocab_size, d_model)
self.blocks = nn.Sequential(*[
SimpleTransformerBlock(d_model) for _ in range(n_layers)
])
self.head = nn.Linear(d_model, vocab_size)
def forward(self, x):
x = self.embed(x)
x = self.blocks(x)
return self.head(x)
# === TRAINING BASELINE (FP32) ===
model = SimpleModel()
model.train()
# === CONFIGURAZIONE QAT ===
# QConfig specifica come quantizzare activations e weights
qconfig_mapping = get_default_qat_qconfig_mapping("x86")
# Prepara il modello per QAT: inserisce FakeQuantize nodes
# che simulano la quantizzazione durante il forward pass
from torch.ao.quantization import prepare_qat_fx
# Traccia il modello con esempio di input
example_input = torch.randint(0, 1000, (2, 32))
model_prepared = prepare_qat_fx(model, qconfig_mapping, example_input)
# === QAT TRAINING LOOP ===
optimizer = torch.optim.Adam(model_prepared.parameters(), lr=1e-4)
criterion = nn.CrossEntropyLoss()
def train_qat(model, n_epochs=10, freeze_quantizer_epoch=8):
for epoch in range(n_epochs):
# Dati sintetici (sostituire con dataset reale)
x = torch.randint(0, 1000, (32, 64))
y = torch.randint(0, 1000, (32, 64))
optimizer.zero_grad()
out = model(x)
loss = criterion(out.view(-1, 1000), y.view(-1))
loss.backward()
optimizer.step()
# Freeze quantizer dopo N epoche: stabilizza le scale
if epoch == freeze_quantizer_epoch:
model.apply(torch.quantization.disable_observer)
print(f"Epoch {epoch}: FakeQuantize observers disabilitati")
if epoch % 2 == 0:
print(f"Epoch {epoch}/{n_epochs}, Loss: {loss.item():.4f}")
train_qat(model_prepared)
# === CONVERSIONE FINALE INT8 ===
model_prepared.eval()
model_int8 = convert(model_prepared)
# Salvataggio
torch.save(model_int8.state_dict(), "model_qat_int8.pt")
# Confronto dimensioni
fp32_size = sum(p.numel() * 4 for p in model.parameters()) / 1024**2
int8_size = sum(p.numel() * 1 for p in model_int8.parameters()) / 1024**2
print(f"Modello FP32: {fp32_size:.1f} MB")
print(f"Modello QAT INT8: {int8_size:.1f} MB")
print(f"Riduzione: {(1 - int8_size/fp32_size)*100:.0f}%")
SmoothQuant: LLM の優れた INT8
スムーズクアント (Xiao et al. 2022) は量子化の特定の問題に取り組んでいます LLM の INT8: ファイル 異常値のアクティベーション。トランスフォーマーの一部のアクティベーション チャネル これらは他の値よりも非常に大きな値を持っているため、均一な量子化が困難になります。 (外れ値をカバーするために範囲が無駄になり、正常値の精度が低下します)。
スムーズクアント 量子化の難易度をアクティベーションから重みに転送します: アクティベーションをチャネルごとのスケーリング係数で除算し、対応する重みを乗算します。 同じ要因のため。結果は数学的に同等ですが、アクティブ化と 重みを量子化するのが簡単です。
# Concetto di SmoothQuant (implementazione semplificata)
import torch
import torch.nn as nn
def compute_smooth_scale(activations: torch.Tensor, weights: torch.Tensor,
alpha: float = 0.5) -> torch.Tensor:
"""
Calcola il fattore di smoothing per SmoothQuant.
alpha: bilanciamento tra difficolta attivazioni e pesi (0.5 = equo)
"""
# Max assoluto per canale nelle attivazioni
act_max = activations.abs().max(dim=0).values # [hidden_dim]
# Max assoluto per canale nei pesi
w_max = weights.abs().max(dim=0).values # [hidden_dim]
# Fattore di scaling: riduce attivazioni, aumenta pesi proporzionalmente
scale = (act_max.pow(alpha) / w_max.pow(1 - alpha)).clamp(min=1e-5)
return scale
def smooth_layer(layer: nn.Linear, calibration_acts: torch.Tensor,
alpha: float = 0.5):
"""
Applica SmoothQuant a un layer Linear.
Divide le attivazioni in input per scale, moltiplica i pesi per scale.
"""
scale = compute_smooth_scale(calibration_acts, layer.weight.T, alpha)
# Modifica in-place (equivalente matematico ma più facile da quantizzare)
layer.weight.data = layer.weight.data * scale.unsqueeze(0)
# Ritorna la scala inversa da applicare alle attivazioni
return scale
# Uso pratico: SmoothQuant e tipicamente applicato con librerie come
# quanto (Hugging Face) o mediante ottimum:
# pip install optimum[quanto]
from transformers import AutoModelForCausalLM, AutoTokenizer
from optimum.quanto import quantize, freeze, qint8
model_name = "meta-llama/Llama-3.1-8B-Instruct"
model = AutoModelForCausalLM.from_pretrained(model_name, torch_dtype=torch.float16)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# Quantizzazione INT8 con Quanto (include SmoothQuant internamente)
quantize(model, weights=qint8, activations=qint8)
# Calibrazione con alcuni esempi
with torch.no_grad():
for text in ["Calibration text 1", "Calibration text 2"]:
inputs = tokenizer(text, return_tensors="pt")
model(**inputs)
# Freeze: consolida i parametri quantizzati
freeze(model)
print("Modello quantizzato INT8 con Quanto/SmoothQuant pronto!")
ベストプラクティスとアンチパターン
量子化のベストプラクティス
- ユースケースに基づいて精度を選択します。 米国の GPU サーバーでの展開用 GPTQ INT4; CPU/エッジの場合は GGUF Q4_K_M を使用します。微調整には、ビットサンドバイトを含む NF4 を使用します。
- 代表的なキャリブレーション データセット: GPTQ と AWQ には同様のデータを使用します ジェネリックではなくターゲット ドメインに。 128 ~ 512 個のサンプルで十分ですが、重要なものでなければなりません。
- 常にドメイン固有のメトリックを使用して評価します。 ウィキテキストの困惑 およびプロキシですが、特定のタスク (コーディング、数学、英語以外の言語) に関する問題は捕捉されません。
- 最適なグループサイズ: group_size=128 およびデフォルトの金庫。 64は品質を向上させます より多くのメモリを犠牲にします。 256 ではメモリは削減されますが、品質が低下します。
- 二重量子化 (bnb): 常に bnb_4bit_use_double_quant=True を有効にします。 品質への影響を最小限に抑えながら、パラメータあたり最大 0.4 ビットを節約します。
- BF16 を計算 dtype として使用します。 bnb_4bit_compute_dtype=torch.bfloat16 FP16 よりも安定しており、Ampere+ (RTX 3000+、A100) によってサポートされています。
避けるべきアンチパターン
- クリティカルなレイヤーを量子化しないでください。 最初と最後の層 (埋め込み、LM ヘッド) 多くの場合、量子化に対してより敏感になります。 GPTQ と AWQ はそれらを自動的に除外します。
- 推論に FP4 を使用しないでください。 NF4 および FP4 を超える LLM 重量、 通常配布されているもの。 FP4 は、非常に特殊なシナリオでのみ役立ちます。
- ベンチマークを行わずに、異なる量子化を比較しないでください。 モデル GPTQ 方式の「INT4」は、ビットサンドバイトの「INT4」とは異なります。実際の精度 それは実装によって異なります。
- 逆量子化のレイテンシーを無視しないでください。 INT4 ビットサンドバイトは必須です 順方向パス中に量子化解除します。メモリ帯域幅が限られている小型 GPU では、これは INT8 よりも遅くなる可能性があります。
- 運用環境では Q2_K を使用しないでください。 品質が劣化しすぎている ほとんどのユースケース。 Q3_K_M は、単純なタスクに許容される最小値です。
導入シナリオ: 選択ガイド
量子化方法の選択は、導入状況によって異なります。実用的なガイドは次のとおりです。
| シナリオ | ハードウェア | 推奨される方法 | 形式 |
|---|---|---|---|
| GPU本番サーバー | A100/H100 80GB | GPTQ INT4 または AWQ INT4 | セーフテンサー GPTQ |
| 消費者向けワークステーション | RTX4090 24GB | GPTQ INT4 (70Bまでのモデル) | セーフテンサー GPTQ |
| Windows/Linux ラップトップ | GPU 8-16GB VRAM | ビットサンドバイト NF4 または AWQ | ハグフェイスハブ |
| Apple Mシリーズのラップトップ | ユニファイドメモリ 16-96GB | GGUF Q4_K_M または Q5_K_M | GGUF + ラマ.cpp/オラマ |
| ラズベリーパイ5 | 8GB RAM | GGUF Q3_K_M または Q4_K_M (モデル 1 ~ 3B) | GGUF + ラマ.cpp |
| NVIDIA Jetson オリン | 16GBユニファイドメモリ | GPTQ INT4 または GGUF Q4_K_M | GPTQ または GGUF |
| 限られた GPU 微調整 | RTX3090 24GB | QLoRA (NF4 + LoRA) | ビットサンドバイト NF4 |
結論
パターンの量子化はニッチな技術からツールになりました LLM を使用する人にとっては必須です。 2026 年のパノラマは豊かです: ビットサンドバイト ラピッドプロトタイピングと QLoRA 微調整用、 GPTQ 最適化された GPU の導入のために、 AWQ 異種混合およびマルチプラットフォームのハードウェア向け、 GGUF CPUごと およびエッジデバイス。
重要なのは、特定のコンテキストに応じて適切な方法を選択することです。 RTX 4090 上の GPTQ を備えた INT4 最適化されたカーネルのおかげで、多くの場合、BF16 よりも高速になります。 M3 を搭載した MacBook Pro 上の GGUF Q4_K_M 専用の GPU を使用せずに、Llama-3.1-8B を 28 トークン/秒で実行できます。これらは譲歩ではありません 定性的: これまで不可能だったシナリオが可能になります。
次の自然なステップは、量子化と モデルの蒸留、 これについては次の記事で取り上げます: 大規模モデルから知識を伝達する方法 両方の圧縮技術を最大限に活用して、より小さなモデルに量子化されます。
次のステップ
- 次の記事: 蒸留モデル: 知識の伝達
- 関連記事: LoRA と QLoRA による微調整
- 以下も参照してください。 Ollama: ラップトップとラズベリー上のローカル LLM
- MLOps シリーズ: モデルの提供と展開







