はじめに
本記事は、ローカルLLMでClaude Codeを動かす検証シリーズの第4弾です。
- 第1弾: DGX Spark 2台スタッキングでQwen3.5-397B — コンテキスト長8Kの壁で成功率40%
- 第2弾: Qwen3.5-122Bでリベンジ — 262Kコンテキストで成功率100%、189.6秒
- 第3弾: claude-localを作った — 検証をツール化してOSS公開
第2弾でDGX Spark(NVIDIA GB10 Blackwell)の実力は確認できました。では、MacStudio RAM256GB(M3 Ultra)だとどうなるか? 同じモデル、同じタスク、同じ評価基準で対決させました。
結果: Mac Studioが1.9倍速。ただし、そこに至るまでに4つのボトルネックを発見・解消する必要がありました。その過程こそが本記事の本題です。
結論
| # | タスク | DGX Spark | Mac Studio | 速度比 |
|---|---|---|---|---|
| 1 | 知識Q&A | 12.3s | 12.0s | 1.0x |
| 2 | ファイル読み取り | 22.4s | 16.0s | 1.4x |
| 3 | コード生成 | 24.1s | 16.1s | 1.5x |
| 4 | バグ修正 | 79.3s | 29.3s | 2.7x |
| 5 | マルチステップ | 51.5s | 25.1s | 2.1x |
| 合計 | 189.6s | 99.4s | 1.9x |
成功率は両方とも 5/5(100%)。
環境
ハードウェア
| 項目 | Mac Studio | DGX Spark |
|---|---|---|
| チップ | Apple M3 Ultra | NVIDIA GB10 (Grace + Blackwell) |
| メモリ | 256 GB (UMA) | 128 GB (UMA) |
| メモリ帯域 | ~800 GB/s | ~273 GB/s |
| CPU | 28コア | 20コア (ARM Cortex) |
| 価格帯 | 約82万円 | 約70万円 |
ソフトウェア
| 項目 | Mac Studio | DGX Spark |
|---|---|---|
| LLMバックエンド | MLX (mlx-lm) | vLLM |
| モデル | Qwen3.5-122B-A10B-4bit | Qwen3.5-122B-A10B-int4 |
| 量子化 | INT4 (63 GB) | INT4 (63 GB) |
| APIゲートウェイ | 自作 (aiohttp) | パススルー |
| Raw速度 | 55.3 tok/s | ~15 tok/s(推定) |
Raw速度ではMac Studioが約3.7倍速い。 メモリ帯域(800 vs 273 GB/s)の差がそのまま出ています。LLM推論はメモリ帯域律速なので、この差は本質的です。
最適化の記録: 289.7秒 → 99.4秒
最終結果に至るまでに4つのボトルネックを発見しました。記事にする価値があるのは、どれも「ハードウェアの問題」ではなく「ソフトウェアの問題」だったことです。
最適化の全体像
| 段階 | 合計時間 | 改善率 | 施策 |
|---|---|---|---|
| 初回 | 起動失敗 | — | Python 3.14でuvloopが壊れてLiteLLM起動不可 |
| 自作ゲートウェイ v1 | 289.7s | ベースライン | Anthropic→OpenAI変換プロキシを自作 |
| thinking無効化 | 252.2s | -13% | Qwen3.5のthinkingモードをOFF |
| settings.json最適化 | 217.2s | -14% | KVキャッシュ無効化ヘッダーを除去 |
--tools制限 |
99.4s | -54% | 22ツール → 3ツール |
ボトルネック1: APIフォーマットの壁
問題: Claude CodeはAnthropic Messages API形式でリクエストを送りますが、MLXはOpenAI Chat Completions形式しか受け付けません。DGX SparkのvLLMはAnthropic形式をネイティブサポートしていたので問題になりませんでした。
最初の試み: LiteLLM — Claude Code公式推奨のAPIゲートウェイですが、macOS 26.3の標準Python 3.14でuvloopのビルドが失敗。Python 3.12で別venvを作っても、LiteLLM内部のルーティングバグ(Anthropic APIがOpenAI Responses APIに誤ルーティングされる)で動かず。
解決策: Anthropic→OpenAI変換ゲートウェイを自作しました。aiohttp + uvloopベースで、ストリーミングSSE変換を含む約300行のPythonスクリプトです。
Claude Code (Anthropic形式)
↓ POST /v1/messages
自作ゲートウェイ (port 4000)
↓ POST /v1/chat/completions
MLXサーバー (port 8000)
↓ Qwen3.5-122B-A10B-4bit
変換で最も難しかったのはツール呼び出しの双方向変換です。
| Anthropic形式 | OpenAI形式 |
|---|---|
content block type: "tool_use"
|
message.tool_calls[] |
content block type: "tool_result"
|
role: "tool" メッセージ |
tool_choice.type = "any" |
tool_choice = "required" |
SSE content_block_delta
|
SSE delta.tool_calls[].function.arguments
|
ボトルネック2: Qwen3.5のthinkingモード
問題: Qwen3.5はデフォルトで「thinking」モードが有効です。応答の前に推論過程をreasoningフィールドに出力しますが、これがmax_tokensを消費します。
# "Say hello in Japanese" に対して:
max_tokens=64: reasoning=64 tokens, content="" (空!)
max_tokens=1024: reasoning=700 tokens, content="こんにちは。"
Claude Codeのようなエージェント用途では、thinkingトークンは無駄です。特にツール呼び出し時は、ツール名と引数を返すだけなのに700トークンの「考え中」が挟まります。
解決策: MLXのchat_template_kwargsでthinkingを無効化。
oai_req["chat_template_kwargs"] = {"enable_thinking": False}
効果: 同じ質問に対して780トークン → 3トークンに削減。
ボトルネック3: MLXのプレフィックスキャッシュの制約
第2弾の記事で紹介したCLAUDE_CODE_ATTRIBUTION_HEADER=0設定。DGX SparkではvLLMの--enable-prefix-cachingと組み合わせて4倍の高速化を実現しました。
しかしMLXでは同じ効果が得られませんでした。
調査の結果、MLXのキャッシュ実装に根本的な制約があることが判明:
# mlx_lm/models/cache.py (v0.31.1)
class ArraysCache:
def is_trimmable(self):
return False # ← ハードコードでFalse!
vLLMはトークン列の共通プレフィックスを検出してKVキャッシュを再利用できますが、MLXは完全一致のみ。Claude Codeのように毎回微妙に異なるプロンプトを送るユースケースでは、MLXのキャッシュはほぼ効きません。
# 同一プロンプト → ヒット
Request 1: cached=20329/20334, time=1.0s
Request 2: cached=20329/20334, time=0.4s ← 完全一致
# 末尾だけ異なる → ミス
Request "Say A": cached=0/20334, time=31.6s
Request "Say B": cached=0/20334, time=31.1s ← 毎回31秒
| 特性 | vLLM | MLX (0.31.1) |
|---|---|---|
| キャッシュ方式 | Block-level prefix sharing | LRU prompt cache |
| プレフィックス再利用 | 共通プレフィックスで再利用 | 完全一致のみ |
| Claude Codeとの相性 | ◎ 25Kトークンの共通部分を再利用 | △ 毎回フルプレフィル |
MLXの今後に期待: ArraysCache.is_trimmable() が True になれば、さらなる高速化が見込めます。
ボトルネック4: ツール定義のサイズ(最大の発見)
これが最大のボトルネックでした。
Claude Codeはデフォルトで22個のツールを送信します。その合計サイズ:
| 要素 | 文字数 | 割合 |
|---|---|---|
| ツール定義(22個) | 69,640 | 79% |
| システムプロンプト | 16,082 | 18% |
| ユーザーメッセージ | 1,833 | 2% |
プロンプトの8割がツール定義です。MLXのプレフィックスキャッシュが効かないため、毎回20,000トークン以上のプレフィルが発生していました。
22ツール: 20,335 tokens → プレフィル 32秒
3ツール: ~6,000 tokens → プレフィル 12秒(初回), 0.8秒(キャッシュヒット時)
解決策: claude --tools "Bash,Read,Write" で必要最小限の3ツールに制限。
この変更だけで 217.2秒 → 99.4秒(54%削減)。最適化の中で最も効果が大きく、かつ最も簡単な変更でした。
なぜDGX Sparkでは問題にならなかったか
DGX Sparkでもツール定義は同じ22個(70K文字)が送られていましたが、vLLMのプレフィックスキャッシュが共通プレフィックスを再利用するため、2回目以降のリクエストでは25Kトークンのプレフィル分が丸ごとスキップされていました。
つまり:
- DGX Spark: 22ツールでもキャッシュが効く → 問題にならない
- Mac Studio: キャッシュが効かない → ツールを減らして物理的にプロンプトを小さくする
同じモデル、同じClaude Codeなのに、バックエンドのキャッシュ実装の違いで最適化アプローチが変わるという、興味深い結果です。
タスク別の詳細比較
Task 1: 知識Q&A(12.0s vs 12.3s)
ツール不要の単純なQ&A。ほぼ同等。Claude Codeの起動オーバーヘッド(~10秒)が支配的で、モデル推論時間はわずか。
Task 4: バグ修正(29.3s vs 79.3s → 2.7倍速)
最も差がついたタスク。バグ修正は:
- ファイルを読む(Read)
- バグを分析する(長いトークン生成)
- 修正版を書く(Write)
ステップ2の長いトークン生成でMac Studioの55.3 tok/sが威力を発揮しました。DGX Sparkの~15 tok/sとの3.7倍の差が、そのままタスク時間の差に表れています。
Task 5: マルチステップ(25.1s vs 51.5s → 2.1倍速)
ディレクトリ作成→実装→テスト作成→テスト実行→結果報告という5ステップ。複数のツール呼び出しが連鎖するため、各ステップでの推論速度の差が累積しました。
再現手順
前提条件
- Mac Studio M3 Ultra (128 GB以上推奨)
- Homebrew, Node.js, Claude Code
- Python 3.12 + Python 3.14
セットアップ
# Python 3.12 venv(ゲートウェイ用)
brew install python@3.12
/opt/homebrew/opt/python@3.12/bin/python3.12 -m venv .venv312
# Python 3.14 venv(MLX用)
python3 -m venv .venv
source .venv/bin/activate
pip install mlx-lm
Claude Code設定
// ~/.claude/settings.json
{
"env": {
"CLAUDE_CODE_ATTRIBUTION_HEADER": "0",
"CLAUDE_CODE_ENABLE_TELEMETRY": "0",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
}
}
実行
# ターミナル1: MLXサーバー
source .venv/bin/activate
python3 -m mlx_lm.server --model mlx-community/Qwen3.5-122B-A10B-4bit --port 8000
# ターミナル2: ゲートウェイ + ベンチマーク
source .venv312/bin/activate
python3 gateway.py & # port 4000
ANTHROPIC_BASE_URL=http://127.0.0.1:4000 \
ANTHROPIC_AUTH_TOKEN=local \
claude --print --tools "Bash,Read,Write" --max-turns 5 --permission-mode bypassPermissions \
-p "Pythonのデコレータの仕組みを3文で説明してください"
まとめ
| 学び | 内容 |
|---|---|
| メモリ帯域が全て | M3 Ultra(800 GB/s)はGB10(273 GB/s)の3倍 → tok/sも3.7倍 |
| キャッシュ実装が鍵 | vLLMのプレフィックスキャッシュとMLXのLRUキャッシュは根本的に違う |
| ツール定義が巨大 | Claude Codeのプロンプトの79%はツール定義。--toolsで制御可能 |
| thinkingは切るべき | エージェント用途ではQwen3.5のthinkingモードは無駄 |
| API変換は避けたい | Anthropic→OpenAI変換ゲートウェイは工数がかかる。vLLMのネイティブ対応は大きな利点 |
DGX Sparkの70万円 vs Mac Studioの82万円。価格は1.17倍ですが、性能は2倍。そしてMac StudioはmacOSの普段使いマシンとしても使えることを考えると、研究者の個人環境としてはMac Studioに分があるかもしれません。
一方、DGX SparkはvLLMのエコシステム(プレフィックスキャッシュ、Anthropic API対応)の恩恵をフルに受けられるため、サーバー用途やチーム共有環境では有利です。
ローカルLLMでClaude Codeを動かすという試みは、ハードウェアの選択だけでなく、ソフトウェアスタックの最適化が性能を大きく左右することを教えてくれました。
シリーズ記事: