0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

EVO-X2(Ryzen AI Max+ 395 / 128GB)でQwen3.8-Flash-Nextを高速化した話。n_cpu_moeを調整したらprefillが速くなったけど罠だった

0
Posted at

前回、「EVO-X2(Ryzen AI Max+ 395 / 128GB)でQwen3.8-Flash-Nextを動かした話」で、Qwen3.8-Flash-Nextを動かして日本語で会話できるところまで確認した。

今回はパラメータ調整でどこまで高速化できるかを試してみた。個人的にローカルLLMで一番よく使うのが長文の要約と、長文に関する質疑応答、2番目によく使うのがコーディングなので、主にこの2つの用途を想定して調整した。

以降、Prompt Processing(入力を読み込む処理)をPP、Token Generation(出力生成)をTGと表記する。

結論

  • Qwen3.8-Flash-Next UD-IQ3_XXSは、EVO-X2で262,144 contextを確保し、255,077 tokensの実入力まで正常に処理できた
  • n_cpu_moe=8にすると、llama-benchでは4K PPが約+37%、128K PPが約+34%まで高速化した
  • ところが実文書入力では、5,337 tokensと126,149 tokensの入力で出力がおかしくなった

つまり、「ベンチマーク上ではかなり速くなった設定が、実データではそのまま使えなかった」という話。

まずはllama-cliで長文コンテキストで動くのかをチェック

以下のコマンドでコンテキスト長 $c を 32768, 65536, 98304, 131072, 262144 に変えて、ちゃんと答えが返ってくるか、メモリ不足で落ちないかをチェックした。

$c = 32768
$Stamp = Get-Date -Format "yyyyMMdd-HHmmss"
.\llama-cli.exe `
  -m "(モデルのパス)\Qwen3.8-Flash-Next-UD-IQ3_XXS-00001-of-00003.gguf" `
  -c $c `
  -ngl 999 `
  -n 128 `
  -lv 4 `
  --jinja `
  --single-turn `
  --reasoning off `
  -p "次の文を一文で言い換えてください。ローカルLLMの長文コンテキスト動作を確認している。" `
  2>&1 | Tee-Object -FilePath "(ログのパス)\$Stamp-llama-cli-ctx-$c.log"

結果は以下の通り。ここでは入力が35トークンしかないので、速度を比較するというより「そのコンテキスト長を確保して起動・生成できるか」のチェックになる。

context 通常KV cache indexer KV cache PP TG 出力結果
32K (32,768) 768 MiB 288 MiB 76.61 tok/s 26.54 tok/s 正常
64K (65,536) 1,536 MiB 576 MiB 76.61 tok/s 26.22 tok/s 正常
96K (98,304) 2,304 MiB 864 MiB 78.17 tok/s 26.45 tok/s 正常
128K (131,072) 3,072 MiB 1,152 MiB 77.59 tok/s 26.53 tok/s 正常
256K (262,144) 6,144 MiB 2,304 MiB 77.59 tok/s 26.35 tok/s 正常

短い入力では、コンテキスト長を256KにしてもTGはほぼ変わらない。少なくとも最大262,144 contextを確保して起動すること自体は可能だった。

余談だが、PowerShell 5.1でllama-cliに日本語プロンプトをそのまま書くと文字化けが発生した。解決方法などは付録に書いた。

次はllama-cliで長文の日本語文書を読み込ませてみる

実際の日本語の論文(詳細は付録に記載)の一部を切り出したもの(9,406字と14,874字)を複数回繰り返して、先頭に

以下は日本語の技術文書の連続入力です。
本文を最後まで読み込んでください。

末尾に

以上が入力文書です。
内容は要約せず、最後に「受信確認: BLOCK B-001」だけを出力してください。

を追加して、各コンテキスト長に収まる日本語プロンプトファイルを作った。実際の入力トークン数はcontext上限より約3K〜7K少ない値になっている。

$c = 32768
$InputFile = (各コンテキスト長に合わせて作成した日本語文書プロンプトファイルのパス)
$Stamp = Get-Date -Format "yyyyMMdd-HHmmss"
.\llama-cli.exe `
  -m "(モデルのパス)\Qwen3.8-Flash-Next-UD-IQ3_XXS-00001-of-00003.gguf" `
  -c $c `
  -ngl 999 `
  -n 128 `
  -lv 4 `
  --jinja `
  --single-turn `
  --reasoning off `
  -f $InputFile `
  2>&1 | Tee-Object -FilePath "(ログのパス)\$Stamp-llama-cli-ctx-$c-long-input.log"

