1T級MoE Kimi-K2.5のCPU推論実測:スレッド最適化からLong Context運用設計まで
Kimi-K2.5(1.03T MoE, Q4_K_S/Q4_K_M)をEPYC 9175FでCPU推論した全記録。スレッド最適化でth=13が最適解になる理由、Q4_K_Mでの16k Long Context実測、LCPキャッシュの効果、そしてDagsterバッチ運用に至る設計判断をまとめた。
背景
Kimi-K2.5はMoonshot AIが開発した1.03兆パラメータのMoEモデル。384個のエキスパートから8個を選択し、推論時の活性パラメータは約32Bに抑えられる。DeepSeek-V2系アーキテクチャ(MLA: Multi-head Latent Attention)を採用しており、KVキャッシュが圧縮されるため、メモリ効率が高い。
Q4_K_S量子化でRSS約523GiB、Q4_K_Mで約579GiB。768GBのDDR5メモリに収まるため、GPUなしのCPU推論が物理的に成立する。問題は「成立する」と「使える」の間にある速度差で、それを埋めるのがこの検証の目的。
目的
- 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 コンテキスト対応-ctk f16 -ctv f16: KVキャッシュを f16 で保持--threads 13 --threads-batch 26: スレッド設定-mla 3 -amb 512: MLA(Multi-head Latent Attention)パラメータ
メモリ配置(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 |
| モデルバッファ | 578.57 GiB(13分割GGUF) |
CPU推論の実演動画
CPU推論の実演動画
実際のCPU推論の出力内容を検証するため、EPYC 9175F上でのKimi-K2.5実行風景を撮影しました。推論サーバーの標準出力に出力されるトークン生成の様子をリアルタイムで見ることで、実際の出力内容をチェックできます。
この動画では以下を確認できます:
- llama.cpp serverの起動とモデル読み込み(量子化ロード)
- Prefill(プロンプト評価)時のトークン生成速度と内容
- Token-by-tokenのGenerate(生成)フェーズの出力
- 実際の生成テキストの品質確認
結果
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 |
スレッド最適化(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 | リソース効率重視 |
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 |
Prompt Cache効果(Q4_K_S)
| 状態 | サイズ | 効果 |
|---|---|---|
| 1,260トークン保存時 | 159.5 MiB | LCP similarity > 0.5でヒット |
| キャッシュ復元 | - | 数十ms(62ms実測) |
| TTFT短縮 | - | 反復実行でprompt eval時間が激減 |
追記:ik_llama.cpp による改善(Expert CPU + Attention GPU Hybrid)
ik_llama.cppの最適化ビルドを使用し、Expert重みをCPU、Attention層をGPUに配置するHybrid構成(-ngl 999 -ot exps=CPU)で実測を取得しました。以下がその結果です。
実行コマンド
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
-ngl 999 -ot exps=CPU: Attention層をGPUにオフロードし、Expert重みはCPUに配置するHybrid構成
ベンチマーク結果(初期)
| 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(LCP)やテンプレート最適化を進めた後の実測:
| 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 | キャッシュ部分不一致 |
メトリクスの説明
| メトリクス | 意味 | 計算例 |
|---|---|---|
| PP(Prompt eval tokens) | Prefill段階で評価されたトークン数 | プロンプト長 + キャッシュ差分 |
| TG(eval tokens) | 生成段階で評価されたトークン数 | 出力トークン数 |
| N_KV | Decode完了時のKVキャッシュ総トークン数 | 蓄積されたキャッシュの深さ |
| T_PP(s) | Prefill所要時間(秒) | 入力処理時間 |
| S_PP(t/s) | Prefill速度(tokens/sec) | PP / T_PP |
| T_TG(s) | 生成所要時間(秒) | Token-by-token生成時間 |
| S_TG(t/s) | 生成速度(tokens/sec) | TG / T_TG |
評価(良くなった点 / 制限事項)
良くなった点:
- Prefill耐性の向上: Run 1, 3で100~130 t/s級のPrefill速度を実現。長いプロンプトでも高速処理
- 生成速度の安定化: S_TGは全Run で19 t/s前後で安定。Blackwell + Q4_K_Sの組み合わせで頭打ち
- キャッシュ効果: Run 2, 3での部分キャッシュヒット時も、S_PPは50~110 t/s を維持
現在の制限:
- 生成速度のボトルネック: S_TGが約19 t/s固定のため、体感の「遅い/速い」はPrefill時間と出力トークン数に左右される
- キャッシュ一貫性: Run 2, 3で “Common part does not match fully” が出現。System Promptやテンプレートの細微な変更(改行・スペース・タイムスタンプ)でキャッシュが割れる
次のステップ(優先度順)
1. キャッシュ効率の最大化 ⭐ 最高効果
- System Prompt・ツール宣言・テンプレートを完全に固定化
- 不要な動的文字列(タイムスタンプ、ランダムID、セッションマーカー)を排除
- OpenWebUI側の動的挿入をシステム化して、プロンプト構造を統一
- 効果: Run 2, 3のPrefill速度が理想値(100+ t/s)に近づく
2. パラメータ一貫性の確保
- サーバ起動時の
-n(最大生成トークン数)とOpenWebUIのmax_tokensを一致させる - 以前のログで見られた
params.n_predict=2048 slot.n_predict=128のようなズレを解消 - 無駄なバッファリングと計算を削減
3. 生成速度の構造的改善 (別の選択肢が必要)
- Kimi-K2.5 Q4_K_SのアーキテクチャではS_TGの向上が難しい
- 次候補:
- 量子化変更: Q4_K_S → IQ4_XS/IQ3_M(ik_llama.cpp推奨)
- モデルサイズ: より軽量なMoEモデルへの切り替え
- アーキテクチャ: MoE活性化パターンがGPU寄りのモデル選択
ビルド検証(SM_120対応)
Blackwell(SM 120)対応ビルドであることを確認する方法:
- 簡易確認: サーバ起動ログで以前の
compiled for: 520が出力されなければ、新ビルドが使用されている - 確実な確認: ビルドログ(cmake configure段階)で以下を確認
CMAKE_CUDA_ARCHITECTURES=120
考察
メモリ帯域とth=13の根拠
Decode速度はth=13-14で飽和する。12チャネルDDR5-6400の理論帯域は約614GB/sだが、MoEのランダムアクセスパターンでは帯域をフルに使い切れない。th=16で12.94 tok/s、th=13で11.67 tok/sと、3スレッド減でも速度低下は10%未満。 th=13で運用する意味は、残り3コアをDagster/Trino等のデータパイプラインに解放できること。推論速度の9割を維持しつつ、他プロセスとの共存が成立する。 他の多コアepycではコア増やすほど速度が上がる指摘もあった。割合的にこのバランスが歩留まりが良いくらいに思ってもらえたら。
Long Contextの現実解
16k一括Prefillに44分かかる事実は、256kコンテキストの「毎回ゼロからPrefill」が非現実的であることを示唆している。20 tok/sで256kを計算すると約3.5時間。
実運用の解は:
- ctx=16k-32kに抑える
- System Digest(8k-16k程度)を起動時に一度Prefill
- Prompt Cache(LCP similarity)で2回目以降は差分のみ処理
- 出力長は1k基本、必要時のみ2k
Decode 2.4 tok/s vs 10 tok/s
Q4_K_Sのctx=16kでは10 tok/s、Q4_K_Mのctx=32kでは2.4 tok/s。この差はコンテキスト長に起因する。32kのKVキャッシュ(4.1GB)のAttention計算がボトルネック。対話UXには厳しいが、バッチ処理なら待ち時間が問題にならない。
感想
1T級モデルをCPUで動かすこと自体は技術的に確立できた。Decode 10 tok/sは対話用途には不十分だが、Dagsterパイプラインのバッチ生成、データセット増幅、蒸留用teacher生成には十分実用範囲。
運用設計の結論: GPU(RTX PRO 6000)側で対話・高速推論(vLLM等)を回し、CPU llama.cppはth=13で常駐させてバッチ知能として使う。768GBメモリのうち523GiBをモデルに使っても、残り200GB以上でDataFrame操作やTrinoクエリが並列実行できる。
再現方法
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. 実行
上記「実施内容」セクションのコマンドを参照。llama.cppのflash-attnとprompt cache機能が必要。
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から速度を抽出。
補足ノウハウ
Prompt Cacheの設計原則
- System Digestは完全固定(改行・空白・日付差分でキャッシュが割れる)
- RAGコンテキストはsystemに混ぜず、user側に差し込む(キャッシュ維持が最優先)
Q4_K_S vs Q4_K_M
Q4_K_Mはモデルサイズが約60GB大きい(520→579GiB)。メモリに余裕があればQ4_K_Mの方が品質は高いが、速度差は大きくない。ctx=16k運用ならQ4_K_Sで十分。
推奨パラメータ(バッチ運用向け)
| パラメータ | 推奨値 | 理由 |
|---|---|---|
| ctx | 16,384-32,768 | 256kは非現実的 |
| threads | 13 | メモリ帯域飽和点、残り3コアをパイプラインに |
| ubatch | 256 | 512より安定 |
| cache-ram | 32,768 MiB | LCPヒット率の安定化 |
| output | 1,024 | 生成速度がボトルネックのため短く |

