- KVSplitは、Apple Silicon上でLLMのattention KV cache に対してkeyとvalueごとに異なる量子化精度を適用し、同じメモリ予算でより長いコンテキストとより大きなモデルの実行を目指す
- 中核となる結果は K8V4 構成で、8Kトークン基準でFP16の176.00MBに対して71.50MBまで削減し、トークン処理速度は54,360 tokens/secから57,438 tokens/secへ向上、perplexityの変化は+0.86%とされる
- keyはvalueより量子化に敏感だという結果に基づき、同じ総ビット数を使う K4V8 はK8V4より品質低下が約7倍大きいとまとめている
- 提供機能には、llama.cppへのパッチ適用、Metal対応ビルド、メモリ・速度・perplexityベンチマーク、CSV/JSONでの結果保存、可視化ツール、Activity Monitorベースのメモリ削減キャプチャが含まれる
- 推奨構成は、品質とメモリ削減のバランスを取る K8V4 であり、最大限のメモリ削減が必要な場合は、72%削減と約6%の品質損失を受け入れる K4V4 という選択肢もある
KVSplitが解決しようとしている問題
- KVSplit は、Apple Silicon MacでLLM推論時のKV cacheメモリを削減するためのプロジェクト
- attentionメカニズムのKV cacheで、keyとvalueに異なる量子化精度 を適用する
- 目標は以下のとおり
- メモリ使用量を最大 72%削減
- 同じメモリ予算で 2〜3倍長いコンテキスト を実行
- FP16比で推論速度を維持または改善
- Apple Silicon向けの Metal対応 を提供
主要ベンチマーク結果
- 8Kトークン基準の構成別結果は以下のとおり
- FP16: 176.00MB, 54,360 tokens/sec
- K8V8: 93.50MB, 51,503 tokens/sec, perplexity +0.03%
- K8V4: 71.50MB, 57,438 tokens/sec, perplexity +0.86%
- K4V8: 71.50MB, 58,690 tokens/sec, perplexity +6.06%
- K4V4: 49.50MB, 55,193 tokens/sec, perplexity +6.15%
- メモリ削減表では、K8V4は8Kトークンで 59%削減、K4V4は 72%削減 とされる
- 性能表では、K8V4はFP16比で +5.7%、K4V8は+8.0%、K4V4は+1.5%の速度向上を示す
- K8V8はFP16比でメモリは削減するが、速度は -5.3% 低下する
シーケンス長によるメモリ使用量
- コンテキスト長が長くなるほど、KV cacheのメモリ削減効果は大きくなる
- 8192トークン基準のメモリ使用量は以下のとおり
- FP16: 176.00MB
- K8V8: 93.50MB
- K8V4: 71.50MB
- K4V8: 71.50MB
- K4V4: 49.50MB
- 4096トークン基準でも、FP16の88.00MBに対しK8V4/K4V8は35.75MB、K4V4は24.75MBを使用する
- 128トークン基準では、FP16は5.50MB、K8V4/K4V8は2.23MB、K4V4は1.55MBとされる
keyとvalueの非対称性
- KV cacheメモリは、各トークンの keyベクトルとvalueベクトル の保存が大半を占める
- プロジェクトの中核的な観察は、keyはvalueより量子化にかなり敏感 だという点
- K8V4は8-bit keyと4-bit valueを使い、次のようなバランス点を提供する
- FP16比でperplexity低下 0.86%
- メモリ削減 59%
- FP16より高速な推論速度
- K4V8はK8V4と同じ総ビット数を使うが、品質低下はK8V4より約 7倍 大きいとまとめられている
- この非対称性によって、consumer hardwareでもより長いコンテキストとより大きなモデルの実行が可能になると説明している
インストールと統合方法
- インストールはリポジトリをcloneした後、
scripts/install_kvsplit.sh を実行する方式
git clone https://github.com/dipampaul17/KVSplit.git
cd kvsplit
chmod +x scripts/install_kvsplit.sh
./scripts/install_kvsplit.sh
- インストールスクリプトでは、Python環境の設定方法を選べる
- Virtual Environment: プロジェクトフォルダ内に独立したPython環境を作成
- System Python: 既存のPythonインストールを使用
- Skip Python Setup: ユーザーがPython環境を自分で管理
- llama.cpp の統合方法も選択可能
- 標準方式: llama.cppをcloneしてKV splitパッチを適用
- Git submodule方式: 開発者や上級ユーザー向けにllama.cppをsubmoduleとして追加
- インストール工程には、Apple Silicon向けのMetal対応llama.cpp設定、差別化されたKV cache量子化の有効化、任意のテストモデルのダウンロード、可視化ツールの設定が含まれる
使用例とCLIオプション
python scripts/quick_compare.py --model models/your-model.gguf
- 比較対象はFP16、K8V8、K8V4、K4V8、K4V4で、メモリ・速度・品質指標をまとめて表示する
- READMEの実行例では、
llama-cli に --flash-attn とKV量子化オプションを併用している
./llama.cpp/build/bin/llama-cli -m models/your-model.gguf -p "Your prompt" \
-t 8 --flash-attn --kvq 8
- K4V8の例では、keyとvalueのビット数を個別に指定する
./llama.cpp/build/bin/llama-cli -m models/your-model.gguf -p "Your prompt" \
-t 8 --flash-attn --kvq-key 4 --kvq-val 8
- 32Kコンテキストの例では、FP16では約 1.4GB、K8V4では約 400MB が必要だとしている
./llama.cpp/build/bin/llama-cli -m models/your-model.gguf \
-c 32768 -n 4096 -t 8 --flash-attn --kvq 8 \
-f your-long-document.txt
- 主なCLIフラグは以下のとおり
-t 8: スレッド数。ほとんどのApple Siliconチップでは8を推奨
--flash-attn: 最適化されたattentionを有効化。Apple Siliconで推奨
--kvq N: keyとvalueのビット数を設定
--kvq-key N: keyのビット数のみ設定
--kvq-val N: valueのビット数のみ設定
-c N: コンテキストサイズ
-n N: 生成するトークン数
-f FILE: 入力ファイル
-m MODEL: .gguf モデルファイルのパス
ベンチマークと可視化ツール
- 全体ベンチマークは
scripts/benchmark_kvsplit.py で実行する
python scripts/benchmark_kvsplit.py
python scripts/benchmark_kvsplit.py --config K8V4 --seq-len 4096
- 可視化は
scripts/visualize_results.py で生成する
python scripts/visualize_results.py
- ベンチマークでは以下を測定する
- Memory Usage: VRAMとKV cacheメモリ
- Performance: シーケンス長ごとのtokens/sec
- Quality:
llama-perplexity を使ったperplexity
- Scaling: シーケンス長に応じたメモリと性能の変化
- 結果はCSV/JSON形式で保存され、自動要約統計と可視化プロットを生成する
capture_memory.sh は、Activity Monitorでメモリ削減をキャプチャするためのツール
Apple Silicon向け最適化と制約
- KVSplitはAppleの Metal framework 向けに最適化されている
- Apple Silicon M seriesのようなメモリ制約のあるデバイスで、メモリ効率を重視している
- READMEでは、llama.cppの 256B page alignment により、実際のメモリ削減量は理論計算とわずかに異なる可能性があるとしている
- 対応対象にはM1、M2、M3、M4チップが含まれる
推奨構成とロードマップ
- 推奨構成は K8V4
- 8-bit key、4-bit value
- 59%のメモリ削減
- 0.86%の品質損失
- FP16比で+5.7%の推論速度
- 最大のメモリ削減は K4V4
- 4-bit keyと4-bit value
- 72%のメモリ削減
- 約6%の品質損失
- 影響に比較的鈍感なアプリケーション向けとされる
- 非常に長いコンテキストにはK8V4またはK4V4が推奨され、コンテキスト長が伸びるほどメモリ削減効果は積み上がる
- 今後の計画は以下のとおり
- トークン重要度ベースの Adaptive Precision
- レイヤーごとに異なる精度を使う Layer-Specific Quantization
- Mistral、Phi-3などに合わせたモデル別最適化
- Webデモ
- iOSとiPadOSのサポート
- ライセンスは MIT で、貢献はissueまたはpull requestで受け付けている
1件のコメント
Hacker News のコメント
興味深い。なぜこういう結果になるのかについての直感があるのか気になる。その直感から発見したのか、それともランダムな実験で見つけたのかも気になる。
インストールスクリプトの「apply patch」段階には、まだプレースホルダーが残っているように見える。
git clone後にパッチを適用させるより、llama.cpp をフォークして Git サブモジュールとして含めるほうがユーザーフレンドリーだと思う。それに、人によってローカルの Python 設定はまちまちなので、Homebrew Python への依存を固定するより、llama.cpp 関連部分と Python 関連部分を分離できるようにするとよさそう。
キーはどのトークンに注目するかを決め、類似度計算を通じて実際のアテンションパターンを作る。値は、アテンションが決まった後に渡される情報を保存しているだけ。
キーベクトルを過度に積極的に量子化すると、すべてのトークン間相互作用の類似度計算が歪む。キーの小さな誤差が、アテンションをまったく見当違いのトークンへ向けてしまうことがある。
値はずっと寛容だ。値ベクトルの量子化誤差は、アテンションパターンがすでに決まった後、その単一トークンの情報内容にだけ影響する。
図書館の目録システムと本そのものの違いに似ている。目録番号(キー)が壊れると、まったく違う書架を見ることになるが、本の一部の単語(値)がにじんでも、なお正しい本を読んでいて、時々ノイズが混じるだけだ。
数学的には、キーは softmax 計算に入り、小さな誤差が正規化過程で指数的に増幅される。値は線形の重み付き平均を通るだけなので、誤差は相殺される傾向がある。
最初は「More for Keys, Less for Values」や「KV-AdaQuant」のような論文でこの非対称性に触れ、Apple Silicon 推論で実際にどれほど影響するのかを定量化したかった。同じメモリでK8V4 と K4V8 の品質差が 7 倍だった点が印象的だった。
インストールに関するフィードバックもありがとう。プレースホルダーを直し、Python 依存をより柔軟にする予定。
arg.cppへ移されたためだ。それでも問題ない理由は、K と V の量子化を設定するオプションがすでに2023 年に llama.cpp に追加されているから。
このパッチがなぜ存在するのか理解できない。既存の設定を別のコマンドライン引数に変えて新しく見せようとしている以外に理由が分からない。
こういう新しいリポジトリの
install.shファイルは、誰も実行しないことを強く勧める。特に、パッチファイルを 1 つ適用するだけのような単純なことに不要な場合はなおさらだ。これは
--cache-type-kと--cache-type-vを使うのと違うのか?リポジトリのほかのおかしな点は、別のコメントにいくつか書いておいた。
なので、以前の
type_k/v方式と Apple GPU で下げられる最低は 16 ビットの f16/bf16 だったのではないかと思う。ただし llama.cpp 内部の専門家ではないので、間違っているかもしれない。このパッチを MLX でもできるのか気になる。MLX のほうが速度が出ているので、このアプローチと組み合わせられれば、Mac ユーザーでも実用的な速度で長い会話ができそうだ。
個人的に一番期待しているのは、信じがたいかもしれないがHaskell バインディングだ。数日前、Haskell の遅延評価がこのパラダイムにかなりよく合い、コンパイルグラフへのほぼ純粋関数的なアプローチも役に立つと誰かが指摘していた。Haskell で機械学習をするのは面白そうだ。
差分的な KV 量子化(例: K8V4)を、すでに
.gguf形式に変換されたモデルへ適用できるのか気になる。それとも特別な対応を入れてモデルを再ビルドする必要があるのか?どの
.ggufファイルとも互換性があるなら、モデルタイプ(Mistral、Phi-3 など)やトークナイザ設定に制限があるのかも気になる。.ggufモデルを再構成したり特別に変換したりせず、そのまま使える点だ。量子化はモデルのロード時や変換中ではなく、実行時の KV キャッシュで行われる。KV キャッシュはトークンを処理しながら推論中に生成され、モデルの重みとは完全に別なので、これが可能になる。
--kvq-keyと--kvq-valフラグは、この中間テンソルをメモリ内でどう保存するかだけを llama.cpp に伝える。Llama-3、Mistral、Phi-2/Phi-3、TinyLlama、Qwen 系で正常にテストした。
唯一の制限は llama.cpp の Metal バックエンドが必要なことと、現在の llama.cpp の Flash Attention 実装はカスタム KV キャッシュ形式を迂回するため、
-fa 0で Flash Attention をオフにする必要があることだ。この手法自体は、標準的なアテンション機構を使うどの Transformer アーキテクチャでも動作するはず。コードを読む時間が取れた。このPRを正しく理解できているなら、この機能はすでに2023年から llama.cpp にあったので、パッチは不要だ: https://github.com/ggml-org/llama.cpp/pull/4312
変更をコミットとして適用した llama.cpp のフォークを提供する代わりに、このリポジトリは
install.shスクリプトを実行させる。このスクリプトはリビジョンを指定せずに llama.cpp の master ブランチをチェックアウトし、その後に短いパッチを適用する。これだけでも何かおかしいという警告サインになるリポジトリには異なるパッチファイルが4つあり、インストールスクリプト内には Heredoc として埋め込まれた追加のパッチ版がさらに1つある。スクリプトにはリポジトリをクローンしてパッチを試みるコードも2バージョン入っている
install.shはcp patch/split_kv_quant.diff patch/fixed_kv_patch.diffという行で、1つのパッチファイルを別のパッチファイルで上書きする。そのため、リポジトリにチェックインされているfixed_kv_patch.diffは適用される前に上書きされる私には本来このパッチを使おうとしているように見える: https://github.com/dipampaul17/KVSplit/blob/main/patch/split...(追記: 末尾のコメントを見ると、実際にはこちらのようだ: https://github.com/dipampaul17/KVSplit/blob/main/patch/fixed...)
このパッチが追加するのは、K と V の量子化を同時に設定するという
--kvq引数だけだが、そのすぐ上に K と V の量子化をそれぞれ設定する組み込み引数がすでにある。これらのパッチをあちこち移している間に、機能がすでに存在することに作者が気づかなかったはずがあるだろうか?こういう新しいリポジトリのシェルスクリプトは実行しないことを強く勧める。特に、これほど複雑なスクリプトならなおさらだ
HN の投稿は200件を超えるアップボートを受け、GitHub リポジトリも200を超えるスターを集めてなお増え続けているが、内容は誤解を招くもののように思える。このスレッドで問題を指摘して大量にフラグを付けられたコメントは、実際には正しかった。作者がこのスレッドに返信し続けながらも、機能がすでに存在するという質問を避けている点も気になる
追記: シェルスクリプトを読み違えていた。実際にはこのパッチを適用するようだ: https://github.com/dipampaul17/KVSplit/blob/main/patch/fixed... パッチを適用した後、奇妙なことに
fixed_kv_patch.diffをsplit_kv_quant.diffで上書きするが、その後は何もしない。これがバイブコーディングの結果なのか、単なる不注意なコード編集なのかは分からないが、知らないリポジトリのこうしたシェルスクリプトは実行すべきではない、ということを繰り返しておきたい追記 2: さらに混乱している。
install.shスクリプトは llama.cpp リポジトリの古いURL(https://github.com/ggerganov/llama.cpp)を参照しているが、このURLはしばらく前に変わっており、今はリダイレクトされる。パッチはcommon.cppの引数パースを修正しようとしているが、そのコードは8か月前にarg.cppへ移されている(https://github.com/ggml-org/llama.cpp/commit/bfe76d4a17228bf...)。つまり、このインストールスクリプトとリポジトリは2024年ごろのコードをベースに、2023年ごろ llama.cpp に追加されたオプションを使っていることになる。いったい何が起きているんだ?警告サインが多い。好意的に見ても、LLM生成コードで GitHub プロフィールを水増ししようとしている人のように見える。そのプロフィールの5月12日の活動を見るだけでいい
しかし、元投稿者の GitHub 活動全体が怪しい。5月12日に複数の人気プロジェクトへLLM寄せ集めPRを投げており、JAX だけが拒否した。それでも、そのおかげで人気プロジェクト群を、まるで自分がコントリビューターであるかのようにプロフィールにピン留めできていた
これがどれほど不快なことか、言葉で表すのは難しい。AI分野で働く誰もが情報汚染に加担しており、その結果はまだ予測すらできない。死んだインターネットとAI寄せ集めの洪水は、始まりにすぎない
64GBや128GBのApple Siliconでは、36GBや48GBよりもこれらは有意に速い、または優れているのか?
大きなコンテキストと大きなモデルは、お金で買える最速・最大級のApple Siliconでも苦痛なほど遅いと読んできた
なので、これがより大きなメモリをうまく活用できるようにするのか、それとも実用上は依然としてApple Siliconでは比較的小さなモデルが答えなのか気になる
ただしKVSplitは計算速度を根本的に変えるものではなく、メモリ効率だけを変える。ベンチマークではK8V4でスループットが14.5%向上したが、これは計算量の削減ではなくメモリ局所性の改善によるもの
Apple Siliconで大規模モデルが「苦痛なほど遅い」主な理由はメモリ制約ではなく、計算性能の限界。70Bパラメータモデルは、利用可能なRAMやKVキャッシュ最適化に関係なく、同程度のトークン生成速度で動くはず
KVSplitは利用可能なメモリをよりうまく使えるようにする。モデルサイズよりもコンテキスト長がボトルネックになっている場合に特に価値がある
実用的なApple Siliconでの利用では、依然としてより小さなモデル(7B〜13B)に拡張されたコンテキストウィンドウを組み合わせるのが適切な落としどころ。これにより、妥当な生成速度を維持しながら、はるかに多くのテキストを処理できる
ワークフローが巨大なコンテキストと大きなモデルの両方を必要とするなら、依然としてサーバー級GPUを検討すべきだが、KVSplitはAppleハードウェアで可能な範囲を少し押し広げる
素晴らしい取り組みで、とても興味深そうだが、理解するにはもう少し高いレベルでの説明が必要
例えば、2048トークンのコンテキストウィンドウのモデルを4〜6Kコンテキストウィンドウで動かせるようにするものなのか? それともgemma3のような128Kモデルを256K以上のコンテキストウィンドウで動かせるようにするものなのか?
ローカルモデルの理想的なユースケースは何か?
実用的には、MacBookで本を丸ごと一度に処理したり、ファイルを分割せずに大きなコードベースを分析したり、チャットアプリケーションで長い会話履歴を維持したりできるという意味
メモリ削減はコンテキスト長に線形に比例する。コンテキストウィンドウが長いほど、絶対的に節約されるメモリ量は大きくなる。私のM4 MacBookでは、8Kコンテキスト時にKVキャッシュが176MBから72MBに減った。128Kコンテキストなら、同じ比率の削減でギガバイト単位のメモリが空く
この最適化は、モデルのパラメータ上限よりもコンテキストウィンドウの上限にぶつかる場合に最も価値がある。大きなモデル重みではなく長い入力のせいでメモリ不足エラーが出るなら、KVSplitが直接そのボトルネックを解消する
学習後にコンテキストウィンドウを伸ばすのは簡単ではないため、自分が何をしているのか正確に分からないなら、より大きなコンテキストウィンドウで学習されたモデルを探す方がよい
ローカルモデルの用途は、オフライン作業、プライバシー/セキュリティなど多様。ただし多くの場合は、モデルを調整しながら実験するために使われる
何か怪しいことが起きているので、これをインストールしたり、あのスクリプトを実行したりしない方がよさそう
投稿にはフラグを立てた
素晴らしいアイデアであり試み。これはGPUにも適用されるのか? また、他の量子化手法とも互換性がありそうだが、おそらくそれぞれ個別のパッチが必要と考えればよいのか?
llama.cppのCUDAバックエンドは、すでに
--cache-type-kと--cache-type-vフラグで個別のキャッシュタイプ設定をサポートしている。この特定のパッチはMetal専用の最適化に焦点を当てているが、核となる手法はそのまま移植できる他の量子化方法との互換性もある。このKVキャッシュ最適化は、モデル重みの量子化(Q4_K_M、GPTQ、AWQなど)と相補的。非対称KVキャッシュ精度は、どのモデル重み形式とも併用できる
KVキャッシュ量子化はトークン処理中の実行時に行われ、モデル重みとは独立しているため、モデル自体がどのように量子化されているかとは衝突しない。推論パイプラインの異なる部分で動作する
追加作業が必要なのは、vLLMやTensorRT-LLMのようにカスタムKVキャッシュ処理を持つ特殊な推論エンジンとの統合。それぞれ非対称KV精度を別途実装する必要がある
GPUで最も即効性のある利点は、おそらくこの知見をFlashAttention実装に直接統合するところから得られる。CUDAハードウェアでは、メモリ帯域幅の削減がより大きな速度向上につながる可能性がある
小さなコンテキストサイズで**パープレキシティ +0.86%**なら、かなり大きい方ではないか? 64〜128Kのようなより現実的なコンテキストサイズではどうなのか?
あるいは、空いたメモリをIDEなど別の用途に使うこともできる