結果はこうなった。

context 実入力 PP prefill時間 TG 出力結果
32K 29,453 tokens 181.23 tok/s 162.5秒(約2分43秒) 12.82 tok/s 正常
64K 61,685 tokens 149.93 tok/s 411.4秒(約6分51秒) 9.20 tok/s 正常
96K 93,917 tokens 129.81 tok/s 723.5秒(約12分04秒) 7.33 tok/s 正常
128K 126,149 tokens 114.72 tok/s 1,099.7秒(約18分20秒) 6.04 tok/s 正常
256K 255,077 tokens 75.50 tok/s 3,378.5秒(約56分18秒) 3.62 tok/s 正常

すべて truncated = 0 で、最後に期待した 受信確認: BLOCK B-001 が返ってきた。

ここで一気に印象が変わった。短いsmoke testではcontextを増やしてもTGはほぼ一定だったのに、実際に長文を埋めるとPPもTGもかなり落ちる。128KでTG 6 tok/s程度、256Kでは3.6 tok/s程度なので、この速度だと長文のリアルタイム処理はかなり待たされる。

Windows側のリソースをpowershellスクリプトを作って監視してみたら、こんな傾向だった。値は実行中サンプルの代表値。

context Windows使用RAM Commit使用率 Adapter Dedicated Adapter Shared Adapter合計
64K 35.0 GiB 88.4% 53.1 GiB 1.0 GiB 54.2 GiB
96K 34.9 GiB 89.7% 54.3 GiB 1.1 GiB 55.5 GiB
128K 35.1 GiB 91.0% 55.5 GiB 1.2 GiB 56.8 GiB
256K 35.5 GiB 96.1% 60.3 GiB 1.7 GiB 62.0 GiB

256KではWindowsのCommit使用率が最大 96.7% まで上がった。物理RAMの「使用中」だけを見ると35GB前後で大きく変わっていないが、Commitにはかなり余裕がなくなっている。つまり「256Kまで動いた」ことと「256Kを余裕を持って常用できる」ことは別物だった。

EVO-X2はUMAなので、Windowsタスクマネージャーの「Dedicated / Shared GPU memory」は物理的に別のVRAMチップがあるという意味ではなく、Windows側のメモリ区分として見るのがよさそう。

96K推論中に撮ったタスクマネージャーでは、だいたい次の状態だった。

  • CPU: 約39〜42%、4.15GHz前後
  • メモリ: 34.9 / 63.6GB(55%)
  • Commit: 93 / 104GB
  • GPU Compute 0: 約81%
  • 専用GPUメモリ: 54.3 / 63.8GB
  • 共有GPUメモリ: 1.1 / 47.6GB
  • GPU温度: 74〜76℃

スクリーンショット 2026-08-30 144756-llama-cli-c96k推論中.png

スクリーンショット 2026-08-30 144802-llama-cli-c96k推論中.png

スクリーンショット 2026-08-30 144806-llama-cli-c96k推論中.png

llama-benchでパラメータを変えて速度を測ってみる

長文入力がかなり遅いので、llama-benchでどこまで改善できるかを試した。

今回触ったパラメータをざっくり書くと、

  • ncmoe (n_cpu_moe): MoE層のうちCPU側に置く層数
  • t (threads): CPU処理に使うスレッド数
  • b (batch): prompt処理時の論理batch size
  • ub (ubatch): batchを実際に処理するときのmicro-batch size
  • ctk: KV cacheのKey側のデータ型
  • ctv: KV cacheのValue側のデータ型

固定した主な条件は、

model: Qwen3.8-Flash-Next-UD-IQ3_XXS-00001-of-00003.gguf
device: ROCm0
-ngl 999
-fa 1
-r 1 または 3

測定用パラメータは、

  • p: prompt processing(PP)で処理するトークン数
  • n: token generation(TG)で生成するトークン数
  • d: TG測定前に入れておくcontextの深さ

