- 必要メモリを「重み + KV キャッシュ + 作業領域 + ランタイム固定費」の 4 項に分解し、実際の数字で埋められる
- 長いコンテキストでは KV キャッシュが重みより大きくなる理由と、GQA・KV 量子化・SWA でどこまで減るか
- メモリに収まらなかったとき、エラーで止まるのではなく静かに遅くなるという失敗の形
- 生成速度が「メモリ帯域 ÷ モデルサイズ」でおおむね決まる理由、プロンプト処理がそれとは別物である理由
- 自分のマシンの実数値を
ollama show/ llama.cpp の起動ログ /lms load --estimate-onlyから読む手順
必要メモリは 4 つの足し算
「このモデルは自分のマシンで動くか」という問いは、勘で答えるものではありません。必要メモリは 4 つの項の足し算に分解でき、そのどれも計算できます。
必要メモリ ≒ ①重み + ②KV キャッシュ + ③作業領域 + ④ランタイム固定費
4 項は性質がまったく違います。①重みは、どのモデルのどのファイルを落とすかを決めた瞬間に確定し、実行中は 1 バイトも変わりません。②KV キャッシュは設定で伸び縮みし、コンテキスト長にほぼ比例して増えます。③作業領域は 1 回の計算で使う一時バッファで、バッチサイズに比例します。④固定費は GPU ドライバのコンテキストや OS・画面表示のように、モデルとは無関係に消える分です。
この分解は本書の説明のための整理ではなく、推論エンジンの実装がそのまま採っている形です。vLLM は「使ってよいメモリの総額」から重みとその他のオーバーヘッドを差し引いた残りを KV キャッシュに割り当てます。その総額は「GPU 総容量 × gpu_memory_utilization」で決まり、既定値は 0.92。つまり公式実装が最初から 8 % を取り分けているということです。差し引かれる中身は③④の節で開けます。
単位の罠を先に潰す。Hugging Face が表示するファイルサイズは 10 進の GB(1 GB = 109 バイト)ですが、GPU の「24GB」やメモリの「16GB」は実質 2 進の GiB(1 GiB = 230 バイト)です。24 GiB = 25.77 GB なので、比べる前にどちらかへ揃えないと 7 % ずれます。llama.cpp の起動ログはすべて MiB(2 進)表記です。本章では、収まるかどうかの判定は GiB で行います。
①重み — パラメータ数 × bpw ÷ 8
重みのサイズは、パラメータ 1 個あたり何ビット使うか(bpw、bits per weight)が分かれば掛け算で出ます。
重みのバイト数 = パラメータ数 × bpw ÷ 8
計算例。Llama 3.1 8B Instruct のパラメータ数は 8,030,261,248 個です。Q4_K_M の実効 bpw は、複数モデルの実ファイルから逆算すると 4.85 前後に収まります(表 8-1)。見積りにこの 4.85 を入れると、8,030,261,248 × 4.85 ÷ 8 = 約 4.87 × 109 バイト = 4.87 GB。実際に配布されている GGUF は 4,920,739,232 バイト(4.92 GB。逆算した実効 bpw は 4.90)なので、誤差 1 % で当たります。
ここで Q4_K_M の実効 bpw が 4 ではなく 4.9 だったことが重要です。名前の数字はブロック内の量子化ビット数であって、ファイル全体の平均ではありません。ブロックごとのスケール値がオーバーヘッドとして乗り、さらに llama.cpp の GGUF はテンソルごとに違う型を混ぜます(第 6 章で見たとおりです)。「4bit なら 8B は 4 GB」と覚えると、必ず 1 割足りません。
複数モデル・複数量子化の実ファイルサイズから逆算すると、実効 bpw はモデルが違ってもほぼ同じ値に収まります。だから暗算ルールが作れます。Q4_K_M なら「パラメータ数(B 単位)× 0.6 = GB」(4.85 ÷ 8 = 0.606 から)。検算すると 8.03 × 0.606 = 4.87 GB(実測 4.92 GB)、70.55 × 0.606 = 42.8 GB(実測 42.52 GB)。どちらも 1 % 以内です。
| モデル(パラメータ数) | Q4_K_M | Q6_K | Q8_0 | 実効 bpw(Q4_K_M) |
|---|---|---|---|---|
| Llama 2 7B chat(6.74B) | 4.08 GB / 3.80 GiB | 5.53 GB | 7.16 GB | 4.85 |
| Llama 3.1 8B Instruct(8.03B) | 4.92 GB / 4.58 GiB | 6.60 GB | 8.54 GB | 4.90 |
| Llama 2 13B chat(13.02B) | 7.87 GB / 7.33 GiB | 10.68 GB | 13.83 GB | 4.84 |
| Gemma 3 27B IT(27.43B) | 16.55 GB / 15.41 GiB | 22.17 GB | 28.71 GB | 4.83 |
| Llama 3.1 70B Instruct(70.55B) | 42.52 GB / 39.60 GiB | 57.89 GB | 74.98 GB | 4.82 |
表 8-1 は Hugging Face の API が返す実バイト数です(2026-08 時点)。実効 bpw は「バイト数 × 8 ÷ パラメータ数」で、llama.cpp が量子化時にログへ出す BPW と同じ定義です。
2 つだけ注意があります。ひとつはマルチモーダルモデルの画像側。Gemma 3 27B の GGUF 本体はテキスト側だけで、画像を扱うには mmproj ファイル(F16 で 0.86 GB)を別に足す必要があります。もうひとつは形式によって bpw の内訳が違うこと。MLX の「4bit」は既定のグループサイズ 64 で実効 4.5 bpw(4 + 32 ÷ 64)であり、GGUF の Q4_K_M(4.85)とは別の数字です。形式ごとの選び分けは第 7 章で扱いました。
②KV キャッシュ — 読者が一番驚くところ
KV キャッシュ(= 各層の attention が過去のトークンについて計算した Key / Value を保存しておく領域)は、トークンを 1 個生成するたびに追記されていきます。これがないと、次の 1 語を出すために過去の全トークン分の計算をやり直すことになります。速度のために置いている「使い回し用のメモ」です。
サイズは完全に決まった掛け算で出ます。
KV バイト数 = 2 × 層数 × KV ヘッド数 × head_dim × コンテキスト長 × 要素バイト数
先頭の 2 は K と V の 2 本ぶん。head_dim は 1 ヘッドが持つ次元数。要素バイト数は既定の f16 なら 2 です。式に入れる値は推測せず、モデルの config.json から読みます。層数は num_hidden_layers、KV ヘッド数は num_key_value_heads、head_dim は head_dim キー(無ければ hidden_size ÷ num_attention_heads)です。
計算例。Llama 3.1 8B Instruct は層数 32、KV ヘッド数 8、head_dim 128 です。1 トークンあたり 2 × 32 × 8 × 128 × 2 = 131,072 バイト = 128 KiB。コンテキスト長 32,768 なら 131,072 × 32,768 = 4,294,967,296 バイト = 4.00 GiB。128k(131,072 トークン)なら 16.00 GiB です。重みが 4.58 GiB でしたから、128k では KV キャッシュが重みの 3.5 倍になります。
| モデル | 層数 | KV ヘッド | head_dim | KiB/token(f16) | 4k | 32k | 128k |
|---|---|---|---|---|---|---|---|
| Llama 2 7B(MHA) | 32 | 32 | 128 | 512.0 | 2.00 GiB | 16.00 GiB | 64.00 GiB |
| Llama 2 13B(MHA) | 40 | 40 | 128 | 800.0 | 3.13 GiB | 25.00 GiB | 100.00 GiB |
| Llama 3.1 8B(GQA 8) | 32 | 8 | 128 | 128.0 | 0.50 GiB | 4.00 GiB | 16.00 GiB |
| Qwen3 8B(GQA 8) | 36 | 8 | 128 | 144.0 | 0.56 GiB | 4.50 GiB | 18.00 GiB |
| Gemma 3 27B(SWA 無視) | 62 | 16 | 128 | 496.0 | 1.94 GiB | 15.50 GiB | 62.00 GiB |
| Llama 3.1 70B(GQA 8) | 80 | 8 | 128 | 320.0 | 1.25 GiB | 10.00 GiB | 40.00 GiB |
表 8-2 の層数・KV ヘッド数・head_dim は各モデルの config.json の実値で、GiB の欄は式に当てはめた計算例です。表中の「MHA」は multi-head attention(= すべての attention ヘッドが自分専用の K/V を持つ、GQA 以前の形)、「GQA」は次に説明する共有型を指します。Gemma 3 の行の「SWA 無視」は、この章の後半で扱う省メモリの仕組みを計算に入れない素朴な値という意味です。
GQA が効く倍率
KV サイズは KV ヘッド数に比例します。Llama 3.1 8B は attention ヘッドが 32 本ありますが、KV ヘッドは 8 本しかありません(GQA、grouped-query attention = 複数の Q ヘッドが 1 組の K/V を共有する仕組み)。もし共有しなければ 512 KiB/token だったものが 128 KiB/token、つまり 1/4 になっています。70B は 64 本の Q ヘッドが 8 組の KV を共有するので 1/8 です。
この結果、直感に反する逆転が起きます。Llama 2 13B の KV(800 KiB/token)は Llama 3.1 70B の KV(320 KiB/token)より大きい。KV は層数と KV ヘッド数で決まり、パラメータ総数とは別の軸だからです。「モデルが大きいほど KV も大きい」は成り立ちません。
もうひとつ忘れやすい掛け算があります。同時リクエスト数です。llama.cpp の公式ドキュメントは「スロットごとに KV キャッシュが確保される」と明記しており、Ollama の FAQ も「必要 RAM は OLLAMA_NUM_PARALLEL × OLLAMA_CONTEXT_LENGTH でスケールする」と書いています。2k のコンテキストで並列 4 なら 8k 相当のメモリが必要です。
KV キャッシュを縮める 3 つのレバー
レバー 1: コンテキスト長を下げる。効果が最も素直で、llama.cpp が公式に示す削減順序の第 1 手でもあります(「まず --ctx-size(-c)を下げる。KV キャッシュは n_ctx にほぼ比例する」)。
レバー 2: KV キャッシュ自体を量子化する。重みの量子化とは完全に別の設定です。llama.cpp では -ctk / -ctv で K 側・V 側の型を個別に指定します。許容値は f32, f16, bf16, q8_0, q4_0, q4_1, iq4_nl, q5_0, q5_1(既定 f16)で、Q4_K のような K 量子化タイプは指定できません。
| 設定 | bpw | f16 比 | Llama 3.1 8B・32k での KV サイズ |
|---|---|---|---|
f16(既定)/ bf16 | 16.0 | 100 % | 4.000 GiB |
q8_0 | 8.5 | 53.1 % | 2.125 GiB |
q5_0 | 5.5 | 34.4 % | 1.375 GiB |
q4_0 / iq4_nl | 4.5 | 28.1 % | 1.125 GiB |
表 8-3 の bpw は llama.cpp のブロック構造体から確定する値、f16 比と GiB はそれを割り算した計算例です。q8_0 でも 47 % 減、q4_0 なら 72 % 減。Ollama の公式 FAQ は q8_0 について「f16 の約半分のメモリで、精度低下はごくわずか。通常モデル品質に体感できる影響はない」、q4_0 については「約 1/4 になるが small-medium な精度低下があり、コンテキストが長いほど目立ちやすい」と説明しています。
制約が 2 つあります。V 側の量子化には Flash Attention(= attention の計算を小さく分割して、巨大な中間行列をメモリに書き出さずに済ませる実装)が必要で、llama.cpp の公式ドキュメントは -fa を「quantized V cache を使うときは必須」と書いています。また --split-mode tensor と KV 量子化は併用できず、起動時にエラーになります。ツール別の入口は、Ollama が OLLAMA_KV_CACHE_TYPE(f16 / q8_0 / q4_0。公式が「現状これはグローバル設定で全モデルに適用される」と警告しています)、vLLM が kv_cache_dtype="fp8"(1 バイト/要素なので f16 比 50 %。スケール較正なしでは全スケールが 1.0 になるため、公式は llm-compressor での較正を推奨)、MLX が --kv-bits(--kv-group-size 既定 64、--quantized-kv-start 既定 5000 トークン目から量子化)です。
レバー 3: SWA を持つモデルを選ぶ。sliding window attention(= 一部の層が直近の窓の中だけを見る仕組み)を採るモデルは、窓つきの層の KV がコンテキスト長に比例しません。Gemma 3 は窓 1024・周期 6 で、62 層のうち全コンテキストを見る層は 10 層だけです。llama.cpp は窓つきの層と全体を見る層で別々のキャッシュを持つため、128k のときの KV は素朴計算の 62.00 GiB に対し 10.61 GiB(約 1/6)になります(計算例)。ただし --swa-full を付けると素朴計算値に戻ります。
③作業領域と④ランタイム固定費
作業領域(compute buffer)は、入力から出力を 1 回計算するあいだだけ必要になる一時的な数値配列の置き場です。llama.cpp は起動時にこれをログへ出します。公式ドキュメントに載っている実ログでは、13,313.00 MiB のモデルに対して compute buffer が 1,260.81 MiB でした。重みの 1 割弱という桁感で見積もれば足ります。バッチサイズ(n_batch / n_ubatch、既定 2048 / 512)に比例するので、足りなければ下げれば減ります。
ランタイム固定費は、モデルとは無関係に消える分です。vLLM のソースはこれを「他プロセスや OS が使っている分」「torch 以外が使う分(GPU ドライバのコンテキスト、通信ライブラリや attention 実装が確保するバッファ)」「CUDA Graph 分」に分類しており、docstring の例では非 KV メモリ 5 GiB のうち重み 2 GiB・活性化のピーク 2 GiB・非 torch 1 GiB という配分を示しています。活性化(= 層と層のあいだで生まれる中間結果)のピークは③の作業領域に相当する項で、この例ではそれが重みと同じ 2 GiB を占めています。「重みが入れば動く」ではないのです。
llama.cpp はこれを自動で扱います。--fit(既定 on)はデバイスの空きメモリを測り、そこから --fit-target(既定 1024 MiB)のマージンを引いた値を上限として、明示されていない引数を調整します。OS や画面表示が既に使っている分は「空き」の測定で自動的に除外され、その上でさらに 1 GiB を残す設計です。逆に言えば、手で設定を全部書くとこの安全網が働きません。
Mac は事情が違います。CPU と GPU が同じメモリプールを直接見る(ユニファイドメモリ)ので、CUDA のような「VRAM に転送する」段階がなく、判定は「システムメモリに収まるか」になります。ただし全量を GPU に使えるわけではありません。MLX の公式ドキュメントは、実質上限を max_recommended_working_set_size(GPU が性能を落とさずに確保できる量の目安)として照会せよと書き、超えたい場合の手順として sudo sysctl iogpu.wired_limit_mb=<size_in_megabytes> を挙げています。MLX 公式ベンチの実測では、ファイル 2.263 GB の 4bit モデルが実行時 3.35 GB、17.181 GB の 30B 4bit が 18.20 GB でした。上乗せは +1 GB 前後で、モデルサイズには比例していません。ただしこれは 2,048 トークンのプロンプトでの計測です。長いコンテキストを指定すれば、この +1 GB の上に②が式どおり積み上がります。
推測をやめて、手元の数字で 4 項を埋めます。所要 10 分。使っているツールの節だけ実行してください。
- Ollama なら、まずモデルの素性を見る。
quantization行とcontext length行が①と②の入力になる。 - ロード後の実測と GPU 配置を見る。
SIZE列はメモリに載った実サイズ(重みだけでなく、指定したコンテキスト分の KV も含んだ値)、PROCESSOR列が GPU に載ったかどうか。 - llama.cpp なら、デバイスの容量を確認してから起動し、ログの
size = ... MiB (... cells, ... layers, ...), K (...), V (...)行(②の実測)とcompute buffer size = ... MiB行(③の実測)を読む。 - LM Studio なら、ロードせずに見積りだけ出せる。コンテキスト長を変えて 2 回叩き、差分が②であることを確認する。
- Mac なら、GPU に使える上限そのものを照会する。
ollama show llama3.2
ollama ps
llama-server --list-devices
llama-server -m ./model.gguf -c 32768 -ctk q8_0 -ctv q8_0 -fa on
lms load --estimate-only <model_key> --context-length 4096
lms load --estimate-only <model_key> --context-length 32768
python -c "import mlx.core as mx; d = mx.device_info(); print(d['memory_size'], d['max_recommended_working_set_size'])"
①②③の合計が、ollama ps の SIZE や --estimate-only の Estimated Total Memory とおおむね一致すれば、見積り方は正しく身についています。合わない場合、ほぼ確実に②のコンテキスト長か並列数の見落としです。
収まらないと何が起きるか
ここが実務でいちばん誤解されています。メモリが足りなくても、多くの場合エラーで止まりません。止まる代わりに遅くなります。しかも「少し遅い」ではありません。
経路は 3 つあります。第 1 は部分オフロード。llama.cpp は「モデルが 1 枚の GPU の VRAM に収まらないなら、一部は相対的に遅いシステム RAM 上で動かすことになる」と明記しています。-ngl(GPU に載せる層数)を下げる対処は公式に「最後の手段」「推論はずっと遅くなる」と書かれています。第 2 はページアウト。llama.cpp は既定でモデルファイルを mmap(= ファイルの中身をメモリ上のデータのように見せる OS の仕組み。実体はディスクに残る)で読むため、物理 RAM が足りなければ OS がその一部をディスクへ追い出します。--load-mode mlock の説明が「スワップや圧縮をさせず RAM に留めることを強制する」となっているのは、この経路が実在するからです。第 3 は vLLM の preemption。KV キャッシュが足りないとリクエストが追い出され、プロンプトを再計算します(既定は RECOMPUTE)。この場合は「モデルが遅い」ではなく「待ち行列が伸びる」形で現れます。
llama.cpp が公式ドキュメントに載せている実測では、A6000(48 GB)で 30B の 4bit モデルを CPU のみで動かすと 1.7 tok/s、全層 GPU で 9.1 tok/s でした(約 5.4 倍)。GGUF より前の形式で測られた古い数字なので絶対値は現在のビルドと違いますが、CPU と GPU の桁の差はここに出ています。部分オフロードはこの 2 点の間に落ちます。
気づくための道具は用意されています。Ollama では ollama ps の PROCESSOR 列が 100% GPU / 48%/52% CPU/GPU / 100% CPU の 3 状態を示し、公式は「モデルを CPU にオフロードしないこと。ollama ps で分割を確認せよ」と指示しています。llama.cpp の公式が示す削る順序は、①コンテキスト長(-c)→②並列スロット数(-np)→③最後の手段として GPU 層数(-ngl)。MoE(= mixture of experts。層の中に「エキスパート」と呼ぶ小さな部品を何個も持ち、1 トークンごとにそのうち数個しか使わない構造)のモデルなら、--cpu-moe でエキスパートの重みだけを CPU に置く逃げ道もあります。毎トークン読む量が少ないので、他の層を CPU に落とすより傷が浅いという理屈です。
速度の見積り — 帯域がすべてを決める
生成が遅い理由は「GPU の計算が追いつかない」ではありません。NVIDIA の技術文書は decode(1 トークンずつ生成する段階)について、「データ(重み・キー・バリュー・活性化)がメモリから GPU へ転送される速度がレイテンシを支配する。計算がどれだけ速いかではない。言い換えれば、これはメモリ律速の処理である」と明言しています。さらに「モデルの実行はしばしばメモリ帯域律速であり、とくに重みについて帯域律速である」とも書いています。
ここから上界が出ます。1 トークン生成するたびに全部の重みを 1 回読むのだから、
tok/s ≦ メモリ帯域(GB/s) ÷ モデルサイズ(GB)
この式そのものを書いた公式文書はありません(「decode はメモリ帯域律速」という一次情報から導いた式です)。だから上界として使います。計算例。Llama 3.1 8B Q4_K_M は 4.92 GB。M4 Pro のメモリ帯域は Apple 公式スペックで 273 GB/s なので、273 ÷ 4.92 ≒ 56 tok/s が上限。RTX 5090 は 1792 GB/s なので 1792 ÷ 4.92 ≒ 364 tok/s が上限です。
| デバイス | 公式スペックの帯域 | 8B Q4_K_M(4.92 GB) | 27B Q4_K_M(16.55 GB) | 70B Q4_K_M(42.52 GB) |
|---|---|---|---|---|
| RTX 5090 | 1792 GB/s | 364 tok/s | 108 tok/s | 42 tok/s |
| RTX 5080 | 960 GB/s | 195 tok/s | 58 tok/s | 23 tok/s |
| RTX 5060 Ti | 448 GB/s | 91 tok/s | 27 tok/s | 11 tok/s |
| M3 Ultra | 819 GB/s | 166 tok/s | 50 tok/s | 19 tok/s |
| M4 Max(40 コア GPU) | 546 GB/s | 111 tok/s | 33 tok/s | 13 tok/s |
| M4 Pro | 273 GB/s | 56 tok/s | 17 tok/s | 6 tok/s |
| M4 | 120 GB/s | 24 tok/s | 7 tok/s | 3 tok/s |
表 8-4 の帯域は NVIDIA と Apple の公式スペックページの実数値(2026-08 時点)、tok/s は式に当てはめた計算例の上界です。モデルが 1 枚に収まっている前提であり、収まらなければ図 8-3 の崖に落ちます。なお RTX 40 シリーズの帯域は NVIDIA の公式比較表に記載がなく、本書では扱いません。
上界が現実とどれくらい離れるかは、公式ベンチで確かめられます。llama.cpp の量子化ドキュメントは Llama 3.1 8B の実測を載せていて、F16(14.96 GiB)で 29.17 tok/s、Q4_K_M(4.58 GiB)で 71.93 tok/s。サイズが 0.31 倍になって速度が 2.5 倍です。MLX の公式ベンチ(64 GB M4 Max、Qwen3-4B)も bf16 の 52.47 tok/s に対し 4bit で 134.52 tok/s(2.56 倍)。量子化はメモリ対策であると同時に、そのまま速度対策です。
ただし「小さくすればするほど速い」ではありません。同じ llama.cpp の表をビット数の大きい方から追うと、Q8_0(7.95 GiB)50.93 → Q6_K(6.14 GiB)58.67 → Q5_K_M(5.33 GiB)67.23 → Q4_K_M(4.58 GiB)71.93 tok/s と伸びますが、そこから Q3_K_M(3.74 GiB)に落としても 71.68 tok/s でほぼ横ばいです。サイズが 0.8 倍になったのに速度は増えていません。読み出したビット列から元の値を復元する計算(逆量子化)が効いてくるためで、Hugging Face の量子化ドキュメントも「転送が速くなる利得はアンパックと逆量子化の計算コストを上回り得る」という条件つきの書き方をしています。「tok/s ≦ メモリ帯域 ÷ モデルサイズ」が等式ではなく上界だというのは、こういう意味です。
同じ 2 つのベンチには、方向の違う発見もあります。プロンプト処理(prefill)はまったく別の挙動を示すのです。llama.cpp では F16 が 923.49 tok/s で最速、Q4_K_M は 821.81 tok/s。MLX でも bf16 の 1780 tok/s に対し 4bit は 1622 tok/s と微減です。理由は図 8-4 のとおりで、prefill は多数のトークンをまとめて処理するため 1 回の重み読み出しを分担でき、律速が演算側に移ります。vLLM の公式ドキュメントが「計算律速(prefill)とメモリ律速(decode)のバランス」という言い方をしているのはこの対比です。「量子化したのに最初の応答が速くならない」のは正常な挙動です。
3 つの変数(モデルサイズ・量子化・コンテキスト長)を動かして、24 GiB のカードに何が入るかを決めます。所要 15 分、紙と電卓だけ。以下はすべて計算例です。
- 予算を書く。24 GiB から固定費 1.0 GiB(llama.cpp の既定マージン)を引いて 23.0 GiB。実際には OS と画面表示が先に食っているので、これは楽観側の予算です。
- 欲しいコンテキストの KV を引く。70B(GQA 8)で 32k なら 10.00 GiB。残り 13.0 GiB。
- 作業領域を重みの 1 割と見て重み予算を出す。13.0 ÷ 1.1 ≒ 11.8 GiB。70B は Q4_K_M で 39.60 GiB、最小の IQ2_XXS でも 17.79 GiB なのでどちらも入らない。
- 変数を 1 つずつ動かす。(a) KV を
q4_0にすると 10.00 × 0.281 = 2.81 GiB。残り 20.2 → 重み予算 18.3 GiB で IQ2_XXS(17.79 GiB)が入る。(b) コンテキストを 8k に落とすと KV は 2.50 GiB で同じく IQ2_XXS が入る。(c) モデルを 27B に落とすと Q4_K_M 15.41 GiB + KV 3.11 GiB(Gemma 3 の SWA 込み 32k)+ 作業 1.5 + 固定 1.0 = 21.0 GiB。 - 3 案を並べて選ぶ。品質を優先するなら (c)、どうしても 70B を触りたいなら (a) か (b)。
llama-server -m ./gemma-3-27b-it-Q4_K_M.gguf -c 32768 -fa on
llama-server -m ./Llama-3.1-70B-IQ2_XXS.gguf -c 32768 -ctk q4_0 -ctv q4_0 -fa on
起動ログの KV サイズ行と compute buffer 行が、上で引き算した値とおおむね一致することを確認してください。ずれていたら、-np(並列スロット数)が 1 より大きい、あるいは --fit がコンテキスト長を勝手に縮めています。
つまずきポイント
「4bit なら 8B は 4 GB」で計算して 1 割足りなくなる。Q4_K_M の実効 bpw は 4.82〜4.90 で、4 ではありません。Q2_K も 2.625 ではなく実効 3.0〜3.4 bpw です(埋め込み層や出力層が Q4_K / Q6_K のまま残されるため。パラメータ数が小さいモデルほど上にずれます)。加えて Hugging Face の GB(10 進)と GPU の GB(実質 GiB)を混ぜると、さらに 7 % ずれます。暗算するなら Q4_K_M は「パラメータ数 × 0.6 GB」、判定は GiB で。
KV キャッシュの式に hidden_size を入れてしまう。NVIDIA の技術記事は「num_heads × dim_head はふつう hidden_size と同じ」と書いていますが、これは MHA(全ヘッドが自分の K/V を持つ)モデルでのみ正しい近似です。Llama 3.1 8B に当てはめると 4 倍、70B なら 8 倍の過大見積りになります。必ず num_key_value_heads を使ってください。head_dim も同様で、hidden_size ÷ num_attention_heads で計算してよいとは限りません。Gemma 3 27B は 5376 ÷ 32 = 168 ですが config.json には head_dim: 128 と明示されています。キーがあるなら必ずそれを使う。
VRAM に「ぴったり」入れようとする。作業領域(重みの 1 割程度)、ランタイム固定費(llama.cpp の既定マージンは 1024 MiB)、GPU コンテキスト、そして OS と画面表示が使う分を忘れると、計算では入るのに起動できません。vLLM が gpu_memory_utilization の既定を 0.92 にして 8 % を残しているのは、まさにこの余白のためです。加えて、同時リクエスト数が KV を掛け算で増やします(llama.cpp は「スロットごとに KV キャッシュを確保」、Ollama は「必要 RAM は並列数 × コンテキスト長でスケール」)。KV を 1 リクエスト分で計算して安心しない。
「エラーが出ないから収まっている」と思ってしまう。足りないときの症状はクラッシュではなく失速です。層が CPU に落ちる、mmap したファイルがページアウトする、OS がメモリを圧縮する。どれも動き続けます。llama.cpp が -ngl を下げる対処に「much slower」と書き、--load-mode mlock の説明に「スワップや圧縮をさせない」と書いているのは、この静かな失敗が現実に起きるからです。なお「VRAM を少し超えたときに GPU ドライバがシステムメモリへ退避して遅くなる」という経路も語られますが、本書の調査では公式文書で仕様として確認できなかったため、倍率などの数値は挙げません。速度が出ないときは、まず ollama ps の PROCESSOR 列と llama.cpp の起動ログを見る。
理解度チェック
Q1. 層数 32・KV ヘッド数 8・head_dim 128 のモデルを、コンテキスト長 32,768・f16 の KV で動かします。KV キャッシュは何 GiB ですか。
4.00 GiB です。2 × 32 × 8 × 128 × 2 = 131,072 バイト(= 128 KiB)が 1 トークンあたりの量で、これに 32,768 を掛けると 4,294,967,296 バイト = 4.00 GiB。同じモデルの Q4_K_M の重みが 4.58 GiB なので、KV がほぼ重みに並びます。
Q2. Llama 2 13B の KV キャッシュ(800 KiB/token)が Llama 3.1 70B(320 KiB/token)より大きいのはなぜですか。
KV サイズはパラメータ総数ではなく、層数と KV ヘッド数と head_dim で決まるからです。Llama 2 13B は MHA で 40 層 × KV ヘッド 40 本、Llama 3.1 70B は GQA で 80 層 × KV ヘッド 8 本。層数は 2 倍でも KV ヘッドが 1/5 なので、正味で 70B のほうが小さくなります。
Q3. Q8_0 から Q4_K_M に落とすと生成速度が上がるのに、プロンプト処理速度は上がりません。なぜですか。
生成(decode)は 1 トークンごとに全重みを 1 回読むためメモリ帯域律速で、この区間ではサイズが小さくなるだけ速くなります(Q3_K_M までさらに落とすと逆量子化の計算が効いてきて頭打ちになります)。プロンプト処理(prefill)は多数のトークンをまとめて処理するので 1 回の読み出しを分担でき、律速が演算側に移ります。llama.cpp 公式実測でも Llama 3.1 8B の生成は F16 29.17 → Q4_K_M 71.93 tok/s と 2.5 倍になる一方、prefill は 923.49 → 821.81 tok/s とわずかに下がります。
Q4. 24 GiB の GPU に Llama 3.1 70B の Q4_K_M(39.60 GiB)を載せたい、と言われました。何と答えますか。
重みだけで 39.60 GiB あるので載りません。作業領域と固定費を足せばさらに増えます。選択肢は、量子化を IQ2_XXS(17.79 GiB)まで落として KV も q4_0 にする、あるいはモデルを 27B クラスに下げて Q4_K_M を使う(Gemma 3 27B なら 32k 込みで約 21 GiB)。-ngl を下げて部分オフロードするのは最後の手段で、速度は桁で落ちます。
Q5. llama.cpp で -ctv q4_0 を付けたら起動に失敗しました。まず何を確認しますか。
Flash Attention です。V 側の KV 量子化には -fa が必要と公式ドキュメントに明記されています。もう 1 つの原因は --split-mode tensor との併用で、この組み合わせは未実装のためエラーになります(対処は -ctk f16 -ctv f16 に戻すこと)。なお -ctk q4_K のような K 量子化タイプは許容値に含まれません。
まとめ
- 必要メモリは「重み + KV キャッシュ + 作業領域 + ランタイム固定費」。重みは パラメータ数 × 実効 bpw ÷ 8(Q4_K_M なら「パラメータ数 × 0.6 GB」)で、名前の「4bit」ではなく実効 4.82〜4.90 bpw を使う
- KV キャッシュは 2 × 層数 × KV ヘッド数 × head_dim × コンテキスト長 × 要素バイト数。
config.jsonの実値を入れる。GQA で 1/4〜1/8、-ctk/-ctvのq4_0で 28.1 %、SWA を持つモデルなら 128k で約 1/6 まで縮む - 作業領域は重みの 1 割程度(公式ログの実例で 13,313 MiB に対し 1,260.81 MiB)、固定費は 1 GiB 級。ぴったり入れようとしない
- 収まらないときはエラーではなく失速。部分オフロード・ページアウト・preemption の 3 経路があり、公式実測では CPU のみと全層 GPU で約 5.4 倍の差が出る。削る順序は コンテキスト長 → 並列数 → GPU 層数
- 生成速度の上界は メモリ帯域 ÷ モデルサイズ。量子化は decode を速くするが prefill は速くしない。しかも decode の伸びは Q4 あたりで頭打ちになる(公式実測で Q4_K_M 71.93、Q3_K_M 71.68 tok/s)
次章では、ここまで散々「小さくする」話をしてきた代償――量子化で品質がどれだけ落ちるのかを、perplexity と KL ダイバージェンスの実測値で見ていきます。
参考リンク
- llama.cpp — llama-server の引数一覧(
-ctk/-ctv/--fit/--load-mode) - llama.cpp — Multi-GPU と OOM の対処順序
- llama.cpp — トークン生成性能のヒント(CPU / GPU 実測)
- llama.cpp — llama-quantize(bits/weight と速度の実測表)
- vLLM — Conserving Memory
- vLLM — Quantized KV Cache
- NVIDIA — Mastering LLM Techniques: Inference Optimization
- Hugging Face Transformers — Quantization concepts(帯域の利得と逆量子化コストの関係)
- MLX — Unified Memory
- mlx-lm — BENCHMARKS(精度・速度・メモリの実測)
- Ollama — Context length
- LM Studio —
lms load(--estimate-only)