前回は「AIの速度はどの数字を見ればいいのか」を実測で整理しました。速度と精度はセットで語られることが多いので、今回はもう片方をやります。
ローカルLLMの話には量子化という言葉が必ず出てきます。モデルを軽くする代わりに、ほんの少し賢さが落ちる——そう説明されます。推論エンジンを比べた回でもそう書きました。ただ、そのとき正直にこう添えていました。
Q6/Q8の実測は今回は行わず、サイズは一般的な目安を載せています
つまり「落ちる」と書きながら、どれだけ落ちるかは測っていなかったわけです。今回はその宿題を回収します😊
・量子化でどれだけ賢さが削られるのかを、Q8からQ2まで自分の数字で
・★数字の崖はQ3から。でもQ2でもFizzBuzzは動くし、17×24も当てる
・★崩れたのは日本語の説明文だけだった(実際の出力を並べます)
・★「日本語のほうが先に壊れる」は、測り方によって結論が変わった
・perplexity が見逃すものと、KL divergence が捉えるもの
「賢さはほとんど落ちない」は本当か
先に今回の結論をひとつ出しておきます。
Q4_K_Mまではほぼ平坦で、Q3から急に折れます。「Q4_K_Mが定番」と言われる根拠が、自分の数字で見えました。
ただ、これは数字の話でしかありません。この記事の面白いところは、ここから先で数字と実物がズレていくところです。
用語集
| 用語 | 意味 |
|---|---|
| 量子化 | モデルの中の数値を粗くして容量を減らすこと。写真をJPEGで圧縮するのに近い |
| F16 / Q8 / Q4 … | 1つの数値を何ビットで持つか。F16は16ビット(元のまま)、Q4は4ビット相当。数字が小さいほど軽く、粗い |
| K-quants | `Q4_K_M` の `K` の部分。場所によってビット数を変える賢い量子化。大事な部分だけ多めのビットを残します |
| perplexity | パープレキシティ。次に来る単語をどれだけ当てにくかったか。小さいほど良い |
| KL divergence | 2つのモデルが「次に何が来ると思ったか」のズレを測るもの。0に近いほど元と同じ振る舞い |
| コーパス | 評価に使う文章のまとまり。何を選ぶかで数字が変わるので、条件を揃える必要があります |
| temperature | 出力のばらつき具合。0にすると毎回同じ答えになるので、比較のときはここを固定します |
「精度」は2つの意味で使われている
本題に入る前に、ひとつ整理しておきます。「精度」という言葉が、まったく違う2つの意味で使われています。
F16を1つ落として、あとは手元で作る
比べるなら同じ元から作った量子化どうしでないと意味がありません。配布元が違うと、作り方の設定も違うかもしれないからです。
そこでF16(元のまま・14.19 GiB)を1つだけダウンロードし、あとは手元で全部生成しました。llama.cppに付いてくる llama-quantize を使います。
for q in Q8_0 Q6_K Q5_K_M Q4_K_M Q3_K_M Q2_K; do llama-quantize モデル-F16.gguf モデル-$q.gguf $q done
6種類がぜんぶで3分弱でできました。ダウンロードは14.19 GiBの1回だけで済むので、同じことを試すのも簡単です。
| 量子化 | サイズ | F16比 |
|---|---|---|
| F16(元のまま) | 14.19 GiB | 100% |
| Q8_0 | 7.54 GiB | 53% |
| Q6_K | 5.82 GiB | 41% |
| Q5_K_M | 5.07 GiB | 36% |
| Q4_K_M | 4.36 GiB | 31% |
| Q3_K_M | 3.55 GiB | 25% |
| Q2_K | 2.81 GiB | 20% |
Qwen2.5-Coder-7B-Instruct。Q2_Kまで落とすと元の5分の1になります
perplexityが測れるもの、測れないもの
賢さを数字で測る定番が perplexity です。ただし、これが何を測っていて何を測っていないかを先に押さえておかないと、後で読み違えます。
実測①:崖はQ3から始まる
まず数字です。英語(Wikipedia)で測ったperplexityがこちら。
| 量子化 | perplexity | F16からの悪化 |
|---|---|---|
| F16 | 9.3439 ± 0.21 | 基準 |
| Q8_0 | 9.3372 | −0.07% |
| Q6_K | 9.3730 | +0.31% |
| Q5_K_M | 9.3711 | +0.29% |
| Q4_K_M | 9.4654 | +1.30% |
| Q3_K_M | 9.8903 | +5.85% |
| Q2_K | 11.4423 | +22.46% |
Q8_0にいたっては、F16よりわずかに良い値が出ました。 もちろん誤差(±0.21)の範囲なので「同じ」と読むべきですが、サイズが半分になっても悪化が測定誤差に埋もれるのは確かです。
Q4_K_Mまでは+1.30%。Q3で+5.85%と4.5倍に跳ね、Q2で+22.46%とさらに4倍。崖の位置がはっきりしました。
実測②:では、実際に何を書くのか
ここからが本題です。「+5.85%悪化」と言われても、それが何を意味するのかは分かりません。 実際に同じ質問を全部に投げて、返ってきた文章をそのまま並べます。
条件は揃えました。temperature 0 で固定(毎回同じ答えになる)、同じ質問を7つの量子化すべてに、出力は整形せず原文のままです。
コードは書けるのか
「FizzBuzzを返すPython関数を書いて」と頼みました。動くか動かないかで客観的に判定できるのが利点です。
| 量子化 | 出力(そのまま) | 実行結果 |
|---|---|---|
| F16 〜 Q5_K_M | ['Fizz' * (not i % 3) + 'Buzz' * (not i % 5) or str(i) for i in range(1, n+1)] | ✅ 正解 |
| Q4_K_M | 同じ(空白の入れ方だけ違う) | ✅ 正解 |
| Q3_K_M | ['Fizz' * (i % 3 == 0) + 'Buzz' * (i % 5 == 0) or str(i) for i in range(1, n+1)] | ✅ 正解 |
| Q2_K | Q3とほぼ同じ | ✅ 正解 |
全部動きました。Q2_Kも含めて。 実際に fizzbuzz(15) を実行して、期待どおりの並びが返ることを確認しています。
面白いのはQ3から書き方が変わったことです。上位は not i % 3、Q3以下は i % 3 == 0。どちらも正しく、後者のほうがむしろ素直な書き方です。ここで起きているのは「劣化」ではなく「選ぶ表現が変わった」と見るべきでしょう。
計算はできるのか
「17 × 24 はいくつか。途中を書いてから答えを1行で」と頼みました。
| 量子化 | 出力(そのまま) |
|---|---|
| F16 〜 Q5_K_M | 17 × 24 = (10 + 7) × 24 → 240 + 168 = 408(4モデルとも一字一句同じ) |
| Q4_K_M | 同じ手順。前置きの言い回しだけ違う → 408 |
| Q3_K_M | 17×4=68、17×20=340、68+340=408(別のやり方だが正解) |
| Q2_K | 17 × 24 = 408 だけ。答えは合っているが「途中を書け」という指示を無視した |
ここでQ2_Kに初めて綻びが出ます。ただし間違えたのではなく、指示を守らなかったという形です。
日本語で説明させると
そして、はっきり差が出たのがここでした。「量子化とは何かを日本語で説明して」と頼んだ結果です。
| 量子化 | 出力(そのまま・抜粋) |
|---|---|
| Q4_K_M | 「量子化とは、物理的な量を離散的な値に制限することを指します。例えば、電子のエネルギーは量子化され、特定の値しか取ることができません。」 |
| Q3_K_M | (別の質問で)「ローカルLLMは、大規模な言語モデルを本地で実行するシステムを指します」 ← 「本地」は中国語で「ローカル」の意味。日本語に混入している |
| Q2_K | 「量子化とは、物理現象を数値や図形などの具体的な形で表現することを指します。主に、物理や数学の専門用語で表現されることが多いですが、その意味は広範で、情報の整理や分析、表現など、様々な場面で使用されます。」 ← 文としては読めるが、中身がほとんど無い |
Q3で中国語が混じり、Q2で内容が空っぽになりました。 コードは動き、計算も当てたのに、日本語の説明だけが崩れています。
・「必ず3行で」という指示は、F16を含む全モデルが守れませんでした(全部1行)。これは量子化のせいではなくモデルの性質なので、比較には使えません
・JSON形式で返す課題は、Q2_Kまで全部正解でした。簡単すぎて差がつきません
・1回の出力で断定はできません。今回は5種類の質問で傾向を見ています
数字の崖と、体感の崖はズレていた
ここまでを並べると、こうなります。
| 量子化 | 数字(perplexity) | 実際の出力 |
|---|---|---|
| Q4_K_M | +1.30% | ほぼ完璧 |
| Q3_K_M | +5.85%(崖) | コードは正解。日本語に中国語が混じる |
| Q2_K | +22.46%(崩壊) | コードは正解・計算も正解・JSONも正解。日本語の説明だけ崩れる |
perplexityが22%悪化した状態でも、FizzBuzzは動き、17×24は当て、JSONの形式も守りました。
数字だけを見て「使い物にならない」と判断すると、実態を見誤ります。逆に、数字がまだ小さいQ3の段階で、日本語にはもう異物が混じっていました。
数字の崖と体感の崖は、同じ場所にありません。
日本語は先に壊れるのか——測り方で結論が変わった
ここで、検証を始める前に立てていた予想を確かめます。「日本語のほうが先に壊れるのではないか」——学習データの比率やトークナイザの都合で、英語より早く崩れるのではないか、というものです。
実際の出力を見る限り、当たっているように見えます。ではperplexityではどうか。日本語(青空文庫『こころ』)でも測りました。
| 量子化 | 英語の悪化 | 日本語の悪化 |
|---|---|---|
| Q4_K_M | +1.30% | +0.48% |
| Q3_K_M | +5.85% | +3.19% |
| Q2_K | +22.46% | +20.56% |
逆でした。 どの段階でも日本語のほうが悪化が小さい。予想は外れ——と、ここで結論しかけました😅
ただ、ひとつ引っかかることがありました。日本語の基準値(F16での値)が51.89と、英語の9.34に比べて極端に高いのです。このモデルはコード特化(Qwen2.5-Coder)なので、そもそも日本語が得意ではありません。
元が悪ければ、そこから少し悪くなっても割合としては小さく見えてしまうのではないか。 そう考えて、2つの方法で裏を取りました。
裏取り①:コーパスのジャンルを揃える
英語はWikipediaなのに、日本語は1914年の小説でした。文語調の古い文章は現代の百科事典より予測しにくいはずです。そこで日本語版Wikipediaでも測りました。
| 量子化 | 英語Wikipedia | 日本語Wikipedia | 日本語 小説 |
|---|---|---|---|
| F16の基準値 | 9.34 | 13.04 | 51.89 |
| Q4_K_M | +1.30% | +1.11% | +0.48% |
| Q3_K_M | +5.85% | +5.14% | +3.19% |
| Q2_K | +22.46% | +23.35% | +20.56% |
ジャンルを揃えると、日本語も英語とほぼ同じ悪化率になりました。 Q2ではむしろ日本語のほうがわずかに悪い。
つまり「日本語は頑丈」に見えていたのは、コーパスが古い小説で元々予測しにくかったからでした。言語の性質ではなく、テキストの選び方の問題だったわけです。
裏取り②:基準値に左右されない指標で測る
ただし、これでもまだ「同じくらい」までしか言えません。実際の出力では明らかに日本語が崩れているのに、数字では差が出ない。
そこでKL divergenceで測り直しました。これは「F16と比べて、次に来る語の予想がどれだけズレたか」を直接測るので、そのコーパスが難しいかどうかに左右されません。
はっきり差が出ました。 全段階で日本語のほうがズレが大きく、その比率は1.44〜1.65倍で安定しています。
もっと直感的なのが「元のモデルと同じ単語を1位に選べた割合」です。
Q2_Kでは、英語77.1%に対して日本語は64.9%。 日本語では3回に1回以上、元のモデルとは違う単語を選んでいます。Q3の段階でも81.5%対87.8%と6ポイント開いています。
・perplexity(小説)→ 日本語のほうが良い(外れて見える)
・perplexity(Wikipedia)→ ほぼ同じ(差が見えない)
・KL divergence → 日本語が1.5倍脆い(一貫して)
・実際の出力 → 日本語だけ崩れる
KL divergenceと実際の出力だけが、同じ答えを出しました。
なぜKL divergenceが必要だったのか
2つの指標で結論が変わった理由は、測っているものが違うからです。
perplexityは「その文章をどれだけ予測できたか」の平均です。だから文章の難しさに左右されます。KL divergenceは「元のモデルとどれだけ違う判断をしたか」なので、文章が難しくても関係ありません。
量子化で知りたいのは「元をどれだけ保てているか」です。だとすれば、基準を持つ後者のほうが素直に答えを出してくれる、ということでした。
小さくすれば速くなる、とは限らない
もうひとつ、測っていて予想外だったことがあります。
Q3_K_Mだけ、へこんでいます。 Q4_K_M(4.36 GiB)より小さい3.55 GiBなのに、生成速度は17.8 tok/s と、Q4_K_Mの21.0 tok/s より遅いのです。
理由は量子化の形式ごとに計算のしかたが違うためです。K が付く量子化は「場所によってビット数を変える」仕組みで、その組み合わせ方が形式ごとに違います。数値を取り出す手間が形式によって変わるので、小さい=速い が常に成り立つわけではありません。
ファイルサイズだけを見て速度を推測すると、ここで外します。
実務の使い分け
測って分かったことを、使い方の形にまとめます。
| やりたいこと | 選ぶもの |
|---|---|
| 迷ったら | Q4_K_M。サイズは31%、悪化+1.30%、1位一致93.6%。定番と言われる理由が数字で確認できました |
| 元をなるべく保ちたい | Q8_0。サイズ半分で悪化は測定誤差の範囲、1位一致99.3%。実質ロスレス |
| 日本語で使う | Q4_K_Mより下げない。Q3で中国語の混入が出ます。日本語は英語より1.5倍脆い |
| コードだけ書かせる | Q3でも実用になりました。Q2でも動くコードは出ましたが、1回の確認で断定はできません |
| とにかく容量を詰めたい | Q2_Kは元の20%。ただし日本語の説明は期待できません |
| 自分で評価したい | perplexityだけで判断しない。KL divergenceと実際の出力を併せて見る |
まとめ
- F16を1つ落とせば、量子化は手元で全部作れます。 6種類で3分弱。同じ元から作るので比較の条件も揃います
- ★数字の崖はQ3から。 Q4_K_Mまでは+1.30%、Q3で+5.85%、Q2で+22.46%。どのコーパスでも崖の位置は同じでした
- ★ところが数字の崖と体感の崖はズレていました。 「崩壊」と言われるQ2_Kでも、FizzBuzzは動き、17×24は当て、JSONも守る。崩れたのは日本語の説明文だけ
- ★「日本語のほうが先に壊れる」という予想は当たっていました。 ただしperplexityだけを見ていたら、外れたと結論していました。基準値の高さと、コーパスの選び方に引きずられていたからです
- ★KL divergenceと実際の出力だけが、同じ答えを出しました。 日本語は英語より1.44〜1.65倍ズレる。Q2_Kの1位一致率は英語77.1%に対し日本語64.9%
- 小さくすれば速くなるとは限りません。 Q3_K_MはQ4_K_Mより小さいのに遅い(17.8 vs 21.0 tok/s)
今回いちばん怖いと思ったのは、最初にperplexityだけを見て「予想は外れた」と書きかけたことです。数字は出ていたし、3つのコーパスで測って傾向も一致していた。それでも間違っていました。
数字が揃っていることと、正しい数字を見ていることは別——前回の速度の回でも「測っている場所が違うだけ」という話に行き着きましたが、精度でも同じところに戻ってきました😅
それでは、今回はここまで。最後までありがとうございました😊
【実測環境】Apple M4 / macOS 26.5.2 / RAM 32GB / llama.cpp b10090(7347430f4・Metal)。モデルは Qwen2.5-Coder-7B-Instruct の F16(unsloth 配布のGGUF)を取得し、llama-quantize で Q8_0 / Q6_K / Q5_K_M / Q4_K_M / Q3_K_M / Q2_K を生成。perplexity と KL divergence は llama-perplexity で 60チャンク・n_ctx 512。コーパスは英語=wikitext-2 test(128万文字)、日本語Wikipedia=ja.wikipedia.org のAPIから閲覧数上位の記事を取得(18記事・25.8万文字)、日本語小説=青空文庫の夏目漱石『こころ』(著作権切れ・ルビと注記を除去、16.2万文字)。KL divergence はF16のロジットを基準ファイルに保存して比較。出力の採取は llama-cli で temperature 0・同一プロンプトを全量子化に投入し、整形せず保存(本文中の引用は llama-cli 自身が出す統計行のみ除去)。速度は llama-bench -p 512 -n 128 -r 3。コード生成の判定は、出力をそのまま実行して fizzbuzz(15) の結果を照合しています。数値は2026年7月時点の実測で、モデル・版により変わる可能性があります。