Skip to content
Published on

ローカルLLM推論最適化 — 量子化からVRAM限界の突破まで

Authors

はじめに — ローカルLLMが再び盛り上がる理由

2026年上半期、ローカルLLMコミュニティが再び熱を帯びています。Hacker NewsではGPUの余ったVRAMをLinuxのスワップデバイスとして使うnbd-vramのような逆転の発想プロジェクトが話題になり、GeekNewsにもローカル推論最適化の記事が続々と上がっています。いくつかの流れが重なった結果です。

第一に、プライバシーです。コードやドキュメント、日記までLLMに入れる時代になり、機密データを外部APIに送ることへの抵抗感が強まりました。npmサプライチェーン攻撃がRed Hat Cloud Servicesまで侵入した2026年6月の事件や、VSCode拡張のバグでGitHubトークンが盗まれた事例は、「自分のデータは自分のマシンに」という感情を強化しました。

第二に、コストです。AIコーディングエージェントの普及でトークン消費量が爆発しました。エージェントが数時間も自律的に動くワークフローではAPI請求額が無視できない水準になり、反復的な補助作業をローカルモデルに回したいという需要が生まれました。

第三に、ビッグテック疲れです。DuckDuckGoのno-AI検索トラフィック急増やGmail離脱の流れが示すように、すべてがクラウドAIに収束することへの反動は確かに存在します。ローカルLLMはこの感情の技術的な出口です。

折しもハードウェアとソフトウェアも整いました。ユニファイドメモリを大幅に増やしたコンシューマーハードウェア、成熟した量子化エコシステム、そしてllama.cppとvLLMという2本柱の推論エンジンです。本記事はローカル推論最適化の全体地図を描きます。

ハードウェア選択 — VRAM中心で考える

ローカルLLMハードウェア選択の第一原則は単純です。演算速度よりメモリ容量と帯域幅が先ということです。モデルがメモリに収まらなければどんなに速いGPUも無意味で、収まった後のトークン生成速度はおおむねメモリ帯域幅が決めます。

トークン生成速度の一次近似:

  トークン/秒 ~= メモリ帯域幅 (GB/s) / モデルサイズ (GB)

例: 量子化後5GBのモデル
  - 帯域幅 100 GB/s (CPU DDR5デュアルチャネル) -> 約20トークン/秒
  - 帯域幅 400 GB/s (Apple Mシリーズ Max)      -> 約80トークン/秒
  - 帯域幅 1000 GB/s (ハイエンドdGPU)          -> 約200トークン/秒

選択肢は大きく2つに分かれます。

項目ユニファイドメモリ (Apple Silicon, Strix Halo系)ディスクリートGPU (RTX系)
メモリ容量大きい (64GB〜512GB)小さい (16GB〜32GBが一般的)
メモリ帯域幅中間 (200〜800 GB/s)高い (800〜1700 GB/s)
大型モデルの搭載70B以上も量子化で可能単一カードでは困難
生成速度中間速い
プロンプト処理速度相対的に遅い非常に速い
電力/騒音低い高い
拡張性不可(購入時に決まる)マルチGPUで拡張可能

要約すると、大きいモデルをそこそこの速度で動かしたければユニファイドメモリ、中型モデルを最高速度で動かしたければdGPUです。長い文書を頻繁に入れるワークロード(RAG、コードベース分析)ならプロンプト処理が速いdGPUの体感メリットが大きく、会話中心ならユニファイドメモリの容量メリットが効きます。

モデルが占めるメモリの概算は次の通りです。

モデル重みのメモリ (概算):

  fp16:      パラメータ数 x 2バイト
  8bit (Q8): パラメータ数 x 1バイト + 若干のオーバーヘッド
  4bit (Q4): パラメータ数 x 0.5バイト + 若干のオーバーヘッド

例: 8Bモデル  -> fp16 16GB / Q8 8.5GB / Q4 4.7GB
例: 70Bモデル -> fp16 140GB / Q8 75GB / Q4 40GB

ここにKV cacheとアクティベーションのメモリが加わる(後述)

量子化フォーマット — GGUF、4bit、AWQ、GPTQ

量子化は重みを低い精度で表現し、メモリと帯域幅の要求量を減らす技術です。ローカルLLMでは事実上必須です。

GGUF — llama.cppエコシステムの標準

GGUFはllama.cppが使う単一ファイルフォーマットです。重み、トークナイザー、メタデータが1ファイルに収まり、配布が簡単です。量子化レベルはファイル名の接尾辞で区別します。

量子化ビット水準8Bモデルのサイズ品質への影響
Q8_08ビット約8.5GBほぼ無損失
Q6_K6ビット台約6.6GB非常に小さい
Q5_K_M5ビット台約5.7GB小さい
Q4_K_M4ビット台約4.9GB体感しにくい水準(推奨デフォルト)
Q3_K_M3ビット台約4.0GBタスクによって体感される
Q2_K2ビット台約3.2GB明確な低下、非常用

コミュニティの経験則は明確です。同じメモリなら、小さいモデルの高精度より大きいモデルの4ビットの方がおおむね良いということです。例えば8GBの予算では、8B Q8より14B Q4の方が良い結果を出すことが多いのです。

AWQとGPTQ — GPUサービング陣営の量子化

vLLMのようなGPUサービングエンジンでは、AWQとGPTQ系が主流です。

  • GPTQ:キャリブレーションデータを流しながらレイヤーごとに量子化誤差を最小化する事後量子化。4ビットで良い品質を示します。
  • AWQ:アクティベーション値の分布を観察し、重要な重みチャネルを保護しながら量子化する方式。キャリブレーション依存が低く品質維持が良いため、4ビットGPUサービングの基本選択肢になりました。
  • FP8:最新GPUのハードウェアサポートを受ける8ビット浮動小数点。スループットサービングで急速に標準になりつつあります。

要約すると、llama.cppで動かすならGGUFのQ4_K_Mから、vLLMで動かすならAWQ 4bitまたはFP8から始めるのが無難です。

推論エンジン比較 — llama.cpp vs vLLM vs Ollama

項目llama.cppvLLMOllama
主なターゲット個人マシン、エッジGPUサーバー、チーム/本番個人マシン(手軽さ優先)
ハードウェアCPU、Apple Silicon、GPUすべてNVIDIA/AMD GPU中心llama.cppベースと同じ
モデルフォーマットGGUFHF safetensors, AWQ, GPTQ, FP8GGUF(内部はllama.cpp)
同時処理限定的(少数スロット)強力(連続バッチング、PagedAttention)限定的
CPUオフロード強力(レイヤー単位)限定的サポート(自動)
APIOpenAI互換サーバー内蔵OpenAI互換サーバー内蔵独自API + OpenAI互換
運用難易度中(ビルド/フラグの理解が必要)中〜高非常に低い
合うユーザーチューニングを楽しむパワーユーザースループットが必要なチームまず使ってみたい入門者

選択基準を一行ずつにまとめると次の通りです。

  • Ollama:5分以内に始めたい。細かいチューニングは後の話。
  • llama.cpp:自分のハードウェアから最後の一滴まで絞り出したい。VRAMが足りずオフロードが必要。
  • vLLM:複数のユーザーやエージェントが同時に叩くサーバーが必要で、GPUがモデルを丸ごと収められる。

同じマシンでも用途が違えば併用する構成が普通です。普段はOllamaで軽く、チームのデモではvLLMでスループットを出す、という具合です。

KV cacheとコンテキスト長のメモリ計算

ローカルLLMで最も頻繁にぶつかる壁は、モデルの重みではなくKV cacheです。コンテキストが長くなるほどトークンごとにアテンションのキーと値を保存する必要があり、これがVRAMを食いつぶします。

計算式は次の通りです。

def kv_cache_bytes(n_layers, n_kv_heads, head_dim, seq_len,
                   batch=1, bytes_per_elem=2):
    """KV cacheのメモリ(KとVの2テンソルなのでx2)。
    bytes_per_elem: fp16/bf16=2, q8=1, q4=0.5
    """
    return 2 * n_layers * n_kv_heads * head_dim * seq_len * batch * bytes_per_elem

# 例1: 8B級モデル(32レイヤー、KVヘッド8、head_dim 128、fp16)
per_token = kv_cache_bytes(32, 8, 128, 1)
print(per_token)            # 131072バイト = トークンあたり128KB

print(kv_cache_bytes(32, 8, 128, 8192) / 2**30)    # 8kコンテキスト: 1.0GB
print(kv_cache_bytes(32, 8, 128, 32768) / 2**30)   # 32kコンテキスト: 4.0GB
print(kv_cache_bytes(32, 8, 128, 131072) / 2**30)  # 128kコンテキスト: 16.0GB

# 例2: 70B級モデル(80レイヤー、KVヘッド8、head_dim 128、fp16)
print(kv_cache_bytes(80, 8, 128, 32768) / 2**30)   # 32kコンテキスト: 10.0GB