と理解して使った。

最初の n_cpu_moe 探索で使ったコマンドはこんな感じ。llama-bench はカンマ区切りで複数値を渡せるので、この場合は ncmoe の6条件を順番に測定している。

.\llama-bench.exe `
  -m $Model `
  -dev ROCm0 `
  -ngl 999 `
  -ncmoe 0,8,16,24,32,48 `
  -t 16 `
  -b 2048 `
  -ub 512 `
  -fa 1 `
  -p 4096 `
  -n 128 `
  -r 3 `
  -o jsonl

常用を想定しているcontextは128Kだが、いきなり128Kで何度も回すと時間がかかるので、まずは p=4096 / n=128 で探索した。

まず n_cpu_moe を振る

最初は t=16 / b=2048 / ub=512 / F16 KVncmoe を変えた。

ncmoe PP 4096 TG 128 PP差 vs ncmoe=0 コメント
0 298.35 ± 2.40 27.26 ± 0.07 基準 全MoEをGPU側に置く基準
8 449.22 ± 0.43 26.78 ± 0.10 +50.6% かなり速い
16 448.77 ± 1.33 26.54 ± 0.08 +50.4% 8とPPはほぼ同じ
24 328.58 ± 8.50 25.23 ± 0.33 +10.1% 効果が薄い
32 245.81 ± 5.59 24.08 ± 0.10 -17.6% 逆に遅い
48 199.07 ※ 22.58 ※ -33.3% CPUに寄せすぎ

8〜16層だけCPU側へ回すと、PPが約1.5倍という予想以上の伸びになった。一方、さらにCPU側へ寄せると急速に悪化したので、この環境では「CPU offloadは多いほど良い」ではなかった。

リソース監視結果を見ると、ncmoeを増やしたときのメモリの動きもかなり分かりやすい。

ncmoe Windows使用RAM(中央値) Adapter Dedicated Adapter Shared Adapter合計 備考
0 34.9 GiB 51.0 GiB 0.8 GiB 52.0 GiB 基準
8 40.3 GiB 42.3 GiB 8.4 GiB 50.9 GiB RAM側へ移動
16 47.9 GiB 34.8 GiB 15.9 GiB 50.9 GiB さらにRAM使用増
48 55.6 GiB 4.7 GiB 45.9 GiB 50.8 GiB RAMは最大63.6GiB付近まで到達

Adapter合計は大きく変わらないのに、DedicatedからSharedへ大きく移り、Windows使用RAMも増えている。UMAなのでこのDedicated/Sharedの内訳だけで実際のtensor配置を断定はできないが、少なくともncmoeを増やすほどメモリ圧力がCPU/RAM側へ移る傾向は確認できた。

threadを振る

次に ncmoe=8 / b=2048 / ub=512 を固定してthread数を変えた。

threads PP 4096 TG 128
2 454.77 ± 1.66 26.84 ± 0.04
4 489.87 ± 2.15 26.78 ± 0.06
8 474.31 ± 0.69 26.77 ± 0.06
16 448.71 ± 0.42 26.74 ± 0.08

このモデル・設定では t=4 がPP最速だった。16coreだから16threadが速い、という単純な話ではなかった。TGはあまり変わらない。

batch / ubatchを振る

次は ncmoe=8 / t=4 を固定。

batch ubatch PP 4096 TG 128
2048 512 485.27 ± 0.87 26.66 ± 0.06
2048 1024 504.39 ± 1.24 26.77 ± 0.06
2048 2048 490.72 ± 1.12 26.80 ± 0.04
4096 512 486.99 ± 1.61 26.79 ± 0.05
4096 1024 501.49 ± 1.49 26.76 ± 0.06
4096 2048 490.40 ± 0.75 26.78 ± 0.05

ここでは b=2048 / ub=1024 がPP最速。ここでも。TGはあまり変わらない。

条件を揃えて ncmoe=0〜8 を再比較

t=4 / b=2048 / ub=1024 に揃えて、ncmoeをもう一度比較した。

