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?

【第1回】llama.cppでMTPドラフターを使い、ローカルLLM推論を高速化する

0
Last updated at Posted at 2026-07-24

ローカルLLMを実用していると、モデルサイズを上げたときにまず気になるのは「回答が出始めるまで」「最後まで出力し終わるまで」の待ち時間です。

この記事では、llama.cpp の MTP ドラフター構成を使い、gemma-4-31B-it 系のGGUFモデルで推論速度がどの程度変わるかを測定しました。

検証環境は Intel GPU + SYCL ですが、主題はハードウェア紹介ではありません。NVIDIA環境ならCUDA、AMD環境ならROCm/HIPやVulkanなど、利用するGPUに合わせてバックエンドを読み替えれば、考え方自体は応用できます。

この記事で扱うこと

  • MTP / Speculative Decoding の概要
  • llama.cppでMTPドラフターを使う起動方法
  • MTPなし / MTPありの同一プロンプト比較
  • Intel GPU + SYCLでの実測値
  • Vision対応モデルを使う場合の注意点

この記事で扱わないこと

  • OpenCodeなど、特定のエージェントソフトウェアへの組み込み
  • iGPUとdGPUの役割分担
  • Vision入力そのもののベンチマーク
  • モデル品質の詳細評価

OpenCodeやiGPU活用の話は面白いテーマですが、MTP構成とは読者層も論点も少し違います。この記事では、まずMTPの構築と効果に絞ります。

MTP / Speculative Decoding とは

通常のLLM推論では、モデルは基本的に1トークンずつ次のトークンを生成します。

MTPやSpeculative Decodingでは、メインモデルとは別にドラフトモデルを使います。ドラフトモデルが先に複数トークンの候補を生成し、メインモデルがそれを検証します。候補が採用されれば、メインモデル単体で1トークンずつ進めるよりも速く生成できます。

ざっくり言えば、次のような構成です。

user prompt
    |
    v
main model  <---- verifies ----  draft model
    |
    v
accepted tokens
    |
    v
response

ただし、常に速くなるわけではありません。ドラフトモデルの品質、メインモデルとの相性、ドラフト候補の受理率、GPUバックエンドの性能、コンテキスト長などで効果が変わります。

参考:

検証環境

今回の検証では、メインモデルを31Bに固定しました。

全体のハードウェア構成は次の通りです。

項目 内容
CPU AMD Ryzen 7 8845HS
MEM DDR5 64GB
GPU1 AMD Radeon 780M Graphics
iGPU memory 8GB割当
GPU2 Intel Arc Pro B70
dGPU VRAM 32GB
dGPU power limit 150W

B70の最大消費電力は、/usr/local/bin/set-b70-tdp-limit.sh をsystemdから呼び出して150Wに制限しています。

理由は、夏場の気温上昇に対する安全マージンを取るためです。PCゲームのように瞬間的な最高性能を狙う用途ではなく、ローカルLLMを長時間安定して動かす用途なので、過大な電力を投入するよりも、発熱と安定性のバランスを優先しています。

私の環境では、スクリプトの中身は次のようなものです。

#!/bin/bash

PCI_ADDR="0000:03:00.0"
TARGET_FILE=$(find /sys/bus/pci/devices/${PCI_ADDR}/ -name "power1_cap" -print -quit 2>/dev/null)

if [ -n "${TARGET_FILE}" ] && [ -f "${TARGET_FILE}" ]; then
  echo "Setting TDP limit to 150W for Arc Pro B70 at ${TARGET_FILE}"
  echo 150000000 > "${TARGET_FILE}"
  exit 0
else
  echo "[ERROR] Arc Pro B70 hwmon power1_cap not found under PCI ${PCI_ADDR}" >&2
  exit 1
fi

power1_cap はマイクロワット単位なので、150Wは 150000000 として指定します。

PCI_ADDR は環境ごとに変わります。card0 / card1hwmon の番号は起動ごとにずれることがあるため、固定の hwmonX を直接書かず、PCIデバイス配下から power1_cap を探すようにしています。

