LM Studio MTP投機的デコーディング|32GB Macで速度を測る手順
- LM Studio 0.4.14 で安定版になった MTP(Multi-Token Prediction)投機的デコーディング の仕組みと有効化手順がわかる
- 32GB Apple Silicon Mac mini で載るモデルサイズの考え方と、メモリ不足で落ちないための注意点がわかる
- OpenAI互換APIで ON/OFFの tok/s を自分で計測する Python スクリプト(標準ライブラリのみ)をそのまま使える
- 対象:LM Studio でローカルLLMを動かしていて「もう少し速くしたい」人
要点(TL;DR)
- MTPは、モデル自身が持つ「数トークン先を予測するヘッド」を下書き役に使う投機的デコーディング。別の小型ドラフトモデルを用意する従来方式より、語彙の不一致などの相性問題が起きにくい。
- LM Studio では 0.4.14(2026年5月)で MTP 投機的デコーディングが stable になった。使えるのは MTP ヘッド付きのモデル(例:Qwen3.6 系の MTP 版 GGUF)に限られる。
- 速くなるかは出力の性質次第。コードや定型文のように予測しやすい出力は受理率が上がり効きやすく、自由な創作文は効きにくい。
- コミュニティ報告では Qwen3.6-27B で「約20→約33 tok/s」といった例があるが、筆者環境での再現値は未検証。だからこそ下の計測スクリプトで自分のマシンで測るのが確実。
- 32GB Mac では GPUに割り当てられるメモリ上限が先に問題になる。MTP分の追加メモリも見込んでモデル・量子化を選ぶ。
動作環境 / 前提
| 項目 | 内容 |
|---|---|
| マシン | Mac mini(Apple Silicon, ユニファイドメモリ 32GB) |
| OS | macOS(Apple Silicon 対応の最近のバージョン) |
| LM Studio | 0.4.14 以降(MTP 投機的デコーディングが stable になった版) |
| 推論エンジン | llama.cpp ランタイム(GGUF)。MLX エンジンでの MTP 対応状況は未検証 |
| モデル例 | Qwen3.6-27B-MTP-GGUF / Qwen3.6-35B-A3B-MTP-GGUF(報道ベースで対応が確認されているもの) |
| 計測用 | Python 3.10+(標準ライブラリのみ) |
注意:LM Studio は更新が速く、UI の項目名や必要なランタイム版は変わり得ます。手元の画面と違う場合は公式 changelog を優先してください。
出典:LM Studio 0.4.14 changelog
MTP投機的デコーディングとは何か
通常の生成が遅い理由
LLM の生成は「1トークン出す → それを入力に足す → 次の1トークン」の繰り返しです。ローカル環境ではこの1ステップごとにモデルの重み全体をメモリから読むため、計算よりメモリ帯域がボトルネックになりがちです。Apple Silicon はメモリ帯域が広いとはいえ、27B級になると1トークンずつの逐次生成が体感速度を決めます。
投機的デコーディングの発想
「安い方法で数トークン先をまとめて下書きし、本体モデルでまとめて検証する」のが投機的デコーディングです。本体モデルは複数トークンを1回の forward で検証できるので、下書きが当たれば当たるほど、1回の重み読み込みで進むトークン数が増えます。下書きが外れたトークンは捨てて本体の出力を採用するため、出力の品質(分布)は本体モデル単体と同じになるのがポイントです。
従来方式(ドラフトモデル)と MTP の違い
| 観点 | ドラフトモデル方式 | MTP 方式 |
|---|---|---|
| 下書き役 | 別の小型モデル(同系列・同じトークナイザが必要) | 本体モデルに学習済みの追加ヘッド |
| 相性問題 | 語彙が違うと使えない、系列の小型版が無いと困る | 本体と一体なので起きにくい |
| 追加メモリ | 小型モデル1つ分 | MTP ヘッド分(ドラフトモデルより小さい傾向。正確な量はモデル次第で未検証) |
| 使える条件 | 適合する小型モデルがあること | MTP ヘッド付きで配布されたモデルであること |
LM Studio は以前(0.3.x 系)からドラフトモデル方式の投機的デコーディングに対応しており、0.4.14 で MTP 方式が stable 入りしました。
LM Studioで MTP を有効にする手順
UI の文言は版によって揺れるため、流れとして押さえてください。
- LM Studio を 0.4.14 以降に更新し、ランタイム(llama.cpp)も最新にする(設定 → Runtime)。
-
MTP 対応モデルをダウンロードする。検索窓で
MTPを含めて探す(例:Qwen3.6-27B-MTP)。通常版 GGUF には MTP ヘッドが入っていないので、名前に MTP が付いた配布物を選ぶ。 - モデル読み込み時の設定パネルで Speculative Decoding の項目を開き、MTP(または対応するドラフタ)を選択する。
- 解説記事によって「MTP トグルを ON にする」「ドロップダウンで MTP ドラフタを選ぶ」と記述が分かれている。手元の版の表示に従う。
- チャットで動作確認。生成後の統計表示に下書きの受理数(accepted draft tokens 等)が出ていれば効いている。
CLI から確認したい場合は lms を使えます。
# 読み込み済みモデルの確認
lms ps
# ローカルサーバ起動(既定ポート 1234)
lms server start
自分のMacで tok/s を計測するスクリプト
「速くなった気がする」で終わらせず、同じプロンプトで ON/OFF を比べるのが大事です。下は LM Studio の OpenAI 互換 API(http://localhost:1234/v1)に投げて、生成トークン数と所要時間から tok/s を出すスクリプトです。依存ライブラリはありません。
#!/usr/bin/env python3
"""LM Studio の OpenAI 互換APIで生成速度(tok/s)を計測する。
使い方:
python3 bench_tps.py --model qwen3.6-27b-mtp --runs 3 --label mtp_on
(LM Studio 側で MTP を OFF にして再読み込みし)
python3 bench_tps.py --model qwen3.6-27b-mtp --runs 3 --label mtp_off
"""
import argparse
import json
import statistics
import time
import urllib.request
PROMPTS = {
# 予測しやすい出力(MTPが効きやすい想定)
"code": "Pythonで、CSVを読み込み列ごとの平均と最大値を表示する関数を書いてください。コードのみ。",
# 予測しにくい出力(効きにくい想定)
"story": "雨の日の図書館を舞台に、意外な結末の短い物語を日本語で400字程度で書いてください。",
}
def run_once(base_url: str, model: str, prompt: str, max_tokens: int) -> dict:
body = json.dumps({
"model": model,
"messages": [{"role": "user", "content": prompt}],
"max_tokens": max_tokens,
"temperature": 0, # 比較のため決定的に寄せる
"stream": False,
}).encode("utf-8")
req = urllib.request.Request(
f"{base_url}/chat/completions",
data=body,
headers={"Content-Type": "application/json"},
)
t0 = time.perf_counter()
with urllib.request.urlopen(req, timeout=600) as res:
data = json.load(res)
elapsed = time.perf_counter() - t0
tokens = data.get("usage", {}).get("completion_tokens", 0)
return {"tokens": tokens, "sec": elapsed, "tps": tokens / elapsed if elapsed else 0.0}
def main() -> None:
ap = argparse.ArgumentParser()
ap.add_argument("--base-url", default="http://localhost:1234/v1")
ap.add_argument("--model", required=True)
ap.add_argument("--runs", type=int, default=3)
ap.add_argument("--max-tokens", type=int, default=512)
ap.add_argument("--label", default="run")
args = ap.parse_args()
# 1回目はウォームアップ(読み込み・キャッシュの影響を除く)
run_once(args.base_url, args.model, "こんにちは", 16)
for name, prompt in PROMPTS.items():
results = [run_once(args.base_url, args.model, prompt, args.max_tokens)
for _ in range(args.runs)]
tps = [r["tps"] for r in results]
print(f"[{args.label}] {name:5s} tokens={results[-1]['tokens']:4d} "
f"tok/s median={statistics.median(tps):6.2f} "
f"min={min(tps):6.2f} max={max(tps):6.2f}")
if __name__ == "__main__":
main()
出力イメージ(形式のみ。数値は環境で変わるため、ここでは意図的に記載しません):
[mtp_on] code tokens= 512 tok/s median= xx.xx min= xx.xx max= xx.xx
[mtp_on] story tokens= 412 tok/s median= xx.xx min= xx.xx max= xx.xx
計測のコツ
- ウォームアップを捨てる:初回はモデル読み込みやプロンプト処理の影響が大きい。
- temperature=0 に寄せる:サンプリングの揺れを減らして ON/OFF を比べやすくする。
- 出力の種類を分ける:コード系と創作系で効果が違うのが投機的デコーディングの特徴。平均1本で語らない。
- この計測はプロンプト処理(prefill)時間も含む。純粋な生成速度を見たい場合は LM Studio の UI 統計(tok/s 表示)と併用する。
記録用テンプレ
| モデル / 量子化 | 出力種別 | MTP OFF tok/s | MTP ON tok/s | 倍率 |
|---|---|---|---|---|
| Qwen3.6-27B-MTP Q4_K_M | code | 未検証 | 未検証 | 未検証 |
| Qwen3.6-27B-MTP Q4_K_M | story | 未検証 | 未検証 | 未検証 |
| Qwen3.6-35B-A3B-MTP Q4_K_M | code | 未検証 | 未検証 | 未検証 |
参考までに、コミュニティのチュートリアルでは Qwen3.6-27B で約20→約33 tok/s、全体として 1.5〜2倍程度の報告が見られます(公式ベンチマークではなく、ハードウェアも異なる点に注意)。
出典:localllm.in: MTP LM Studio Tutorial
32GB Mac miniでのモデル選びの考え方
32GB のユニファイドメモリでも、GPU(Metal)に割り当てられる量は全量ではありません。macOS 側の上限(wired memory の上限)があり、これを超えるとモデルが読み込めない・極端に遅くなる・アプリが落ちる、といった症状になります。
目安の考え方(数値は量子化・コンテキスト長で大きく変わるため、ファイルサイズを実際に確認してください):
- 27B 密モデル:Q4 量子化でもファイルだけで十数GB後半になる。コンテキストを欲張ると上限に当たりやすい。
- 35B-A3B(MoE):アクティブパラメータが少ないので1トークンあたりは速いが、重み全体はメモリに載せる必要がある。ファイルサイズは 27B より大きくなりがちなので、32GB では量子化を下げる必要が出る可能性がある(実際に載るかは未検証)。
- MoE はもともと1トークンが軽いので、MTP の上積み幅は密モデルと異なる可能性がある。これも計測で確かめたいポイント。
GPU 割当上限の確認・調整は別記事で詳しく書いています(iogpu.wired_limit_mb の話)。
つまずきポイント
Speculative Decoding の選択肢に MTP ドラフタが出てこない
- まずモデルが MTP 版かを確認(ファイル名に MTP が入っているか)。通常版では出ません。
- ランタイム(llama.cpp)が古いままだと出ないことがある。設定 → Runtime で更新する。
- Gemma 4 の MTP ドラフタが macOS でドロップダウンに出ないという GitHub issue(#2044)が報告されています。モデル側の問題の可能性もあるので、まず Qwen3.6 系で動作確認するのが近道です。
出典:LM StudioのMCP設定とGemma 4の動かし方(ISSOH)
ON にしたのに速くならない / むしろ遅い
- 受理率が低い出力(創作・高 temperature)では、下書きを捨てるコストが勝って伸びないことがある。コード系プロンプトで比較し直す。
- メモリ上限ギリギリだとスワップや部分オフロードが起きて、MTP 以前に全体が遅くなる。Activity Monitor のメモリプレッシャーを確認する。
- 比較対象が「別モデル(MTP無し通常版)」になっていないか。同じ MTP 版モデルで ON/OFF を比べる。
読み込み時にクラッシュする
- Gemma 4 QAT GGUF+MTP ドラフタの組み合わせでクラッシュする報告(issue #2036, 環境は RTX 5070 Ti)があります。Mac で同様の問題が出るかは未検証。まずコンテキスト長を下げて読み込めるか試す。
まとめ
- MTP 投機的デコーディングは、本体モデル内の予測ヘッドで下書き→本体で一括検証する高速化。出力品質は本体単体と同じ。
- LM Studio 0.4.14 で stable。MTP 版モデル+最新ランタイムが前提。
- 効果は出力の種類で変わる。コード系と創作系を分けて、同じモデルで ON/OFF を計測するのが正しい比べ方。
- 32GB Mac では GPU 割当上限とコンテキスト長が先にボトルネックになる。
- 未検証事項:筆者環境での実測 tok/s、35B-A3B が 32GB で快適に載る量子化、MLX エンジンでの MTP 対応。計測できたら追記します。
次に読む / 続編
- 32GB Mac で「モデルが載らない」ときの GPU メモリ上限の話:筆者の
iogpu.wired_limit_mb解説記事(プロフィールの記事一覧から) - 続編予定:Qwen3.6-27B と 35B-A3B の MTP 実測比較(ストックしておくと更新に気づけます)。フォローもお待ちしています。