Kimi-K2.5のCPU推論とprompt cache
Kimi-K2.5 Q4_K_S/Q4_K_MをEPYC 9175Fで計測しました。LLM導入の非同期処理に向け、th=13の速度、16k入力の待ち時間、LCP cacheとHybridの改善を示します。
CPU推論で測ったこと
Kimi-K2.5 を CPU で動かし、非同期バッチに使うための速度を測りました。用途は Dagster、データセット増幅、蒸留用 teacher 生成、ローカルでの検証です。即時応答より、待ち時間を許容する処理を想定しています。
AMD EPYC 9175F、768GB DDR5-6400、llama.cpp server で Kimi-K2.5 Q4_K_S を試しました。CPU-only で動きますが、12チャネル帯域と prompt cache に合わせた運用が必要でした。
背景
Moonshot AI の1.03兆パラメータ MoE です。384 expert のうち8つを使い、活性パラメータは約32Bです。DeepSeek-V2 系の MLA(Multi-head Latent Attention)で KV cache を圧縮します。
RSS は Q4_K_S が約523GiB、Q4_K_M が約579GiBです。768GBに収まるため、GPUなしで起動できます。速度を次の用途で確認しました。
想定用途です。
- Dagster pipeline
- 非同期バッチ生成
- データセット増幅 / 蒸留
- GPU agent の補助 / 検証用 LLM
- フォールバック用ローカルLLM
非同期処理で許容できる待ち時間と、context の使い方を検討します。
比較項目
- Q4_K_S量子化でのCPU推論速度をベンチマーク(基本性能)
- スレッド数とスループットの関係を測定し、最適スレッド数を特定
- Q4_K_Mでの32kコンテキスト運用時のPrefill/Decode速度を計測
- Prompt Cache(LCP similarity)の実効性を検証
- Dagsterパイプライン用途として実用に足るか判定
実験環境
| 項目 | 仕様 |
|---|---|
| CPU | AMD EPYC 9175F(Zen 5, 16C, L3 512MB) |
| メモリ | DDR5-6400 768GB(12ch) |
| GPU | NVIDIA RTX PRO 6000 Blackwell Max-Q 96GB |
| OS | Ubuntu 24.04 LTS |
| Runtime | llama.cpp server(Podman rootless) |
モデル仕様
| 項目 | Q4_K_S | Q4_K_M |
|---|---|---|
| アーキテクチャ | deepseek2(MoE + MLA) | 同左 |
| 総パラメータ | 1.03T | 同左 |
| レイヤー数 | 61 | 同左 |
| エキスパート数 | 384(活性8) | 同左 |
| 量子化 | Q4_K_S | Q4_K_M(4.84 bpw) |
| モデルサイズ | 約520GiB(RSS) | 578.57 GiB |
| 学習コンテキスト長 | 262,144 | 同左 |
実施内容
llama-sweep-benchの実行
MODEL=/models/snapshots/386fed8b054275941d6a495a9a7010fbf31b560d/Q4_K_S/Kimi-K2.5-Q4_K_S-00001-of-00013.gguf
IMG=compute.home.arpa/ik_llama-cuda:latest
podman run --rm -it \
--device nvidia.com/gpu=all \
--shm-size 16g \
--cap-add=SYS_NICE \
-v /mnt/data/hf/hub/models--unsloth--Kimi-K2.5-GGUF:/models:ro,Z \
$IMG \
/app/llama-sweep-bench \
--model "$MODEL" \
--no-mmap --merge-qkv \
-mla 3 -amb 512 \
-b 4096 -ub 4096 \
-ctk f16 -ctv f16 \
-c 131072 \
-ngl 999 -ot exps=CPU \
--threads 13 \
--threads-batch 26 \
--warmup-batch \
-n 128
各オプションの役割です。
llama-sweep-bench: ベンチマーク専用ツール-ngl 999 -ot exps=CPU: GPU 層オフロード、Expert は CPU-c 131072: 131k context-ctk f16 -ctv f16: KV cache は f16--threads 13 --threads-batch 26: スレッド数-mla 3 -amb 512: MLA のパラメータ
スレッド数別の起動コマンド
スレッド数を変えて計測しました。次は CPU-only の代表的なコマンドです。
th=16 (Maximum Performance)
podman run --rm -p 8081:8080 --shm-size 16g --cap-add=SYS_NICE \
-v /mnt/data/hf/hub/models--unsloth--Kimi-K2.5-GGUF:/models:Z \
compute.home.arpa/llamacpp-zen5:latest \
-m /models/snapshots/386fed8b054275941d6a495a9a7010fbf31b560d/Q4_K_S/Kimi-K2.5-Q4_K_S-00001-of-00013.gguf \
--cache-type-k q8_0 --cache-type-v q8_0 --flash-attn on \
--ctx-size 8192 --parallel 1 --threads 16 --threads-batch 16 \
--batch-size 2048 --ubatch-size 512 --jinja --host 0.0.0.0 --port 8080
th=13(他の処理との共存)
podman run --rm -p 8081:8080 --shm-size 16g --cap-add=SYS_NICE \
-v /mnt/data/hf/hub/models--unsloth--Kimi-K2.5-GGUF:/models:Z \
compute.home.arpa/llamacpp-zen5:latest \
-m /models/snapshots/386fed8b054275941d6a495a9a7010fbf31b560d/Q4_K_S/Kimi-K2.5-Q4_K_S-00001-of-00013.gguf \
--cache-type-k q8_0 --cache-type-v q8_0 --flash-attn on \
--ctx-size 8192 --parallel 1 --threads 13 --threads-batch 13 \
--batch-size 2048 --ubatch-size 512 --jinja --host 0.0.0.0 --port 8080
メモリ配置(Q4_K_S実測)
| 領域 | サイズ |
|---|---|
| KV cache (K) | 1,098 MiB |
| KV cache (V) | 976 MiB |
| CPU compute buffer | 348 MiB |
| Total RSS | 約523 GiB / 755 GiB |
| Swap使用 | 799 MiB(si/so発生なし) |
メモリ配置(Q4_K_M / ctx=32k)
| 領域 | サイズ |
|---|---|
| KV cache | 4,148 MiB(K: 2,196 / V: 1,952) |
| CPU compute buffer | 348 MiB |
| CPU repack buffer | 459,665 MiB |
| モデルバッファ | 578.57 GiB(13分割GGUF) |
16k context を含めても768GBに収まります。使用量の大半は KV cache ではなくモデル本体です。
CPU推論の実演動画
EPYC 9175F での CPU 推論を動画に記録しました。
動画リンク: https://www.youtube.com/watch?v=n8htU2pmzNI
動画では次の処理を確認できます。
- llama.cpp server の起動と量子化モデルの読み込み
- prefill 時の速度と入力内容
- token ごとの生成
- 実際の出力テキスト
結果
Q4_K_S基本ベンチマーク(th=14, ctx=16k)
| リクエスト | Prompt(tok) | PP速度(tok/s) | Gen(tok) | TG速度(tok/s) | 合計(s) |
|---|---|---|---|---|---|
| 1st(キャッシュなし) | 823 | 22.24 | 438 | 10.27 | 79.7 |
| 2nd(キャッシュ保存) | 1,335 | 19.98 | 1,012 | 8.76 | 115.6 |
| 3rd(LCPヒット) | - | - | - | - | cache lookup 62ms |
初回は重く、対話では待ち時間が気になります。約80秒の生成なら、夜間や非同期バッチの候補になります。
3回目は LCP similarity が一致し、cache lookup が62msになりました。既存の prompt 状態を再利用すると、反復 job の待ち時間を減らせます。
スレッド最適化(ctx=8k)
| スレッド数 | PP速度(tok/s) | TG速度(tok/s) | 評価 |
|---|---|---|---|
| 16 | 24.43 | 12.94 | 最大出力(基準) |
| 14 | 21.32 | 12.50 | 帯域飽和の開始点 |
| 13 | 21.58 | 11.67 | スイートスポット |
| 12 | 14.58 | 11.86 | リソース効率重視 |
decode は th=13–14付近で伸びが止まりました。計算量よりメモリ帯域が制約になっている可能性があります。
th=13は推論速度の約9割を保ち、残り3コアを Dagster / Trino などに使うための候補です。
Q4_K_M Long Context実測(th=13, ctx=32k)
| リクエスト | Prompt(tok) | PP速度(tok/s) | Gen(tok) | TG速度(tok/s) | 備考 |
|---|---|---|---|---|---|
| 1st | 16,148 | 6.15 | 333 | 2.44 | 16k一括Prefill、約44分 |
| 2nd(LCP 0.978) | 356 | 3.40 | 2,048 | 2.26 | キャッシュヒット、差分のみPrefill |
| 3rd(LCP 0.999) | 12 | 3.11 | 1,024 | 2.15 | ほぼ全量キャッシュ復元 |
| 4th(LCP 0.939) | 1,050 | 3.21 | 1,024 | 2.07 | 部分キャッシュ + 差分Prefill |
16k の一括 prefill は約44分でした。20 tok/sでも256kなら約3.5時間のため、毎回の全量処理は負担が大きくなります。
2回目以降は356、12、1050 tokens の差分だけを prefill しました。固定 digest が cache の再利用に寄与しました。
prefill は前半10 tok/s台、中盤7 tok/s前後、終盤4 tok/s台に低下しました。decode は2.44 tok/sで、1k出力に約6–7分、2kに約13–14分の見積もりです。対話には遅く、非同期バッチを想定します。
Prompt Cache効果(Q4_K_S)
| 状態 | サイズ | 効果 |
|---|---|---|
| 1,260トークン保存時 | 159.5 MiB | LCP similarity > 0.5でヒット |
| キャッシュ復元 | - | 数十ms(62ms実測) |
| TTFT短縮 | - | 反復実行でprompt eval時間が激減 |
長文のログにも LCP similarity の再利用があります。
selected slot by LCP similarity, sim_best = 0.978 (> 0.100 thold), f_keep = 0.980selected slot by LCP similarity, sim_best = 0.999 (> 0.100 thold), f_keep = 0.870selected slot by LCP similarity, sim_best = 0.939 (> 0.100 thold), f_keep = 0.940
固定 prefix を再評価せず、差分を処理しています。
追記:ik_llama.cpp による改善(Expert CPU + Attention GPU Hybrid)
追記の計測では ik_llama.cpp を最適化してビルドし、-ngl 999 -ot exps=CPU で Expert を CPU、Attention を GPU に置きました。
実行コマンド
podman run --rm -it --device nvidia.com/gpu=all \
-p 8081:8080 \
--shm-size 32g \
--cap-add=SYS_NICE \
-v /mnt/data/hf/hub/models--unsloth--Kimi-K2.5-GGUF:/models:ro,Z \
$IMG \
--host 0.0.0.0 --port 8080 \
-m "$MODEL" --no-mmap --jinja \
-c 131072 \
-n 128 \
--threads 13 --threads-batch 26 \
-b 2048 -ub 512 \
-ngl 999 -ot exps=CPU \
-ctk f16 -ctv f16 \
--merge-qkv -mla 3 -amb 512
ベンチマーク結果(初期)
| Task | PP(tok) | TG(tok) | N_KV(tok) | T_PP(s) | S_PP(t/s) | T_TG(s) | S_TG(t/s) |
|---|---|---|---|---|---|---|---|
| 0 | 5,264 | 744 | 6,007 | 59.596 | 88.33 | 37.815 | 19.67 |
| 747 | 765 | 259 | 6,287 | 13.277 | 57.62 | 13.164 | 19.68 |
| 1,007 | 279 | 1,024 | 7,331 | 6.243 | 44.69 | 52.452 | 19.52 |
| 2,032 | 1,037 | 1,024 | 8,368 | 16.772 | 61.83 | 51.793 | 19.77 |
| 3,057 | 1,041 | 310 | 8,695 | 16.637 | 62.57 | 16.124 | 19.23 |
| 平均 | - | - | - | - | 63.0 | - | 19.6 |
後続実測
Prompt Cache とテンプレートを調整した後の値です。
| Run | PP(tok) | TG(tok) | N_KV(tok) | T_PP(s) | S_PP(t/s) | T_TG(s) | S_TG(t/s) | 備考 |
|---|---|---|---|---|---|---|---|---|
| 1 | 5,330 | 401 | 5,730 | 41.298 | 129.06 | 20.458 | 19.60 | 新規リクエスト |
| 2 | 416 | 2,241 | 7,986 | 8.363 | 49.75 | 114.552 | 19.56 | キャッシュ部分不一致 |
| 3 | 2,255 | 919 | 8,919 | 20.631 | 109.30 | 48.056 | 19.12 | キャッシュ部分不一致 |
評価
観測した値:
- Run 1, 3のPrefillは100-130 t/sでした
- S_TGは全Runで19 t/s前後でした。このBlackwell + Q4_K_Sの条件では、生成速度に差がありません
- Run 2, 3の部分cache hitでは、S_PPは50-110 t/sでした
現在の制限:
- 生成速度のボトルネック: S_TGが約19 t/s固定のため、体感の「遅い/速い」はPrefill時間と出力トークン数に左右される
- キャッシュ一貫性: Run 2, 3で “Common part does not match fully” が出現。System Promptやテンプレートの細微な変更(改行・スペース・タイムスタンプ)でキャッシュが割れる
考察
メモリ帯域とth=13の根拠
th=16は12.94 tok/s、th=13は11.67 tok/sでした。3スレッド減らしても低下は10%未満です。12チャネル DDR5-6400 の理論帯域は約614GB/sですが、MoE のランダムアクセスでは全帯域を使えないと考えています。
長い入力の運用案
長い入力では、毎回全量を prefill しない運用を検討します。
運用案です。
- ctx=16k–32kに抑える
- System Digest(8k–16k程度)を起動時に一度 prefill する
- LCP similarity で以降は差分を処理する
- 出力は1kを基本とし、必要時のみ2kにする
量子化とcontextが異なる2構成の比較
Q4_K_S / ctx=16kは10 tok/s、Q4_K_M / ctx=32kは2.4 tok/sでした。32kの KV cache(4.1GB)の Attention 計算が影響した可能性があります。量子化と context の両方が違うため、単一要因の比較ではありません。
L3 cacheとCPU構成の仮説
CPU の速度には、再利用が多い作業セットの L3 cache hit が寄与した可能性があります。全体が cache に入るわけではなく、次の領域が候補です。
再利用が多いと考えられる領域です。
- Router / Gatingロジック
- Projection周辺
- 直前レイヤのweight / 中間テンソル
- KV再利用部分
EPYC 9175F の次の特性も、速度に関係すると考えています。
- L3(512MB)と16コア: コア数に対するcache容量が大きく、コア間競合が少ない可能性があります
- 12チャネルと16コア: メモリコントローラの待ち行列が短い可能性があります
- Zen 5のBF16 / AVX-512: 512-bit datapathとBF16ネイティブ処理が
llama.cppの最適化に合うと考えています
Prompt Cacheを維持する入力設計
- System Digestは完全固定(改行・空白・日付差分でキャッシュが割れる)
- RAGコンテキストはsystemに混ぜず、user側に差し込む(キャッシュ維持が最優先)
- 出力スタイルを短めに固定する(生成が遅い以上、出力長を抑えることもそのままUX改善になる)
aichat の .file /path/to/file で必要な文書を user 側に渡し、system 側の digest を固定する方法も使えます。
結論
1T級を CPU-only で動かせました。decode 10 tok/sは、今回想定した非同期の生成、データセット増幅、teacher 生成の候補になります。
LLM導入の運用案は、GPU(RTX PRO 6000、vLLMなど)を対話、CPU llama.cpp / th=13をバッチに分けるものです。768GB中523GiBを使い、200GB以上を DataFrame や Trino に残す見積もりですが、並列時の負荷は別に確認が必要です。
確認できたのは th=13付近の速度と、LCP similarity の再利用です。これをもとに、推論とデータ処理の資源配分を検討します。
推奨パラメータ(バッチ運用向け)
| パラメータ | 推奨値 | 理由 |
|---|---|---|
| ctx | 16,384-32,768 | 256kは非現実的 |
| system digest | 8k(長くても12k-16k) | 起動時に一度prefillし以後はcache |
| threads | 13 | メモリ帯域飽和点、残り3コアをパイプラインに |
| ubatch | 256 | 512より安定(失速しにくい可能性あり) |
| cache-ram | 32,768 MiB | LCPヒット率の安定化 |
| output | 1,024 | 生成速度がボトルネックのため短く |
運用推奨コマンド
podman run --rm -p 8081:8080 --shm-size 16g --cap-add=SYS_NICE \
-v /mnt/data/hf/hub/models--unsloth--Kimi-K2.5-GGUF:/models:Z \
compute.home.arpa/llamacpp-zen5:latest \
-m /models/snapshots/386fed8b054275941d6a495a9a7010fbf31b560d/Q4_K_S/Kimi-K2.5-Q4_K_S-00001-of-00013.gguf \
--cache-type-k q8_0 --cache-type-v q8_0 --flash-attn on \
--ctx-size 131072 --parallel 1 --threads 13 --threads-batch 13 \
--batch-size 2048 --ubatch-size 512 --jinja --host 0.0.0.0 --port 8080
再現方法
1. モデル取得
# Q4_K_S
huggingface-cli download unsloth/Kimi-K2.5-GGUF \
--include "Q4_K_S/*" \
--local-dir /mnt/data/hf/hub/models--Kimi-K2.5-GGUF
# Q4_K_M
huggingface-cli download unsloth/Kimi-K2.5-GGUF \
--include "Q4_K_M/*" \
--local-dir /mnt/data/hf/hub/models--Kimi-K2.5-GGUF
2. 実行
「実施内容」のコマンドを使います。flash-attn と prompt cache 対応の llama.cpp が必要です。
3. 計測
curl -s http://localhost:8081/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model":"kimi","messages":[{"role":"user","content":"Explain MoE architecture"}],"max_tokens":512}'
ログの prompt eval time と eval time から速度を確認します。
補足ノウハウ
Q4_K_S vs Q4_K_M
Q4_K_M は約60GB大きく、520→579GiBです。品質を期待する場合の候補ですが、今回の ctx=16k バッチでは Q4_K_S を候補にしています。
比較用ベンチマーク:Llama-4-Maverick-17B-128E
別の MoE でも量子化ごとの値を確認しました。
| Quant | Prefill (tok/s) | Decode (tok/s) | TTFT (1k context) |
|---|---|---|---|
| Q4_K_M | 65-68 | 21-24 | 12-17s |
| Q8_0 | 50-52 | 15-16 | 16-20s |
量子化とモデル構造が CPU 推論にどう影響するかを比べる参考値です。