この計算が教えてくれる実戦感覚はこうです。

  • 8BモデルをQ4で落とせば重みは5GB余りですが、128kコンテキストを使った瞬間にKV cacheが16GBと重みの3倍になります。「モデルは載るのにコンテキストを伸ばすと死ぬ」理由がこれです。
  • 同時ユーザー(バッチ)が増えるとKV cacheは正比例で増えます。マルチエージェントが同時に接続するローカルサーバーでコンテキスト上限を保守的に設定すべき理由です。
  • 対策は3つです。KV cacheの量子化(fp16の代わりにq8/q4で半分〜4分の1)、GQAが強いモデルの選択(KVヘッド数が少ないほど有利)、そしてコンテキスト上限の設定です。

llama.cppでKV cacheを量子化するには次のようにします。

# KV cacheをq8に: メモリ半分、品質への影響は軽微
llama-server -m model-q4_k_m.gguf \
  --ctx-size 32768 \
  --cache-type-k q8_0 \
  --cache-type-v q8_0

VRAM予算別の現実的な構成ガイド

重みとKV cacheの計算を合わせると、メモリ予算別に現実的な構成が見えてきます。

メモリ予算無難な構成コンテキスト余裕用途の感覚
8GB7〜8BモデルQ48k前後軽い補助、要約、分類
12GB8B Q6 または 14B Q48k〜16k日常のコーディング補助の下限
16GB14B Q4 + KV q816k〜32kコーディング/文書作業の実用ゾーン
24GB32B Q4 または 14B高精度32kローカル単独で十分な最初のゾーン
48GB (2枚またはユニファイド)70B級Q416k〜32kAPI依存度を大きく下げるゾーン
96GB以上 (ユニファイド)70B高精度、大型MoE Q464k以上ローカルワークステーションの最終形

2つの補正が必要です。第一に、表のコンテキスト余裕はKV cacheをq8に量子化した場合の基準です。第二に、MoEモデルは同じ総パラメータでもアクティブパラメータが小さく生成速度がはるかに速いため、ユニファイドメモリのマシンでは大型MoEのQ4が同級のデンスモデルより体感が良いことが多いです。

モデル系列の選択を単純化するとこうなります。コーディング補助ならコード特化のファインチューニングモデルを、多言語(日本語を含む)品質が重要なら多言語トークナイザーが優れた系列を、エージェント用途ならツール呼び出し(tool calling)形式を公式サポートするモデルを第一候補にし、必ず自分の作業サンプル10〜20個で直接比較してください。リーダーボードの順位は参考にすぎません。

オフロード戦略 — 足りないVRAMと戦う方法

正攻法:レイヤー単位のCPUオフロード

llama.cppの代表機能です。モデルレイヤーの一部だけGPUに載せ、残りはCPUメモリで実行します。

# 70B Q4モデル(約40GB)を24GB GPUで:
# 80レイヤーのうち48個だけGPUに、残りはCPUに
llama-server -m llama-70b-q4_k_m.gguf \
  --n-gpu-layers 48 \
  --ctx-size 8192 \
  --threads 16

経験則は「GPUに載せたレイヤーの割合だけ速くなる」ではなく、CPUに残ったレイヤーが全体速度を支配するです。半分だけ載せたら、GPUがどんなに速くてもCPU速度に収束します。それでも「そもそも動かせないモデルを動かせるようにする」価値は大きいです。MoEモデルなら、エキスパート(expert)の重みだけCPUに送るオプションが効果的です。アクティブなエキスパートだけがその都度使われるため、体感の低下が小さいのです。

# MoEモデル: アテンションなど共有重みはGPUに、エキスパートFFNはCPUに
llama-server -m moe-model-q4.gguf \
  --n-gpu-layers 99 \
  --n-cpu-moe 30

逆転の発想:余ったVRAMをシステムスワップに — nbd-vram

2026年のHacker Newsを沸かせたnbd-vramは発想を逆転させます。「VRAMが足りないからシステムメモリを借りる」のではなく、遊んでいるVRAMをLinuxのブロックデバイスとして公開し、スワップやRAMディスクとして使うプロジェクトです。ネットワークブロックデバイス(NBD)プロトコルでGPUメモリを包み、カーネルからはただの速いディスクに見えるようにします。

普通の発想:   モデルがVRAMより大きい -> RAM/ディスクへオフロード
nbd-vram:    RAMが足りない          -> 余ったVRAMをスワップに活用

+----------+     NBDプロトコル      +-----------------+
| Linux    | <------------------> | GPU VRAM        |
| カーネル  |   (ブロックデバイス)    | (CUDAバッファ)   |
| スワップ  |                      |                 |
+----------+                      +-----------------+

