ローカルの大型言語モデル(LLM)は、最初は驚くほど高速に感じられます。7Bや13Bのパラメータモデルをロードして短いプロンプトを投げれば、トークンは快適なペースで画面上に流れていきます。しかし、長いコードブロックを貼り付けたり、チャット履歴が数十ターンにわたって膨れ上がったりすると、モデルの動きは目に見えて鈍くなります。この低速化は、決して緩やかなものではありません。それは「崖」のようなものです。さっきまでGPUが次々とトークンを生成していたかと思えば、次の瞬間にはシステムモニターにメモリプレッシャーの増大が表示され、生成がガクガクと途切れ始めます。これがいつ起こるのかを正確に予測できる整った公式は存在しません。唯一信頼できるガイドは、ハードウェアそのものです。
コンテキストに隠されたコスト
生成されるすべてのトークンは、KVキャッシュに状態を追加します。このキャッシュは、プリフィル(prefill)および生成フェーズ中に計算されたキー(Key)と値(Value)を保存するもので、モデルの重み、アテンション・バッファ、およびランタイムのオーバーヘッドと共にメモリ内に存在します。12GBまたは16GBのVRAMを搭載した一般的なコンシューマー向けGPUでは、KVキャッシュは最終的に他のあらゆる要素と容量を奪い合うことになります。専用ビデオメモリがいっぱいになっても、オペレーティングシステムはエラーを出して停止することはありません。代わりに、オーバーフローしたデータを共有メモリへと静かに溢れさせ、PCIeバスを介してGPUとシステムRAMの間でデータをやり取りし始めます。そのバスはファイル転送には高速ですが、グラフィックスカード内部のメモリ帯域幅と比較すると、極めて低速です。その結果として起こるのは、わずかなパフォーマンスの低下ではなく、性能の崩壊です。
限界(崖)が到来したことを示す3つの兆候
モデルの実行中は、ハードウェアモニターを注視してください。パフォーマンスの崖に衝突すると、以下の3つの明確な兆候が現れます。
- 共有VRAMの上昇。 これは、GPUドライバーが専用ビデオRAMからホストOSが管理するプールへと押し出したメモリです。このメトリクスがゼロを超えた瞬間、あなたは境界線を越えたことになります。
- システムRAM使用量の増大。 オーバーフローしたデータはどこかに着地しなければならず、その目的地がメインメモリです。モデルがトークンを生成している間にRAM使用量が増加しているなら、データがGPUからオフロードされています。
- Eval速度が半分以下に低下。 10%程度の低速化であれば、サーマルスロットリングやバックグラウンドプロセスの影響かもしれません。しかし、50%以上の低下、あるいはそれ以上の急落は、ボトルネックがテンソルコアからメモリ帯域幅とPCIeのレイテンシへと移行したことを意味します。生成速度が2桁から1桁へと落ち込んだとき、あなたはすでに崖から転落しています。
クイックベンチマークが嘘をつく理由
短時間の動作確認(スモークテスト)では、誤った安心感を得てしまう可能性があります。もし100トークン程度のプロンプトでモデルのベンチマークを行い、良好なスループットを確認して「よし、大丈夫だ」と判断したなら、あなたは単に「ハネムーン期間」を測定したに過ぎません。その時点ではKVキャッシュはほぼ空であり、長いプリフィルによってレイヤーに負荷もかかっていません。真のフットプリントは、モデルが相当量のプロンプトを処理し、キャッシュが実際の稼働サイズまで満たされた後に初めて明らかになります。深いプリフィルと長時間の生成実行を用いてテストする必要があります。実際にコンテキストを蓄積させてください。そうして初めてメモリプレッシャーが安定し、真の限界が見えてきます。
llama.cppで限界を見極める
llama.cppを使用してモデルを実行している場合、簡単な計算と根気強いテストによって、自身の限界点(壁)を測定できます。
1. 共有メモリ使用量を測定する。
最小限のプロンプトを用いて、ベースラインとなる専用VRAMの使用量を記録します。次に、長文コンテキストのタスクを実行し、ピーク時の使用量を記録します。ピーク値からベースラインを引いた差分が、GPUから共有システムメモリへと溢れ出したデータ量です。
2. RAMの差分を計算する。
システムRAMについても同様の引き算を行います。長時間の実行中のピークRAM使用量から、ベースラインのRAM使用量を引きます。この数値は、ビデオカードからメインメモリへとどれだけのデータが押し出されたかを正確に示します。これにより、バスを介したデータの漏出を定量化できます。
3. Eval速度の崩壊を計測する。
ベースラインの1秒あたりのトークン数(tokens-per-second)と、モデルが長いドキュメントを読み終えた後の速度を比較します。コンテキストが新鮮なときは毎秒17トークンで軽快に動いていたモデルが、キャッシュが膨れ上がると毎秒わずか2トークンしか出せなくなる、といった現象が起こり得ます。この15トークンの低下こそが、あなたの「炭鉱のカナリア(警告の兆候)」なのです。
限界点の特定
精度高く曲線をマッピングするには、たった一つのデータポイントで妥協してはいけません。16,000トークン、32,000トークン、65,000トークンの3つの異なる条件で試行を行ってください。2つの点があれば直線が示唆されるかもしれませんが、2つの点は単なる推測に過ぎません。3つ目の点があって初めて、それが測定ノイズなのか、それとも真のメモリウォールなのかが証明されます。各試行の結果の差を差し引くことで、使用しているモデル、量子化レイヤー、GPUの特定の組み合わせにおいて、トークンが1,000増えるごとにどれだけの追加メモリを消費するかを算出できます。
その傾きが得られれば、将来の予測が可能になります。トークンあたりのコストに目標コンテキスト長を掛け、単位を変換するために1024で割り、その結果をベースモデルのVRAM負荷に加算します。数式は以下の通りです。
モデルのVRAM負荷 + (トークン数 × トークンあたりのメモリ ÷ 1024) = 理論上のVRAM使用量
この予測は予言ではありません。実際の挙動から導き出された指標です。本番稼働を開始する前に、限界値を推定するために活用してください。
理論上の数式が通用しない理由と、量子化で解決できること
教科書的な数式は、ローカル推論における複雑な現実を無視しています。アーキテクチャによってアテンションバッファの割り当て方は異なります。また、オペレーティングシステムは、ディスプレイドライバ、コンポジタ、およびCUDAコンテキストのためにVRAMを確保します。ドライバのバージョンによって、共有メモリの使用の激しさも変わります。理論上の数式では、ブラウザのタブを大量に開いた状態の午後2時に、あなたのマシンで実際にどれだけのVRAMが空いているかを知ることはできません。特定のハードウェア上でモデルを実行し、メーターを監視する必要があります。
量子化は部分的な緩和策となります。KVキャッシュを f16 から q8_0 に変更すると、ほぼすべての実用的なタスクにおいて十分な精度を維持しつつ、メモリフットプリントを半分に抑えることができます。この変更によって、メモリの余裕(ヘッドルーム)を確保できます。しかし、それによって問題が完全に回避できるわけではありません。キャッシュは、入力されるトークンごとに依然として線形に増加します。最終的には、たとえサイズを削減していても、利用可能な専用メモリを使い果たし、システムRAMへのスピルオーバーが始まります。この圧力は、コンテキストウィンドウが制限されるか、データの移動が止まるまで止まりません。
真の教訓
マーケティング資料やパラメータ数、あるいは大まかな概算を鵜呑みにしてはいけません。モデルをロードしてください。システムモニターを開いてください。65,000トークンのスレッドを実行し、RAMの増加を監視しながら、1秒あたりのトークン数(tokens per second)をカウントしてください。あなたの特定の画面、特定のGPUに表示される数値こそが、唯一意味のある数値です。コンテキストは常に勝利します。あなたの仕事は、あなたのマシンにおいて、それがいつ勝利するかを正確に把握することです。