ソフトウェアとモデルの条件は次の通りです。

項目 内容
llama.cpp b10073
commit 91d2fc387
build IntelLLVM 2026.1.0 for Linux x86_64
oneAPI 2026.1
backend SYCL
device SYCL0: Intel(R) Arc(TM) Pro B70 Graphics
main model gemma-4-31B-it-qat-UD-Q4_K_XL.gguf
draft model mtp-gemma-4-31B-it.gguf
context 98304
KV cache q8_0 / q8_0
threads 8
flash attention enabled

パスは記事用にマスクしています。

/home/<user>/.local/opt/llm/models/gemma-4-31B-it-qat-UD-Q4_K_XL.gguf
/home/<user>/.local/opt/llm/models/mtp-gemma-4-31B-it.gguf

なお、今回B70を選んだ主な理由は次の2点です。

  • VRAMが32GBあり、30B級モデルを扱いやすい
  • 同程度のVRAM容量を持つNVIDIA / AMD GPUと比べてコストパフォーマンスがよい

もちろん、ソフトウェア対応や周辺ツールの情報量ではNVIDIA CUDA環境の方が有利な場面もあります。今回は、ローカルLLM用途で「大きめのVRAMを比較的安価に確保する」ことを優先しました。

バックエンドの考え方

今回はIntel GPUなのでSYCLを使いました。

llama.cppでは複数のバックエンドが使えます。GPUごとの大まかな選択肢は次のようになります。

GPU 候補
Intel GPU SYCL
NVIDIA GPU CUDA
AMD GPU ROCm/HIP、Vulkan
汎用 CPU、BLAS、Vulkan

この記事のコマンドはSYCL向けですが、MTPの考え方はバックエンド固有ではありません。別GPUでは、ビルドオプションとデバイス指定を読み替える必要があります。

参考:

ビルド方針

SYCL向けのビルドでは、oneAPI環境を読み込んだ上で、llama.cppをSYCL有効でビルドします。

実際の検証環境では、CIで次のような方針を取っています。

  • origin/master を取得
  • oneAPI 2026.1 の icx / icpx を使用
  • GGML_SYCL=ON
  • GGML_SYCL_F16=ON
  • GGML_SYCL_DNN=ON
  • GGML_SYCL_GRAPH=ON
  • Level Zero API対応を有効化
  • ビルド成果物は本番バイナリとは分離

概念的には次のようなCMake設定です。

cmake -S llama.cpp -B build -G Ninja \
  -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_C_COMPILER=/opt/intel/oneapi/2026.1/bin/icx \
  -DCMAKE_CXX_COMPILER=/opt/intel/oneapi/2026.1/bin/icpx \
  -DGGML_NATIVE=ON \
  -DGGML_SYCL=ON \
  -DGGML_SYCL_F16=ON \
  -DGGML_SYCL_DNN=ON \
  -DGGML_SYCL_GRAPH=ON \
  -DGGML_SYCL_HOST_MEM_FALLBACK=ON \
  -DGGML_SYCL_SUPPORT_LEVEL_ZERO_API=ON

起動前の環境変数

本番起動時と同じく、oneAPI環境を読み込み、SYCL / Level Zero向けの環境変数を設定しています。

source /opt/intel/oneapi/2026.1/oneapi-vars.sh --force

export ONEAPI_DEVICE_SELECTOR=level_zero:gpu
export GGML_SYCL_ENABLE_OPT=1
export GGML_SYCL_USE_LEVEL_ZERO_API=1
export GGML_SYCL_ENABLE_VMM=1
export GGML_SYCL_ENABLE_DNN=1
export GGML_SYCL_ENABLE_FLASH_ATTN=1
export GGML_SYCL_ENABLE_GRAPH=0
export UR_L0_ENABLE_RELAXED_ALLOCATION_LIMITS=1
export LD_LIBRARY_PATH=/home/<user>/.local/opt/llm/bin-sycl/lib:${LD_LIBRARY_PATH:-}

