- Published on
ローカルでLLMを動かすにはVRAMがどれだけ必要か — 表ではなく数式で計算する
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに — 表を探すのではなく、数式を覚えてください
- VRAMは何で埋まるのか — 3つの塊
- 重みの計算 — 「4ビット」は4ビットではない
- なぜQ4_K_Mは4.5ではなく4.89なのか
- KVキャッシュ — コンテキストに比例して育つ側
- それで、あなたのカードには何が入るのか
- 量子化は品質をどれだけ削るのか — 測定された値だけ
- tok/sの数字がハードウェアなしで来たら捨ててください
- ビットをさらに削れば速くなるのか — ある線までだけ
- 計算が変わる場合 — MoEとハイブリッドアテンション
- それで、何をあきらめるのか
- おわりに
- 参考資料
はじめに — 表を探すのではなく、数式を覚えてください
「8Bモデルを動かすにはVRAMは何GB必要ですか?」この質問に対するインターネットの標準回答は、どこかからコピーされてきた表です。その表はたいてい出典がなく、コンテキスト長を明示せず、どの量子化なのか曖昧で、何よりあなたのケースには当てはまりません。モデルもコンテキストも量子化もあなたが選ぶのに、表は誰かが一度選んだ組み合わせのスナップショットにすぎないからです。
そこで、先に答えを差し上げます。VRAMは大きく2つの塊に分かれ、それぞれに数式があります。
(1) 重み = パラメータ数 × bpw / 8 [バイト]
bpw = bits per weight (量子化レベルの「実際の」ビット数)
(2) KVキャッシュ = 2 × レイヤー数 × KVヘッド数 × head_dim × バイト/要素 × トークン数
2 = KとV、2セット分
バイト/要素 = f16なら2、q8_0なら1
(1)は定数です。モデルと量子化を選んだ瞬間に固定されます。(2)はコンテキストに正比例して育ちます。そして、ほとんどの人がローカルLLMで壁にぶつかる理由は(1)ではなく(2)です。先に結論を言うと — Llama-3.1-8BをQ4_K_Mで量子化すると重みは4.58 GiBですが、このモデルがサポートする128Kコンテキストをf16のKVキャッシュで使い切ると、キャッシュだけでちょうど16 GiBです。重みの3.5倍です。
本稿ではこの2つの式を立て、llama.cppのソースコードと公式の公表表に直接照らして検証します。そのあとで正直な部分 — ビットを削ると品質をどれだけ失うのか(測定された値だけで)、さらに削れば本当にもっと速くなるのか(なりません) — を扱います。
関連して、ブラウザの中で動かす場合の制約はllama.cpp WebGPUバックエンドの回で別途扱いました(そこではボトルネックはVRAMではなくタブのメモリです)。量子化手法そのものの地形図 — GPTQ・AWQ・FP8・MXFP4がそれぞれ何なのか — はLLM量子化総まとめの回にあります。本稿はただ一つ、メモリの算数だけに集中します。
VRAMは何で埋まるのか — 3つの塊
まず地形から。GPUメモリに入るものは3つです。
- 重み。 モデルファイルそのもの。コンテキストと無関係に固定。
- KVキャッシュ。 処理済みのトークンごとにアテンションのKとVを保存しておいたもの。トークン数に正比例。
- 残り。 計算用の一時バッファ(compute buffer)、CUDAコンテキスト、そしてデスクトップを同じカードにつないでいるなら画面出力の分まで。
3つ目は式にできません。バックエンド・バッチサイズ・フラッシュアテンションの有無で変わるからです。ここで数字をでっち上げることはしません — 代わりに、llama.cppがモデルをロードするときに直接印字してくれます。ログのKV self sizeとcompute buffer sizeの行を読めばいいのです。本稿は(1)と(2)、つまり下限を正確に計算することに集中します。下限を知って初めて、3つ目がどれだけ乗っているのかも判断できます。
重みの計算 — 「4ビット」は4ビットではない
式(1)は簡単に見えますが、罠はbpwに潜んでいます。「4ビット量子化」のbpwは4ではありません。
理由は量子化の仕組みにあります。重みを4ビット整数に変えるには、「この整数たちを実際の値に戻す倍率(scale)」を一緒に保存しなければなりません。その倍率は4ビットではありません。だから実際のビット数は常に名目値より大きくなります。
どれだけ大きいかは推定する必要がありません。ggmlのブロック構造体にそのまま書いてあります。ggml/src/ggml-common.hで最も単純なQ8_0を見るとこうです。
#define QK8_0 32
typedef struct {
ggml_half d; // delta
int8_t qs[QK8_0]; // quants
} block_q8_0;
読み方はこうです。重み32個(QK8_0)が1ブロックで、ブロックごとにint8が32個(32バイト)とggml_halfの倍率が1個(2バイト)入ります。すると、
Q8_0 = (32 × 8 + 16) ビット / 32 個の重み
= 272 / 32
= 8.5 bits per weight
名目は8ビットなのに実際は8.5ビットです。32個ごとにfp16の倍率が1つ付いて、ちょうど0.5ビットが追加されたわけです。
次にk-quantを見ましょう。Q4_Kは256個(QK_K)を1つのスーパーブロックにまとめます。
#define QK_K 256
#define K_SCALE_SIZE 12
typedef struct {
ggml_half d; // スーパーブロック倍率 2バイト
ggml_half dmin; // 最小値用スーパーブロック倍率 2バイト
uint8_t scales[K_SCALE_SIZE]; // 6ビットに量子化された倍率と最小値 12バイト
uint8_t qs[QK_K/2]; // 4ビット量子値 128バイト
} block_q4_K;
合計144バイトで重みが256個なので、144 × 8 / 256 = 4.5 bpwです。そしてこれは私の計算ではなく、ソースのコメントが直接語っています — block_q4_Kのすぐ上に// Effectively 4.5 bits per weightと書いてあります。同じファイルに5つあります。
ggml-common.h のコメントが直接明かした「実効」bpw
Q2_K 2.625 Q3_K 3.4375 Q4_K 4.5
Q5_K 5.5 Q6_K 6.5625
(Q8_0にはコメントがありませんが、構造体から8.5がそのまま出ます)
式が正しいか検証する
ここまでは導出です。合っているか確かめなければなりません。llama.cppの公式ドキュメント(tools/quantize/README.md)がLlama-3.1-8Bについて量子化ごとのbpwとファイルサイズを公表しています。式を裏返してパラメータ数を逆算してみると — パラメータ数 = サイズ × 8 / bpw — すべての行で同じ値が出るはずです。
quant 公表bpw 公表サイズ(GiB) 逆算したパラメータ数
F16 16.0005 14.96 8.031 B
Q8_0 8.5008 7.95 8.033 B
Q6_K 6.5633 6.14 8.036 B
Q5_K_M 5.7036 5.33 8.027 B
Q4_K_M 4.8944 4.58 8.038 B
Q3_K_M 3.9960 3.74 8.040 B
Q2_K 3.1593 2.95 8.021 B
IQ1_S 2.0042 1.87 8.015 B
すべて8.02~8.04Bに集まります。そしてLlama-3.1-8Bの実際のパラメータ数は、Hugging Faceのsafetensorsメタデータに整数で刻まれています — 8,030,261,248個。式は正しい。丸め誤差の範囲内で、すべての行が実際の値を言い当てます。
もっと良い確認もあります。ブロック構造体から導いた値と、llama.cppが実際に測定して公表した値を並べてみましょう。
構造体から導出 公表された実測 差
Q8_0 8.5000 8.5008 +0.0008
Q6_K 6.5625 6.5633 +0.0008
Q5_K_S 5.5000 5.5704 +0.0704
Q5_K_M 5.5000 5.7036 +0.2036
Q4_K_S 4.5000 4.6672 +0.1672
Q4_K_M 4.5000 4.8944 +0.3944
Q3_K_S 3.4375 3.6429 +0.2054
Q3_K_M 3.4375 3.9960 +0.5585
Q2_K 2.6250 3.1593 +0.5343
Q8_0とQ6_Kは小数第3位まで一致します(8.5000対8.5008)。純粋なブロックの算数だけで公表数値を予測できたわけです。ところが残りは最大0.56ビットまで超過します。この超過分が次の節の主題です。
なぜQ4_K_Mは4.5ではなく4.89なのか
_S(small)と_M(medium)は量子化のタイプではなく混合レシピです。すべてのテンソルをQ4_Kで刻むのではなく、一部のテンソルはより高い精度のまま残します。
レシピはsrc/llama-quant.cppに明記されています。Q4_K_Mの場合、こんな規則がかかります。
- アテンションの
attn_vテンソル — 一部のレイヤーでQ6_Kへ昇格(use_more_bitsが真のレイヤー) - FFNの
ffn_downテンソル — 同じく一部のレイヤーでQ6_Kへ昇格 - アテンションのQKVテンソル —
Q5_Kへ昇格
つまりQ4_K_Mは「大部分は4.5ビットだが、品質に敏感な一部のテンソルは6.5ビット」です。だから平均が4.5から4.8944へ上がります。ソースのコメントがこの設計思想をよく説明しています — 70Bモデルのattn_vに付いたコメントの意味を移すと、8個のヘッドがattn_vの重みを共有するためそのテンソルはattn_qより8倍小さく、したがって「モデルサイズをほとんど増やさずに量子化の精度をかなり上げられる」というわけです。
ここで実務的に重要な結論を一つ。_Mが_Sより常に良いとは限りません。_Mはビットを余分に使う代わりに品質を買う取引であり、その取引が有利かどうかは後述の品質数値を見なければ分かりません。そしてQ4_K_Mがローカル陣営のデフォルトのように固まった理由も後で出てきます。
KVキャッシュ — コンテキストに比例して育つ側
ここからが本当の問題です。
式(2)をもう一度書きます。
KVキャッシュ = 2 × L × n_kv_heads × head_dim × bytes × n_tokens
この式が正しいかどうかもllama.cppのソースで確認できます。src/llama-kv-cache.cppはレイヤーごとにKとVのテンソルをこう作ります。
const uint32_t n_embd_k_gqa = hparams.n_embd_k_gqa(il);
ggml_tensor * k = ggml_new_tensor_3d(ctx, type_k, n_embd_k_gqa, kv_size, n_stream);
ggml_tensor * v = ggml_new_tensor_3d(ctx, type_v, n_embd_v_gqa, kv_size, n_stream);
そしてn_embd_k_gqaの定義はsrc/llama-hparams.cppにあります。
uint32_t llama_hparams::n_embd_k_gqa(uint32_t il) const {
const uint32_t n_head_kv = this->n_head_kv(il);
return n_embd_head_k(il) * n_head_kv;
}
つまりhead_dim × n_kv_headsのベクトルがトークン(kv_size)ごとに、KとVの2セット、レイヤーごとに確保されます。式(2)そのままです。
ここでの核心はn_kv_headsです。アテンションヘッド数ではなくKVヘッド数です。最近のモデルはほぼすべてGQA(Grouped-Query Attention)を使ってKVヘッドを大幅に減らしています。Llama-3.1-8Bはアテンションヘッドが32個なのに、KVヘッドは8個だけです。ここに32を入れると答えが4倍狂います。インターネットの表がよく間違えるのが、まさにこの点です。
Llama-3.1-8Bで計算してみる
設定値はconfig.jsonにあります — レイヤー32個、アテンションヘッド32個、KVヘッド8個、hidden 4096、最大コンテキスト131,072。head_dimは別途ないのでhidden / n_heads = 4096 / 32 = 128です。(ちなみにMetaの公式リポジトリはアクセス承認が必要で、匿名の照会は塞がれています。上の値と後で出てくるパラメータ数はミラーリポジトリから読んだもので、出典は参考資料に明記してあります。)
トークン1個あたり = 2 × 32レイヤー × 8KVヘッド × 128 × 2バイト(f16)
= 131,072 バイト
= 128 KiB / トークン
128Kコンテキスト(131,072トークン)を全部使うと:
131,072 バイト × 131,072 トークン = 17,179,869,184 バイト = ちょうど16 GiB
トークン1個に128 KiB。覚えやすい数字です。そして128Kを全部埋めると16 GiB、ぴったりです。
では、さっきの数字と並べてみてください。
Llama-3.1-8B, Q4_K_M
重み 4.58 GiB (固定)
KV @ 4K 0.5 GiB
KV @ 32K 4.0 GiB ← ここですでに重みに匹敵
KV @ 128K 16.0 GiB ← 重みの3.5倍
あなたを阻むのはモデルではなくコンテキストです。 8Bモデルを4ビットに縮めることに成功したからといって、128Kコンテキストが使えるという意味ではまったくありません。
他のモデルは違います — だから式が必要です
同じ「8B」でも値は違います。Qwen3-8Bのconfig.jsonはレイヤー36個、KVヘッド8個、head_dim 128、最大コンテキスト40,960です。
Qwen3-8B: 2 × 36 × 8 × 128 × 2 = 147,456 バイト = 144 KiB / トークン
最大コンテキスト40,960トークンを使い切ると → 5.625 GiB
トークンあたりではLlamaより12.5%多く食います(レイヤーが36個だから)が、最大コンテキストが40,960なので総量ははるかに小さくなります。「8B」というラベルはKVキャッシュについて何も教えてくれません。 レイヤー数、KVヘッド数、head_dim、そしてあなたが実際に開くコンテキスト — この4つが決めます。
KVキャッシュも量子化できます
llama.cppはKVキャッシュ自体を低い精度で保存するオプションを提供しています。tools/server/README.mdからそのまま移すと、
-ctk, --cache-type-k TYPE— 許容値:f32, f16, bf16, q8_0, q4_0, q4_1, iq4_nl, q5_0, q5_1(デフォルトf16)-ctv, --cache-type-v TYPE— 同じ許容値、同じデフォルト
q8_0に変えると要素あたり2バイトが1バイトに減り、KVキャッシュがちょうど半分になります。Llama-3.1-8Bの128Kキャッシュが16 GiBから8 GiBへ下がります。許容値リストで注目すべき点が一つ — k-quant(q4_Kのようなもの)はリストにありません。 KVキャッシュにはレガシー量子化しか使えないのです。
ただし、ここには品質の代償があり、その代償は重みの量子化ほどよく測定されて公開されてはいません。この方向の最新研究はINT4 KVキャッシュ量子化の回で別途扱いました。ここではメモリがちょうど半分になるという算数だけを確定して先へ進みます。
それで、あなたのカードには何が入るのか
いよいよ組み立てです。Llama-3.1-8BをQ4_K_M(4.58 GiB)で載せるとして、残りの空間をすべてKVに使うなら、コンテキストをどれだけ開けるのか。
注意 — 以下は計算バッファと画面出力を除いた理論上の上限です。実際にはここからさらに減ります。その分は前に言ったとおりでっち上げず、あなたのログから読み取ってください。
VRAM 重みを引いた残り f16 KVで開けるコンテキスト(上限) q8_0 KVなら
16 GiB 11.4 GiB 約 93,000 トークン 約 187,000
24 GiB 19.4 GiB 約 159,000 トークン 約 318,000
32 GiB 27.4 GiB 約 224,000 トークン 約 449,000
読み方が重要です。
- 16GBカード: 8Bモデルは余裕で入ります(4.58 GiB)。しかし128Kコンテキストは、f16では上限が93,000トークンなので開けません。ここから計算バッファまで引くと、さらに開けません。
-ctk q8_0 -ctv q8_0を有効にして、ようやく128Kが射程に入ります。 - 24GBカード(RTX 3090/4090): f16で128Kが上限内には収まります(159,000)が、余裕は大きくありません。
- 32GBカード(RTX 5090): f16の128Kが楽に入ります。
一行でまとめると — 「モデルがカードに入るか」は間違った質問です。 正しい質問は「モデル + 自分が実際に使うコンテキストが入るか」です。
量子化は品質をどれだけ削るのか — 測定された値だけ
ここからは主張せず、引用だけにします。
llama.cppはtools/perplexity/README.mdで量子化ごとの品質損失を測定して公開しています。この表が良いのは条件を明かしているからです。
出典: llama.cpp tools/perplexity/README.md — "LLaMA 3 8b Scoreboard"
Revision : f364eb6f
Backend : CUDA
CPU : AMD Epyc 7742
GPU : 1x NVIDIA RTX 4090
データセット : Wikitext-2 テストセット
まず大きな警告を一つ。このrevision f364eb6fをGitHub APIで照会してみると、コミット日付は2024年4月30日です。つまりこの表は現在のmasterのREADMEに載っていますが、データ自体は2年以上前のビルドから出たものです。その間、llama.cppの量子化実装は変わり続けました。だからこれらの数字は量子化レベル間の相対的な序列を読むために使い、絶対値を今日のビルドへそのまま持ち込まないでください。ただし、これはプロジェクトが自ら測定して公開した、条件が明示された唯一の公開データです。
指標は2つ見ます。PPL(perplexity)は低いほど良く、KLD(KLダイバージェンス)はfp16と確率分布がどれだけ変わったかを示し、0なら完全に同じです。README自身が、KLDをより信頼する方向に表を並べています。
quant サイズ(GiB) PPL ΔPPL KLD 平均Δp
f16 14.97 6.233160 — 0.000551 0.001 %
q8_0 7.96 6.234284 0.002650 0.001355 -0.019 %
q6_K 6.14 6.253382 0.021748 0.005452 -0.007 %
q5_K_M 5.33 6.288607 0.056974 0.010762 -0.114 %
q4_K_M 4.58 6.407115 0.175482 0.031273 -0.596 %
q3_K_M 3.74 6.888498 0.656864 0.101913 -1.990 %
q2_K 2.96 9.751568 3.519934 0.445132 -9.123 %
iq1_S 1.88 58.097760 51.866126 2.211278 -32.471 %
(imatrixなし基準。iq1_Sのみ "WT 1m" imatrixを使用)
この表の読み方。
- q8_0は事実上タダです。 PPLは6.233から6.234に行くだけです。サイズは半分なのにKLDは0.0014。fp16に固執する理由はほとんどありません。
- q6_K、q5_K_Mも非常に安い。 平均Δp(正解トークンの確率変化)はそれぞれ-0.007%、-0.114%です。
- q4_K_Mが膝です。 サイズはfp16の31%水準なのに、平均Δpは-0.596%。これがQ4_K_Mがローカル陣営のデフォルトになった理由です。
- q3_K_Mから目に見えて痛くなります。 PPLが6.41から6.89へ跳ねます。
- q2_Kは別のモデルになります。 PPL 9.75、平均Δpは-9.1%。
- iq1_Sは崩壊します。 PPL 58.1。元が6.23なので9.3倍です。
README自身が付けた警告をそのまま移します
これらの数字を使う前に、ドキュメント自身が直接付けた但し書きがあります。原文の意味をそのまま移すと、
- perplexityはモデル間で直接比較できません。特にトークナイザーが違う場合はそうです。
- ファインチューニングをすると、人間が評価した出力品質が上がってもperplexityはたいてい上がります。
- llama.cppの数値は実装の詳細に大きく依存するため、他のプロジェクトの数値と直接比較できません。
3つ目が特に重要です。この表のPPLをGPTQやAWQの論文のPPLと並べて比較してはいけません。 互いに異なる実装の自己測定値であり、共通のベンチマークではないからです。
そして2つ目の警告が含意するところは大きい — perplexityは品質の代理指標にすぎず、品質そのものではありません。「q4_K_Mの平均Δpが-0.596%」という文は、「あなたのコード生成作業が0.596%悪くなる」という意味ではありません。それは誰も測ってくれていません。あなたの作業で測るしかないのです。
同じ量子化がモデルごとに違う痛み方をする
これがこの表で最も知られていない、そして最も重要な事実です。同じドキュメントの "LLaMA 2 vs. LLaMA 3 Quantization comparison" の表を見ると(同じrevision、同じ4090)、
Mean PPL ratio (1.0 = 損失なし)
L2 7b L3 8b
q8_0 1.000689 1.000425
q6_K 1.002406 1.003490
q4_K_M 1.014242 1.028160 ← 1.4% vs 2.8%、2倍
q2_K 1.107955 1.564849 ← 10.8% vs 56.5%、5倍
Mean KLD
q2_K 0.108903 0.445132 ← 4.1倍
同じq2_KがLlama-2 7BではPPLを10.8%上げるのに対し、Llama-3 8Bでは56.5%上げます。q4_K_Mも1.4%対2.8%で、ちょうど2倍です。
示唆は大きい。「Q4_K_Mは品質損失が微々たるものだ」という文は、モデルへの限定なしには真ではありません。 より多くのトークンでより厳しく学習されたモデルほど重みに余裕がなく、だから同じビット予算でより多く失います。2026年のモデルはLlama-3よりもさらに多く学習されています。だから上の表の数値は、今日の最新モデルに対して楽観的な方向に間違っている可能性が高いのです。あなたのモデルで測り直す以外に方法はありません。
ちなみにimatrix(importance matrix)についてもREADMEは正直な観察を残しています — Wikitextのトークンを増やしてimatrixを作っても、一貫した改善は見られなかったというものです。imatrix自体は役に立ちます(q4_K_MのKLDが0.0313から0.0282へ)。しかし「トークンを増やす = より良いimatrix」はこのデータでは支持されません。
tok/sの数字がハードウェアなしで来たら捨ててください
次は速度です。そしてここで一番言いたいのは方法論です。
tok/sの数字1つが意味を持つには、最低4つが一緒に来なければなりません — 正確なハードウェア(GPUのモデル名)、モデル、量子化、そしてコンテキスト長(またはバッチ長)。4つのうち1つでも欠ければ、その数字は他人に何の情報も与えません。
これが小言ではないことをお見せします。llama.cppの公式README(tools/quantize/README.md)に、こんな表が載っています。
Measure Q4_K_M Q8_0 F16
bits/weight 4.8944 8.5008 16.0005
size (GiB) 4.58 7.95 14.96
prompt processing t/s @ 512 821.81 ±21.44 865.09 ±8.30 923.49 ±0.53
text generation t/s @ 128 71.93 ±1.52 50.93 ±0.08 29.17 ±0.04
モデルは明かしています(Llama-3.1-8B)。量子化も明かしています。コンテキスト長も明かしています(@ 512、@ 128)。誤差範囲まで付いています。ところがハードウェアがどこにもありません。 ドキュメント全体を見渡しても、GPUも、CPUも、バックエンドも、ビルドのrevisionもありません。この表を最後に修正したPR(#24133、2026年6月5日マージ)の本文にもありません。
だから「Q4_K_Mは71.93 tok/s」という文は単独では役に立ちません。 3090ででしょうか、5090ででしょうか、それともM4 Maxででしょうか? 3090と5090ではメモリ帯域幅が1.9倍違います。同じドキュメントを書いた同じプロジェクトが、perplexityのREADMEではCPU・GPU・バックエンド・revisionをすべて明かしていたことを考えると、これはドキュメント側のミスに近いでしょう。
しかし — そしてここが面白いところですが — この表からまだ読み取れるものはあります。 絶対値は読めませんが、すべての行が同じ(未知の)ハードウェアから出ている以上、行と行の比率は有効です。次の節がその話です。
ビットをさらに削れば速くなるのか — ある線までだけ
まず、なぜ量子化が速度を上げるのかから。バッチ1でトークンを生成するとき、モデルはトークン1個ごとに重み全体をメモリから1回ずつ読みます。 演算量は大したことがないのに、読むものが多い。だからデコードはメモリ帯域幅に縛られます(memory-bandwidth-bound)。ここから天井の式が出ます。
tok/s上限 = メモリ帯域幅 / モデルサイズ (denseモデル、バッチ1)
NVIDIAのRTX Blackwellアーキテクチャのホワイトペーパーに載っている帯域幅スペックで計算してみましょう。ホワイトペーパー本文のとおり — RTX 5090は28 GbpsのGDDR7を積み、1.792 TB/secのピークメモリ帯域幅を出します。同じホワイトペーパーの比較表は、3090を936 GB/sec、4090を1008 GB/secと記しています。
Llama-3.1-8B Q4_K_M (4.58 GiB) の理論的天井
RTX 3090 (936 GB/s) → 190 tok/s
RTX 4090 (1008 GB/s) → 205 tok/s
RTX 5090 (1792 GB/s) → 364 tok/s
これは天井です。実測は常にこれより低くなります。しかしこの式は、あなたのカードについて「これより速くはなり得ない」を教えてくれ、どのベンチマーク数値が物理的にあり得るのかを判定させてくれます。ある記事が4090で8BのQ4_K_Mを300 tok/sで回したと言うなら、それはバッチ1のデコードではないか、何か別のものを測っています。
ところが式は下の方で壊れます
ここで、さっきの「比率は有効だ」を使います。もしデコードが純粋に帯域幅に縛られているなら、サイズ × tok/sはすべての量子化で一定の値(= 実効帯域幅)になるはずです。llama.cpp公式表の数字で直接掛けてみます。(以下の掛け算は、彼らの公表数値から私が計算した値です。)
quant サイズ(GiB) 生成tok/s サイズ × tok/s
F16 14.96 29.17 436
Q8_0 7.95 50.93 405
Q6_K 6.14 58.67 360
Q5_K_M 5.33 67.23 358
Q4_K_M 4.58 71.93 329
Q3_K_M 3.74 71.68 268
Q2_K 2.95 79.85 236
IQ2_XXS 2.23 79.86 178
IQ1_S 1.87 79.73 149
一定ではありません。436から149まで下がり続けます。 ビットを削るほど帯域幅を使えなくなっているという意味です — 読むデータは減ったのに、それを解きほぐす(dequantize)演算が増えて、ボトルネックが帯域幅から演算へ移っていったのです。
結果を比率で見ると、痛いほど明確です。
F16 → Q4_K_M : サイズ3.27倍小さく、速度2.47倍速い (取引成立)
Q4_K_M → Q2_K : サイズ1.55倍小さく、速度1.11倍速い (取引は悪い)
Q4_K_M → IQ1_S : サイズ2.45倍小さく、速度1.11倍速い (取引は最悪)
Q4_K_MからIQ1_Sへ下りるとモデルは2.45倍小さくなるのに、速度は11%しか速くなりません。 その間、品質は — 前節の表で — PPLが6.41から58.1へ(9.1倍)、KLDが0.031から2.21へ(71倍)行きます。9倍悪くなったモデルを受け取って、速度11%をもらう取引です。
但し書きを正直に付けます。この速度表(Llama-3.1-8B、ハードウェア不明)と前の品質表(Llama-3 8B、RTX 4090、2024年4月のrevision)は互いに別の実行です。モデルも微妙に違い、ビルドも違います。だから2つの表を横断して読むのは厳密な比較ではなく、方向を見るためのものです。それでも2つの表それぞれの内部序列は堅牢で、その序列が同じ場所を指しています。
だから、非常に低いビットへ下りる唯一の正当な理由は、速度ではなく「それ以外では入らないから」です。 速度のためにIQ1_Sを選ぶのは、算数が合いません。
計算が変わる場合 — MoEとハイブリッドアテンション
2つの式には前提があります。最近のモデルはその前提を壊します。
MoE(Mixture of Experts)。 OpenAIのgpt-oss-20bのモデルカードをそのまま引用すると、このモデルは「21Bパラメータで、アクティブパラメータは3.6B」です。ここで2つの式が分岐します。
- メモリは全体が必要です。 どのエキスパートが選ばれるか事前に分からないので、21Bを全部載せなければなりません。
- 帯域幅はアクティブ分だけ読みます。 トークンごとに3.6B分だけ読みます。
つまり、先の天井の式帯域幅 / モデルサイズの分母が、全体サイズではなくアクティブサイズに変わります。MoEが「重いのに速い」理由がこれです。メモリでは21Bの代金を払い、速度では3.6Bの恩恵を受けます。同じモデルカードが、gpt-oss-20bはMXFP4でMoEの重みを量子化して16GBメモリの中で動くと明かしています。
ハイブリッドアテンション。 gpt-oss-20bのconfig.jsonを見ると、layer_typesがsliding_attentionとfull_attentionを交互に配置し、sliding_windowは128です。スライディングウィンドウのレイヤーは直近128トークンのKVだけ持っていればいいので、そのレイヤーのKVはコンテキストに比例して育ちません。 式(2)のn_tokensがレイヤーごとに変わるということです。
これが例外ではない、というのがポイントです。llama.cppのソースはすでにこれを反映しています — 先ほど見たn_embd_k_gqa(il)がレイヤーインデックスilを引数に取る理由が、まさにレイヤーごとにKVヘッド数が違い得るからです。モデルのconfig.jsonを開いて見ることの代わりになる表は存在しません。
それで、何をあきらめるのか
整理します。ローカルLLMであなたがあきらめるものは3つです。
第一に、品質。 測定された値で言えば — q8_0は事実上タダ(KLD 0.0014)、q5_K_Mまでも安く(KLD 0.011)、q4_K_Mが膝(KLD 0.031)、q3_K_Mから痛くなり(KLD 0.102)、q2_K以下は別のモデル(KLD 0.445+)です。ただし、この序列は2024年4月ビルドのLlama-3 8B基準であり、より多く学習されたモデルほど同じビットでより多く失います。
第二に、コンテキスト。 これが最も過小評価されている項目です。8Bモデルを16GBカードに載せるのは簡単です。そのモデルに128Kコンテキストをf16で与えるのは不可能です。ほとんどの人が「モデルは入ったのに、なぜ長い文書が入らないのか」でつまずきます。
第三に、速度の予測可能性。 他人のtok/sはあなたのtok/sではありません。ハードウェアが明示されていない数値は捨て、明示された数値でも、あなたのカードの帯域幅で天井を計算し直してください。
決定ルール
「場合による」では終わらせません。こうしてください。
- 量子化はQ4_K_Mから始めてください。 品質/サイズの膝であり、そこからさらに下りても速度はほとんど付いてきません。カードに余裕があるなら、Q4より下へ下りて余った空間を遊ばせておくより、Q5_K_MやQ6_Kへ上げる方が常に良い選択です。
- 重みを決めたら、残りのVRAMをKV予算と置いてコンテキストを逆算してください。 逆にしないでください。コンテキストが先に決まっているなら、それが量子化を決めます。
- KVが入らないなら、重みをさらに削る前に
-ctk q8_0 -ctv q8_0を先に有効にしてください。 キャッシュがちょうど半分になります。重みをQ4からQ3へ下げるより、こちらの方がたいてい得です。 - モデルを替えるたびに
config.jsonを開いてください。 レイヤー数、KVヘッド数、head_dim。30秒で済みます。 - あなたのカード、あなたのモデル、あなたのコンテキストで一度測ってください。 そして測ったなら、ハードウェアを明かして共有してください。
いつローカルを使ってはいけないか
正直に言うと — ローカルが答えではないケースは多いのです。
- 品質が最優先のとき。 4ビットの8Bはフロンティアモデルではありません。その差は量子化では埋められません。
- 長いコンテキストが本質のとき。 KVの算数があなたに勝ちます。128Kを常に使うなら、ローカルの1枚のカードは間違った道具です。
- 同時ユーザーがいるとき。 上の式はすべてバッチ1基準です。複数のリクエストを同時に受ければ、KVキャッシュはユーザー数分だけ掛け算されます。
- たまにしか使わないとき。 カードの代金は、膨大な量のAPIトークンに相当します。
逆にローカルが勝つ場合は明確です — データが機器の外に出てはいけないとき、オフラインでなければならないとき、呼び出し量が大きく予測可能で固定費が償却できるとき、そしてレイテンシがネットワーク往復より重要なとき。 そしてRTX 5090で小さなモデルを実際に回してみた記録で見るように、小さなモデルで十分な作業は思いのほか多いのです。
おわりに
ローカルLLMのVRAM問題への正解は、数字ではなく2つの数式です。
重み = パラメータ数 × bpw / 8
KVキャッシュ = 2 × レイヤー × KVヘッド × head_dim × バイト × トークン数
1つ目は固定で、2つ目が育ちます。そして本稿で確認したとおり、2つ目は1つ目を軽々と超えます — Llama-3.1-8B Q4_K_Mの重みは4.58 GiBなのに、128Kコンテキストのf16 KVは16 GiBです。
これらの式を信じていい理由は、私がそう言うからではありません。ggmlのブロック構造体から導いたQ8_0の8.5 bpwがllama.cppの公表した8.5008と一致し、その式を裏返して逆算したパラメータ数8.02~8.04Bが実際の値8,030,261,248と一致するからです。式がベンダーの自己公表数値を再現するなら、その式はあなたのケースでも機能します。 表はそうではありません。
最後に、方法論をもう一つだけ。本稿で最も長く手こずった問題は、llama.cpp公式READMEの速度表にハードウェアがないことでした。あの表はモデルも、量子化も、コンテキスト長も、さらには誤差範囲まで明かしているのに、GPUの名前がありません。だから「71.93 tok/s」は引用できない数字になってしまいます。あなたがベンチマークを共有するときは — GPU、モデル、量子化、コンテキスト長。4つ全部を書いてください。4つがそろっていない数字は、誰にも使えません。
参考資料
- llama.cpp — tools/quantize/README.md(Llama-3.1-8Bのbpw・サイズ・速度表)
- llama.cpp — tools/perplexity/README.md(LLaMA 3 8b Scoreboard、PPL・KLD測定値と条件)
- llama.cpp — ggml/src/ggml-common.h(ブロック構造体と "Effectively N bits per weight" コメント)
- llama.cpp — src/llama-quant.cpp(Q4_K_Mなど混合レシピの規則)
- llama.cpp — src/llama-kv-cache.cpp(KVテンソルの割り当て)
- llama.cpp — src/llama-hparams.cpp(n_embd_k_gqaの定義)
- llama.cpp — tools/server/README.md(--cache-type-k / --cache-type-v の許容値)
- llama.cpp PR #24133 — Update quantization readme(2026-06-05マージ)
- NVIDIA RTX Blackwell GPUアーキテクチャ ホワイトペーパー(3090/4090/5090のメモリ帯域幅)
- openai/gpt-oss-20b モデルカード(総パラメータ21B、アクティブ3.6B、MXFP4)
- Qwen/Qwen3-8B config.json
- NousResearch/Meta-Llama-3.1-8B — Llama-3.1-8Bのconfig.jsonとパラメータ数8,030,261,248(Meta公式リポジトリがゲーティングされているためミラーを使用)
- The Llama 3 Herd of Models (arXiv 2407.21783)
- LLM量子化総まとめ — GPTQ・AWQ・FP8・MXFP4(関連記事)
- KVキャッシュを4ビットに — SAW-INT4(関連記事)
- llama.cppがブラウザにやってきた — WebGPUバックエンド(関連記事)
- RTX 5090一枚で小さなモデルを実際に回してみる(関連記事)