M2 Pro Mac Mini で Qwen3.5-35B-A3B を llama.cpp で動かす

この記事について
先日、Reddit の r/LocalLLaMA で llama.cpp の高速化 PR が話題になっていた。
https://www.reddit.com/r/LocalLLaMA/comments/1rn7w7b/
「Qwen3.5 / Qwen-Next のトークン生成速度が大幅に向上」とのこと。 CUDA 環境では 1.5〜2倍という報告もある。
ただし自分の環境は M2 Pro の Mac。Metal 対応はどうなのか? 実際にセットアップして計測した結果、9B の dense モデルより 35B の MoE モデルの方が速いという直感に反する結果が得られたので、検証過程をまとめておく。
話題の PR: GATED_DELTA_NET 演算子の追加
https://github.com/ggml-org/llama.cpp/pull/19504
Qwen3.5 系モデルが採用している Gated DeltaNet のための演算子を ggml に追加した PR。CUDA と CPU に対応しており、CUDA 環境では大幅なスピードアップが確認されている。
Reddit で報告されていた数値を抜粋する。
| ユーザー | GPU | モデル | 変化 |
|---|---|---|---|
| hajime-owari | GTX 1660S 6GB | Qwen3.5-35B-A3B | PP: 70→140 t/s, TG: 20→22 |
| SkyFeistyLlama8 | Snapdragon X Elite | Qwen3.5-35B-A3B IQ4_NL | TG: 9.67→17.32 (約1.8倍) |
| andy2na | -- | Qwen3.5-9B | TG: 63→68 (+6-8%) |
Mac (Metal) はどうか
結論から言うと、PR #19504 の Metal 対応はまだない。現状は fallback で動作する。ggerganov が Metal カーネルの追加を示唆するコメントを残しているが、時期は未定。
ただし、別の PR #19375 (グラフ最適化) で Metal 向けの改善は既に入っている。M2 Ultra での計測では PP 1.19〜1.37倍、TG 1.28〜1.34倍の改善が報告されている。
つまり Mac ユーザーも llama.cpp を最新にする価値はある。
Qwen3.5-35B-A3B とは何者か
検証に入る前に、このモデルの構成を整理しておく。
公式リポジトリ から引用すると:
- 総パラメータ: 35B
- アクティブパラメータ: 3B (モデル名の「A3B」= Active 3B)
- エキスパート構成: 256個中 8 Routed + 1 Shared = 9個がアクティブ
レイヤー構成は以下の通り。
10 × (3 × (Gated DeltaNet → MoE) → 1 × (Gated Attention → MoE))
全40レイヤーのうち30が Gated DeltaNet、10が Gated Attention。なお、llama.cpp 内部のメタデータには ssm_* パラメータが存在するが、これは DeltaNet の状態管理が SSM と類似のインターフェースを使っているためで、Mamba 系の SSM とは別物である。
セットアップ
検証環境
- Apple M2 Pro / 32GB Unified Memory
- macOS Darwin 25.2.0
- llama.cpp b8230 (Homebrew)
llama.cpp のインストール
Homebrew で一発。Metal は自動で有効になる。
brew install llama.cpp
# 既にインストール済みなら
brew upgrade llama.cpp
モデルのダウンロード
unsloth のリポジトリから Q4_K_M (20.5GB) を取得した。
from huggingface_hub import hf_hub_download
path = hf_hub_download(
repo_id="unsloth/Qwen3.5-35B-A3B-GGUF",
filename="Qwen3.5-35B-A3B-Q4_K_M.gguf",
local_dir="~/models"
)
ちなみに bartowski のリポジトリは bartowski/Qwen_Qwen3.5-35B-A3B-GGUF とアンダースコア区切りになっている。bartowski/Qwen3.5-35B-A3B-GGUF では 404 になるので注意。
:::message
Homebrew 版の huggingface-cli は Python 3.10 にハードコードされている。mise 等で別バージョンの Python を使っている場合、tqdm の依存関係でエラーになることがある。上記のように Python スクリプトから直接ダウンロードすれば回避できる。
:::
ベンチマーク: 35B-A3B vs 9B
ここが本題。「35B は重すぎるのでは? 9B の方が速いのでは?」と思い、両方試した。
測定条件
llama-bench -m <model.gguf> -p 128,256,512 -n 64,128,256 -r 3
結果
| テスト | Qwen3.5-9B (dense, 5.3GB) | Qwen3.5-35B-A3B (MoE, 20.5GB) | 差 |
|---|---|---|---|
| pp128 | 221.7 t/s | 238.0 t/s | +7% |
| pp256 | 233.0 t/s | 291.9 t/s | +25% |
| pp512 | 237.0 t/s | 330.8 t/s | +40% |
| tg64 | 16.7 t/s | 19.4 t/s | +16% |
| tg128 | 16.6 t/s | 19.7 t/s | +19% |
| tg256 | 16.6 t/s | 18.7 t/s | +13% |
全テストで 35B-A3B の勝ち。
なぜ 35B の方が速いのか
一見おかしいが、仕組みを考えれば納得できる。
- 9B dense: 毎トークン、9B パラメータ全てを計算する
- 35B-A3B MoE: 256エキスパートのうち 9個だけ選択し、さらに 30/40 レイヤーが軽量な Gated DeltaNet。公式発表の通り実効アクティブパラメータは約 3B
メモリは 20.5GB 消費するが (全エキスパートの重みをロードするため)、実際の演算量は 9B より少ない。Apple Silicon の Unified Memory に全部載りさえすれば、計算量の差がそのまま速度差になる。
メモリに載るなら MoE の方が「速くて賢い」。 これが今回の最大の学びでした。
実用: サーバーモードで使う
起動コマンド
llama-server \
-m ~/models/Qwen3.5-35B-A3B-Q4_K_M.gguf \
-c 4096 \
--port 8081 \
--reasoning-budget 0
http://127.0.0.1:8081 を開くとチャット UI が表示される。
Thinking を OFF にしてみる
Qwen3.5 はデフォルトで <think> タグ内に内部推論を出力する。品質向上に寄与するが、数百〜千トークンを消費するため体感速度が大きく落ちる。
--reasoning-budget 0 を付けると thinking が完全に無効化される。カジュアルな会話ではこちらの方が快適だった。
| オプション | 効果 |
|---|---|
--reasoning-budget -1 |
thinking 無制限 (デフォルト) |
--reasoning-budget 0 |
thinking 完全 OFF |
API からの利用
/completion エンドポイントが安定して動作する。
curl http://127.0.0.1:8081/completion \
-H "Content-Type: application/json" \
-d '{
"prompt": "<|im_start|>user\nHello\n<|im_end|>\n<|im_start|>assistant\n",
"temperature": 0.7,
"n_predict": 300,
"stop": ["<|im_end|>", "<|im_start|>"]
}'
:::message alert
OpenAI 互換の /v1/chat/completions も存在するが、b8230 時点では Qwen3.5 の thinking パーサーの問題で 500 エラーが発生する場合がある。--reasoning-budget 0 で回避できる可能性はあるが、確実に動かすなら /completion を使う方がよい。
:::
llama-cli のクラッシュ
b8230 時点で llama-cli は Qwen3.5 の thinking 出力をパースする際にクラッシュする。対話的に使いたい場合もサーバーモード + ブラウザ UI の方が安定している。
まとめ
| 項目 | 結論 |
|---|---|
| Mac での推奨モデル | Qwen3.5-35B-A3B (MoE) |
| 推奨量子化 | Q4_K_M (20.5GB) |
| TG 速度 | 35B-A3B: 18.7〜19.7 t/s / 9B: 16.6〜16.7 t/s |
| PP 速度 | 35B-A3B: 238〜331 t/s / 9B: 222〜237 t/s |
| Thinking | OFF 推奨 (--reasoning-budget 0) |
| インターフェース | llama-server + ブラウザ UI |
「パラメータ数が大きい = 遅い」という思い込みを脱却せねばと感じた。 モデルダウンロードする前にベンチしてどれが一番適しているオープンウェイとモデルなのかを探すのが難しい時代になってきてるのかなと
Metal 向けの GATED_DELTA_NET カーネルが実装されれば、さらなる高速化が期待できる。llama.cpp のリポジトリは引き続きウォッチしておきたい。
検証環境: llama.cpp b8230 / Apple M2 Pro 32GB