デバイスは llama-server --list-devices で確認します。

Available devices:
  SYCL0: Intel(R) Arc(TM) Pro B70 Graphics

MTPなしの起動例

まずは比較用に、ドラフトモデルなしで起動します。

llama-server \
  -m /home/<user>/.local/opt/llm/models/gemma-4-31B-it-qat-UD-Q4_K_XL.gguf \
  --alias local-text \
  --host 127.0.0.1 \
  --port 18081 \
  -c 98304 \
  -ctk q8_0 \
  -ctv q8_0 \
  --jinja \
  -ngl all \
  -sm none \
  -t 8 \
  --device SYCL0 \
  --parallel 1 \
  -fa 1 \
  --metrics

MTPありの起動例

MTPを使う場合は、メインモデルに加えてドラフトモデルを指定します。

llama-server \
  -m /home/<user>/.local/opt/llm/models/gemma-4-31B-it-qat-UD-Q4_K_XL.gguf \
  -md /home/<user>/.local/opt/llm/models/mtp-gemma-4-31B-it.gguf \
  --spec-type draft-mtp \
  --spec-draft-n-max 4 \
  --spec-draft-p-min 0.8 \
  --spec-draft-device SYCL0 \
  --spec-draft-ngl all \
  --alias local-text-mtp \
  --host 127.0.0.1 \
  --port 18081 \
  -c 98304 \
  -ctk q8_0 \
  -ctv q8_0 \
  --jinja \
  -ngl all \
  -sm none \
  -t 8 \
  --device SYCL0 \
  --parallel 1 \
  -fa 1 \
  --metrics

重要なのは以下です。

オプション 意味
-m メインモデル
-md ドラフトモデル
--spec-type draft-mtp MTPドラフターを使う
--spec-draft-n-max 4 ドラフト候補の最大数
--spec-draft-p-min 0.8 ドラフト候補のしきい値
--spec-draft-device SYCL0 ドラフトモデルを動かすデバイス
--spec-draft-ngl all ドラフトモデルをGPUへ載せる

ドラフトモデルを重くしすぎたり、n-max を上げすぎたりすると、逆に遅くなる可能性があります。ここは環境ごとに調整が必要です。

ベンチマーク方法

合成ベンチとして llama-bench も使えますが、この記事では実運用に近い比較として、OpenAI互換APIへ同一プロンプトを投げました。

条件は固定しています。

項目
prompt 固定
temperature 0
seed 42
max_tokens 1024
thinking disabled
stream false
実行回数 5回
集計対象 2〜4回目
捨てた値 1回目と5回目

1回目はウォームアップの影響を受けやすく、5回目は終了前の状態変化を避けるため、今回は2〜4回目だけを平均しました。

プロンプトは以下です。

Intel Arc GPUでローカルLLMを運用する際、性能と安定性を両立するための重要事項を5項目で簡潔に説明してください。

リクエスト例です。

{
  "model": "local-text-mtp",
  "messages": [
    {
      "role": "user",
      "content": "Intel Arc GPUでローカルLLMを運用する際、性能と安定性を両立するための重要事項を5項目で簡潔に説明してください。"
    }
  ],
  "temperature": 0,
  "seed": 42,
  "max_tokens": 1024,
  "chat_template_kwargs": {
    "enable_thinking": false
  },
  "stream": false
}

結果

2〜4回目の平均値です。

条件 平均wall time 生成速度 prompt eval completion tokens prompt tokens
MTPなし 19.21 sec 20.52 tok/s 43.20 tok/s 390 45
MTPあり 11.19 sec 35.37 tok/s 46.10 tok/s 389 45

MTPを有効にしたことで、生成速度は約1.72倍になりました。

35.37 / 20.52 = 1.72

wall timeは約41.7%短縮されています。

(19.21 - 11.19) / 19.21 = 0.417

今回の条件では、MTPの効果はかなりはっきり出ました。

個別実行結果

参考として、5回分の実行結果も載せます。