PCIeを経由するため本物のRAMよりは遅いものの、NVMeスワップよりはるかに速いです。ゲーミングGPUが遊んでいるワークステーションでメモリ大食いの作業(大規模データ処理、コンパイル)を回すとき、意外に実用的だという評価が多くありました。ローカルLLMと直接組み合わせれば、CPU推論中の巨大モデルの一部ページがVRAMスワップに載るという妙な構成も可能です。技術的な整合性より重要なのは、このプロジェクトが示す姿勢です。VRAM、RAM、ディスクは固定された役割ではなく速度の違うメモリ階層にすぎず、組み合わせは自分たちが決めるということです。

サービング設定の例

llama.cppサーバー — 単一マシンのパワーユーザー向け

# ビルド(CUDAの例)
git clone https://github.com/ggml-org/llama.cpp.git
cd llama.cpp
cmake -B build -DGGML_CUDA=ON
cmake --build build --config Release -j

# OpenAI互換サーバーの起動
./build/bin/llama-server \
  -m models/qwen2.5-14b-q4_k_m.gguf \
  --host 0.0.0.0 --port 8080 \
  --ctx-size 16384 \
  --n-gpu-layers 99 \
  --flash-attn \
  --cache-type-k q8_0 --cache-type-v q8_0 \
  --parallel 2

Ollama — 最速のスタート地点

# インストール後、モデルの取得と実行が各一行
ollama pull qwen2.5:14b
ollama run qwen2.5:14b

# カスタム設定はModelfileで
cat > Modelfile <<'EOF'
FROM qwen2.5:14b
PARAMETER num_ctx 16384
PARAMETER temperature 0.7
SYSTEM あなたは簡潔に答えるコーディングアシスタントです。
EOF
ollama create my-coder -f Modelfile
ollama run my-coder

Ollamaは内部的にllama.cppを使うため性能特性は似ていますが、デフォルトが保守的です。特にnum_ctxのデフォルトが小さく、長い会話で静かにコンテキストが切られる罠があるので、明示的に増やしておくのがよいでしょう。

vLLM — 同時リクエストがあるチームサーバー向け

# docker-compose.yml
services:
  vllm:
    image: vllm/vllm-openai:latest
    ports:
      - "8000:8000"
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 1
              capabilities: [gpu]
    volumes:
      - ./models:/root/.cache/huggingface
    command: >
      --model Qwen/Qwen2.5-14B-Instruct-AWQ
      --quantization awq
      --max-model-len 16384
      --gpu-memory-utilization 0.92
      --max-num-seqs 16

vLLMの主要フラグの意味は次の通りです。

  • gpu-memory-utilization:全VRAMのうちvLLMが先取りする割合。重みを除いた残りがすべてKV cacheプールになります。
  • max-model-len:シーケンスの最大長。KV cacheの計算に直結するため、ワークロードに合わせて保守的に。
  • max-num-seqs:同時処理シーケンス数の上限。スループットと個別レイテンシのトレードオフのダイヤルです。

マルチGPU — 1枚で足りないとき

24GBカード2枚で70Bを動かすような構成は今や普通です。方式は2つあります。

方式原理長所短所
テンソル並列 (TP)各レイヤーの行列をカードが分担レイテンシ削減にも寄与カード間通信が頻繁、同一カード推奨
パイプライン並列 (PP)レイヤーを区間に分けて分担異種カードの組み合わせ可能単一リクエストのレイテンシは改善しない
# vLLM: 2-wayテンソル並列
vllm serve Qwen/Qwen2.5-72B-Instruct-AWQ \
  --quantization awq \
  --tensor-parallel-size 2 \
  --max-model-len 8192

# llama.cpp: カードごとの分担比率を指定(異種構成の例)
llama-server -m llama-70b-q4_k_m.gguf \
  --n-gpu-layers 99 \
  --split-mode layer \
  --tensor-split 60,40

コンシューマー向けマザーボードではPCIeレーンがボトルネックになりがちです。テンソル並列は通信量が多くx8/x8以上を推奨し、レーンが足りない構成ならパイプライン(レイヤー)分割が安全です。

性能測定の方法論 — 勘ではなく数字で

ローカルLLMのチューニングは、測定なしには迷信になります。標準的な指標は4つです。

TTFT  (Time To First Token)  : 最初のトークンまでの時間。プロンプト処理速度を反映
TPOT  (Time Per Output Token): 出力トークンあたりの時間。生成速度の逆数
pp速度 (prompt processing)   : プロンプトトークン/秒。長い入力のワークロードの体感を左右
tg速度 (token generation)    : 生成トークン/秒。会話ワークロードの体感を左右

