← ブログに戻る

RTX 4070 で Qwen 35B が 34.6 tok/s になった 2 フラグ実測

結論から書きます。RTX 4070 (12GB) 上で Qwen3.5-35B-A3B (Q4_K_M) を動かすとき、-ngl 99--cpu-moe の 2 つのフラグを両方入れることで、生成速度は 12.2 tok/s → 34.6 tok/s になりました。約 2.8 倍です。片方だけでは、この数字は出ません。

以前書いた記事では --cpu-moe 1 つを紹介しましたが、あの記事は「勝ち構成の入口だけ」を扱っていました。今回は 2 フラグの分解実測と、KV 量子化のトレードオフ、そして「速さは賢さを削っていないのか」の 7 問チェックを、まとめて残します。

なぜ 1 フラグでは足りないのか

-ngl 99 は「全レイヤーを GPU に載せろ」の意思表示です。48 層の実数を超える 99 を渡すのは、単に「全部」と言い切るための慣用句です。ただし、これだけを渡すと 12GB の VRAM に 20.49 GiB のモデルが載るわけがなく、実装は勝手にオフロード判断をします。ここが問題でした。

一方、--cpu-moe は「MoE のエキスパートテンソルだけは CPU に逃がす」という例外指定です。単独で使うと -ngl の既定値に引きずられて、そもそも GPU オフロードが十分に働きません。

2 つを セットで 入れて、はじめて次の役割分担が成立します。

  • GPU 側: アテンションと KV キャッシュ (メモリ帯域が効く部分)
  • CPU 側: MoE エキスパート (計算が疎で、CPU 帯域でも捌ける部分)

これが「勝ち構成」の物理的な意味です。

-ngl 99 と --cpu-moe を組み合わせたときの役割分担と速度

実測 1: フラグの組み合わせで速度がどう動くか

計測環境は RTX 4070 (12GB)、RAM 31GB、WSL2 Ubuntu 24.04、CUDA 12.9。モデルは Qwen3.5-35B-A3B の Q4_K_M 量子化 (20.49 GiB) です。llama.cpp を CUDA 有効でビルドし、llama-bench で tg128 (生成 128 トークン) を 3 試行しました。

第 2 章のスイープ表を再掲します。ここでは -ngl 99 を固定し、CPU に置くエキスパート層の数 n_cpu_moe を動かしています。n_cpu_moe=48--cpu-moe (全 CPU) と等価です。

n_cpu_moeGPU 側のエキスパート層生成速度 tg128 (tok/s)ベースライン比
480 (全 CPU)34.602.8 倍
44427.192.2 倍
40816.881.4 倍
361215.291.3 倍
321614.061.2 倍
282012.851.1 倍
242411.710.96 倍

きれいな単調変化です。エキスパートを 1 層でも GPU に戻すと、そのぶんアテンションと KV の帯域予算が削られ、速度は落ちます。24 層まで戻すと、もう Ollama の自動設定 (12.2 tok/s) 以下です。

つまり、直感的な「GPU に載るだけ載せる」は、MoE ではちょうど遅い側に倒れます。私はこれを最初に見たとき、しばらく画面の前で首をかしげました。

実測 2: KV 量子化のトレードオフを 3 段階で

-c 4096 の設定ではまだ VRAM に約 600 MiB の余白が残っていますが、文脈を 32768 まで伸ばそうとすると即座に天井にぶつかります。KV キャッシュが単純計算で 8 倍になるからです。

ここで効くのが KV キャッシュ側の量子化です。-ctk-ctv を指定します。

llama-server -m qwen35.gguf -ngl 99 --cpu-moe -c 32768 \
  -ctk q8_0 -ctv q8_0

f16 (16bit)、q8_0 (8bit)、q4_0 (4bit) の 3 段階を、標準 7 問 (後述) に通した結果がこれです。

KV 型VRAM 使用生成速度 tg128 (tok/s)7 問スコア備考
f1611.7 GiB34.67/7既定、-c 4096 が限界
q8_011.0 GiB34.17/7-c 32768 まで安全に伸ばせる
q4_010.4 GiB33.96/77 問目 (バット・ボール) で誤答

