前回、「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℃
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 KV で ncmoe を変えた。
| 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=0もncmoe=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=CPUとper_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のドラフトモデルをダウンロードして、起動オプションに追加するだけで動くか確認したが、モデルロード時点で失敗した。
- Repository:
dzannotti/Qwen3.8-Flash-Next-MTP-GGUF - URL: https://huggingface.co/dzannotti/Qwen3.8-Flash-Next-MTP-GGUF
- File:
Qwen3.8-Flash-Next-MTP-Q4_K_M.gguf
試した起動条件は次の通り。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
使用したモデル
- Repository:
unsloth/Qwen3.8-Flash-Next-GGUF - URL: https://huggingface.co/unsloth/Qwen3.8-Flash-Next-GGUF
- Quantization: UD-IQ3_XXS
参考までに、前回比較対象にした 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
- Repository:
google/gemma-4-31B-it-qat-q4_0-gguf - URL: https://huggingface.co/google/gemma-4-31B-it-qat-q4_0-gguf
- File:
gemma-4-31B_q4_0-it.ggufログ上のサイズ 16.42 GiB
Gemma 4 26B A4B
- Repository:
unsloth/gemma-4-26B-A4B-it-qat-GGUF - URL: https://huggingface.co/unsloth/gemma-4-26B-A4B-it-qat-GGUF
- File:
gemma-4-26B-A4B-it-qat-UD-Q4_K_XL.ggufログ上のサイズ 13.26 GiB
Qwen3.8 27B
- Repository:
unsloth/Qwen3.8-27B-GGUF - URL: https://huggingface.co/unsloth/Qwen3.8-27B-GGUF
- File:
Qwen3.8-27B-UD-Q5_K_XL.ggufログ上のサイズ 19.43 GiB