条件 run wall time 生成速度 completion tokens prompt cache
MTPなし 1 20.85 sec 20.68 tok/s 389 0
MTPなし 2 19.24 sec 20.54 tok/s 390 40
MTPなし 3 19.25 sec 20.52 tok/s 390 40
MTPなし 4 19.13 sec 20.50 tok/s 390 40
MTPなし 5 19.13 sec 20.51 tok/s 390 40
MTPあり 1 13.82 sec 34.08 tok/s 401 0
MTPあり 2 11.23 sec 35.40 tok/s 389 40
MTPあり 3 11.24 sec 35.36 tok/s 389 40
MTPあり 4 11.11 sec 35.36 tok/s 389 40
MTPあり 5 11.13 sec 35.31 tok/s 389 40

1回目はプロンプトキャッシュが効いていないため、他の回より遅くなっています。比較に使うなら、ウォームアップ後の値を見る方が実態に近いです。

draft acceptance

MTPありの2〜4回目では、ドラフト受理率は安定していました。

draft acceptance = 0.65385
221 accepted / 338 generated
mean len = 2.64

ドラフトモデルが生成した候補のうち、約65%が採用されています。この受理率が低いと、ドラフト生成のコストだけが増えて効果が出にくくなります。

今回の構成では、ドラフトモデルとメインモデルの相性がよく、MTPの恩恵を受けられたと判断できます。

llama-benchの参考値

同じ31Bモデルで、llama-bench の合成ベンチも取得しています。

test tokens/sec
pp512 451.18 tok/s
tg128 21.71 tok/s

tg128 の値は、MTPなしAPIベンチの生成速度 20.52 tok/s と近い値になっています。合成ベンチだけでなく、実際のAPI呼び出しでも近い傾向が出ている点は確認材料になります。

VRAMが少ない場合の参考: 26B A4B

31BモデルはVRAMに余裕がある環境向けです。VRAMが16GB〜24GB程度の場合は、より小さいモデルを選ぶ方が現実的なことがあります。

そこで参考値として、次の26B A4B構成でも同じベンチを取りました。

main : gemma-4-26B-A4B-it-qat-UD-Q4_K_XL.gguf
draft: mtp-gemma-4-26B-A4B-it.gguf

測定条件は31Bと同じです。

  • 同一プロンプト
  • temperature: 0
  • seed: 42
  • max_tokens: 1024
  • stream: false
  • 5回実行し、1回目と5回目を除外
  • 2〜4回目の平均を採用

結果は次の通りです。

条件 平均wall time 生成速度 prompt eval completion tokens prompt tokens
26B MTPなし 10.15 sec 51.60 tok/s 87.22 tok/s 519 45
26B MTPあり 10.13 sec 63.18 tok/s 85.94 tok/s 634 45

26Bでも、MTPありでは生成速度が約1.22倍になりました。

63.18 / 51.60 = 1.22

一方で、wall timeはほぼ同じです。これはMTPありの方が出力トークン数が多かったためです。

MTPなし: 519 completion tokens
MTPあり: 634 completion tokens

同一プロンプト、同一seedでも、MTP有無で完全に同じ文章になるとは限りません。そのため、wall timeだけを見ると誤解します。このケースでは、1秒あたりの生成トークン数は伸びていますが、生成されたトークン数も増えたため、総時間としてはほぼ同等になりました。

MTPありの2〜4回目では、ドラフト受理率は次の通りです。

draft acceptance = 0.66163
350 accepted / 529 generated
mean len = 2.72

26B A4Bは、31Bより小さいモデルを使いたい場合の候補になります。ただし、今回の測定では「MTPでwall timeが大きく短縮された」とは言い切れません。生成速度は伸びたものの、出力長の差で相殺された、という見方が妥当です。

Vision対応モデルで使う場合

Vision対応モデルを使う場合、テキストモデル本体とは別に mmproj が必要になることがあります。

今回の環境では、次のようなファイルを使います。