q8_0 なら、速度はほぼ据え置きのまま文脈だけを 8 倍に伸ばせます。これは「重みは量子化するけど KV は触らない」と身構えていた自分にとって、ちょっと悔しい交換でした。

一方 q4_0 は、速度こそ据え置きですが、標準 7 問のうち推論問題 (バットとボールの引っかけ) で 5 セントではなく 10 セントを返しました。VRAM は最も節約できますが、そのぶん「賢さ」を薄く削ります。エージェント用途で長文脈と精度を両立するなら q8_0 が現状の妥協点です。

KV 量子化 3 段階の VRAM・速度・品質

標準 7 問のたねあかし

「7 問スコア」の中身も残しておきます。日本語と英語、知識と推論、自然言語とコードを混ぜた 7 問セットです。

  • 東京の人口 (日本語・短答)
  • WebRTC と WebSocket の違いを 3 点 (日本語・技術説明)
  • 機械学習とディープラーニングの違いを 2 文で (日本語・比較)
  • What is the capital of France? (英語・短答)
  • クイックソートの計算量 (英語・技術短答)
  • sort を使わずに上位 3 つの最大値 (コード生成)
  • バットとボールで 1 ドル 10 セント、バットはボールより 1 ドル高い、ボールはいくら? (論理推論)

最後の 1 問は認知バイアスを突く有名な問題で、直感で 10 セントと答えたくなります。正解は 5 セントです。KV q8_0 までは、モデルが「直感では 10 セントと答えたくなるが、式を立てると 2x+1=1.10、よって x=0.05」と自分で否定してくれました。私が初めてこの問題を見たときは普通に 10 セントと答えたので、モデルに一敗です。

再現手順 (2 フラグを両方入れる)

私の環境で 34.6 tok/s を再現したコマンドはこの 2 つです。

# ベンチ (llama-bench)
./build/bin/llama-bench -m qwen35.gguf -ngl 99 --cpu-moe -n 128 -r 3

# サーバとして常駐 (llama-server)
./build/bin/llama-server -m qwen35.gguf -ngl 99 --cpu-moe -c 4096

長文脈まで踏み込むなら、-c 32768 -ctk q8_0 -ctv q8_0 を足してください。Qwen Code CLI のような重めのエージェントは、初回リクエストだけで約 19,000 トークンを消費するので、この設定がないと動きません。

雑にコピーして 34.6 tok/s が出ないケースは、たいてい次のいずれかです。

  • VRAM が本当は空いていない (Windows 側の常駐プロセスが握っているとよく起きます)
  • 量子化が違う (Q4_K_M 以外だと VRAM の使い方がずれます)
  • CUDA ビルドしていない (-DGGML_CUDA=ON を忘れると CPU のみで動きます)
  • llama.cpp の古いビルドで --cpu-moe が未実装 (2026 年 3 月以降のビルドを推奨)

まとめ: 2 フラグと 1 組の量子化指定で全部

RTX 4070 で 35B の MoE モデルを実用速度で動かすうえで、重要なのは 2 フラグと 1 組の量子化指定だけでした。

  • -ngl 99--cpu-moeセットで 入れる (片方だけでは意味が薄い)
  • 長文脈が必要なら -ctk q8_0 -ctv q8_0 を足す (速度ほぼ据え置き、7 問品質も維持)
  • q4_0 は VRAM が最も浮くが、推論問題で薄く賢さを失う (エージェント用途では避ける)

12GB の VRAM を「ちょうど 95% まで使い切る」構成に落とし込めれば、家庭用 GPU で 35B は無理ではなくなります。VRAM は残り 600 MiB。もう少しだけ、余白の使い方を工夫する余地はあります。そこから先は、書籍側で全 10 章にわたって扱いました。


もっと深く追いたい方へ。Ollama から llama.cpp への切り替え、KV 量子化の全パターン計測、エージェント CLI (Qwen Code / claude-code) との相性、Qwen3.5 と 3.6 の世代差ベンチまで通しで扱ったのが RTX 4070 で動かす 35B ローカル LLM です。