大規模言語モデルをローカルで動かすとき、最初に問題になるのがGPUメモリです。モデルのサイズが大きいほど回答品質に期待できますが、そのまま読み込むとVRAMが足りず、推論を始めることすらできません。
そこで今回は、同じモデルを4bitと8bitに量子化して読み込み、メモリ使用量、生成速度、回答品質を比較しました。結論から言うと、4bitはメモリ削減効果が大きく、手元のGPUで大きめのモデルを動かしたい場合に有効です。一方、回答品質と扱いやすさのバランスでは8bitが安定していました。
量子化すると必ず高速になるわけではありません。実際の速度は、GPU、量子化方式、バッチサイズ、入力トークン数、利用するカーネルによって変わります。
検証環境と比較条件
今回は日本語を扱いやすいモデルとして、Qwen/Qwen2.5-3B-Instructを使用しました。モデル自体の差が出ないよう、4bitと8bitで同じチェックポイントを読み込み、同じプロンプトを与えています。
検証環境は次のようにしました。
- Python 3.11
- PyTorch
- Transformers
- Accelerate
- bitsandbytes
- CUDA対応NVIDIA GPU
do_sample=Falsemax_new_tokens=128
量子化とは、モデルの重みをFP16やFP32より少ないビット数で表現する方法です。8bitではメモリ使用量を大きく抑えられ、4bitではさらに圧縮できます。Hugging Face Transformersでは、BitsAndBytesConfigを使って4bit・8bitモデルを読み込めます。
まずライブラリをインストールします。
python -m venv .venv
.venv\Scripts\activate
pip install --upgrade torch transformers accelerate bitsandbytes
Linuxの場合は、仮想環境の有効化コマンドを次のように置き換えます。
source .venv/bin/activate
4bitと8bitでモデルを読み込む
量子化設定だけを切り替えられるよう、モデルを読み込む関数を作りました。4bitではNF4形式を使い、計算時の型にはfloat16を指定しています。
import torch
from transformers import (
AutoTokenizer,
AutoModelForCausalLM,
BitsAndBytesConfig,
)
MODEL_NAME = "Qwen/Qwen2.5-3B-Instruct"
def load_model(bits: int):
if bits == 8:
quant_config = BitsAndBytesConfig(
load_in_8bit=True,
)
elif bits == 4:
quant_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
bnb_4bit_use_double_quant=True,
)
else:
raise ValueError("bitsは4または8を指定してください")
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
model = AutoModelForCausalLM.from_pretrained(
MODEL_NAME,
quantization_config=quant_config,
device_map="auto",
torch_dtype=torch.float16,
)
return tokenizer, model
推論処理は4bitと8bitで共通にしました。
def generate_answer(tokenizer, model, prompt):
messages = [
{
"role": "system",
"content": "あなたは技術記事を書くアシスタントです。",
},
{
"role": "user",
"content": prompt,
},
]
inputs = tokenizer.apply_chat_template(
messages,
return_tensors="pt",
add_generation_prompt=True,
).to(model.device)
with torch.inference_mode():
outputs = model.generate(
inputs,
max_new_tokens=128,
do_sample=False,
pad_token_id=tokenizer.eos_token_id,
)
generated = outputs[0][inputs.shape[-1]:]
return tokenizer.decode(generated, skip_special_tokens=True)
device_map="auto"を指定すると、Accelerateが利用可能なデバイスへモデルを配置します。ただし、CPUへのオフロードが発生すると、GPUだけで動かした場合より大幅に遅くなることがあります。
生成速度とメモリ使用量を測る
4bitと8bitの比較手順と、最終的な使い分けを図にすると次の通りです。
速度は、モデルの読み込み時間と推論時間を分けて測る必要があります。今回は比較対象を推論処理に絞り、GPU上で同期してから時間を計測しました。
import time
import torch
PROMPT = """
量子化とは何かを、機械学習を始めたばかりの人にも分かるように、
300文字以内で説明してください。
"""
def benchmark(bits: int, rounds: int = 3):
tokenizer, model = load_model(bits)
# 初回だけ発生するキャッシュ作成などを避ける
generate_answer(tokenizer, model, PROMPT)
if torch.cuda.is_available():
torch.cuda.synchronize()
torch.cuda.reset_peak_memory_stats()
elapsed = []
for _ in range(rounds):
if torch.cuda.is_available():
torch.cuda.synchronize()
start = time.perf_counter()
answer = generate_answer(tokenizer, model, PROMPT)
if torch.cuda.is_available():
torch.cuda.synchronize()
elapsed.append(time.perf_counter() - start)
peak_memory = None
if torch.cuda.is_available():
peak_memory = torch.cuda.max_memory_allocated() / 1024**3
print(f"{bits}bit")
print(f"平均推論時間: {sum(elapsed) / len(elapsed):.3f}秒")
print(f"最大VRAM使用量: {peak_memory}")
print(answer)
benchmark(8)
benchmark(4)
手元で確認した傾向として、4bitは8bitよりモデルの重みが小さくなるため、VRAMに余裕ができました。特に、KVキャッシュや入力コンテキストのための領域を確保しやすくなった点が実用上のメリットです。
ただし、4bitのほうが常に速いわけではありませんでした。GPUやbitsandbytesの実装によっては、4bitの計算変換に時間がかかり、8bitと同程度、あるいは少し遅くなる場合があります。
比較するときは、1回目の推論ではなく、ウォームアップ後の複数回の平均を見るのが重要です。また、短い回答だけでなく、入力と出力を長くした条件でも測定する必要があります。
回答品質を比べる
回答品質は、生成結果を眺めるだけでは判断しにくいため、用途別のプロンプトを用意しました。
TEST_PROMPTS = [
"Pythonのリストとタプルの違いを、コード例付きで説明してください。",
"HTTPのステータスコード404と500の違いを説明してください。",
"次のSQLの問題点を指摘し、修正版を示してください。SELECT * FROM users;",
"量子化モデルを本番利用するときの注意点を3つ挙げてください。",
]
評価では、次の観点を5段階で記録しました。
- 指示に従っているか
- 内容に誤りがないか
- 日本語が自然か
- コードが実行可能そうか
- 不要な説明や断定が多くないか
同じ乱数条件で比較するため、今回はdo_sample=Falseにしています。それでも生成結果が完全に同じになるとは限りません。量子化によって内部の計算値が変わるため、長い回答では表現や結論が変化することがあります。
実際に比較すると、一般的な説明や短いコード生成では、4bitと8bitの差は目立ちませんでした。一方で、複数の条件を同時に満たす指示、長い文脈の要約、細かいコード修正では、8bitのほうが安定している印象でした。
4bitで明らかに品質が低下するとは限りませんが、モデルや量子化方式との相性があります。したがって、ベンチマークの数値だけで決めず、自分の用途に近いテストケースを用意することが大切です。
まとめ
4bitと8bitを比較して、次のように整理できました。
4bitはメモリ削減を優先したい場合に向いています。より大きなモデルを載せたり、長いコンテキストを扱ったりする余地が増えるため、ローカル推論では特に効果を感じやすい方式です。
8bitは、メモリを節約しつつ回答品質の変化を小さく抑えたい場合に向いています。速度も環境によっては4bitより安定するため、まず試す量子化方式として扱いやすいと感じました。
ただし、量子化による速度差は環境依存です。最終的には、利用するモデル、GPU、プロンプト、出力長を固定し、メモリ・速度・品質を同じ条件で測定する必要があります。
私の使い分けは、開発中や小さなGPUで動かす場合は4bit、本番に近い回答品質を確認したい場合は8bitです。量子化は「何bitなら正解」という話ではなく、必要な品質と利用可能なメモリのバランスを取るための選択肢だと考えています。