はじめに
前回は、RTX 4070 12GBとRAM 32GiBを組み合わせ、約20.76GiBのQwen3.6-35B-A3B Q4_K_MをCPU/GPUハイブリッド推論で動かしました。
しかし、ロードできることと日常的に使えることは別問題です。
35B級MoEを12GB GPUで動かせる。
では、返事を待っていられる速度なのか。
今回の主題はllama.cppとik_llama.cppの勝敗ではありません。VRAMだけでは収まらない35B級MoEをRTX 4070で動かしたとき、最初のtokenが何秒で返り、回答が何tok/sで流れ、どれだけRAMを使うのかを確認します。
今回は、速度を1つの数字で雑に決めません。最初のtokenが返るまでの時間、回答tokenが流れる速度、長めの入力を読む速度、VRAMとRAMの使い方を分けて見ます。
モデルはVRAMに収まりません。では、会話のテンポまでRAMへ退避してしまうのか。そこを測ります。
この記事で確認すること
今回の問いは「llama.cppとik_llama.cppのどちらが勝つか」ではありません。
見たいのは次の5点です。
- 4K contextの短文chatで、最初のtokenは待てる範囲で返るか。
- 回答生成は文章を読む側が待たされる速度なのか。
- 512 tokens程度の入力処理は、backendや配置でどれだけ変わるか。
- VRAMを節約する
CPU MoEは、速度とRAMにどんな代償を出すか。 - llama.cppとik_llama.cppの違いを、勝敗ではなく構成選択としてどう読むか。
なお、第1回の4K成立試験と、今回のllama-benchによるPP 512・TG 128測定は別条件です。特にfitはbackendがbufferを含めて自動配置を選ぶため、同じ名前でもVRAM使用量が一致するとは限りません。
Chat総時間と128 tokensのTG換算も別の値です。chat smoke testは最大128 tokensを許可した短い応答であり、必ず128 tokensを生成したわけではありません。一方、TG換算は128 tokensを最後まで生成すると仮定した計算です。
なぜ35B級を12GB GPUで動かすのか
Qwen3.6-35B-A3Bは総parameter数35B、tokenごとのactive parameterは3B級のMoEモデルです。毎tokenですべてのexpertを計算するわけではありませんが、全weightには保持先が必要なため、約20.76GiBのQ4_K_M GGUFは12GB VRAMへ丸ごと収まりません。
そこで一部のweight、特にMoE expertをCPU RAMへ置き、GPUとCPUで分担します。純粋なGPU完結モデルより遅くなる代わりに、GPUのVRAM容量を超えるモデルを選択肢にできます。
総35B級は、12GBのconsumer GPUで扱うローカルモデルとしては大きい部類です。一般にparameter数の大きいモデルには、より多くの知識や表現能力を保持できる容量があります。ただし、実際の回答品質は学習データ、アーキテクチャ、量子化、用途、promptによって変わります。parameter数だけで必ず小型モデルより賢いとは断定できません。
また、本記事では精度比較を行いません。限定した日本語promptで得た回答を一般的な精度評価として扱っても、Qiita読者それぞれの用途、入力形式、system promptでは結果が変わるためです。今回は文字化けや反復などの明らかな破綻がないことだけを確認し、速度、待ち時間、VRAM、RAMに評価範囲を限定します。
「実用速度」をどう見るか
LLMの速度を単に「tok/s」と書くと、入力と出力のどちらを測ったのか分からなくなります。
| 指標 | 意味 | 体感への影響 |
|---|---|---|
| PP | Prompt Processing | 入力promptを読み終わるまでの速度 |
| TG | Token Generation | 回答tokenが流れる速度 |
| TTFT | Time To First Token | 最初のtokenが返るまでの時間 |
| Total | Total Latency | 応答が完了するまでの時間 |
短いチャットではTTFTとTGが目立ちます。長文要約やRAGでは大量の入力を読むため、PPの差が効きます。
したがって、今回は「最初の返事が早いか」「回答が滑らかに流れるか」「長い入力を待てるか」を分けて判断します。
比較環境
| 項目 | 値 |
|---|---|
| GPU | NVIDIA GeForce RTX 4070 12GB |
| CPU | Intel Core i7-14700F、20 cores / 28 threads |
| RAM / Swap | 32GiB nominal / 8GiB |
| OS | Ubuntu 24.04.4 LTS |
| CUDA toolkit | 12.8 |
| Model | Qwen3.6-35B-A3B Q4_K_M |
| GGUF size | 22,285,080,192 bytes |
| llama.cpp | c34b92235b2d6a07963f896085f9ca077ff400b4 |
| ik_llama.cpp | 5f917a64b391b7d31839845153a473a65f630458 |
モデルとbackendのビルド方法は第1回と同じです。比較中はcommit、GGUF、量子化、GPUを変更していません。
ベンチマーク条件
性能比較には両backendのllama-benchを使用しました。
PP: 512 tokens
TG: 128 tokens
repetitions: 3
warm-up: llama-bench内で実施
fit margin: 1,024MiB
fit context: 4,096
配置: fit / manual / CPU MoE
4K contextの成立試験と、このPP 512・TG 128ベンチマークは別の測定です。4K成立試験の数値と混ぜて倍率を計算してはいけません。
また、fitは各backendが自動的に配置を決めます。したがってfit比較は同じtensor配置ではなく、同じ自動配置方針を指定した結果の比較です。
Chat serverでは0.330~0.617秒で生成開始
同じ4K context、同じ日本語prompt、temperature 0、thinking無効で、OpenAI互換chat endpointへ単一requestを送りました。
| Backend | 配置 | TTFT | 総時間 | Peak VRAM |
|---|---|---|---|---|
| ik_llama.cpp | fit |
0.330秒 | 1.100秒 | 10,688MiB |
| llama.cpp | fit |
0.421秒 | 1.174秒 | 10,516MiB |
| llama.cpp | manual |
0.597秒 | 1.259秒 | 11,221MiB |
| llama.cpp | CPU MoE |
0.617秒 | 1.491秒 | 3,133MiB |
測定した4構成では、いずれも1秒未満に最初のtokenが返り、短い応答全体も1.5秒以内に完了しました。約20.76GiBのモデルをCPU/GPUへ分割していることを考えると、4K contextの短文・単一requestとしては現実的な待ち時間です。
特にCPU MoEはPeak VRAMを約3.1GiBまで抑えながら、TTFT 0.617秒、総時間1.491秒でした。GPUを画面表示や別の処理と共用したい場合でも、チャットが極端に重くなるわけではありません。
ただし、このchat結果は各構成1回のsmoke testです。厳密な性能順位を決める反復ベンチではなく、通常のchat templateを通した際に実用不能な待ち時間にならないことの確認として扱います。
回答生成は約45~61 tok/s
回答tokenを生成するTGは次の結果でした。
| 配置 | ik_llama.cpp | llama.cpp | llama / ik |
|---|---|---|---|
fit |
50.85 tok/s | 56.96 tok/s | 1.12倍 |
manual |
61.12 tok/s | 59.61 tok/s | 0.98倍 |
CPU MoE |
44.79 tok/s | 45.73 tok/s | 1.02倍 |
128 tokensを同じ速度で生成すると仮定した単純換算は次の通りです。
| Backend | 配置 | TG | 128 tokensの単純換算 |
|---|---|---|---|
| ik_llama.cpp | fit |
50.85 tok/s | 約2.52秒 |
| ik_llama.cpp | manual |
61.12 tok/s | 約2.09秒 |
| ik_llama.cpp | CPU MoE |
44.79 tok/s | 約2.86秒 |
| llama.cpp | fit |
56.96 tok/s | 約2.25秒 |
| llama.cpp | manual |
59.61 tok/s | 約2.15秒 |
| llama.cpp | CPU MoE |
45.73 tok/s | 約2.80秒 |
これはモデルロード、prompt処理、API通信を含まないTGだけの換算であり、実際の総応答時間ではありません。それでも、回答が数秒に1 tokenしか出ないような速度ではなく、文章を読む速度より速くtokenが流れる領域です。
manualではik_llama.cppが61.12 tok/s、llama.cppが59.61 tok/sでしたが、差は約2.5%です。今回の条件ではTGにbackend間の決定的な差はなく、どちらも実用的な生成速度でした。
CPU MoEでも両backendが約45 tok/sを維持しています。VRAM節約の代償はありますが、短文チャットが成立しなくなるほどの低下ではありません。
入力処理は構成によって約0.6~3.1秒
512 tokensのpromptを処理するPPは、TGよりbackend差が大きくなりました。
| 配置 | ik_llama.cpp | llama.cpp | llama / ik |
|---|---|---|---|
fit |
338.92 tok/s | 800.25 tok/s | 2.36倍 |
manual |
335.02 tok/s | 812.65 tok/s | 2.43倍 |
CPU MoE |
164.71 tok/s | 503.30 tok/s | 3.06倍 |
512 tokensを処理する時間へ単純換算すると次のようになります。
| Backend | 配置 | PP | 512 tokensの単純換算 |
|---|---|---|---|
| ik_llama.cpp | fit |
338.92 tok/s | 約1.51秒 |
| ik_llama.cpp | manual |
335.02 tok/s | 約1.53秒 |
| ik_llama.cpp | CPU MoE |
164.71 tok/s | 約3.11秒 |
| llama.cpp | fit |
800.25 tok/s | 約0.64秒 |
| llama.cpp | manual |
812.65 tok/s | 約0.63秒 |
| llama.cpp | CPU MoE |
503.30 tok/s | 約1.02秒 |
短い質問なら、いずれも待てる範囲です。一方、入力が数千tokensになるRAGや長文要約ではこの差が積み上がるため、llama.cppのPP性能が効いてきます。
今回の固定commitとQ4_K_Mでは、llama.cppがik_llama.cppよりPPで2.36~3.06倍高速でした。ik_llama.cppに独自機能や追加最適化があることと、すべての処理でmainlineより速いことは同義ではありません。
入力を読む速度では大差、返事を書く速度ではほぼ互角でした。llama.cppは速読家ですが、筆速は同じくらいです。
VRAMを約3.1GiBまで減らせる
性能比較時のPeak VRAMとMax RSSは次の通りです。
| Backend | 配置 | Peak VRAM | Max RSS |
|---|---|---|---|
| ik_llama.cpp | fit |
5,123MiB | 17,616MiB |
| ik_llama.cpp | manual |
10,577MiB | 12,166MiB |
| ik_llama.cpp | CPU MoE |
3,151MiB | 19,218MiB |
| llama.cpp | fit |
10,832MiB | 21,833MiB |
| llama.cpp | manual |
11,353MiB | 21,601MiB |
| llama.cpp | CPU MoE |
3,099MiB | 21,600MiB |
CPU MoEでは両backendともPeak VRAMが約3.1GiBまで下がりました。35B級MoEを動かしながらGPUメモリを約9GiB残せるため、画面表示、画像生成、音声処理などとの共存に意味があります。
一方、Max RSSは約19~22GiBです。VRAMから移動したweightは消えず、CPU RAM側で待機します。
VRAMの請求書をRAMへ転送しただけですが、支払先を選べること自体がCPU/GPUハイブリッド推論の利点です。
fitのVRAM量がbackend間で大きく違うのは、各backendが選んだ自動配置が異なるためです。VRAM使用量だけを見て一方が効率的と断定することはできず、PPやTGも合わせて判断する必要があります。
なお、Max RSSは/usr/bin/time -vが報告したprocessの最大常駐メモリです。/proc/meminfoから計算したPC全体のsystem RAM使用量とは定義が異なるため、同じ列には混ぜていません。
配置ごとの実用上の選び方
| 配置 | 利点 | 欠点 | 向く用途 |
|---|---|---|---|
fit |
指定が簡単、8Kでも成立 | backendやbuffer条件で配置が変わる | まず動かす、通常利用 |
manual |
tensor配置を固定でき、TGが約60 tok/s | モデル構造の理解が必要、8Kでswap発生 | 4K固定の再現実験 |
CPU MoE |
VRAMを約3.1GiBまで削減 | RAM消費とPP低下 | GPUを他処理と共用 |
fit
まず35B級MoEを使うならfitが素直です。配置をbackendへ任せられ、前回の試験では4Kだけでなく8K contextもswap増加なしで動作しました。
ただし、自動配置はcontext、buffer、backendによって変わります。同じfitという名前でも、tensorが完全に同じ場所へ置かれるとは限りません。
manual
配置を再現したい場合はmanualです。今回の4Kでは両backendとも約60 tok/sで生成できました。
一方、前回の8K試験では約11.16GiBのpinned host memoryが確保され、swapが4,176MiB増加しました。配置を固定できることと、固定した配置が常に安全であることは別です。
CPU MoE
VRAMを他用途へ残すならCPU MoEです。Peak VRAMは約3.1GiBまで下がり、TGも約45 tok/sを維持しました。
代わりにRAM使用量が増え、PPは低下します。RTX 4070をLLM専用にするならfitやmanual、別のGPU処理と共存させるならCPU MoEという使い分けになります。
llama.cppとik_llama.cppの比較は構成選択の材料
今回の主題は35B級MoEの実用性ですが、現実的な速度を得るためにはbackendの特性も無視できません。
| 要件 | 選択候補 | 理由 |
|---|---|---|
| 長いprompt、RAG、長文要約 | llama.cpp |
PPが2.36~3.06倍高速 |
| 短文チャット | 両backend | TGは両backend、TTFTは測定した4構成で実用範囲 |
| 4Kで配置を固定 | 両方 | manualのTGは約60 tok/s |
| VRAM最小化 | 両backendのCPU MoE | Peak VRAM約3.1GiB |
| 追加量子化や配置実験 | ik_llama.cpp |
実験的なquantとtensor制御を試しやすい |
| 今回のQ4_K_Mを通常利用 | まずllama.cpp
|
PPが速くTGも同等 |
llama.cppは今回のQ4_K_MでPPが明確に高速でした。通常利用、特に長い入力を扱うなら有力です。
ik_llama.cppの価値は、今回のベンチマークで必ず最速になることではありません。12GB GPUでは収まらない35B級MoEについて、fit、CPU MoE、tensor overrideなどを使い、どのweightをVRAMへ置き、どこをRAMへ逃がすかを検証できることにあります。
道具としての価値と、特定条件の速度順位は分けて考える必要があります。
小型軽量モデルとの比較は補助情報
全weightが12GB VRAMへ収まる小型軽量モデルは、CPU RAMへのoffloadが不要なため、一般には今回の35B級ハイブリッド構成より高速になります。
今後、小型モデルと比較する目的は「どちらが優れたモデルか」を決めることではありません。GPU内で完結する軽量モデルを速度の基準とし、35B級MoEを選んだ場合にどれだけ速度を支払い、それでも実用範囲に残るかを確認するためです。
比較する場合も、見るのはTG、PP、TTFT、VRAM、RAMです。回答精度は用途とpromptへの依存が大きいため、本記事の速度表へ混ぜません。
小型モデルに速度で負けることは問題ではありません。重要なのは、より大きなモデルを選べる代わりに生じる速度低下が、チャットとして許容できる範囲かどうかです。今回の約45~61 tok/sと、chat smoke testで測定した4構成のTTFT 0.330~0.617秒は、少なくとも4K contextの短文・単一requestでは肯定的な結果でした。
測定コマンド
共通変数を設定します。
サンプルコード(共通変数)
MODEL="<Qwen3.6-35B-A3B Q4_K_M GGUF>"
IK_BENCH="<ik_llama.cppのllama-bench>"
LLAMA_BENCH="<llama.cppのllama-bench>"
MANUAL_PATTERN='blk[.](1[6-9]|[23][0-9])[.]ffn_.*_exps.*=CPU'
GPU使用量とMax RSSを同時に残すため、次のshell functionから各コマンドを実行します。
サンプルコード(ベンチ実行関数)
bench() {
name="$1"
shift
gpu_log="$(mktemp)"
bench_json="$(mktemp)"
stderr_log="$(mktemp)"
nvidia-smi \
--query-gpu=timestamp,memory.used,memory.free,utilization.gpu,temperature.gpu,power.draw \
--format=csv \
-lms 500 > "${gpu_log}" &
monitor_pid=$!
set +e
/usr/bin/time -v timeout --signal=TERM --kill-after=30s 15m \
"$@" \
> "${bench_json}" \
2> "${stderr_log}"
status=$?
set -e
kill "${monitor_pid}" 2>/dev/null || true
wait "${monitor_pid}" 2>/dev/null || true
return "${status}"
}
ik_llama.cpp
自動配置:
サンプルコード(ik_llama.cpp fit)
bench ik-fit \
"${IK_BENCH}" \
-m "${MODEL}" \
-p 512 \
-n 128 \
-r 3 \
-o json \
--fit 1 \
--fit-margin 1024
expert層16~39をCPUへ固定:
サンプルコード(ik_llama.cpp manual)
bench ik-manual \
"${IK_BENCH}" \
-m "${MODEL}" \
-p 512 \
-n 128 \
-r 3 \
-o json \
-ngl 999 \
-ot "${MANUAL_PATTERN}"
MoE expertをCPUへ配置:
サンプルコード(ik_llama.cpp CPU MoE)
bench ik-cpu-moe \
"${IK_BENCH}" \
-m "${MODEL}" \
-p 512 \
-n 128 \
-r 3 \
-o json \
-ngl 999 \
--n-cpu-moe 999
llama.cpp
自動配置:
サンプルコード(llama.cpp fit)
bench llama-fit \
"${LLAMA_BENCH}" \
-m "${MODEL}" \
-p 512 \
-n 128 \
-r 3 \
-o json \
-fitt 1024 \
-fitc 4096
expert層16~39をCPUへ固定:
サンプルコード(llama.cpp manual)
bench llama-manual \
"${LLAMA_BENCH}" \
-m "${MODEL}" \
-p 512 \
-n 128 \
-r 3 \
-o json \
-ngl 999 \
-ot "${MANUAL_PATTERN}"
MoE expertをCPUへ配置:
サンプルコード(llama.cpp CPU MoE)
bench llama-cpu-moe \
"${LLAMA_BENCH}" \
-m "${MODEL}" \
-p 512 \
-n 128 \
-r 3 \
-o json \
-ngl 999 \
-ncmoe 999
測定で注意したこと
PP 512と4K contextは別条件
速度比較はPP 512・TG 128です。4K・8Kはモデルを保持して生成できるかを見る成立試験です。
PP 3,968 tokensのsynthetic benchmarkも試しましたが、自動配置が変化し、swap安全弁が作動しました。長いPPを測るときは、単にtoken数だけでなく配置条件まで変わる可能性があります。
単純換算時間はAPIの総時間ではない
PPとTGから算出した秒数は、token数をtok/sで割った単純換算です。モデルロード、chat template、sampling、API通信などを含む実測の総応答時間ではありません。
実際の体感確認には、前半で示したchat serverのTTFTと総時間を使っています。
モデルが返答しても常用可能とは限らない
8K manualは生成と速度計測まで完了しましたが、swapが4GiB以上増えました。今回の判定では、出力が返ることと安全に使えることを分けています。
一番大きく生成されたのが回答ではなくログファイル、という試行もありました。測定にはstdout上限も必要です。
まとめ
今回の主な結論は次の5点です。
- RTX 4070 12GBでも、約20.76GiBのQwen3.6-35B-A3B Q4_K_MをRAM併用し、少なくとも4Kの短文・単一requestで現実的な速度にできた。
- Chat smoke testで測定した4構成では、4Kの短文・単一requestでTTFT 0.330~0.617秒、総時間1.100~1.491秒だった。
- TGは約45~61 tok/sで、128 tokensは単純換算で約2.1~2.9秒だった。
- CPU MoEではPeak VRAMを約3.1GiBまで減らせたが、RAM消費とPP低下を伴った。
- llama.cppはPPが2.36~3.06倍高速で、ik_llama.cppは大規模MoEの配置や量子化を検証する道具として価値がある。
小型軽量モデルのほうが速いことは、この結果の反証ではありません。今回確認したかったのは、12GB VRAMへ収まらない35B級MoEを選んでも、会話として使える速度を維持できるかです。
その答えは、少なくとも4Kの短文チャットではyesでした。
RTX 4070は35B級MoEをVRAMだけでは抱えられません。しかし、RAMに席を用意すれば、測定した4構成では最初の返事が1秒以内に始まり、llama-benchの6構成では最大約61 tok/sで生成できました。
35B級をローカルモデルの選択肢へ入れられることが、今回の一番大きな成果です。
今後の検証
- 全weightが12GB VRAMへ収まる小型軽量モデルとの同条件速度比較
- RAM 48GiB・64GiBで8K
manualとCPU MoEを再測定 - MTPあり・なし
- 16K以上のcontext
- 並列request
- 用途別の品質評価は、評価データと採点基準を別途定義して実施