ncmoe PP 4096 PP差 vs 0 TG 128 TG差 vs 0
0 367.85 ± 1.27 基準 27.25 ± 0.04 基準
1 371.75 ± 2.20 +1.1% 27.18 ± 0.04 -0.2%
2 368.35 ± 0.98 +0.1% 27.13 ± 0.02 -0.4%
4 370.97 ± 2.76 +0.8% 26.92 ± 0.05 -1.2%
8 504.39 ± 1.24 +37.1% 26.77 ± 0.06 -1.8%

ncmoe=1〜4の差は誤差と実行間変動を考えると実用上ほぼ同じ。ncmoe=8だけPPがとても速い。

この段階のパラメータの最有力候補は、

ncmoe 8
t 4
b 2048
ub 1024
ctk f16
ctv f16
fa 1
ngl 999

となった。

128Kでも測ってみる

次に、本命の128K付近で確認する。

PPは p=131072、TGは実データの126,149トークンに合わせて d=126000 / n=128 を別に測定した。

ここはllama-benchの読み方で少し注意が必要だった。普通に -p 131072 -n 128 とすると、PP 131072の行と、depth=0のTG 128の行が別々に出る。そのTG値は「128Kを読み込んだ後の生成速度」ではない。長文contextでのTGを見るにはdを指定した測定を見る必要がある。

PP 131072

ncmoe PP 131072
0 119.75 ± 0.51 tok/s 基準
8 159.88 ± 1.03 tok/s +33.5%

128Kでも、ncmoe=8のPPはncmoe=0より速くなっている。

TG 128: depth 0と126K

ncmoe TG d=0 TG d=126000
0 27.39 ± 0.08 6.409 ± 0.008
8 26.87 ± 0.05 6.404 ± 0.010

長いcontextではTGが約6.4 tok/sまで落ちることも再現した。一方、ncmoe=8にしても長contextのTGはほぼ速くならない。ncmoeの恩恵は主にPP側だった。

KV cacheをQ8_0にしてみる

ctk=ctv=q8_0も試した。

KV ncmoe PP 131072 TG d=0 TG d=126000
F16 0 119.75 27.39 6.409
Q8_0 0 118.73 27.32 6.391
F16 8 159.88 26.87 6.404
Q8_0 8 159.39 26.73 6.297

速度だけを見るとQ8_0にするメリットはほぼなく、むしろ少し下がった。今回はKV容量削減量そのものを同条件で詳細比較していないので、ここでは速度面の結果だけとして、常用候補はデフォルトのF16のままにした。

llama-benchで測った最適値が、llama-cliで実データを使っても再現できるか確認

llama-bench上の最有力だった

ncmoe 8
t 4
b 2048
ub 1024
fa 1

を使い、context 128K、先ほどの126,149トークンの日本語文書で再確認した。

と思ったらここで、トンデモナイことが発覚。

プロンプトで求めている出力は

受信確認: BLOCK B-001

だけで、最初のテストではちゃんと出力されていた。ところが最適化後は、

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

になった。

比較するとこうなる。

ncmoe t b ub PP TG 出力結果
0 16 2048 512 114.72 6.04 正常
8 4 2048 1024 174.07 6.69 /×128で崩壊
8 4 2048 512 186.37 6.68 /×128で崩壊
0 4 2048 1024 117.72 6.41 正常

数字だけならncmoe=8は元の114.72 tok/sから174.07 tok/sへ約+51.7%で、かなり魅力的。でも出力が壊れていたら意味がない。

ub=1024 → 512に戻しても壊れたまま。ncmoe=8 → 0へ戻すと正常化した。presence penaltyを追加する実験もしたが、/の反復自体は止まらなかった。

リソース監視結果も比較した。

条件 Windows使用RAM(中央値) Adapter Dedicated Adapter Shared Adapter合計 出力結果
ncmoe0 / ub1024 34.3 GiB 56.2 GiB 1.7 GiB 58.1 GiB 正常
ncmoe8 / ub1024 41.9 GiB 48.4 GiB 9.3 GiB 57.9 GiB 崩壊
ncmoe8 / ub512 41.5 GiB 47.7 GiB 8.9 GiB 56.8 GiB 崩壊

少なくとも単純な「メモリを使い切って壊れた」ようには見えない。むしろ正常なncmoe=0 / ub1024の方がDedicated側の使用量は大きいし、どの条件にも物理メモリの空きは残っている。

