はじめに
「話題のオープンモデルを自分のPCで動かしてみたら、メモリ不足のエラーで落ちた」
「モデルの配布ページに Q4_K_M や 4bit と書いてあるが、どれを選べばいいのか分からない」
「GPUサーバーの請求額が高い。もっと小さい構成で動かせないのか」
LLM(大規模言語モデル)を自分で動かそうとすると、かなりの確率でこのあたりの壁にぶつかります。そして、この壁を越えるときに必ず出てくるキーワードが「量子化(Quantization)」です。
この記事では、量子化について次の3点を順番に解説します。
- 量子化とは何で、なぜ行うのか(メモリ・速度・コストの話)
- 量子化の中で実際に何が起きているのか(Pythonの小さなコードで数値の変化を確認します)
- 実際にどう実装するのか(Hugging Faceのモデルを4bitで読み込む方法と、llama.cppでGGUF形式に変換してCPUやMacで動かす方法)
想定読者は、LLMに興味がある非エンジニアの方から、Pythonは触ったことがあるが量子化は初めてという初心者エンジニアの方までです。前半は数式なしで読めるように書いていますので、コードの部分は読み飛ばしても全体像は掴めるようになっています。
量子化を一言でいうと
量子化とは、「モデルの中にある大量の数値を、粗い目盛りで表し直して、データ量を減らす技術」です。
身近な例で考えてみます。0.01kg刻みで測れる精密な体重計と、1kg刻みでしか測れない体重計があるとします。「今日は62.37kg」と「今日はだいたい62kg」では、後者のほうが情報としては粗いですが、日常的な用途ではほとんど困りません。そして、記録するときの桁数は明らかに後者のほうが少なくて済みます。
量子化はこれと同じ発想です。LLMが持っている数値を「そこまで細かい精度は要らない」と割り切って粗い目盛りに丸め、その分だけメモリを節約します。
もうひとつ、写真のJPEG圧縮に例えることもできます。JPEGは元の画像データを完全には保存しない「非可逆圧縮」ですが、見た目はほとんど変わらず、ファイルサイズは大幅に小さくなります。量子化もまさに非可逆で、元の数値には完全には戻せませんが、モデルの振る舞いはほとんど変わらない、というのが基本的な狙いです。
そもそもLLMの中身は「数値の塊」です
量子化の話をするには、LLMの正体を少しだけ知っておく必要があります。
LLMの本体は、「パラメータ(重み)」と呼ばれる膨大な数値の集まりです。「7Bモデル」という表記を見たことがあるかもしれませんが、これは「パラメータが70億(7 Billion)個ある」という意味です。文章を生成するとき、モデルはこの70億個の数値を使って計算を繰り返しています。
ここで重要なのが、「1個の数値を保存するのに何バイト必要か」です。通常、LLMのパラメータは16bit(2バイト)の浮動小数点数で保存されています。BF16やFP16という形式です。すると、7Bモデルの大きさは単純計算で次のようになります。
70億個 × 2バイト = 140億バイト ≒ 14GB
このモデルを動かすには、GPUのメモリ(VRAM)に14GB分の数値をまるごと載せなければなりません。一般的なゲーミングPC向けGPUのVRAMは8〜16GB程度、ハイエンドでも24〜32GB程度ですので、7Bモデルですら「そのままでは載らない」環境が多いことが分かります。70Bモデルであれば140GBですから、個人の環境ではまず不可能です。
パラメータ1個あたりのビット幅を変えると、必要なメモリがどう変わるかを表にまとめます。
| 形式 | 1パラメータあたり | 7Bモデル(理論値) | 70Bモデル(理論値) |
|---|---|---|---|
| FP32(32bit浮動小数点) | 4バイト | 約28GB | 約280GB |
| FP16 / BF16(16bit浮動小数点) | 2バイト | 約14GB | 約140GB |
| INT8(8bit整数) | 1バイト | 約7GB | 約70GB |
| INT4(4bit整数) | 0.5バイト | 約3.5GB | 約35GB |
16bitから4bitにすると、必要なメモリは4分の1になります。14GBで載らなかった7Bモデルが3.5GB程度になれば、8GBのGPUでも余裕を持って動かせます。これが量子化の最も分かりやすい効果です。
なお、実際の4bit量子化では後述する「目盛りの情報」なども一緒に保存するため、1パラメータあたり4.5〜5bit程度になり、7〜8Bモデルで4GB台後半になるのが普通です。理論値よりは少し大きくなると覚えておいてください。
なぜ量子化するのか:3つの理由
理由1:メモリに載せるため
前の節で見たとおり、最大の理由はこれです。手元のGPU、ノートPC、Macのメモリに収まらなければ、そもそもモデルを動かせません。量子化は「動かない」を「動く」に変える技術です。
理由2:生成を速くするため
意外に思われるかもしれませんが、LLMが文章を生成するときのボトルネックは「計算の速さ」よりも「メモリからの読み出し速度」であることが多いです。
LLMは1トークン(おおよそ1文字〜1単語)を生成するたびに、モデルのパラメータをメモリから読み出して計算します。一般的な構造のモデルであれば、全パラメータを毎回読みます。14GBのモデルなら、1トークンごとに14GBを読む必要があるわけです。GPUの計算能力が余っていても、データの転送が追いつかなければ待ち時間が発生します。
量子化でモデルが4分の1になれば、読み出すデータ量も4分の1になり、その分だけ生成が速くなります。llama.cppというツールの公式ドキュメントでは、Llama 3.1 8Bモデルを使った測定例として、16bit版(約15GB)が毎秒約29トークンだったのに対し、Q4_K_Mという4bit版(約4.6GB)では毎秒約72トークンと、2倍以上の速度が出たことが示されています(測定条件により数値は変わります)。
理由3:コストを下げるため
メモリが小さくて済むということは、より安いハードウェアで動かせるということです。
- クラウドのGPUインスタンスを、より小さくて安いものに変えられる
- GPUなしのCPUサーバーや、手元のノートPC・Macで動かせる
- スマートフォンや組み込み機器などのエッジ環境に載せられる
- 社内データを外部に送らず、ローカルで完結する構成を組みやすくなる
企業での利用を考えると、「そこそこのモデルを安い環境で安定して動かす」ことは、最先端モデルを使うことと同じくらい重要なテーマです。量子化はその土台になります。
代償:精度が少し落ちます
もちろん、良いことばかりではありません。JPEGと同じで、量子化は非可逆な変換ですので、元のモデルと比べると出力の質は少し落ちます。
経験則としては、8bitであれば元モデルとほとんど区別がつかないレベル、4bitであれば「注意深く比べると違いが分かることがある」レベル、3bit以下になると劣化がはっきり目立ってくる、と言われています。また、パラメータ数が小さいモデルほど劣化の影響を受けやすい傾向があります。
つまり量子化とは、「メモリ・速度・コスト」と「精度」のトレードオフをどこで取るかを決める作業です。この感覚が、後半で紹介する「どのビット幅を選ぶか」の判断につながります。
量子化の仕組みをざっくり理解する
ここからは、量子化の中で実際に何が起きているのかを見ていきます。数式は最小限にして、Pythonの小さなコードで数値の変化を確認します。
浮動小数点と整数の違い
16bit浮動小数点(FP16 / BF16)は、0.0012のような小さい値も、3000のような大きい値も、小数点の位置を動かしながら表現できる形式です。柔軟ですが、1個あたり16bit必要です。
一方、8bit整数(INT8)は -128〜127 の整数しか表せません。4bit整数(INT4)ならさらに狭く、-8〜7 の16通りです。小数は一切表せませんが、必要なビット数は8bit、4bitで済みます。
量子化とは、この「柔軟だが重い浮動小数点」を「表現力は乏しいが軽い整数」に写し替える作業です。
「目盛り」を決めて丸める
小数を整数に写すには、「目盛り1つ分がどれくらいの大きさか」を決める必要があります。これを scale(スケール)と呼びます。
たとえば、重みの値が -3.1〜3.1 の範囲に収まっているとします。これを -127〜127 の整数に写したければ、scale = 3.1 ÷ 127 ≒ 0.0244 とします。すると、それぞれの値は次のように変換できます。
- 量子化:整数値 = round(元の値 ÷ scale)
- 復元(逆量子化):元の値 ≒ 整数値 × scale
たとえば 1.75 という値は、1.75 ÷ 0.0244 ≒ 71.7 なので、四捨五入して 72 として保存します。復元するときは 72 × 0.0244 ≒ 1.757 となり、元の 1.75 とは 0.007 だけずれます。このずれが「量子化誤差」で、精度低下の正体です。
保存するのは整数値だけで、scale は後述するブロックごとに1個持てば済むため、全体として大幅にデータ量を減らせます。
手を動かして確認する:Pythonで8個の数値を量子化してみる
言葉だけでは実感しにくいので、実際に計算してみます。以下はNumPyだけで動く、8個の数値を8bitと4bitに量子化するコードです。GPUは不要で、手元のPythonですぐ試せます。
import numpy as np
# 元の重み(float32、8個だけの小さな例)
w = np.array([0.12, -0.83, 1.75, 0.02, -2.90, 0.44, 3.10, -1.20], dtype=np.float32)
def quantize(w, bits):
qmax = 2 ** (bits - 1) - 1 # int8なら127、int4なら7
scale = np.abs(w).max() / qmax # 「目盛り1つ分」の幅
w_q = np.clip(np.round(w / scale), -qmax, qmax).astype(np.int8)
w_deq = w_q.astype(np.float32) * scale # 復元(逆量子化)
return scale, w_q, w_deq
for bits in (8, 4):
scale, w_q, w_deq = quantize(w, bits)
print(f"--- {bits}bit ---")
print("scale :", round(float(scale), 5))
print("量子化後 :", w_q)
print("復元後 :", np.round(w_deq, 3))
print("誤差 :", np.round(w - w_deq, 3))
print("最大誤差 :", round(float(np.abs(w - w_deq).max()), 4))
実行結果は次のようになります。
--- 8bit ---
scale : 0.02441
量子化後 : [ 5 -34 72 1 -119 18 127 -49]
復元後 : [ 0.122 -0.83 1.757 0.024 -2.905 0.439 3.1 -1.196]
誤差 : [-0.002 -0. -0.007 -0.004 0.005 0.001 0. -0.004]
最大誤差 : 0.0075
--- 4bit ---
scale : 0.44286
量子化後 : [ 0 -2 4 0 -7 1 7 -3]
復元後 : [ 0. -0.886 1.771 0. -3.1 0.443 3.1 -1.329]
誤差 : [ 0.12 0.056 -0.021 0.02 0.2 -0.003 0. 0.129]
最大誤差 : 0.2
8bitでは最大誤差が0.0075で、元の値とほぼ変わりません。一方4bitでは、0.12 と 0.02 がどちらも 0 になってしまい、最大誤差は 0.2 まで広がっています。目盛りが粗くなるほど、小さい値の違いが潰れてしまうことが分かります。
これが「8bitはほぼ劣化なし、4bitは少し劣化する」の数値的な理由です。
外れ値問題と「ブロック単位」の量子化
上の例では、値の範囲が -3.1〜3.1 に収まっていたので、4bitでもそれなりに情報が残りました。ところが実際のLLMの重みには、ごく一部だけ極端に大きい値(外れ値)が混ざっています。この外れ値が量子化を大きく狂わせます。
先ほどの8個の数値のうち、1つだけを 30.0 に変えて4bit量子化してみます。
import numpy as np
def quantize(w, bits):
qmax = 2 ** (bits - 1) - 1
scale = np.abs(w).max() / qmax
w_q = np.clip(np.round(w / scale), -qmax, qmax).astype(np.int8)
return scale, w_q, w_q.astype(np.float32) * scale
# 1つだけ極端に大きい値(外れ値)が混ざったケース
w = np.array([0.12, -0.83, 1.75, 0.02, -2.90, 0.44, 30.0, -1.20], dtype=np.float32)
scale, w_q, w_deq = quantize(w, 4)
print("scale :", round(float(scale), 4))
print("量子化後 :", w_q)
print("復元後 :", np.round(w_deq, 3))
# 4個ずつのブロックに分けて、それぞれ別のscaleで量子化
print("\n--- ブロック単位(4個ごと)で量子化 ---")
for i in range(0, len(w), 4):
block = w[i:i+4]
scale, w_q, w_deq = quantize(block, 4)
print(f"ブロック{i//4 + 1}: scale={round(float(scale), 4)}, 量子化後={w_q}, 復元後={np.round(w_deq, 3)}")
実行結果です。
scale : 4.2857
量子化後 : [ 0 0 0 0 -1 0 7 0]
復元後 : [ 0. 0. 0. 0. -4.286 0. 30. 0. ]
--- ブロック単位(4個ごと)で量子化 ---
ブロック1: scale=0.25, 量子化後=[ 0 -3 7 0], 復元後=[ 0. -0.75 1.75 0. ]
ブロック2: scale=4.2857, 量子化後=[-1 0 7 0], 復元後=[-4.286 0. 30. 0. ]
外れ値の 30.0 に合わせて scale が 4.29 まで大きくなった結果、それ以外の値がほぼ全部 0 に潰れてしまいました。たった1個の外れ値のせいで、残り7個の情報が消えたことになります。
そこで、数値を小さなブロックに分けて、ブロックごとに別々の scale を持たせるのが実用上の定石です。上の結果でも、外れ値を含まないブロック1では 1.75 や -0.83 の情報が残っています。外れ値の影響が、それを含むブロックの中だけに閉じ込められたわけです。
実際の量子化手法では、32〜256個程度の重みを1ブロックとして scale を持ちます。後述するGGUF形式の「Q4_K」や、GPTQ・AWQの「group_size=128」、bitsandbytesの「blocksize=64」といった設定は、すべてこの「何個ごとに目盛りを持つか」を指しています。
このほかにも、「外れ値だけ16bitのまま残す(LLM.int8)」「実際にデータを流して、どの重みが重要かを測ってから量子化する(GPTQ、AWQ、llama.cppのimatrix)」「整数ではなく、正規分布に合わせた特殊な4bit値を使う(NF4)」など、誤差を減らすための工夫が各手法の違いになっています。
「Q4_K_M」のような名前の読み方
モデル配布ページでよく見かける名前の意味を整理しておきます。GGUF形式(llama.cpp用)の名前を目にする機会が一番多いと思います。
| 名前の例 | 意味 | 1パラメータあたりのビット数(目安) |
|---|---|---|
| Q8_0 | 8bit。ほぼ劣化なしで、16bit版の半分のサイズ | 約8.5bit |
| Q6_K | 6bit。Q8_0に近い品質で一回り小さい | 約6.6bit |
| Q5_K_M | 5bit。品質とサイズのバランスが良い | 約5.7bit |
| Q4_K_M | 4bit。最も広く使われる定番。「K」はブロック単位の改良方式、「M」はMedium(中サイズ)の意味 | 約4.9bit |
| Q4_K_S | Q4_K_MのSmall版。少し小さく、少し品質が落ちる | 約4.7bit |
| IQ2_XS など | 2〜3bit台。importance matrix(重要度情報)を使って極限まで小さくしたもの。劣化は大きい | 約2〜3bit |
Hugging Faceのtransformersで使われる bitsandbytes では、「NF4」(4bit)や「LLM.int8」(8bit)という名前が出てきます。また、GPTQ や AWQ という名前が付いたモデルは、それぞれの手法で4bit(まれに8bit)に量子化されたGPU向けのモデルです。
量子化の種類と代表的な方式
学習後量子化(PTQ)と量子化考慮学習(QAT)
量子化には大きく2つのタイミングがあります。
- 学習後量子化(PTQ: Post-Training Quantization):学習済みのモデルを、後から量子化する方法です。追加の学習は不要で、数分〜数時間で終わります。この記事で扱うのはすべてPTQです
- 量子化考慮学習(QAT: Quantization-Aware Training):学習の段階から「あとで量子化される」ことを織り込んでモデルを訓練する方法です。品質は高くなりますが学習リソースが必要なので、モデル提供元が公式に「QAT版」として配布する形が多いです
実務で「量子化する」と言えば、ほぼPTQのことだと考えて問題ありません。
もうひとつ、「何を量子化するか」という軸もあります。この記事で扱うのは重み(パラメータ)だけを量子化する方式で、weight-only量子化と呼ばれます。計算途中の値(活性化)まで量子化する W8A8 や FP8 といった方式もあり、こちらは主にデータセンターの本番推論で使われます。
主な方式・フォーマットの比較
代表的な方式を、用途の観点で比較します。
| 方式 / 形式 | 動作環境 | 事前のキャリブレーション | 主な用途 | 特徴 |
|---|---|---|---|---|
| bitsandbytes(NF4 / LLM.int8) | NVIDIA GPU | 不要(読み込み時にその場で量子化) | 手軽な検証、QLoRAでの学習 | コード数行で使える。推論速度は速くない |
| GPTQ | NVIDIA GPU | 必要(数百〜数千件のサンプルテキスト) | vLLMなどでのGPU推論 | 誤差最小化の計算で高品質。量子化にGPUと時間が必要 |
| AWQ | NVIDIA GPU | 必要 | vLLMなどでのGPU推論 | 重要な重みを保護する方式。GPTQより量子化が速い傾向 |
| GGUF(llama.cpp) | CPU / Mac / GPU | 任意(imatrixを使うと品質向上) | ローカル運用全般。Ollama、LM Studioもこの形式 | 環境を選ばない。ローカルLLMの事実上の標準 |
| FP8 | 対応する新しいGPU(H100など) | 不要 | データセンターでの本番推論 | ハードウェアが直接8bit浮動小数点を扱うので高速 |
実務で選ぶときの目安
どれを選ぶかは、「どこで動かすか」でほぼ決まります。
- 手元のPCやMacで動かしたい、Ollamaを使いたい:GGUF一択です。公開済みのGGUFを探すか、自分で変換します
- NVIDIA GPUのサーバーでvLLMなどを使って本番運用したい:AWQかGPTQ、対応GPUがあればFP8を選びます
- Hugging Faceのモデルをとりあえず小さく試したい、QLoRAで学習したい:bitsandbytesが最も手軽です
企業案件では「検証はローカルのGGUF、本番はGPUサーバーでAWQやFP8」という組み合わせがよくあるパターンです。
実践1:Hugging Faceのモデルを4bitで読み込む(bitsandbytes)
ここからは実際に手を動かします。まずは最も手軽な、bitsandbytesを使った「読み込み時に量子化する」方法です。
前提条件は次のとおりです。
- NVIDIA製GPU(VRAM 4GB以上あれば今回の例は動きます)
- Linux または Windows(bitsandbytesはmacOSでの動作が限定的です。Macの方は実践2へ進んでください)
- Python 3.10以降
今回使うモデルは、軽量で日本語も扱える Qwen/Qwen2.5-1.5B-Instruct です。パラメータ数は約15億で、16bit版のサイズは約3.1GBです。手順は次のとおりです。
- 仮想環境を作成し、必要なライブラリをインストールします。
# 作業用ディレクトリで実行
python -m venv .venv
source .venv/bin/activate # Windowsの場合: .venv\Scripts\activate
# PyTorch、transformers、accelerate、bitsandbytesをインストール
pip install -U torch transformers accelerate bitsandbytes
WindowsでGPU版のPyTorchが入らない場合は、PyTorch公式サイトの案内に従ってCUDA対応版を指定してインストールしてください。
- 以下のスクリプトを
load_4bit.pyとして保存します。
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
model_id = "Qwen/Qwen2.5-1.5B-Instruct"
# 4bit量子化の設定
bnb_config = BitsAndBytesConfig(
load_in_4bit=True, # 4bitで読み込む
bnb_4bit_quant_type="nf4", # 4bitの形式。NF4が定番
bnb_4bit_compute_dtype=torch.bfloat16, # 計算時に一時的に戻す精度
bnb_4bit_use_double_quant=True, # scale自体も量子化してさらに節約
)
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
model_id,
quantization_config=bnb_config, # これを付けるだけで読み込み時に量子化される
device_map="auto", # GPUに自動で配置
)
print(f"メモリ使用量: {model.get_memory_footprint() / 1024**3:.2f} GB")
messages = [
{"role": "user", "content": "LLMの量子化とは何ですか?小学生にも分かるように2文で説明してください。"}
]
inputs = tokenizer.apply_chat_template(
messages, add_generation_prompt=True, return_dict=True, return_tensors="pt"
).to(model.device)
with torch.no_grad():
outputs = model.generate(**inputs, max_new_tokens=128, do_sample=False)
# 入力部分を除いた生成結果だけを表示
print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True))
- 実行します。初回はモデルのダウンロード(約3GB)が走ります。
python load_4bit.py
メモリ使用量は1.1GB前後と表示されるはずです(環境やライブラリのバージョンで多少前後します)。16bitのままだと約3.1GBですので、3分の1程度に収まっていることが確認できます。その下に、モデルからの回答が表示されます。
- 比較のために、量子化なしでも読み込んでみます。
quantization_configの行を消して、代わりにdtype=torch.bfloat16を指定するだけです。
model = AutoModelForCausalLM.from_pretrained(
model_id,
dtype=torch.bfloat16, # 量子化せず16bitのまま読み込む
device_map="auto",
)
同じプロンプトで両方の出力を見比べてみてください。do_sample=False を指定しているので、毎回同じ結果が返ります。1.5Bという小さなモデルなので4bitではやや違いが出るかもしれませんが、「メモリが3分の1になっても、ちゃんと日本語で答えられる」ことは体感できるはずです。
ポイントは、量子化のために特別な前処理をしていないことです。BitsAndBytesConfig を渡すだけで、読み込みの際にその場で重みが4bitに変換されます。この手軽さがbitsandbytesの最大の利点です。
一方で注意点もあります。bitsandbytesの4bitは、計算のたびに重みを16bitに戻してから計算するため、生成速度は16bit版より遅くなることが普通です。「メモリを減らす」目的には向いていますが、「速くする」目的には向いていません。速度が欲しい場合は、次のllama.cppや、AWQ・FP8といった専用の計算処理を持つ方式を使います。
実践2:llama.cppでGGUFに変換して、CPUやMacで動かす
次は、ローカルLLMの定番であるllama.cppを使って、モデルをGGUF形式に変換し、自分で量子化する手順です。GPUがなくても動きますし、Macであれば内蔵GPU(Metal)が自動で使われます。
前提条件は次のとおりです。
- macOS、Linux、Windows のいずれか
- git、cmake、C++コンパイラ(Macなら Xcode Command Line Tools、Ubuntuなら build-essential)
- Python 3.10以降
- 空きメモリ8GB以上(今回の1.5Bモデルなら4GB程度でも動きます)
手順は次のとおりです。
- llama.cppを取得してビルドします。
git clone https://github.com/ggml-org/llama.cpp
cd llama.cpp
# ビルド(NVIDIA GPUを使う場合は cmake -B build -DGGML_CUDA=ON)
cmake -B build
cmake --build build --config Release -j
ビルドが終わると、build/bin/ の中に llama-quantize、llama-cli、llama-server などの実行ファイルができます。Windowsの場合は build\bin\Release\ 以下に .exe として生成されます。
- 変換スクリプト用のPythonライブラリをインストールします。
# llama.cppディレクトリ内で実行
python -m venv .venv
source .venv/bin/activate # Windowsの場合: .venv\Scripts\activate
pip install -r requirements.txt
pip install -U huggingface_hub
- Hugging Faceからモデルをダウンロードします。
# llama.cppディレクトリ内で実行。models/ 以下に保存される
hf download Qwen/Qwen2.5-1.5B-Instruct --local-dir models/Qwen2.5-1.5B-Instruct
- ダウンロードしたモデルをGGUF形式に変換します。この段階ではまだ量子化せず、元の精度(bf16)のままです。
python convert_hf_to_gguf.py models/Qwen2.5-1.5B-Instruct \
--outfile models/qwen2.5-1.5b-instruct-bf16.gguf \
--outtype bf16
「なぜ一度bf16のGGUFを作るのか」というと、llama.cppの量子化ツールはGGUF形式しか入力に取れないためです。変換と量子化を分けておくと、同じbf16ファイルから Q8_0 や Q5_K_M など複数の量子化版を作れるという利点もあります。
-
llama-quantizeで量子化します。まずは定番の Q4_K_M です。
./build/bin/llama-quantize \
models/qwen2.5-1.5b-instruct-bf16.gguf \
models/qwen2.5-1.5b-instruct-Q4_K_M.gguf \
Q4_K_M
引数は「入力ファイル」「出力ファイル」「量子化の種類」の順です。最後の引数を Q8_0 や Q5_K_M に変えれば、別の量子化版も同じ要領で作れます。1.5Bモデルなら数十秒で終わります。
- ファイルサイズを確認します。
ls -lh models/*.gguf
bf16版が約3.1GB、Q4_K_M版が1.1GB前後になっているはずです。Q8_0版も作った場合は1.6GB前後です。
- 量子化したモデルを動かします。対話モードで起動する場合は次のとおりです。
./build/bin/llama-cli -m models/qwen2.5-1.5b-instruct-Q4_K_M.gguf
プロンプトが表示されたら、日本語で質問を入力してみてください。終了は Ctrl+C です(生成中に押した場合は生成が中断されるので、もう一度押します)。GPU対応でビルドした場合は、-ngl 99 のようにGPUに載せる層の数を指定するとGPUで動きます。
なお、2026年のllama.cppには各ツールをひとつにまとめた llama コマンドも追加されており、llama-cli は llama cli(スペース区切り)と書くこともできます。ドキュメントで両方の書き方を見かけても、同じものだと思って問題ありません。
- アプリケーションから使う場合は、
llama-serverでOpenAI互換のAPIサーバーとして起動します。
./build/bin/llama-server -m models/qwen2.5-1.5b-instruct-Q4_K_M.gguf --port 8080
別のターミナルから、OpenAI APIと同じ形式でリクエストを送れます。
curl http://localhost:8080/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"messages": [
{"role": "user", "content": "LLMの量子化とは何ですか?2文で説明してください。"}
],
"max_tokens": 128
}'
OpenAI互換なので、既存のアプリのAPI接続先をこのURLに向けるだけで、ローカルの量子化モデルに差し替えられます。
自分で変換しなくても良いケース
ここまで自分で変換する手順を紹介しましたが、実務では「公開済みの量子化モデルを使う」ほうが多いです。
- Hugging Faceで「モデル名 GGUF」と検索すると、モデル提供元や有志(bartowski、unslothなど)が各種の量子化版を配布しています
- Hugging Faceの「GGUF-my-repo」というSpaceを使うと、ブラウザ上で変換と量子化ができます
- Ollamaで
ollama run qwen2.5:1.5bのように実行した場合も、標準で4bit(Q4_K_M)に量子化されたモデルが使われています
自分で変換する必要があるのは、「自社データでファインチューニングしたモデルを配布したい」「公開されていない量子化の組み合わせを試したい」といった場合です。ただ、手順を一度自分で通しておくと、配布されているファイルの意味が理解できるようになるので、一度は試しておく価値があります。
量子化したモデルの精度をどう確認するか
「精度が少し落ちる」と書きましたが、実際にどれくらい落ちているかは確認しなければ分かりません。確認方法は大きく2つあります。
定量的な確認:パープレキシティ
パープレキシティ(Perplexity)は、「モデルがテキストをどれだけ自然に予測できるか」を表す指標で、値が小さいほど良いとされます。llama.cppには専用のツールがあり、量子化前後で同じテキストを使って比較できます。
# 任意のテキストファイルを渡す(日本語で確認したい場合は日本語の文章を用意する)
./build/bin/llama-perplexity -m models/qwen2.5-1.5b-instruct-Q4_K_M.gguf -f sample.txt
bf16版やQ8_0版の値と比べて、差が小さければ劣化が少ないと判断できます。より厳密にはKLダイバージェンスという指標も使われますが、まずはパープレキシティで十分です。
定性的な確認:自分の業務プロンプトで比べる
実務ではこちらのほうが重要です。ベンチマークの数値が良くても、自分の用途で崩れていたら意味がありません。
- 実際の業務で使うプロンプトを10〜20本用意します(要約、分類、抽出、質問応答など、用途に合わせて)
- 量子化前(または Q8_0)と量子化後で同じプロンプトを実行し、出力を並べます
- 日本語の崩れ、指示の無視、数値や固有名詞の取り違え、同じ文の繰り返しがないかを確認します
進め方の目安としては、まず Q4_K_M を試し、問題があれば Q5_K_M か Q6_K に上げる、という順番が効率的です。逆に Q8_0 で問題が出るなら、量子化ではなくモデル自体の性能不足を疑ったほうが良いです。
実務でハマりやすいポイント
最後に、実際に量子化モデルを扱うときに知っておくと助かる点をまとめます。
4bitにしても速くならないことがある
前述のとおり、bitsandbytesの4bitは速度目的には向いていません。「量子化したのに遅くなった」という相談の多くはこれが原因です。速度が目的なら、GGUF(llama.cpp)、AWQ、FP8など、量子化された状態のまま効率よく計算できる専用実装を使ってください。
小さいモデルほど劣化しやすい
1〜3B程度の小さいモデルは、もともとの余裕が少ないぶん、4bit化の影響を受けやすいです。小さいモデルは Q8_0 や Q6_K を使い、7B以上のモデルで初めて4bitを検討する、というのがひとつの目安です。
日本語性能は必ず自分で確認する
量子化の品質評価は英語のデータで行われていることが多く、日本語での劣化具合はそのまま当てはまらない可能性があります。llama.cppのimatrixを使った量子化では、キャリブレーション用のテキストに日本語を含めることで、日本語の品質を保ちやすくなります。
KVキャッシュのメモリは別勘定
量子化で減るのはモデル本体のメモリです。長い文章を扱うときに必要なKVキャッシュ(会話の履歴を保持する領域)は別に必要で、コンテキストが長いほど増えます。llama.cppではKVキャッシュ自体を8bitなどに量子化するオプション(--cache-type-k / --cache-type-v)があるので、長文を扱う場合は検討してください。
量子化済みモデルを再量子化しない
Q8_0からQ4_K_Mを作るような「量子化の二度がけ」は、bf16から直接作る場合より品質が大きく落ちます。llama-quantizeでは、明示的にオプションを付けないと再量子化を拒否する仕様になっているほどです。必ず元の16bitモデルから量子化してください。
ライセンスは元モデルのものが引き継がれる
量子化してもモデルのライセンスは変わりません。商用利用の可否や再配布の条件は、元モデルのライセンスを確認してください。
トラブルシューティング
よくあるエラーと対処法を表にまとめます。
| 症状 | 主な原因 | 対処法 |
|---|---|---|
CUDA out of memory |
VRAM不足 | より小さいモデルかビット幅を選ぶ。max_new_tokens やコンテキスト長を減らす |
bitsandbytes のインポートエラー、Macで動かない |
非対応環境(macOS、CPUのみ) | bitsandbytesはNVIDIA GPU前提。MacやGPUなし環境はllama.cppを使う |
convert_hf_to_gguf.py で「Model ... is not supported」 |
llama.cppが古く、新しいモデル構造に未対応 |
git pull で最新化して再ビルド。pip install -U transformers も試す |
llama-quantize: command not found |
ビルド未完了、またはパス違い |
build/bin/ 以下を確認。ビルドログにエラーがないか確認 |
| GGUF実行時にメモリ不足で落ちる | モデルサイズ + KVキャッシュがメモリ超過 | より小さい量子化版を使う。-c でコンテキスト長を小さくする |
| 出力が文字化けする、同じ文を繰り返す | ビット幅が低すぎる、チャットテンプレート不一致 | Q5以上に上げる。Instructモデルを使っているか、テンプレートが正しく適用されているか確認する |
hf: command not found |
huggingface_hubが古い |
pip install -U huggingface_hub。古い環境では huggingface-cli download を使う |
まとめ
この記事の要点を振り返ります。
- 量子化とは、モデルの数値を粗い目盛りで表し直し、メモリ・速度・コストを改善する技術です。代わりに精度が少し落ちる、非可逆な変換です
- LLMは数値の塊で、7Bモデルなら16bitで約14GB必要です。4bitにすれば約3.5GBまで減り、一般的なPCでも動かせるようになります
- 仕組みは「scaleを決めて丸める」だけですが、外れ値対策としてブロック単位でscaleを持つのが定石で、Q4_K_Mなどの名前はその方式の違いを表しています
- 手元で試すならbitsandbytes、ローカル運用ならGGUF(llama.cpp)、GPUサーバーでの本番ならAWQ / GPTQ / FP8が目安です
- まずQ4_K_Mを試し、自分の業務プロンプトで劣化を確認してから採用するのが実務的な進め方です
次のステップとしては、Ollama や LM Studio で公開済みのGGUFモデルを触ってみること、そして余裕があれば llama.cpp の imatrix を使って日本語データでキャリブレーションした量子化を試してみることをおすすめします。さらに踏み込みたい方は、QLoRA(4bit量子化したモデルに対する軽量ファインチューニング)に進むと、量子化の知識がそのまま活きます。
参考リソース
- llama.cpp 公式リポジトリ(quantizeツールのREADMEに、量子化方式ごとのサイズ・速度の測定表があります): https://github.com/ggml-org/llama.cpp/blob/master/tools/quantize/README.md
- Hugging Face transformers の bitsandbytes ドキュメント: https://huggingface.co/docs/transformers/en/quantization/bitsandbytes
- Qwen 公式ドキュメント(llama.cppでの量子化手順): https://qwen.readthedocs.io/en/latest/quantization/llama.cpp.html
- GGUF-my-repo(ブラウザ上でGGUF変換ができるHugging Face Space): https://huggingface.co/spaces/ggml-org/gguf-my-repo
- QLoRA 論文(NF4の提案元): https://arxiv.org/abs/2305.14314
- AWQ 論文: https://arxiv.org/abs/2306.00978
- GPTQ 論文: https://arxiv.org/abs/2210.17323