
ローカルLLMを本格的に動かす:大きなモデルとKVキャッシュ
重み以外に必要なメモリ(KVキャッシュ)の計算、複数GPUへの分割、MoE、同時利用、サーバー用ツールの選び方を整理する。
大きなモデルや複数人で使う場合は、重みよりもKVキャッシュと速度(メモリ帯域)が効いてきます。キャッシュは「層数 × ヘッド × トークン数 × 同時利用数」で増えるので、先に見積もります。
この記事で扱うこと
ローカルLLM入門では、重みの大きさを「パラメータ数 × 1つあたりのバイト数」で見積もりました。手元で1人が試すだけなら、それで十分です。一方、大きなモデルを動かしたり、長い文章を扱ったり、複数人で共有したりすると、重み以外の要素が効いてきます。この記事は、その見積もり方と、道具の選び方を整理します。
モデルや道具の性能は入れ替わりが早いので、特定のモデル名や速度の数字は載せません。計算例の設定も、説明用の仮の値です。
重みのほかに必要なもの:KVキャッシュ
モデルは文章を1トークンずつ作るとき、それまでの全トークンについて計算した中間結果(KeyとValue)を、メモリに保存しておきます。毎回計算し直さずに済むからです。これがKVキャッシュで、会話が長いほど、同時に使う人が多いほど、大きくなります。
KVキャッシュの大きさ = 2(KeyとValue) × 層数 × KVヘッド数 × ヘッドの次元 × 1要素のバイト数 × トークン数 × 同時利用数
層数やヘッド数は、モデルの設定ファイルや公式ページに書かれています。次のコードは、仮の設定(32層、KVヘッド8、次元128、16ビット)で計算します。JavaScript版とPython版の出力は同じです(筆者がNode.jsとPython 3で実行して確認)。
// KVキャッシュ = 2(KとV) × 層数 × KVヘッド数 × ヘッド次元 × 1要素のバイト数 × トークン数
// 層数などは説明用の仮の値(実在のモデルの設定ではない)。
const model = { layers: 32, kvHeads: 8, headDim: 128, bytes: 2 }; // 16bitのキャッシュ
const perToken = 2 * model.layers * model.kvHeads * model.headDim * model.bytes;
const GiB = (n) => n / 1024 ** 3;
console.log(`1トークンあたり: ${perToken / 1024} KiB`);
for (const tokens of [4096, 32768, 131072]) {
for (const users of [1, 4]) {
console.log(`${tokens} トークン × ${users}人: ${GiB(perToken * tokens * users).toFixed(1)} GiB`);
}
}# KVキャッシュ = 2(KとV) × 層数 × KVヘッド数 × ヘッド次元 × 1要素のバイト数 × トークン数
# 層数などは説明用の仮の値(実在のモデルの設定ではない)。
model = {"layers": 32, "kv_heads": 8, "head_dim": 128, "bytes": 2} # 16bitのキャッシュ
per_token = 2 * model["layers"] * model["kv_heads"] * model["head_dim"] * model["bytes"]
gib = lambda n: n / 1024 ** 3
print(f"1トークンあたり: {per_token // 1024} KiB")
for tokens in [4096, 32768, 131072]:
for users in [1, 4]:
print(f"{tokens} トークン × {users}人: {gib(per_token * tokens * users):.1f} GiB")1トークンあたり: 128 KiB
4096 トークン × 1人: 0.5 GiB
4096 トークン × 4人: 2.0 GiB
32768 トークン × 1人: 4.0 GiB
32768 トークン × 4人: 16.0 GiB
131072 トークン × 1人: 16.0 GiB
131072 トークン × 4人: 64.0 GiBこの仮の設定では、4千トークンの会話なら0.5 GiBで済みますが、13万トークンを4人で使うと、重みとは別に64 GiBが要ります。長い文脈を大きなモデルで使うときの壁は、重みよりキャッシュになることがあります。なお、この表の単位は GiB(1024の累乗)で、入門記事のGB(10億単位)とは少し違います。
- KVヘッド数が少ない設計(複数のヘッドでKeyとValueを共有する方式)のモデルは、キャッシュが小さくなる
- ツールによっては、KVキャッシュも量子化して小さくできる。ただし品質が下がることがある
- 文脈の長さの上限を、必要な分に絞るだけでも、メモリを大きく節約できる
大きなモデルを動かす方法
1枚のGPUに収まらないモデルは、次の方法で動かします。
| 方法 | 内容 | 注意点 |
|---|---|---|
| 量子化を強める | 4ビットなどで重みを小さくする | 品質が少しずつ下がる |
| 複数GPUに分ける | 層や計算を複数のGPUに分担させる(テンソル並列など) | GPU間の通信が速くないと、速度が出にくい |
| 一部をCPU側に置く | 載りきらない層をメインメモリに置く(llama.cppなどが対応) | 載せた分だけ遅くなる |
| 大きなメモリの機材を使う | ユニファイドメモリが大きい機種など | 帯域(後述)が速度を決める |
MoE(専門家混合)モデルの見積もり
MoEは、中にたくさんの「専門家」の部分を持ち、1トークンごとに一部だけを使うモデルです。重みは全部の専門家分をメモリに置く必要があるので、必要なメモリは総パラメータ数で見積もります。一方、1トークンの計算に使うのは一部だけなので、同じ総パラメータ数の通常のモデルより、計算は軽くなります。「メモリは総数、計算量は使う分」と覚えておきます。
速度を決めるもの
文章の生成は、1トークンごとに、ほぼ全部の重みをメモリから読み出します。このため、生成の速さは、計算能力よりも**メモリの帯域(1秒に読める量)**に左右されやすいと言われます。重みが小さい(量子化している)ほど、読む量が減って速くなります。
- 重みを小さくする量子化は、メモリだけでなく速度にも効く
- 同じ重みなら、帯域の広い機材の方が速い。載るかどうかとは別の条件になる
- 最初の返事が出るまでの時間は、入力の長さに応じて延びる。長い文書を読ませるほど待つ
複数人で使う:サーバー用のツール
1人で使うなら、Ollamaやllama.cppで十分です。複数人が同時に使うなら、同時に来た要求をまとめて処理する(バッチ処理する)サーバー用のツールが向きます。vLLMはその例で、要求を効率よくまとめることや、KVキャッシュをメモリ上でうまく管理することを特長にしています。詳細は公式ドキュメントで確認してください。
| 用途 | 向くツールの例 |
|---|---|
| 1人で試す、手軽さ優先 | Ollama、LM Studio |
| CPUや一部GPU、細かい調整 | llama.cpp |
| 複数人の同時利用、GPUサーバー | vLLM など、サーバー向けの推論エンジン |
ツールによって、使えるモデルの形式も違います。たとえばllama.cppはGGUF形式、GPU向けのサーバーはAWQやGPTQなどの量子化形式を使うことが多いので、モデルを探すときは対応する形式を確かめます。
本格運用の前に確かめること
- 重みのメモリ(見積もりツールで確認)に、KVキャッシュ(最長の文脈 × 同時利用数)を足しても、使えるメモリに収まるか
- 複数GPUに分けるなら、GPU間の接続は十分に速いか
- 必要な速度は、機材のメモリ帯域で出るか。小さなモデルや強い量子化で足りないか
- 同時に使う人数と、1人あたりの文脈の長さを決めたか
- モデルのライセンスが、運用の形(社内利用、商用など)を許しているか
- 品質は、自分のタスクで、クラウドのモデルと比べたか。コストだけで決めない
- 更新、監視、障害時の対応を、誰が担当するか決めたか
クラウドのAPIは、これらをすべて事業者が担います。自分で運用するのは、データを出せない、大量に回す、細かく制御したい、といった理由があるときに、割に合う選択です。