またllama.cpp自身の終了時memory breakdownでも、ncmoe=8 / ub1024ではROCm0のfreeが約34.8GiB残っていた。

したがって今回のログから言えるのは、このbuild・このモデル/量子化・この長文入力では、出力崩壊がncmoe=8と強く相関しているというところまで。内部的にどの処理が壊れているかまではログだけで断定できない。

実データを優先した長文用の最適パラメータ設定は、結局

ncmoe 0
t 4
tb 4
b 2048
ub 1024
fa 1
ngl 999

になった。PPは114.72 → 117.72 tok/sで約+2.6%しか速くならないが、少なくとも出力は正常だった。

llama-serverを起動して、前回と同じ短文タスクをチェック

長文ではダメそうなことは分かったが、ncmoe=8のPP高速化も捨てがたいので、短文なら大丈夫かを試した。複数のプロンプトを連続して試したかったので、ここではllama-serverを使った。

短文確認用の起動コマンドは次のような形にした。短文だけの確認なので、ここでは -c 4096 の例を載せる。ncmoe=0側は -ncmoe 8-ncmoe 0 に変え、それ以外の主要パラメータは揃えた。

.\llama-server.exe `
  -m $Model `
  -c 4096 `
  -np 1 `
  -ngl 999 `
  -ncmoe 8 `
  -t 4 `
  -tb 4 `
  -b 2048 `
  -ub 1024 `
  -fa 1 `
  --reasoning off `
  --host 127.0.0.1 `
  --port 8081 `
  -lv 4

前回と同じプロンプトで、

  • 短文要約
  • コード生成
  • 簡単な計画・条件付き指示
  • 一文の自己紹介

を試した範囲では、ncmoe=0ncmoe=8も正常だった。ncmoe=8は「有効にした瞬間に必ず壊れる」わけではない。

速度はllama-serverのログから取った。prompt eval time がPP、eval time がTGに相当する。prompt cacheが効いた試行ではPPが「新しく処理した分だけ」の値になって比較しにくいため、下表は CachedTokens=0 の試行だけを抜き出した。

タスク Prompt tokens ncmoe=0 PP / TG ncmoe=8 PP / TG 出力結果
一文の自己紹介 22 50.75 / 27.01 tok/s 50.08 / 26.40 tok/s 両方正常
短文要約 74 101.04 / 27.13 tok/s 105.31 / 26.96 tok/s 両方正常
PowerShellコード生成 95 118.83 / 27.40 tok/s 113.77 / 26.64 tok/s 両方正常
条件付き手順提案 153 165.93 / 27.39 tok/s 147.84 / 26.76 tok/s 両方正常

短いプロンプトではPP、TGともにncmoeによる差はあまりない。ncmoe=8でも出力は崩れることなく正常だった。

続けて、中程度の長さの文を入力して要約してもらう

次に、長文テスト用に切り出した日本語論文の9,406字(5,337 prompt tokens)を、繰り返しなしで普通に入力して800字程度で要約してもらった。

ncmoe Prompt tokens PP 生成tokens TG 出力結果
0 5,337 304.60 tok/s 552 21.14 tok/s 正常に要約
8 5,337 501.73 tok/s 1,024 21.93 tok/s /反復、上限まで生成

またしてもPPは約+64.7%とすごく速くなった。しかしncmoe=8側は/を繰り返して生成上限まで走ったので、結果として処理全体はむしろ長くなった。

これで、少なくとも今回のIQ3_XXSでは

短いプロンプト: ncmoe=8でも正常な例あり
5,337 tokensの日本語技術文書: ncmoe=8で崩壊
126,149 tokensの日本語技術文書: ncmoe=8で崩壊

というところまでは確認できた。

ただし「5K tokensを超えると必ず壊れる」とまでは言えない。今回はその境界を二分探索したわけではないし、入力内容への依存もあり得る。記事ではこの5,337-token文書で再現したという事実にとどめておく。

128Kのcontext確保が原因なのか? 8Kでも試す

ここで一つ気になった。5,337-tokenの文書を128K contextのserverで処理していたので、ncmoe=8そのものではなく、大きなcontextを確保した状態との組み合わせで壊れている可能性もある。

