背景

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推論が物理的に成立する。問題は「成立する」と「使える」の間にある速度差で、それを埋めるのがこの検証の目的。

目的

  1. Q4_K_S量子化でのCPU推論速度をベンチマーク(基本性能)
  2. スレッド数とスループットの関係を測定し、最適スレッド数を特定
  3. Q4_K_Mでの32kコンテキスト運用時のPrefill/Decode速度を計測
  4. Prompt Cache(LCP similarity)の実効性を検証
  5. Dagsterパイプライン用途として実用に足るか判定

実験環境

項目仕様
CPUAMD EPYC 9175F(Zen 5, 16C, L3 512MB)
メモリDDR5-6400 768GB(12ch)
GPUNVIDIA RTX PRO 6000 Blackwell Max-Q 96GB
OSUbuntu 24.04 LTS
Runtimellama.cpp server(Podman rootless)

モデル仕様

項目Q4_K_SQ4_K_M
アーキテクチャdeepseek2(MoE + MLA)同左
総パラメータ1.03T同左
レイヤー数61同左
エキスパート数384(活性8)同左
量子化Q4_K_SQ4_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 buffer348 MiB
Total RSS約523 GiB / 755 GiB
Swap使用799 MiB(si/so発生なし)

メモリ配置(Q4_K_M / ctx=32k)

領域サイズ
KV cache4,148 MiB(K: 2,196 / V: 1,952)
CPU compute buffer348 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(キャッシュなし)82322.2443810.2779.7
2nd(キャッシュ保存)1,33519.981,0128.76115.6
3rd(LCPヒット)----cache lookup 62ms

スレッド最適化(ctx=8k)

スレッド数PP速度(tok/s)TG速度(tok/s)評価
1624.4312.94最大出力(基準)
1421.3212.50帯域飽和の開始点
1321.5811.67スイートスポット
1214.5811.86リソース効率重視

Q4_K_M Long Context実測(th=13, ctx=32k)

リクエストPrompt(tok)PP速度(tok/s)Gen(tok)TG速度(tok/s)備考
1st16,1486.153332.4416k一括Prefill、約44分
2nd(LCP 0.978)3563.402,0482.26キャッシュヒット、差分のみPrefill
3rd(LCP 0.999)123.111,0242.15ほぼ全量キャッシュ復元
4th(LCP 0.939)1,0503.211,0242.07部分キャッシュ + 差分Prefill

Prompt Cache効果(Q4_K_S)

状態サイズ効果
1,260トークン保存時159.5 MiBLCP 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構成

ベンチマーク結果(初期)

TaskPP(tok)TG(tok)N_KV(tok)T_PP(s)S_PP(t/s)T_TG(s)S_TG(t/s)
05,2647446,00759.59688.3337.81519.67
7477652596,28713.27757.6213.16419.68
1,0072791,0247,3316.24344.6952.45219.52
2,0321,0371,0248,36816.77261.8351.79319.77
3,0571,0413108,69516.63762.5716.12419.23
平均----63.0-19.6

後続実測

Prompt Cache(LCP)やテンプレート最適化を進めた後の実測:

RunPP(tok)TG(tok)N_KV(tok)T_PP(s)S_PP(t/s)T_TG(s)S_TG(t/s)備考
15,3304015,73041.298129.0620.45819.60新規リクエスト
24162,2417,9868.36349.75114.55219.56キャッシュ部分不一致
32,2559198,91920.631109.3048.05619.12キャッシュ部分不一致

メトリクスの説明

メトリクス意味計算例
PP(Prompt eval tokens)Prefill段階で評価されたトークン数プロンプト長 + キャッシュ差分
TG(eval tokens)生成段階で評価されたトークン数出力トークン数
N_KVDecode完了時の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)対応ビルドであることを確認する方法:

  1. 簡易確認: サーバ起動ログで以前の compiled for: 520 が出力されなければ、新ビルドが使用されている
  2. 確実な確認: ビルドログ(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 timeeval 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で十分。

推奨パラメータ(バッチ運用向け)

パラメータ推奨値理由
ctx16,384-32,768256kは非現実的
threads13メモリ帯域飽和点、残り3コアをパイプラインに
ubatch256512より安定
cache-ram32,768 MiBLCPヒット率の安定化
output1,024生成速度がボトルネックのため短く