第8回「EVO-X2(Ryzen AI Max+ 395 / 128GB)+ Qwen3.8-Flash-Nextをいじっていたら、禁断のllama.cpp改造に手を出していた話」
第9回「EVO-X2(Ryzen AI Max+ 395 / 128GB)+ Qwen3.8-Flash-Next用のllama.cpp改造再開でベースのリポジトリを変更して、ROCm版を初ビルドした話」
第10回「EVO-X2(Ryzen AI Max+ 395 / 128GB)+ Qwen3.8-Flash-Next用のllama.cpp改造で長コンテキストのTGを高速化した話」
の続き。
第9回でベースのllama.cppを公式(upstream)の最新版(当時)に入れ替えて、公式llama.cppの更新に追従してベースを簡単に入れ替えられるように環境整備した。その後、第10回でVulkan/ROCmのバックエンド処理のプロファイリングをして長コンテキストのTGを高速化する修正をいくつか入れた。
次の高速化項目に着手しようと思って最新情報を調査すると、upstreamの更新で類似の機能が入っていたので、独自実装は見送ってベースのllama.cppを入れ替えることにした。
第9回の方針変更から第10回の高速化実装、入れ替え判断まで4日しか経っていない。実装のほとんどはAIに頼んでいるとはいえ、何やってんだか、という気もする。
以降、Prompt Processing(入力を読み込む処理)をPP、Token Generation(出力生成)をTGと表記する。どちらも単位はtok/sで、高いほど速い。
結論
今回も、最新upstreamに入れ替えたらそのまま速くなる、という単純な話ではなかった。
- ベースを更新すると、Vulkan 64kのPPが以前より遅くなった。調べるとMoEのtile選択方法の変更が大きな要因で、この部分は旧方式に切り替えた
- MTPはupstreamにすでに実装されていた。UnslothのMTPドラフトを使うための小さな修正だけで動いたが、最初は一部条件で MTP ONの方がTGが遅い という予想外の結果になった。
- 調べてみると、dense MTP draftで使わないindexerの管理と、target側QSAで不要なlayout再構築が行われていた。2段階の修正により、TGの改善が確認できた。
- MTP ONではPPが遅くなる問題がまだ残っていて、MTPで長文要約の処理全体が速くなったわけではない。
ソース、Windowsビルド、検証スクリプトなどはここで公開している。
https://github.com/ariera-j/llama.cpp-evox2-windows
今回の作業は r4/upstream-refresh-20261002 ブランチで行った。
検証環境
速度に関係するところだけ先に。
| 項目 | 内容 |
|---|---|
| PC | GMKtec NucBox EVO-X2 |
| APU | AMD Ryzen AI MAX+ 395 / Radeon 8060S |
| RAM | 128GB UMA |
| UMA Frame Buffer | 96GB |
| OS | Windows 11 Pro 25H2 |
| メインモデル | Unsloth Qwen3.8-Flash-Next UD-IQ3_XXS |
| モデル配置 | 初期clean計測はオリジナル版、その後は主にPLE16変換版 |
| MTPドラフト | Unsloth mtp-Qwen3.8-Flash-Next-Q8_0.gguf(MTP ON時) |
| KV cache | f16(MTP draft側もf16) |
| batch / ubatch | 2048 / 1024 |
| MTP | OFF/ONを切り替え |
| Vulkan compiler | LLVM / Clang 20.1.8 |
| ROCm | 10.0.0 / TheRock |
| ROCm compiler | AMD Clang 23.0.0 |
| GPU target | gfx1151 |
| 主な入力 | 61,789 / 126,253 / 255,181 tokens |
細かなオプションとモデルの違いはそれぞれの比較で揃え、比較に影響するものは本文にも書く。詳細は記事末尾にまとめる。
最初にベースを入れ替えてそのまま速度を測ったら、遅くなってる?
まずはgit worktreeを使って、本家(upstream)llama.cppから、作業開始時の最新版(コミット bed0a856606ee4a24a164066f73d2379447033f5)のソースをローカルの別フォルダに持ってきた。
git fetch upstream master --prune
git worktree add `
-b r4/upstream-refresh-20261002 `
C:\llama-build\llama.cpp-evox2-windows-r4 `
bed0a856606ee4a24a164066f73d2379447033f5
それから、ビルドスクリプト、計測スクリプトなど、第9回で作ったスクリプト類とドキュメントだけを新フォルダにもコピー。過去の高速化パッチはまず何も入れず、新upstreamそのままの性能を測ることにした。
Vulkan版とROCm版をビルドして、いつもと同じ日本語論文データを使ってllama-cliで速度を計測した。モデルはUnsloth Qwen3.8-Flash-Next UD-IQ3_XXS(PLE16テーブル変換をしていないオリジナルのもの)、MTPはOFF。
| Backend | Context | PP tok/s | TG tok/s |
|---|---|---|---|
| Vulkan | 64k | 248.38 | 24.61 |
| ROCm | 64k | 369.72 | 20.98 |
| Vulkan | 128k | 168.09 | 22.81 |
| ROCm | 128k | 275.00 | 17.13 |
| Vulkan | 256k | 126.76 | 19.26 |
| ROCm | 256k | 185.95 | 12.23 |
第9回で計測した旧upstream(r3の初期)の数字はこちら。こちらはPLE16対応パッチを入れ、変換後モデルで測っているため、厳密には同一バイナリ・同一モデルの比較ではない。
| Backend | Context | PP tok/s | TG tok/s |
|---|---|---|---|
| Vulkan | 64k | 267.90 | 17.20 |
| ROCm | 64k | 357.93 | 14.86 |
| Vulkan | 128k | 159.56 | 12.00 |
| ROCm | 128k | 259.41 | 9.91 |
| Vulkan | 256k | 103.75 | 7.30 |
| ROCm | 256k | 167.35 | 5.74 |
全体的に速くなっている感じだけど、よく見ると Vulkan 64kのPPだけ遅くなってる?
267.90→248.38 tok/s、約7.3%の低下。これは単なる測定誤差ではなさそう。
そしてTGは、旧upstreamの初期値と比べればかなり速くなっている。でも第10回で高速化した最終値よりは遅い。第10回の最終値は、256kでは Vulkan 21.69、ROCm 17.37 tok/sだった。第10回で実装した独自パッチを移植していないためかもしれないので、こちらは今回は深入りしないことにする。
MoE tile選択を旧方式と比較する
Vulkan 64kのPPだけ旧バージョンより遅くなっているのが気になったので、原因を調べてみた。
まず、第10回でも使った環境変数 GGML_VK_PERF_LOGGER=1 でVulkanバックエンドの処理時間を計測。新旧のGPU演算時間を比較すると、差の大きな部分はFlash AttentionよりもMoEの行列積だった。
MoE(Mixture of Experts)では、tokenに応じて一部のexpertを選んで実行する。その行列積をVulkan GPUに投げる際、何行ずつまとめて計算するか、データをどのように揃えるかを決めるのが今回の tile選択。選び方が違うと、計算する内容が同じでもGPUの使い方が変わり、速度に影響する。
チャッピー様にupstreamの変更を調べてもらうと、94a0ae3e7 付近でMoEのtile/alignment選択が変わっていた。変更箇所を調べて、環境変数で新方式と旧方式と切り替えられるように修正した。
GGML_VK_MOE_LEGACY_TILE_SELECTION |
選択方法 |
|---|---|
0(未設定時も同じ) |
新upstream方式:expertあたりの平均行数を目安に選ぶ |
1 |
旧方式:全token数を基準に選ぶ |
変更するのはVulkanのMUL_MAT_IDで使うtile/alignment選択だけ。expertの振り分け、QSAやFlash Attentionは変更しない。元のupstreamと同じ挙動を既定値にして、旧方式を使う場合だけ明示的に環境変数を設定することにした。
今回のPPでは、例えば1024 tokenを処理する箇所のtile選択が新方式では20、旧方式では1024に変わっていた。まず64kでGPU profilerを使って両方式を比べると、
| 指標 | 新upstream方式 | 旧方式 | 変化 |
|---|---|---|---|
| PP GPU演算合計 | 253.097秒 | 230.763秒 | -8.82% |
| PP MoE演算合計 | 63.933秒 | 47.807秒 | -25.22% |
| PP Flash Attention | 108.658秒 | 108.654秒 | ほぼ同じ |
| steady TG GPU平均 | 40.648 ms/token | 40.582 ms/token | ほぼ同じ |
MoEだけで約16.13秒短縮しており、PP GPU演算全体の短縮22.33秒の約72%を説明できる。
さらにprofilerをOFFにし、同一バイナリ・同一入力で新方式→旧方式→旧方式→新方式(ABBA)の通常計測を行った。
| MoE tile選択 | 64k PP tok/s | 64k TG tok/s |
|---|---|---|
| 新upstream方式(A、2回平均) | 248.475 | 25.195 |
| 旧方式(B、2回平均) | 268.985 | 24.565 |
PPが+8.25%。新upstreamで旧upstreamより速度が遅くなった原因の大部分は、このMoE tile選択の違いで説明できそう。
TGは短い128token生成で測定値にばらつきがあり、25.195→24.565の差をこの変更による速度低下とは判断しない。
以降のVulkanの主な比較では GGML_VK_MOE_LEGACY_TILE_SELECTION=1 を固定することにした。もちろん今回の検証環境・条件での結果なので、他のGPUやモデルでも旧方式が速いとは限らない。
過去に入れた変更を移植
最初のベースを作るまでに予想外に時間がかかってしまったけど、次は第8回で入れた「PLE16変換テーブル対応」「QSA grouped-union」の2つの変更を移植した。
「PLE16変換テーブル対応」(COMMON-001)は高速化が主目的ではなく、今まで使ってきたUnsloth Qwen3.8-Flash-Next UD-IQ3_XXSのPLE16変換モデルを新upstreamでも読み込めるようにするためのもの。高速化の結果はできるだけ同じモデルで比較したいので、今まで計測に使ってきたモデルが読み込めるように、こちらを最初に移植した。
新upstreamのloader構造に合わせて移植し、オリジナル版と変換版の両方が動くようにした。
64kで両モデルを同じ条件で測ったところ、
| Backend | モデル | PP tok/s | TG tok/s |
|---|---|---|---|
| Vulkan | オリジナル | 267.51 | 24.53 |
| Vulkan | PLE16 | 268.79 | 25.46 |
| ROCm | オリジナル | 370.32 | 21.17 |
| ROCm | PLE16 | 370.37 | 21.16 |
PPはVulkanで約0.5%、ROCmではほぼ差がない。先ほどのclean計測からVulkan PPが大きく上がっているのは、先ほど修正したMoE tile選択を旧方式に固定したためで、PLE16変換だけの効果ではない。
もう一つの「QSA grouped-union」(VULKAN-002)は、第8回で一番効果のあったPP高速化項目。ただし新upstreamではsparse Flash Attentionやselected-idの内部構造が変わっていたので、そのままcherry-pickするのではなく、変更後の構造に合わせて移植した。
GGML_VK_QSA_UNION=0/1 でOFF/ONを切り替え、MoE tile選択など他の条件を固定して比較した結果がこちら。
| モデル | Context | PP OFF | PP ON | PP改善 | TG OFF | TG ON |
|---|---|---|---|---|---|---|
| オリジナル | 64k | 269.08 | 337.45 | +25.41% | 24.72 | 24.82 |
| PLE16 | 64k | 268.42 | 335.38 | +24.95% | 25.30 | 25.40 |
| PLE16 | 128k | 178.97 | 295.68 | +65.21% | 23.22 | 23.28 |
| PLE16 | 256k | 132.96 | 266.75 | +100.62% | 19.58 | 19.59 |
新upstreamでも長い入力ほど効果が大きく、TGはほぼ変わらない。これでVulkan PPについては、ひとまず良いところまで戻せた。なお、この機能もソースの既定値はOFFのままで、計測時に環境変数を設定してONにしている。
第10回で入れたTG高速化2件は、完全に同じではないにせよ、類似の主要構造がupstreamにも入り始めていた。今回の最初の計測結果から、完全に同じものが入っているわけではなさそうだけど、まだupstream側に動きがありそうなので、この2件の移植はいったん保留しにした。
ベースを入れ替えて、過去に実装済みのパッチを選んで移植するだけでも、おかしくなっていないか1個ずつチェックするために速度計測と出力チェックをしていたら、結構時間がかかる。
MTPを試してみる
今回のメインテーマとして、MTPを試してみることにした。
実は第8回の記事をアップした後、第8回のllama.cpp Strix Halo専用forkベースの改造版にUnslothのMTPモデルを読み込めるようにして、MTPを入れるとTGは速くなるけどPPが遅くなるのを何とかしようとしていた。けど時間切れでそのまま休暇入りして、休暇明けは最新情報を調べるのに気を取られて、すっかり忘れていた。
MTP(Multi-Token Prediction)は、メインモデルが次のtokenを1つずつ確定する代わりに、ドラフト用の小さな追加モデルで先のtokenを予測し、メインモデルがまとめて確認する仕組み。予測が採用(accept)されれば、そのぶんTGが速くなることが期待できる。ただしドラフト側にも計算やcache管理があるので、採用率が高くても必ず速くなるとは限らない。
休暇前に実装したMTP対応パッチを移植する前にupstreamの更新情報を調べたら、
c061df19838ff60970faf54fd7e414953590125d
Qwen4Exp: add MTP (#29761)
という変更が、2026年10月1日にupstreamへマージされていて、今回のr4ベースにもすでに含まれていた。
なので、過去のパッチを丸ごと移植せず、まずは普段のllama-cliにドラフトモデルの指定を追加して、手元のUnsloth Q8_0 MTPドラフトを読み込んでみることにした。主要な追加オプションは次のとおり。
-md "(MTPドラフトGGUFのパス)" `
--spec-type draft-mtp `
--spec-draft-n-max 2 `
--spec-draft-p-min 0 `
--n-gpu-layers-draft 999 `
--cache-type-k-draft f16 `
--cache-type-v-draft f16
ところが、
ggml-backend.cpp:345: GGML_ASSERT(buffer)
というエラーで止まり、ドラフトモデルの初期化に失敗した。やっぱり、そんなにうまく行くわけないか。ということでチャッピー様に原因を調べてもらった。
その結果、今使っているUnslothのMTPドラフトモデルはQSA(Qwen Sparse Attention)ではなくdense attentionを使うMTPモデルだった。MTPモデルのMTP layer 48のcompression ratioは0で、このMTPブロック自身はindexer poolを使わない。
(QSAについては第10回で簡単に説明したので今回は説明を省略する)
upstreamのMTP graphでは、メインモデル全体にindexer poolが存在すると、ドラフトモデル側で使わないpool入力までsetterに登録していた。graph内で未使用のためその入力bufferが確保されず、setterがそこへアクセスしてassertになっていた。
そこでMTP layer自身のcompression ratioが正の場合だけpool入力を登録するように修正した。これなら手元のratio=0ドラフトはdenseのまま動き、QSA対応MTPを使う経路も変更しない。
そのままでは動かなかったけど、元の専用forkベースのMTP用パッチと比べるとだいぶ簡単な改造で済んだ。
MTPを入れたら遅くなる?
MTPドラフトモデルを読み込めるようになったので、さっそくVulkan版とROCm版の両方で速度を測ってみた。MTP OFF/ONを各2回のABBA順で比較し、64k/128k/256kでは512token生成、temperature 0.2、seed 1234などの条件を揃えた。Vulkanは先に実装したMoE tile選択旧方式とQSA unionをONにした。
| Backend | Context | PP OFF → ON | TG OFF → ON | TG増減 | MTP採用率 |
|---|---|---|---|---|---|
| Vulkan | 64k | 334.68 → 310.19 | 25.75 → 29.84 | +15.9% | 72.8% |
| Vulkan | 128k | 294.27 → 266.09 | 23.27 → 23.02 | -1.1% | 67.0% |
| Vulkan | 256k | 265.02 → 228.08 | 19.76 → 15.25 | -22.8% | 67.2% |
| ROCm | 64k | 370.13 → 347.41 | 21.12 → 25.48 | +20.6% | 70.1% |
| ROCm | 128k | 277.57 → 260.15 | 16.88 → 17.73 | +5.1% | 69.6% |
| ROCm | 256k | 185.72 → 173.81 | 12.41 → 10.96 | -11.7% | 65.6% |
MTPを入れるとPPが遅くなるのは前からなので今は気にしないことにして、高速化を期待したTGでも、Vulkanの128kで効果が消え、256kではVulkan/ROCmともMTPを入れた方が遅くなっている。
これだとMTPの意味がなくない?
第3回で同じモデルを当時のVulkanプレビルド版llama.cppで測ったときは128kまでMTPを入れた方が速かったし、記事化していない休暇前の専用forkベースの改造版でも、MTP ONでTGが遅くなることはなかった。
しかも、今回のVulkanの採用率は128kで約67.0%、256kでも約67.2%。採用率の急落だけではこの速度低下を説明できない。Vulkan 256kではMTP OFFもONも2回の結果が揃っていたので、測定の偶然とも考えにくい。
せっかくMTPが動くようになったのに、このままだと実用性ゼロなので、MTP専用の診断ログ出力機能を追加して、処理時間をドラフト処理・target側の検証・CPUでのlayout管理などに分けて調べてみた。
特に気になったのが、QSA用poolのlayoutをCPUで組み直す処理。MTP ON時の256kでは約15.015秒、128kでは約5.207秒だったのに対し、256k MTP OFF時は約0.004秒。長い履歴を持つ状態で、speculative decodingに伴うsequence管理のたびに余分な仕事が発生しているようだった。
ただし、これはMTP専用診断で計測したCPU layout処理時間であって、TG時間のすべてでもGPU演算時間でもない。
修正1: dense MTP draftに不要なindexerを持たせない
最初に注目したのはドラフト側。先ほど確認したとおり、手元のUnsloth MTPドラフトはcompression ratio=0のdense attentionなので、QSA indexerのpoolを使わない。
ところがmemoryの構築側はメインモデルにQSAがあることから、使わないはずのドラフト側にもindexer cacheとpool layoutの管理を持たせていた。speculative decodingでは、採用・不採用に応じて履歴を修正するため、この余分な管理が何度も動く。
そこで、対象が単一MTPブロックのdense draftであることなどを確認できた場合にだけ、その不要なindexerの確保・管理を省略できるようにした。環境変数はLLAMA_MTP_SKIP_DENSE_INDEXER=1。既定値はOFF。QSAを使うMTPドラフトやメインモデル側のindexerはそのままにしている。
256k、512token生成、同一バイナリで、A=LLAMA_MTP_SKIP_DENSE_INDEXER OFF(0)、B=ON(1)としてABBA通常計測をした。
| dense draft indexer省略 | PP tok/s | TG tok/s |
|---|---|---|
| OFF(A、2回平均) | 228.835 | 15.220 |
| ON(B、2回平均) | 229.650 | 20.185 |
TGは15.220 → 20.185 tok/s(+32.62%)。 PPはほぼ変わらない。
だいぶ速くなったけど、さっき測った MTP OFFの 19.76 tok/s と大差ないので、MTPの効果を出すにはもう少し何とかしたい。
修正2: target QSAのno-op layout invalidationを抑える
修正1でドラフト側の無駄は取れたが、今度はメインモデル(target)側にまだlayout再構築の時間が残っていた。
原因を調べると、MTPの検証後にsequenceから不要なtokenを取り除く処理で、実際にはindexer poolに属するcell数が変わっていないのに、layoutを「古くなった」として無効化するケースがあった。すると次回の処理でlayoutを最初から作り直してしまう。
そこで、削除前後の実際のpool membership countを比べ、変化がない場合に限って新しい無効化を省略するようにした。もちろん、実際にcellが減ったときは従来どおり無効化し、すでに無効化されている状態を勝手に解除することもしない。
この修正はLLAMA_QSA_SKIP_NOOP_INVALIDATION=1で有効化し、既定値はOFF。まず診断ログ出力ONの256k比較では、
| target側の処理 | 修正前 | 修正後 |
|---|---|---|
| full layout rebuild | 217回 | 95回 |
| no-op invalidation抑制 | 0回 | 123回 |
| 本当にcell数が減った場合の無効化 | 95回 | 95回(維持) |
| CPU layout処理時間 | 5.530秒 | 2.436秒 |
となった。無駄な再構築がかなり減っている。なお、抑制した123回のうち最後の1回は次のdecodeがないため、実際に減ったfull rebuildは122回(217→95回)になる。
次に診断出力ログをOFFにして、修正1はONに固定したまま修正2だけOFF/ONを切り替えるABBA通常計測を行った。Vulkan 256k、入力255,181tokens、生成512tokens、MoE tile選択旧方式とQSA grouped-unionはどちらもON。
| 実行 | 修正2 | PP tok/s | TG tok/s | accepted / drafted |
|---|---|---|---|---|
| A1 | OFF | 230.73 | 20.24 | 293 / 436 |
| B1 | ON | 229.58 | 22.82 | 293 / 436 |
| B2 | ON | 229.55 | 22.84 | 293 / 436 |
| A2 | OFF | 229.15 | 20.17 | 293 / 436 |
| A平均 | OFF | 229.940 | 20.205 | 293 / 436 |
| B平均 | ON | 229.565 | 22.830 | 293 / 436 |
TGは20.205 → 22.830 tok/s(+12.99%)。 PPは約-0.16%で、ほぼ変わらない。4回とも生成本文が一致し、採用数も293/436(約67.2%)で一致していた。今回のVulkan 256kでは、修正2がTG改善につながったと判断してよさそうだ。
ROCmでも同じ修正を確認
ROCmでも2つの修正を使った256k計測を行った。比較したのは、MTP OFF、MTP ON+修正1 ON+修正2 OFF(A)、MTP ON+修正1 ON+修正2 ON(B)の3条件。こちらは各1回の通常計測。
| 条件 | PP tok/s | TG tok/s | PP時間 | TG時間 | PP+TG |
|---|---|---|---|---|---|
| MTP OFF | 185.76 | 12.39 | 1373.72秒 | 41.25秒 | 1414.97秒 |
| A:MTP ON/修正2 OFF | 173.91 | 13.57 | 1467.31秒 | 37.65秒 | 1504.95秒 |
| B:MTP ON/修正2 ON | 173.84 | 14.44 | 1467.90秒 | 35.39秒 | 1503.29秒 |
BのTGはMTP OFF比で約+16.5%。当初のROCm 256kではMTP ONの方が遅かったので、最終構成ではTGの逆転が見られなくなった。
ただし、ROCmでは少し注意が必要だった。AとBで生成本文が一致せず、採用数も289/441と286/448で違っていた。このため、13.57→14.44 tok/sという差を、修正2だけの純粋な高速化効果とは断定できない。
ここまでで、今回のEVO-X2+Qwen3.8-Flash-Nextの256k条件では、当初観測したMTP ONで TGが遅くなる現象をかなり改善できた。ただしROCmの修正2の効果や、128kを含む他条件での再現性には未確認の部分が残る。
でも、以前から残っているMTPを入れるとPPが遅くなる問題は解決できていない。
現在速度計測に使っている「日本語長文を入力して要約してもらう」というタスクでは、処理時間のほとんどが読み込み時間で、生成文は短いので生成時間の比率は小さい。なので、総処理時間はMTPオフの方がまだ短い。
MTP ON時のPP低下を直接調べるのがよいのか、それ以外のPP高速化を先にするのがよいのか、もう少し考えてみたい。
まとめ、今後の予定
今回はまたまたupstreamを入れ替えるところから始めた。
Vulkanの64k PPが以前より遅くなったのは、upstream側で変更されたMoE tile選択が大きな要因だった。旧方式へ切り替えたところPPは約8%改善。さらに過去のPLE16対応とQSA grouped-unionを移植し、256kではgrouped-unionのPP改善が約2倍になった。
続いてMTPを試すと、upstreamにすでに実装されていたおかげで、以前より少ない修正で手元のドラフトモデルを動かせた。ところが256kではMTP ONでTGが遅くなり、調べるとドラフト側の使っていないindexer管理と、target側の不要なQSA layout再構築が原因の一部だった。
Vulkan 256kでは、2つの修正それぞれでTG改善を確認できた。ROCmでも同じ計測内で最終構成のTGはOFFを上回ったが、生成結果の差があったので修正2単独の性能効果については追加確認の余地がある。
TGが速くなってもPPの低下は残る。 この問題は別途調べるつもり。
実はこの記事を書いている時点で、ベース入れ替え~MTPを試す実験から1週間ぐらい経っている。その間に色々脱線しまくって、やっとllama.cppのMTP検証に戻ってきたところだったりする。
次回はllama-cliだけでなくllama-benchでも測ってみようと思ったら結果が一致しなくて、というところからの脱線話を書く予定。
付録
長文要約テストのコマンド例
通常のllama-cliでのテスト例は次のとおり(モデルと入力ファイルのパスは置き換える)。これ自体はMTP OFFの例。
.\llama-cli.exe `
-m "(メインモデルのパス)" `
-c (コンテキスト長) `
-ngl 999 `
-ncmoe 0 `
-t 4 `
-tb 4 `
-b 2048 `
-ub 1024 `
-fa auto `
-ctk f16 `
-ctv f16 `
--fit off `
--cache-ram 0 `
--ctx-checkpoints 0t `
--temp 0.2 `
--top-k 20 `
--top-p 0.8 `
--min-p 0.05 `
--jinja `
--single-turn `
--reasoning off `
-f "(入力ファイルのパス)" `
-n 512 `
--seed 1234 `
--ignore-eos `
-lv 4
MTP ONでは、上記へ次を追加した。
-md "(MTPドラフトGGUFのパス)" `
--spec-type draft-mtp `
--spec-draft-n-max 2 `
--spec-draft-p-min 0 `
--n-gpu-layers-draft 999 `
--cache-type-k-draft f16 `
--cache-type-v-draft f16
※実際の計測スクリプトではMTP OFF時に --spec-type none も明示している。上記はコマンドを理解するための例で、初期baselineやMoE A/Bなど、生成token数やモデル配置が異なる計測も含まれる。512tokenの例を全測定に共通する唯一のコマンドとみなさず、各節に記載した比較条件を優先してほしい。
Vulkanの後半計測では、主に以下の環境変数を指定した。独自変更はいずれも既定値で強制ONにはしていない。
$env:GGML_VK_MOE_LEGACY_TILE_SELECTION = '1'
$env:GGML_VK_QSA_UNION = '1'
$env:LLAMA_MTP_SKIP_DENSE_INDEXER = '1'
$env:LLAMA_QSA_SKIP_NOOP_INVALIDATION = '1'
テストデータについて
今回も前回までと同じ日本語長文入力を使用した。
| context | 実入力 |
|---|---|
| 64k | 61,789 tokens |
| 128k | 126,253 tokens |
| 256k | 255,181 tokens |
256kでは要求context 262,144の約97.3%まで入力している。前回までの記事と同じデータなので詳細は省略する。
詳細な検証環境
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: 128GB
- UMA Frame Buffer: 96GB
- Windows CPU側に見えるRAM: 約32GB
- GPU: AMD Radeon 8060S Graphics
- GPU arch:
gfx1151
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
- Upstream repository:
ggml-org/llama.cpp - r4 upstream pin:
bed0a856606ee4a24a164066f73d2379447033f5 - Commit title:
CUDA: fuse shared experts into MMVQ (#29184) - r4 branch:
r4/upstream-refresh-20261002 - 補足: 本文のベース、MoE、grouped-union、MTP最終計測は順次パッチを追加して再ビルドしているため、全表が同一ビルドではない。各A/Bの実行ファイル・条件一致はリンク先の検証記録で確認した。
Vulkan:
- Compiler: LLVM / Clang 20.1.8
- Vulkan SDK: 1.4.357.0
ROCm:
- ROCm: 10.0.0 / TheRock
- Compiler: AMD Clang 23.0.0
- Host toolchain: Visual Studio 2022 Build Tools / MSVC 14.44
- GPU target:
gfx1151
使用したモデル
メインモデル:
- Repository:
unsloth/Qwen3.8-Flash-Next-GGUF - URL: https://huggingface.co/unsloth/Qwen3.8-Flash-Next-GGUF
- Quantization:
UD-IQ3_XXS - File size reported by llama.cpp: 76.32 GiB
- BPW: 3.71
- Model params: 176.94B
上記モデルのオリジナルと、LaurentZuijdwijkのStrix Halo専用fork内の gguf-py\gguf\scripts\gguf_split_ple_heads.py で変換したPLE16モデルを使った。
MTPドラフトモデル:
- Repository: 同上(Unsloth配布のsidecar)
- File:
mtp-Qwen3.8-Flash-Next-Q8_0.gguf - Size: 約3.85GiB
- 既存ファイルのMTP block 48のcompression ratio: 0(dense attention)