そこで同じIQ3_XXS、同じ文書、同じncmoe=8 / t4 / b2048 / ub1024 / fa1のまま、contextだけ-c 8192まで縮めて再実行した。samplingもtemperature=0.2 / top_p=0.8で同じ。

context Prompt tokens Cached tokens PP 生成tokens TG Finish reason 出力結果
8,192 5,337 0 504.68 tok/s 768 22.21 tok/s length /反復

結果は、8Kでも同じように/を繰り返して生成上限まで走った。Prompt 5,337 tokens + 生成768 tokensでも合計6,105 tokensなので8,192以内に収まっていて、serverログでもtruncated = 0だった。

同じserverプロセスでもう2回実行すると、2回目以降は5,333 tokensがprompt cacheから復元されたが、出力はどちらも同じ/反復になった。つまりこの試行では 3/3で破綻を再現した。なお、cache利用時のPPは新規に評価した4 tokensだけの速度になるので、性能比較には使っていない。

これで少なくとも、128Kという大きなcontextを確保したこと自体が破綻の必要条件ではないことは確認できた。何token・どんな入力から壊れるかまでは今回切り分けていない。

per_layer_token_embdのCPU overrideも試した

per_layer_token_embdをCPU側へ明示的に置くtensor overrideも気になったので試してみた。

c=8192 / ncmoe=0 / t4 / tb4 / b2048 / ub1024 / fa1を固定。同じ5,337-tokenの日本語技術文書を1回ずつ処理した。

指定方法についても念のため確認した。最初は

-ot "per_layer_token_embd=CPU"

で試し、その後

-ot "per_layer_token_embd.weight=CPU"

でも取り直した。結果はこうなった。

設定 PP TG 出力結果
overrideなし 297.50 tok/s 21.15 tok/s 正常
per_layer_token_embd=CPU 298.98 tok/s 21.20 tok/s 正常
per_layer_token_embd.weight=CPU 301.19 tok/s 21.17 tok/s 正常

3条件ともほぼ同じだった。baselineに対してper_layer_token_embd.weight=CPUでもPPは約+1.2%、TGは約+0.1%で、1回ずつの測定としては実用上ほぼ同等と見てよさそうだった。

さらに、起動ログに出るmodel buffer内訳も、確認した範囲ではoverrideなしと両指定で変化しなかった。代表としてbaselineと.weight指定を並べると次の通り。

model buffer overrideなし per_layer_token_embd.weight=CPU
CPU 27,465.95 MiB 27,465.95 MiB
ROCm0 50,191.17 MiB 50,191.17 MiB
ROCm_Host 497.31 MiB 497.31 MiB

つまり、指定方法の違いを疑ってper_layer_token_embd=CPUper_layer_token_embd.weight=CPUの両方を試したが、今回の測定ではどちらも速度・出力・buffer内訳に意味のある差は出なかった

少なくとも今回の llama.cpp b10679 + ROCm + Qwen3.8-Flash-Next UD-IQ3_XXS では、明示的なCPU overrideによる実効的な配置変更や速度改善は観測できなかった。そもそも指定が無効だったのかどうかは、よく分からない。

他のモデルとの比較

ここまでQwen3.8-Flash-Next単体の挙動を見てきたので、参考として、普段使っているGemma 4 26B A4B、Gemma 4 31B、最近使い始めたQwen3.8 27Bとも比較した。

この3モデルは普段はMTPを入れていて、実運用ではもう少し高速に動くが、ここではQwen3.8-Flash-Nextとできるだけ同条件にして、

ROCm0
ngl 999
t 4
b 2048
ub 1024
fa 1
ctk/ctv f16
ncmoe 0
p 131072
TG: n=128, d=0 / d=126000

で測った。

モデルの量子化方式やアーキテクチャ、ファイルサイズが違うので、これは品質を含めた公平な「優劣」ではなく、同じEVO-X2での速度感の比較として見ている。

モデル 量子化 ファイルサイズ PP 131072 TG d=0 TG d=126000
Gemma 4 26B A4B Q4_0系 13.26 GiB 326.41 68.54 35.99
Qwen3.8-Flash-Next IQ3_XXS 76.32 GiB 119.75 27.39 6.41
Qwen3.8 27B Q5_K 19.43 GiB 102.06 10.52 7.75
Gemma 4 31B Q4_0 16.42 GiB 65.88 11.56 6.52