llama.cppには専用のベンチマークツールが内蔵されています。

# プロンプト512トークン処理 + 128トークン生成のシナリオを測定
./build/bin/llama-bench \
  -m models/qwen2.5-14b-q4_k_m.gguf \
  -p 512 -n 128 \
  -ngl 99

# 出力例(列: 設定, pp512, tg128 トークン/秒)
# 量子化レベル、ngl、スレッド数を変えながら表を作れば、
# 自分のマシンの品質-速度曲線が描ける

サーバー全体の負荷テストは同時実行数を変えながら測定します。

# vLLM内蔵ベンチマーク: 同時実行数を変えてTTFT/TPOTを測定
vllm bench serve \
  --backend openai \
  --base-url http://localhost:8000 \
  --model Qwen/Qwen2.5-14B-Instruct-AWQ \
  --num-prompts 200 \
  --request-rate 4

測定時には3つの原則を守る必要があります。

  1. ウォームアップ後に測定:最初のリクエストはモデルロードとカーネルコンパイルが混ざり、必ず遅いです。
  2. 自分のワークロードと同じ分布で:短いプロンプトのベンチマークはRAGワークロードの性能をまったく代表しません。入出力の長さの分布を実際に合わせてください。
  3. 平均ではなくp95:エージェントパイプラインは最も遅い呼び出しが全体を遅らせます。テールレイテンシを見てください。

よくある落とし穴

最後に、コミュニティで繰り返し見られる落とし穴です。

  • コンテキスト上限を忘れる:デフォルト設定のコンテキストは短いです。会話が長くなると静かに前半が切り捨てられ、モデルが突然バカになったように見えます。ctx-sizeを明示し、その分のKV cacheメモリを計算に入れてください。
  • 過度な量子化による微妙な品質崩壊:Q2〜Q3の量子化は短い回答ではまともに見えますが、長い推論チェーンやコード生成で崩れます。自分のタスクの評価セットで量子化レベル別の品質を直接比較すべきです。
  • ベンチマークと体感の乖離:tgトークン/秒が高くてもppが遅ければRAGではもどかしいです。2つの数字を常に一緒に見てください。
  • サンプリングパラメータの放置:モデルごとに推奨temperatureとtop-pが違います。デフォルトのまま使ってモデルのせいにするケースが多いです。
  • 電力と発熱の無視:24時間サーバーとして回す場合、電力上限を少し下げれば(power limit)トークン/秒数パーセントの損失で電力と発熱を大きく減らせます。
  • セキュリティの放置:ローカルサーバーだからと認証なしで0.0.0.0に開けておくと、同じネットワークの誰でもあなたのGPUを使えます。最低限APIキーとファイアウォールルールを設定してください。
  • 出所不明のモデルファイル:量子化モデルは誰でもアップロードできます。サプライチェーン攻撃が日常になった時代に、ダウンロード数が多く検証済みのアップローダーのリポジトリを使い、ハッシュを確認する習慣はローカルでも有効です。
  • アップデート追跡の怠慢:llama.cppとvLLMは数週間単位で性能が目に見えて改善されます。半年前のベンチマークで下した結論はすでに古いかもしれません。

おわりに

ローカルLLM推論最適化は、結局1つの問いに収束します。**自分のメモリ予算の中で品質、速度、コンテキスト長をどう配分するか。**量子化は重みの予算を減らし、KV cache量子化とコンテキスト上限はキャッシュの予算を治め、オフロードは予算そのものを借りてきます。nbd-vramのような逆転の発想は、その予算台帳の仕切りが思ったより自由であることを示しています。

今日始めるなら、お勧めの経路はこうです。

  1. Ollamaで14B Q4モデルを取得し、まず自分の作業で数日使ってみる
  2. コンテキスト不足や速度の不満が出たらllama.cppに降りて、KV量子化とオフロードをチューニングする
  3. 同時ユーザーが現れたらvLLMに上がり、連続バッチングのスループットを手に入れる
  4. 各ステップでllama-benchと負荷テストにより、変更前後を数字で比較する

もうひとつ付け加えると、この分野は変化のスピードが速いです。今日の最適な設定は来四半期には古い知識になり得るので、測定スクリプトを自動化しておき定期的に回し直す習慣こそが、最も長持ちする資産です。

クラウドAPIとローカルモデルは代替関係ではなく役割分担です。機密データ、反復作業、オフライン環境はローカルへ、最高品質が必要な瞬間はAPIへ。その境界線を自分のワークロードの数字で引けるようになること、それが本記事で扱った測定と計算の目的です。

参考資料