Mac mini (M4 Pro) で Qwen3.8-27B + DFlash2 を 33 tok/s で動かすまで — 性能が出ないときの3つの落とし穴
はじめに
M4 Pro(48GB)の Mac mini で、Gemma-4-31B や Qwen3.8-27B 級のローカル LLM を動かし、話題の DFlash2(投機的デコーディング)で高速化を目指した記録です。
結論から言うと、最終的に Qwen3.8-27B-4bit + DFlash2 で 33 tok/s(decode) を達成しました。ただし、そこに至るまでに3つの落とし穴がありました。
ちなみにこの構成のモデルは、あの「ドイツ人が魚を飼う」アインシュタインの謎解き(ゼブラパズル)をちゃんと解けるレベルの推論力があります。ローカルモデルがここまで賢い時代なのだな、と実感しました。
環境
- Mac mini M4 Pro / 48GB / 16-core GPU
- mlx-dspark 0.15.1(MLX 版 DSpark / DFlash)
- モデル:
mlx-community/Qwen3.8-27B-4bit+ drafterincoai/Qwen3.8-27B-DFlash2
落とし穴1: llama.cpp + gemma-4-31B + DFlash は逆効果だった
最初は llama.cpp + gemma-4-31B で試しました。
./build/bin/llama-server \
-hf unsloth/gemma-4-31B-it-GGUF:UD-Q4_K_XL \
-hfd Anbeeld/gemma-4-31B-it-DFlash-GGUF:Q4_K_M \
--spec-type draft-dflash --spec-draft-n-max 7 \
-ngl 999 -fa on -c 8192 --parallel 1
結果: 1.78 tok/s。DFlash なしの baseline(4.33 tok/s)より 2.4 倍遅い。加速どころか大惨事です。
受け入れ率は 21%・平均 2.41 token と正常なのに、なぜ遅いのか。測ってみたら原因がはっきりしました。
投機的デコーディングは「N token の検証 ≈ 1 token のデコード」という前提に立っています。しかし Metal バックエンドで「一度の前向き計算で N token 処理する」コストを実測すると:
| batch | 1回の前向き | 相対コスト |
|---|---|---|
| 1 | 153 ms | 1.0× |
| 2 | 351 ms | 2.3× |
| 4 | 666 ms | 4.3× |
| 8 | 1303 ms | 8.5× |
| 16 | 761 ms | 5.0× |
batch 2〜8 の範囲には専用の小バッチパスがあり、コストがまったく償却されません。8.5 倍のコストを払って 2.4 token 得ても損なのです。
コミュニティでも同現象が llama.cpp issue #23752 として報告されています(M1 Max で受け入れ率 100% でも 11% 遅い)。Metal バックエンドの構造的な問題で、設定で直るものではありません。しかも DFlash2 はそもそも llama.cpp の mainline にまだマージされていない PR ブランチでした。
落とし穴2: 「フレームワークを変える」が正解だった
同じ投機的デコーディングのアルゴリズムでも、MLX は検証をひとつの計算グラフに融合するため、トークンごとの kernel dispatch ペナルティがありません。
MLX 移植版の mlx-dspark に乗り換えて Qwen3.8-27B-4bit + DFlash2 を試すと:
| シナリオ | baseline | +DFlash2 |
|---|---|---|
| 一般文 | 7.1 tok/s | 8.0(1.13×) |
| 数学 | 7.1 tok/s | 10.9(1.54×) |
初めて正の利益が出ました。数学プロンプトでは 1 ラウンドあたり平均 4.47 token 受け入れ、160 token の生成にわずか 37 回の検証で済んでいます。
ちなみに 8bit 版も試しました。加速「倍率」は大きい(1.57〜1.68×)ものの、絶対速度は 4bit に及ばず、48GB では swap が膨らみました。実用は 4bit 一択です。
しかし、それでも公式ベンチマークの半分程度の数字しか出ていませんでした。
落とし穴3(本当の元凶): macOS の「低電力モード」
あれこれ疑いました。
- Multipass の VM が 1 コアを 100% 占有していた → 消してみた → +10% だけ
- GPU のコア数違い(16 コア vs 20 コア版)を疑った → 誤診だった
正解は、macOS のシステム設定 > エネルギー > エネルギーモードが「低電力」になっていたことでした。
Apple Silicon は低電力モードだと無言でスループットを約 45% も落とします。エラーメッセージは一切出ない。デスクトップの Mac mini は常時給電なので、低電力にする理由はないのです。
「高出力」に切り替えた瞬間:
| 低電力 | 高出力 | |
|---|---|---|
| baseline | 8.8 decode | 15.1 |
| DFlash2 | 17.6 decode | 33.0 |
公式ベンチマーク(baseline 14.7 / DFlash2 33.8)とほぼ一致。 しかも Zoom 会議を開いたままの数字なので、余力はまだあります。
各要因の実際の寄与はこうでした:
| 要因 | 影響 |
|---|---|
| macOS 低電力モード | 約 −45% |
| 100% 稼働中の VM | 約 −10% |
| GPU コア数(16 vs 20) | なし |
学び
- 投機的デコーディングは実行コスト次第。 アルゴリズムが悪いのか、バックエンドの実行コストが悪いのかを切り分けること。受け入れ率が正常なのに遅いなら、まず検証コスト(小バッチ前向き)を測りましょう。
-
Apple Silicon で性能測定する前に
pmset -g | grep powermodeを確認(2 = 高出力)。低電力モードは無言で性能を半減させます。 - ベンチマークは公表条件をよく読む。 decode と end-to-end は別物。単発の数字はノイズ(±14%)なので複数回の中央値を。
- 48GB の M4 Pro はローカル LLM の実用ラインを超えている。 27B を 33 tok/s で回し、エージェントも動かせる。プライベートなデータを手元で処理したい人には十分魅力的な選択肢です。