Gemma 4 26B A4Bが別格に速い。Gemma 4 31Bは出力が結構気に入っているのだけど、こうしてみると遅い。

Qwen3.8-Flash-Nextはモデルサイズが格段に大きいのに、PPとd=0のTGはQwen3.8 27B、Gemma 4 31Bより速い。denseとMoEの違いだろうか?
そして、コンテキストが埋まってd=12600になるとQwen3.8-Flash-Nextは最下位まで落ちるのだけど、並べてみるとQwen3.8 27B、Gemma 4 31Bと大差ない気もする。

MTPを試そうとして失敗

最後にMTPのドラフトモデルをダウンロードして、起動オプションに追加するだけで動くか確認したが、モデルロード時点で失敗した。

試した起動条件は次の通り。MTP配布元の案内に合わせ、このPowerShellセッションだけ LLAMA_ATTN_ROT_DISABLE=1 を設定した。

$MtpDraft = "C:\llama\models\Qwen3.8-Flash-Next-MTP-GGUF\Qwen3.8-Flash-Next-MTP-Q4_K_M.gguf"
$env:LLAMA_ATTN_ROT_DISABLE = "1"

.\llama-cli.exe `
  -m $Model `
  -md $MtpDraft `
  --spec-type draft-mtp `
  -ngl 999 `
  -ngld 999 `
  -ncmoe 0 `
  -t 4 `
  -b 2048 `
  -ub 1024 `
  -fa 1 `
  -c 4096 `
  --spec-draft-n-max 3 `
  --spec-draft-p-min 0.75 `
  --temp 0.2 `
  --top-p 0.8 `
  --jinja `
  --single-turn `
  --reasoning off `
  -n 256 `
  -p "ローカルLLMの性能評価で、prefillとdecodeを分けて測る理由を、日本語で300字程度に説明してください。"

主要なエラーはこれ。

check_tensor_dims: tensor 'blk.0.hc_attn_norm.weight' not found
common_speculative_init_result: failed to load draft model
llama-server: exiting due to model loading error

対象モデル自体はロードに進むが、MTP draft modelのロードで必要なtensorが見つからず終了している。

少なくとも、今回使った llama.cpp b10679 + Qwen3.8-Flash-Next-MTP-Q4_K_M.gguf の組み合わせでは、そのままでは動かなかった。

ドラフトモデル側のREADMEも特定のllama.cpp実装・構成を前提としている可能性を示しているように見えたが、今回のログだけでは原因を断定できない。llama.cppのビルドや別forkまで追い始めるとかなり大掛かりになりそうなので、今回はここで一区切りにした。

まとめ

最初は「n_cpu_moeを8にするとprefillがものすごく速くなる」という気持ちのいい最適化記事になると思っていた。

実際、llama-benchでは、

4K PP: 367.85 → 504.39 tok/s  (+37.1%)
128K PP: 119.75 → 159.88 tok/s (+33.5%)

とかなり効いた。

ところが実データでは、

126,149-token文書: PPは高速化したが / の反復で出力崩壊
5,337-token文書:   PPは高速化したが / の反復で出力崩壊
同じ5,337-token文書を8K contextにしても / 反復を3/3で再現
短文:              手元で試した範囲では正常
per_layer_token_embd=CPU / .weight=CPU: 両方試したが速度・buffer内訳に有意な差なし

になった。

なので今回一番の教訓は、llama-benchのtok/sだけで常用パラメータを決めない、だった。

128Kでllama-serverを常用するなら、今回の結果からは例えば次の起動コマンドが良さそう。

$Model = "(モデルのパス)\Qwen3.8-Flash-Next-UD-IQ3_XXS-00001-of-00003.gguf"

.\llama-server.exe `
  -m $Model `
  -c 131072 `
  -np 1 `
  -ngl 999 `
  -ncmoe 0 `
  -t 4 `
  -tb 4 `
  -b 2048 `
  -ub 1024 `
  -fa 1 `
  --reasoning off `
  --host 127.0.0.1 `
  --port 8081 `
  -lv 4

ncmoe=8は数字だけ見ると本当に惜しいので、llama.cpp側の更新で挙動が変わったらまた試したい。

次回の予定