/home/<user>/.local/opt/llm/models/gemma-4-31B-mmproj/mmproj-BF16.gguf

Vision対応で起動する場合は、MTPありの起動例に --mmproj を追加します。

llama-server \
  -m /home/<user>/.local/opt/llm/models/gemma-4-31B-it-qat-UD-Q4_K_XL.gguf \
  -md /home/<user>/.local/opt/llm/models/mtp-gemma-4-31B-it.gguf \
  --spec-type draft-mtp \
  --spec-draft-n-max 4 \
  --spec-draft-p-min 0.8 \
  --spec-draft-device SYCL0 \
  --spec-draft-ngl all \
  --mmproj /home/<user>/.local/opt/llm/models/gemma-4-31B-mmproj/mmproj-BF16.gguf \
  --alias local-vision \
  --host 127.0.0.1 \
  --port 18081 \
  -c 98304 \
  -ctk q8_0 \
  -ctv q8_0 \
  --jinja \
  -ngl all \
  -sm none \
  -t 8 \
  --device SYCL0 \
  --parallel 1 \
  -fa 1

注意点があります。

MTPは画像理解そのものを高速化する仕組みではありません。画像入力では、画像の前処理や埋め込み生成、projector処理が別途発生します。

一方で、画像入力後のテキスト回答生成フェーズでは、モデルとllama.cpp側の実装が対応していれば、MTPの恩恵を受けられる可能性があります。

この記事ではVisionの性能比較はしていません。あくまで、Vision対応モデルを運用する場合の起動オプションとして紹介するに留めます。

参考:

ハマりどころ

メインモデルとドラフトモデルの対応

MTPでは、メインモデルとドラフトモデルの対応関係が重要です。

今回の構成では次の組み合わせに固定しています。

main : gemma-4-31B-it-qat-UD-Q4_K_XL.gguf
draft: mtp-gemma-4-31B-it.gguf

別サイズや別ファミリのドラフトモデルを雑に組み合わせると、起動できなかったり、受理率が落ちたり、速度が出なかったりします。

--spec-draft-n-max は大きければよいわけではない

--spec-draft-n-max は、ドラフト側が先読みする最大トークン数です。

値を大きくすれば常に速くなる、というものではありません。ドラフト候補の検証コストや不採用時の無駄も増えます。

今回の検証では 4 を使いました。

ドラフトモデルの配置デバイス

今回のようにメインモデルとドラフトモデルを同じGPUに載せる場合、次の指定を使いました。

--device SYCL0
--spec-draft-device SYCL0
--spec-draft-ngl all

複数GPU構成やiGPU/dGPU構成では、ドラフトモデルを別デバイスに逃がす設計も考えられます。ただし、その場合はデータ転送やバックエンド差も含めて測定が必要です。

llama.cpp master追従時の挙動変化

llama.cppは開発が速いプロジェクトです。特にSYCL、multimodal、speculative decoding周辺はバージョン差の影響を受ける可能性があります。

記事や検証結果を読むときは、必ず以下を確認した方がよいです。

  • llama.cpp build number
  • commit hash
  • backend
  • compiler / CUDA / oneAPI / ROCm などのバージョン
  • モデルとドラフトモデルの組み合わせ

まとめ

gemma-4-31B-it 系モデルでMTPドラフターを使ったところ、今回の固定プロンプトでは生成速度が約1.72倍になりました。

条件 生成速度 wall time
MTPなし 20.52 tok/s 19.21 sec
MTPあり 35.37 tok/s 11.19 sec

MTPは単純な「有効化すれば必ず速くなる」機能ではありません。ドラフトモデルとの相性、受理率、GPUバックエンド、起動オプションに依存します。

ただし、条件が合えばローカルLLMの体感速度を大きく改善できます。特に31Bクラスのモデルでは、生成待ち時間の短縮がそのまま使い勝手に効きます。

次回は、OpenCode環境でiGPUを補助LLMに使い、タイトル生成やRAG処理をdGPUから逃がした話を書く予定です。

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?