Gemma 4 MTP の GB10 実機ベンチ — 推論を 26B で 1.83 倍、31B Dense で 3.52 倍に高速化
コード生成で 3.52 倍、自然言語 (英語・日本語) で約 2.5 倍 — 31B Dense + 1 GPU での独立計測 (Google 公式『最大 3 倍』を上回る)
結論サマリ
NVIDIA GB10 (GX10、121.6GB unified LPDDR5X、273 GB/s) で Google の Gemma 4 MTP (Multi-Token Prediction) drafter (本モデルの予測を補助する小型の予測モデル) を実機検証しました。3 種類の workload (どのような出力を生成するか) すべてで MTP の効果が確認でき、特に 31B Dense では Python コード生成で推論を 3.52 倍に高速化しました。これは Google 公式ブログ の「最大 3 倍 faster」(no quality loss) という主張に対し、独立した 1 GPU 環境で同等以上の数字を得たことになります。
| Category | 26B-A4B + MTP+patch | 31B Dense + MTP+patch |
|---|---|---|
| 日本語金融分析 | 1.20 倍 (16.47s → 13.74s) | 2.54 倍 (91.95s → 36.23s) |
| 英語マクロ分析 | 1.36 倍 (15.15s → 11.14s) | 2.52 倍 (91.41s → 36.22s) |
| Python コード生成 | 1.83 倍 (15.17s → 8.30s) | 3.52 倍 (91.41s → 26.00s) |
26B (水色) と 31B (緑) を並べて視覚化すると、特に Python では 31B の伸びが顕著です。
検索範囲では、31B Dense + 1 GPU + MTP の独立ベンチマーク (公式以外の第三者計測) は公開ブログ・GitHub Issue ともに見当たらず、本検証がそうした事例として最初のものになります。なお、検証で使った vLLM image (公開直後) はそのままでは acceptance rate (採択率: drafter の提案のうち target モデルが受理した割合) が 0% で動かず、PR #41745 の head commit を bind mount (ホスト側のファイルでコンテナ内のファイルを上書きマウントする手法) で当てる必要がありました。詳細はセクション 5 で扱います。
1. 検証環境
| 項目 | 値 |
|---|---|
| ハードウェア | NVIDIA GB10 (GX10 内蔵 SoC、ARM aarch64、SM 12.1) |
| メモリ | LPDDR5X 128GB unified (CPU/GPU 共有、cache coherent) |
| メモリ帯域 | 273 GB/s |
| target モデル (本モデル) | RedHatAI/gemma-4-26B-A4B-it-FP8-Dynamic (26B-A4B MoE) / RedHatAI/gemma-4-31B-it-FP8-Dynamic (31B Dense) |
| drafter (補助モデル) | google/gemma-4-26B-A4B-it-assistant (832MB) / google/gemma-4-31B-it-assistant (832MB) |
| vLLM image | vllm/vllm-openai:gemma4-0505-arm64-cu130 (Google MTP 公開と同日 push) |
| patch | PR #41745 の head commit d8b3826 を bind mount |
| 設定 | num_speculative_tokens=4 (= K、1 step で提案する token 数)、kv-cache-dtype fp8、max-model-len 4096、temperature=0.7 |
「本番」と書いた場合は、筆者が日次運用しているニュース要約 cron 用の vLLM サーバー (毎日 02:00 JST、31B-FP8-block) を指します。
2. 詳細結果
2.1 workload 別の高速化倍率 (3 prompts × 3 runs)
3 種類のプロンプトを num_speculative_tokens=4 固定で計測しました。プロンプト全文はセクション 4.5 (検証で使ったプロンプト全文) に掲載しています。
26B-A4B + MTP+patch
| Category | baseline (Dynamic、MTP なし) | + MTP+patch (K=4) | Speedup |
|---|---|---|---|
ja_finance | 16.47s, 60.2 ch/s, 992 chars | 13.74s, 73.6 ch/s, 1011 chars | 1.20 倍 |
en_finance | 15.15s, 170.1 ch/s, 2576 chars | 11.14s, 232.7 ch/s, 2592 chars | 1.36 倍 |
py_code | 15.17s, 148.9 ch/s, 2260 chars | 8.30s, 279.8 ch/s, 2321 chars | 1.83 倍 |
31B Dense + MTP+patch
| Category | baseline (Dynamic、MTP なし) | + MTP+patch (K=4) | Speedup |
|---|---|---|---|
ja_finance | 91.95s, 10.5 ch/s, 967 chars | 36.23s, 26.3 ch/s, 952 chars | 2.54 倍 |
en_finance | 91.41s, 27.9 ch/s, 2551 chars | 36.22s, 71.0 ch/s, 2572 chars | 2.52 倍 |
py_code | 91.41s, 24.9 ch/s, 2277 chars | 26.00s, 86.7 ch/s, 2254 chars | 3.52 倍 |
「コード生成 > 英語 > 日本語」の順に高速化倍率が伸びるのは、drafter の予測難度に依存するためです。
- Python:
def、import、pd.DataFrame(などが続きやすく drafter が予測を当てやすい (= acceptance が高い) - 英語: 自然言語ではあるものの語順や構造が単調 (= 中程度)
- 日本語: 語順の自由度が高く、文字あたりの token 数も多い (CJK は 1 token あたり 1.5 〜 2 文字) — drafter が当てにくく、表示文字数も稼ぎにくい
2.2 per-position acceptance — 31B drafter は全 position で 26B drafter を上回る
K=4 における per-position acceptance (各位置での採択率) を比較すると、31B drafter が全 position で 26B drafter を上回っていることがわかります。
差は pos 0 で +6.5 ポイント、pos 1 で +5.3 ポイント、pos 2 で +4.4 ポイント、pos 3 で +3.7 ポイントです。深いポジションほど差は縮まりますが、平均すると約 +5 ポイント。31B 用 drafter は 31B target と同じ 32 layer / 隠れ次元構造を共有しているため、predict-then-verify (drafter で予測してから target で検証する) における hidden state alignment (隠れ状態の整合性) が良くなる、と解釈できます。
2.3 26B vs 31B の 4 構成直接比較 (Python yfinance タスク)
絶対速度 (chars/s) で並べると、26B + MTP+patch が圧倒的に速いことが視覚化できます。
| 構成 | sec | chars/s | 26B baseline 比 | 用途 |
|---|---|---|---|---|
| 26B baseline | 15.17s | 148.9 | 1.00 倍 | (基準) |
| 26B + MTP+patch | 8.30s | 279.8 | 1.83 倍 ← 最速 | 速度重視、MoE 活用 |
| 31B baseline | 91.41s | 24.9 | 0.17 倍 | (品質重視、現本番) |
| 31B + MTP+patch | 26.00s | 86.7 | 0.58 倍 | 31B 品質 + MTP の現実的選択肢 |
「最速」は 26B + MTP+patch、「高速化倍率 (= MTP の効き具合) が最大」は 31B + MTP+patch です。用途によって選択が変わります (セクション 6 で扱います)。
2.4 なぜ 31B の方が 高速化倍率 が大きいのか
26B 1.83 倍 → 31B 3.52 倍の差はおよそ 2 倍です。要因は 3 つあります。
1. memory bandwidth bound がさらに支配的
baseline の絶対速度を比較すると、target が大きくなるほどメモリ読み出しの速度がボトルネックになりやすい (= memory bandwidth bound) ことがわかります。
- 26B baseline: 15.17 秒で 600 token = 39.5 tok/s
- 31B baseline: 91.41 秒で 600 token = 6.56 tok/s (= 26B の 1/6)
31B Dense は 26B-A4B (MoE で active 4B) より 5 〜 7 倍重い weight 読み出しが必要です。GB10 273 GB/s 帯域でメモリ読み出しの限界に達しやすく、メモリ帯域の上限で速度が頭打ちになりやすいため、MTP の amortization (K 個の token に分散・共有して 1 回の読み出しを再利用) がより大きく効きます。
2. drafter の accept rate が高い
26B mean 1.98 → 31B mean 2.18 で、effective K (実効的に進む token 数) が +10% 上がっています。
3. drafter overhead が target に対して相対的に小さい
drafter は両方とも gemma-4-*-it-assistant (832MB) で同じです。target が大きくなるほど、drafter forward (drafter モデルを 1 回走らせる処理) の相対コストが下がります。
理論最大の高速化倍率 = (mean_accepted + 1) / (1 + drafter_overhead):
| target | mean_accepted + 1 | 1 + drafter_overhead | 理論の高速化倍率 | 実測 (Python) | 実測/理論 |
|---|---|---|---|---|---|
| 26B | ~2.98 | ~1.7 | 1.75 倍 | 1.83 倍 | 105% |
| 31B | ~3.18 | ~0.9 | 3.5 倍 | 3.52 倍 | 100% |
理論との乖離が縮まる方向に効いていることがわかります。
2.5 Python execution = 「速い + 正しい」
高速化倍率の数字だけでは「MTP が正しいコードを出している」という保証にはなりません。生成された Python コードを subprocess で実際に実行し、yfinance + pandas + matplotlib のスタックで動作するかを確認しました。
取得 trace (max_tokens=1500、26B、完成版)
| variant | total | chars | 関数名 |
|---|---|---|---|
| baseline (run2) | 26.81s | 2816 | fetch_nikkei_monthly_returns() |
| MTP+patch (run3) | 15.58s | 2931 | get_nikkei_monthly_returns() |
両モデルとも以下を達成しました。
- yfinance API で
^N225の 10 年分 monthly close を取得 pct_change()で月次リターン算出[date, close, monthly_return]の DataFrame を返却- 119 ヶ月分のデータ取得成功
- matplotlib で月次リターン棒グラフ (赤=下落 / 緑=上昇) を保存
End-to-end (生成 + 実行):
baseline: 26.81 + 2.35 = 29.16s
MTP+patch: 15.58 + 2.28 = 17.86s
→ end-to-end での高速化倍率 = 1.63 倍 (節約 11.30s)
「速い (1.72 倍)」と「正しい (両者とも動作するコード)」を同時に成立させました。MTP は速度だけでなく出力品質も無損失です。冒頭の動画はこの「生成競争 → 実行 → 両モデルが等価な日経 225 月次リターンチャートを描く」までを 1 本にまとめたものです。
2.6 多言語動画 (生成競争のみ)
日本語金融分析 (日銀 YCC → 銀行株)
baseline 16.47s, 60.2 ch/s → MTP+patch 13.74s, 73.6 ch/s = 1.20 倍の高速化。日本語は drafter が深い位置で予測を当てにくく高速化倍率は控えめですが、確実に速くなります。
英語マクロ分析 (Fed QT → US 10Y yield)
baseline 15.15s, 170.1 ch/s → MTP+patch 11.14s, 232.7 ch/s = 1.36 倍の高速化。英語の方が drafter の予測が accept されやすく、日本語より高速化倍率が伸びます。
3. K-sweep (num_speculative_tokens)
K=1、2、4、8 の 4 段階でコンテナを再起動し、各 K について 3 prompts × 3 runs (max_tokens=600) のトレースを取得しました。
3.1 Speedup vs K (3-run 平均、26B-A4B)
baseline (Dynamic、MTP なし): ja 16.47s, en 15.15s, py 15.17s (= 1.0 倍 基準)
| K | ja_finance | en_finance | py_code |
|---|---|---|---|
| 1 | 11.89s, 1.39 倍 | 11.57s, 1.31 倍 | 10.74s, 1.41 倍 |
| 2 | 12.19s, 1.35 倍 | 11.33s, 1.34 倍 | 9.88s, 1.54 倍 |
| 4 | 11.90s, 1.38 倍 | 11.38s, 1.33 倍 | 8.66s, 1.75 倍 |
| 8 | 15.77s, 1.04 倍 | 14.12s, 1.07 倍 | 9.54s, 1.59 倍 |
3.2 結論
- コード生成 / 構造化出力 (yaml/json/SQL): K=4 が最適
- 自然言語 (日本語要約 / 英語ブログ): K=1 で十分 (K=2 〜 4 でもほぼ変わらず)
- K=8 はどの workload でも逆効果 — pos 7 (8 番目の位置) の acceptance が 8.9% とほぼノイズレベルで、drafter forward を 8 回回すコストと reject (棄却) 後の KV cache rollback (KV キャッシュの巻き戻し) コストが accept gain (採択ぶんの利得) を食いつぶす
vLLM のデフォルト推奨は K=4 ですが、自然言語ワークロードでは K=1 でメモリと compute (計算量) を節約できます。詳細な per-position acceptance / 理論の高速化倍率との比較データは付録 A にまとめています。
4. 再現手順
4.1 26B-A4B + MTP+patch の起動コマンド
docker run -d --name vllm-mtp \
--gpus all --ipc host --shm-size 64gb \
-p 8000:8000 \
-v $HOME/ai_news2fund/hf_cache:/root/.cache/huggingface \
-v $HOME/mtp_assets/gemma4_mtp.py:/usr/local/lib/python3.12/dist-packages/vllm/model_executor/models/gemma4_mtp.py:ro \
vllm/vllm-openai:gemma4-0505-arm64-cu130 \
RedHatAI/gemma-4-26B-A4B-it-FP8-Dynamic \
--served-model-name gemma4-26b-mtp \
--max-model-len 4096 \
--max-num-batched-tokens 4096 \
--gpu-memory-utilization 0.85 \
--kv-cache-dtype fp8 \
--speculative-config '{"method":"mtp","model":"google/gemma-4-26B-A4B-it-assistant","num_speculative_tokens":4}'
4.2 31B Dense + MTP+patch の起動コマンド
docker run -d --name vllm-mtp \
--gpus all --ipc host --shm-size 64gb \
-p 8000:8000 \
-v $HOME/ai_news2fund/hf_cache:/root/.cache/huggingface \
-v $HOME/mtp_assets/gemma4_mtp.py:/usr/local/lib/python3.12/dist-packages/vllm/model_executor/models/gemma4_mtp.py:ro \
vllm/vllm-openai:gemma4-0505-arm64-cu130 \
RedHatAI/gemma-4-31B-it-FP8-Dynamic \
--served-model-name gemma4-31b-mtp \
--max-model-len 4096 \
--max-num-batched-tokens 4096 \
--gpu-memory-utilization 0.85 \
--kv-cache-dtype fp8 \
--speculative-config '{"method":"mtp","model":"google/gemma-4-31B-it-assistant","num_speculative_tokens":4}'
--enforce-eager も --enable-chunked-prefill も不要です。patch (bind mount) だけで動きます。
4.3 patch ファイルの取得
PR #41745 の head commit d8b3826 から gemma4_mtp.py を直接ダウンロードして $HOME/mtp_assets/gemma4_mtp.py に保存してください。
mkdir -p $HOME/mtp_assets
curl -fsSL -o $HOME/mtp_assets/gemma4_mtp.py \
https://raw.githubusercontent.com/vllm-project/vllm/d8b3826648da6b407f8c55457a2103be9aeb5d83/vllm/model_executor/models/gemma4_mtp.py
4.4 メモリ要件
- 26B-A4B: target 26GB FP8 + drafter 832MB BF16 + KV cache (max-model-len 4096 で約 4GB) ≈ 約 31GB GPU RAM
- 31B Dense: target 31GB FP8 + drafter 832MB BF16 + KV cache ≈ 約 36GB GPU RAM
GB10 121.6GB unified では十分余裕があります。DGX Spark / B200 / H100 80GB / RTX 6000 48GB でも問題なく動作する見込みです。
4.5 検証で使ったプロンプト全文
再現性のため、本検証で使った 3 種類のプロンプトを全文掲載します (定義: tools/mtp_traces/prompts.json)。temperature=0.7、max_tokens=600 (Python のみ後段で 1500 も併用)。
ja_finance — 日本語金融分析タスク
日銀のYCC撤廃が日本の銀行株 (三菱UFJ / 三井住友 / みずほ) に与える影響を、NIM・含み損・配当方針の3点で詳細に解説してください。各論点について、足元の数値や具体的な政策のメカニズムを盛り込み、投資家が実務で使える形で整理してください。
en_finance — 英語マクロ経済分析タスク
Explain how the Fed’s quantitative tightening (QT) affects the US 10Y treasury yield through three channels: TGA drawdown, RRP utilization, and bank reserve scarcity. Include current data context (2025-2026), the transmission mechanism for each channel, and provide three actionable insights for fixed income traders managing duration risk.
py_code — Python コード生成タスク (PEP 8 + type hints + error handling 指定)
Write a Python function using yfinance that fetches monthly close prices for the Nikkei 225 (
^N225) for the past 10 years, computes month-over-month returns, and returns a pandas DataFrame with columns[date, close, monthly_return]. Include error handling for missing data and an example usage block. Use type hints and follow PEP 8.
選定基準: ① 筆者の本番ワークロードを反映する 3 タイプ (日本語要約 / 英語要約 / コード生成)、② drafter accept 難度が異なる (自然言語の語順自由度 vs コードの決定性)、③ 同じ max_tokens で 600 〜 2900 chars の出力幅をカバー (CJK は 1 token ≒ 1.5 〜 2 char、英語は 1 token ≒ 4 char、コードは中間)。
5. 背景: なぜ patch が必要なのか
公開直後の vllm/vllm-openai:gemma4-0505-arm64-cu130 を そのまま使うと、量子化された target ではどんな構成でも acceptance rate が 0% になり、MTP は事実上動作しません。--enforce-eager を入れても --kv-cache-dtype fp8 を外しても num_speculative_tokens を 1 に下げても変わりませんでした。
原因は 2 箇所、PR #41745 のコメントで @hospedales が特定し、patch を投稿しています。
| # | バグ箇所 | 内容 | 影響 |
|---|---|---|---|
| 1 | intermediate_size 取得位置 (line 297) | drafter MLP は text_config.intermediate_size = 8192 が必要だが、元コードは top-level config から 4096 を pull → MLP が半サイズ | weight load が失敗するか、計算結果が壊れる |
| 2 | quant_config propagation (lines 186/193/299) | target の quant_config (FP8-block / FP8-Dynamic / NVFP4) を drafter Linear layer に伝播してしまう。drafter は本来 BF16 weights を持つ | vLLM が drafter param に packing を適用して shape 不一致 → weight load 段階で破損 or silent な不具合。量子化された target で必ず発症 |
仕組みとしては、Docker image (gemma4-0505-arm64-cu130) は PR の最初の commit でビルドされており、後から landed (取り込まれた) bug fix を含みません。PR の head commit d8b3826 の gemma4_mtp.py を bind mount で当てるのが、現状の最短解です。
教訓:
- 公開直後の vLLM image は信用しない — PR の最初の commit が image にバンドルされると、後から landed した bug fix が反映されない
- 第三者の動作確認済み事例 (working config) では「完全なコマンド」を読み込む — bind mount の patch がブログ本文の外側に書かれていた事例があり (ai-muninn の DGX Spark 26B 解説)、本文中の docker run コマンドだけ見ていては気付けない
- 修正は upstream で merge され次第 image に反映されるはずだが、それまでは bind mount で運用するのが現実的
6. 本番採用の指針
| 用途 | 推奨構成 | 期待性能 |
|---|---|---|
| 速度最優先 (コード生成、構造化出力) | 26B-A4B + MTP+patch、K=4 | Python 8.30 秒で完成、英語 270+ chars/s |
| 31B 品質が必要、ただし速度も重要 | 31B Dense + MTP+patch、K=4 | 31B baseline の 28% で完了 (= 3.52 倍速)、十分実用的な体感速度 |
| 自然言語要約 (日本語ニュース要約など) | 26B / 31B + MTP+patch、K=1 | K=4 と高速化倍率はほぼ同じで、メモリと compute を節約できる |
| 品質と速度のバランス | 26B-A4B baseline (MTP なし) | 39.5 tok/s で多くのケースに十分、構成も単純 |
筆者の本番 (現状 31B-FP8-block 6.46 tok/s) は 31B-Dynamic + MTP+patch (約 28.5 tok/s、4.4 倍 faster) で同品質となるため、本番採用を真剣に検討する価値があります (要 K=1 検証で日本語適合確認)。
付録 A: K-sweep の詳細データ
A.1 acceptance metrics (累積 9 trace × 4 K、26B-A4B)
| K | drafts | draft tok | accepted | overall rate | mean accepted | per-position rate (%) |
|---|---|---|---|---|---|---|
| 1 | 3027 | 3027 | 2367 | 78.2% | 0.78/1 | 78.2 |
| 2 | 2306 | 4612 | 3091 | 67.0% | 1.34/2 | 76.2 / 57.8 |
| 4 | 1814 | 7256 | 3591 | 49.5% | 1.98/4 | 72.2 / 53.6 / 41.2 / 30.9 |
| 8 | 1576 | 12608 | 3829 | 30.4% | 2.43/8 | 73.4 / 52.3 / 37.1 / 25.8 / 19.3 / 15.2 / 11.0 / 8.9 |
A.2 mean accepted length と高速化倍率の理論比較 (26B、Python)
理論上、高速化倍率 ≒ (mean_accepted + 1) / (1 + drafter_overhead) ≒ mean_accepted + 1 です。
| K | mean accept | 理論の高速化倍率 | 実測 (py) | 実測/理論 |
|---|---|---|---|---|
| 1 | 0.78 | 約 1.78 倍 | 1.41 倍 | 79% |
| 2 | 1.34 | 約 2.34 倍 | 1.54 倍 | 66% |
| 4 | 1.98 | 約 2.98 倍 | 1.75 倍 | 59% |
| 8 | 2.43 | 約 3.43 倍 | 1.59 倍 | 46% |
K が大きいほど理論値との乖離が拡大します。drafter forward (drafter の追加実行)、KV cache rollback (棄却時のキャッシュ巻き戻し)、scheduling overhead (スケジューリング負荷) が顕在化するためで、これが「K=8 が逆効果になる」正体です。
A.3 31B の acceptance metrics (K=4、累積 9 trace + 1 probe)
drafts = 1890
draft_tokens = 7560 (= 1890 × 4)
accepted = 4117
overall accept = 54.5% (26B は 49.5%)
mean accepted = 2.18 / 4 (26B は 1.98 / 4)
per-position acceptance:
pos 0: 78.7% (26B: 72.2%)
pos 1: 58.9% (26B: 53.6%)
pos 2: 45.6% (26B: 41.2%)
pos 3: 34.6% (26B: 30.9%)
出典
- Google blog: Accelerating Gemma 4 with MTP drafters
- vLLM Recipes: Gemma 4 Usage Guide
- NVIDIA Developer Forum: Gemma 4 MTP on DGX Spark — TP=1 working config
- ai-muninn: DGX Spark Gemma 4 hits 108 tok/s — 26B 成功事例
- vLLM Issue #36331: 0% acceptance with Qwen NVFP4
- vLLM PR #40898: SWA drafter low acceptance fix
- vLLM PR #41745: Gemma 4 MTP support — patch 取得元
- HuggingFace: google/gemma-4-31B-it-assistant
- HuggingFace: RedHatAI/gemma-4-26B-A4B-it-FP8-Dynamic
- HuggingFace: RedHatAI/gemma-4-31B-it-FP8-Dynamic
この記事についてのLinkedIn投稿でコメントや意見を共有できます。
LinkedInで議論する