次は出力品質の比較をやりたいけど、今回起動できなかったMTPも気になっている。Gemma 4 26B A4BではMTPで大きく高速化した経験があるので、Qwen3.8-Flash-Nextでも動かせるなら試してみたい。

ただしMTP側はllama.cppのビルドや実装差まで追う必要がありそうなので、どちらを先にやるかは次回考えることにする。

付録

llama-cli使用時の日本語文字化け対策など

ネットで調べたら、llama-cli.exeを実行するPowerShellで

chcp 65001

を実行すればよいようなことが書いてあったが、これだけではダメで、私のPowerShell 5.1環境では

chcp 65001
$Utf8NoBom = New-Object System.Text.UTF8Encoding($false)
[Console]::InputEncoding  = $Utf8NoBom
[Console]::OutputEncoding = $Utf8NoBom
$OutputEncoding           = $Utf8NoBom

の設定が必要だった。

ついでに、出力されるログの文字コードもUTF-16からUTF-8に変えるには、

$PSDefaultParameterValues['Out-File:Encoding'] = 'utf8'

を設定した。PowerShell 7だとTee-Objectの引数で文字コードも設定できるらしいが、これは試していない。

また、Tee-Object出力を画面表示しながらファイルに書き出すと、最初に次のようなPowerShell側のエラー表示が出た。

発生場所 行:1 文字:1
+ .\llama-cli.exe `
+ ~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (...:String) [], RemoteException
    + FullyQualifiedErrorId : NativeCommandError

今回の実行では、その後もllama.cpp自体は動作しており、計測結果も出ている。ログとモデル応答の表示順が混ざるケースもあったため、記事中ではllama.cpp自身のtiming行・JSONL・終了状態を基準に確認した。

テストデータの作り方

乾孝司・奥村学「テキストを対象とした評価情報の分析に関する研究動向」(『自然言語処理』Vol.13, No.3, 2006年、pp.201–241)

この論文の、言語処理学会論文誌LaTeXコーパス

に収録されているLaTeXソースから、第3章の一部を9,406字と14,874字で切り出した。

その後、9,406字のブロックと14,874字のブロックを繰り返して、必要な長さの日本語文書を作成した。

文書作成後に、llama-serverやllama-cliと同じフォルダにあるllama-tokenizeを使って、実際のトークン数を確認した。

.\llama-tokenize.exe `
  -m "(モデルのパス)\Qwen3.8-Flash-Next-UD-IQ3_XXS-00001-of-00003.gguf" `
  -f "(作成した日本語文書ファイルのパス)" `
  --show-count `
  --log-disable

検証環境

基本的には前回と同じで、比較対照用モデルだけ追加した。

Hardware

  • Device: NucBox_EVO-X2
  • CPU / APU: AMD Ryzen AI MAX+ 395 with Radeon 8060S
  • CPU clock displayed by Windows: 3.00 GHz
  • Installed memory: 128 GB
  • Windows usable memory: 63.6 GB
  • UMA Frame Buffer: 64 GB
  • GPU: AMD Radeon 8060S Graphics
  • Storage: 1.82 TB total / 732 GB used / 約1.09 TB free

OS

  • OS: Windows 11 Pro
  • Version: 25H2
  • OS Build: 26200.9168
  • Installed: 2025-10-24
  • Windows Feature Experience Pack: 1000.26100.344.0

llama.cpp

  • b10679: Windows x64 Vulkan
  • b10679: Windows x64 ROCm 7.14
  • 今回の測定ログのbuild: b10679 / commit 50f068fff

HIP SDK

  • HIP SDK: 7.2.60201-38d754472
  • HIP compiler: clang 21.0.0git
  • HIP target: gfx1151

使用したモデル

参考までに、前回比較対象にした IQ1_S を今回の長文入力でも試すと、

Quantization context input PP TG
IQ3_XXS 32K 29,453 181.23 12.82
IQ1_S 32K 29,453 190.58 13.63
IQ3_XXS 128K 126,149 114.72 6.04
IQ1_S 128K 126,149 119.53 6.09

IQ1_Sは少し速いが、この比較ではそこまで違わない。

比較対象用モデル

Gemma 4 31B

Gemma 4 26B A4B

Qwen3.8 27B

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?