← 記事一覧へ
AI生成画像

ローカルLLMを本格的に動かす:大きなモデルとKVキャッシュ

重み以外に必要なメモリ(KVキャッシュ)の計算、複数GPUへの分割、MoE、同時利用、サーバー用ツールの選び方を整理する。

AI作成 · 約9分 · ローカルLLM入門を読んだ人向け
ローカルLLMKVキャッシュvLLM
この記事で扱うこと

大きなモデルや複数人で使う場合は、重みよりも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_cache.py
# 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
人数と文脈の長さで、必要なメモリが増える1人・短い会話収まる4人・長い文脈あふれる使えるメモリの上限OS・他のアプリモデルの重みKVキャッシュKVキャッシュは、文脈の長さ × 同時利用数で増える
図1 重みの大きさは変わらないが、KVキャッシュは使い方で増え、長い文脈を複数人で使うと上限を超えることがある。割合は説明用。

この仮の設定では、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キャッシュをメモリ上でうまく管理することを特長にしています。詳細は公式ドキュメントで確認してください。

複数人で使う推論サーバーの構成ユーザーAユーザーBユーザーC推論サーバー(vLLMなど)要求の待ち行列同時に来た要求をまとめて処理(バッチ)KVキャッシュの管理人ごとのキャッシュを効率よく配置GPU 0重みの半分KVキャッシュGPU 1重みの半分KVキャッシュGPU間の通信分割するときは、GPU間の接続が速いことが要る
図2 要求はサーバーで受けてまとめ、複数のGPUに分けた重みで計算する。キャッシュは人数分のメモリを使う。
用途向くツールの例
1人で試す、手軽さ優先Ollama、LM Studio
CPUや一部GPU、細かい調整llama.cpp
複数人の同時利用、GPUサーバーvLLM など、サーバー向けの推論エンジン

ツールによって、使えるモデルの形式も違います。たとえばllama.cppはGGUF形式、GPU向けのサーバーはAWQやGPTQなどの量子化形式を使うことが多いので、モデルを探すときは対応する形式を確かめます。

本格運用の前に確かめること

  • 重みのメモリ(見積もりツールで確認)に、KVキャッシュ(最長の文脈 × 同時利用数)を足しても、使えるメモリに収まるか
  • 複数GPUに分けるなら、GPU間の接続は十分に速いか
  • 必要な速度は、機材のメモリ帯域で出るか。小さなモデルや強い量子化で足りないか
  • 同時に使う人数と、1人あたりの文脈の長さを決めたか
  • モデルのライセンスが、運用の形(社内利用、商用など)を許しているか
  • 品質は、自分のタスクで、クラウドのモデルと比べたか。コストだけで決めない
  • 更新、監視、障害時の対応を、誰が担当するか決めたか

クラウドのAPIは、これらをすべて事業者が担います。自分で運用するのは、データを出せない、大量に回す、細かく制御したい、といった理由があるときに、割に合う選択です。

公式リファレンス

vLLM — Documentation ↗llama.cpp (GitHub) ↗Ollama — Documentation ↗
PAIBOT / DEVELOPMENT JOURNAL記事一覧に戻る →

知りたいことを探す

タイトル・タグ・本文から検索 esc で閉じる