M4 MacBook Pro 上でのローカルQwen 3.8 27Bモデルを用いた9つのコーディングハーネスのベンチマーク

TL;DR

ローカルで提供されるQwen 3.8 27Bモデルを搭載した1台のM4 MacBook Proで、9つの人気コーディングエージェントハーネスを実行すると、明確な3つのパフォーマンスグループが明らかになります。(1) 軽量で安定したハーネス (pi、mini-swe-agent、chad) は、サブ秒のターンレイテンシーで約8トークン/秒を実現。(2) 高機能だが規律あるハーネス (dsh、cline、codex、goose) は、6〜8トークン/秒を達成するが、最初のトークンまでの待ち時間が長くなる。(3) 起動に時間がかかるハーネス (crush、opencode) は、出力前に3〜4分を費やし、約5〜6トークン/秒に低下する。その違いは、主にシステムプロンプトのサイズ、ツールスキーマの数、キャッシュ再利用の効率に起因します。


実験セットアップ

  • ハードウェア: Apple M4 MacBook Pro、24 GB RAM、macOS 26.6.2。
  • モデル: Qwen 3.8 27B、unsloth/Qwen3.8-27B-GGUF による3ビット量子化、llama.cpp (ビルド10470) で提供。
  • サーバー: すべてのハーネスで共有される単一の llama-server インスタンス。プロキシが均一なサンプリング方式 (temperature = 1.0、top_k = 20、top_p = 0.95、min_p = 0.05) を強制。
  • キャッシュ: 32,768トークンの統合プレフィックスキャッシュを4つのスロットに分割。
  • ベンチマーク: 8つのExercism Python演習を、それぞれ自動承認モードで同一の一文プロンプトを使用して実行。メトリクスはサーバー自身のアカウンティングから収集 (2つの chad 行を除く。これらは内部トレースを使用)。

メトリクスの定義

メトリクス 意味
最初のトークンまでの待ち時間 システムプロンプト、ツールスキーマ、最初のユーザーリクエストをプリフィルする時間。
後続ターンの待ち時間 最初のターン後の待ち時間の中央値と90パーセンタイル (サイドリクエストを除く)。
キャッシュ再利用 後続ターンでプレフィックスキャッシュから提供されるトークンの割合。
実効トークン/秒 生成されたトークンの総数をウォールクロック時間で割った値。プリフィルとツールのオーバーヘッドを含む。
パス (ゲート) 1,200秒のタイムアウト内に完了したExercismタスクの数。

ローカル推論がクラウドより難しい理由

  1. 大きなシステムプロンプトとツールスキーマ – 約90トークン/秒の読み取りと約10トークン/秒の書き込みを行うラップトップでは、2,000トークンのプロンプトは生成前に約22秒かかります。18,000トークンのプロンプト (Opencodeが使用) は約226秒かかります。クラウドGPUは10kトークン/秒以上でプリフィルするため、これらの遅延は数分の1秒に短縮されます。
  2. コンテキストウィンドウの縮小 – システムプロンプトを消費した後、残りのコンテキストは多くの場合32kトークン未満です。Opencodeの18kトークンプロンプトは、実際の作業用にウィンドウの約44%しか残しませんが、軽量なpiハーネスは約94%を保持します。
  3. サイドリクエストのオーバーヘッド – 補助リクエスト (コードサマリーなど) を繰り返し発行するハーネスは、ローカルモデルがキューに入るか再プリフィルを引き起こし、GPUをウォールクロック時間の100%以上「ビジー」にします。

ベンチマーク結果

ハーネス バージョン ツール プロンプト (トークン) 最初のトークンまでの待ち時間 後続ターンの待ち時間 (中央値・p90) キャッシュ再利用 トークン/秒 パス (24件中)
mini-swe-agent 2.4.6 1 1,171 12.2秒 3.6秒・21秒 96% 8.0 11 (14タイムアウト)
pi 0.80.3 4 2,008 21.6秒 1.3秒・22秒 99% 8.1 19 (7タイムアウト)
cline 3.0.60–61 26 5,876 64.1秒 9.9秒・52秒 94% 7.3 17
codex 0.151.0 10 7,804 87.8秒 9.6秒・28秒 94% 6.9 19 (5タイムアウト)
dsh 0.1.1-rc.2 25 8,052 94.4秒 2.2秒・34秒 99% 7.2 18
goose 1.50.0 18 9,617 110.3秒 1.0秒・22秒 100% 8.0 22 (3タイムアウト)
crush 0.92.0 26 16,263 199.8秒 1.8秒・40秒 100% 5.8 18 (8タイムアウト)
opencode 1.17.12 10 18,046 225.7秒 4.6秒・44秒 99% 5.7 15 (13タイムアウト)
chad (llama.cpp) 2.0.3 5 2,563 25.6秒 0.8秒・19秒 99% 7.9 24
chad (MLX, シリアル) * 2.0.3 5 2,566 4.7秒 1.0秒・19秒 99% 12.4 21 (3タイムアウト)
chad (MLX, dflash2) * 2.0.3 5 2,562 4.6秒 0.9秒・36秒 99% 17.4 22 (3タイムアウト)

数値の解釈

  • 軽量ハーネス (pi、mini-swe-agent、chad) はシステムプロンプトを約2kトークン未満に保ち、99%以上のキャッシュ再利用を達成し、約8トークン/秒を維持します。chadのMLXバリアントは、キャッシュをプロセス内で所有することでスループットを2倍 (最大17.4トークン/秒) にします。
  • 高機能だが規律あるハーネス は、最初のトークンまでの待ち時間が長くなります (最大約110秒) が、キャッシュ再利用が高いため、定常状態のスループットは respectable (約7トークン/秒) です。
  • 起動に時間がかかるハーネス (crush、opencode) は、出力前に3分以上費やし、6トークン/秒以下に低下するため、ラップトップでは実用的ではありません。
  • パス率はレイテンシーと相関: chad (両バリアント) だけが24タスクすべてを解決しました。次点はgoose (22/24)。mini-swe-agentの低いパス数は、トークン速度が良いにもかかわらず頻繁なタイムアウトに起因します。

ローカルモデルハーネスの設計教訓

  1. システムプロンプトを削る – 余分なトークンはラップトップで約0.01秒のプリフィルレイテンシーを追加します。2kトークン未満を目指します。
  2. ツールスキーマを制限する – 追加のツールごとにプロンプトサイズが増加し、使用可能なコンテキストウィンドウが減少します。
  3. プレフィックスキャッシュを永続化する – ターン間でキャッシュされたプリフィルを再利用 (95%以上の再利用) すると、繰り返しの作業がなくなり、実効スループットが大幅に向上します。
  4. エージェントループとモデルを同じ場所に配置する – chadのプロセス内MLX実装は、キャッシュを所有することでネットワークラウンドトリップがなくなり、2倍〜3倍の高速化が得られることを示しています。
  5. ドラフターを提供する – DFlash2ドラフターはchadのトークンレートを12.4→17.4トークン/秒に引き上げ、初期トークン生成のための軽量ドラフトモデルの価値を実証しました。

コミュニティフィードバックのハイライト

「リソースが制約された環境向けのコーディングエージェントが必要なら、hax をチェックしてください。最小限のプロンプトとツールセットを備えた0.7 MBのネイティブバイナリです。」 – OleksandrC

「このようなベンチマークは急速に変化します。再現可能なリポジトリがあれば、誰でも自分のハードウェアでターンごとのトークン数、最初のトークンのレイテンシー、パス率を比較できます。」 – humbleferret

「jcodeは小さなRustバイナリでRAM使用量においてすべてを上回りますが、この研究には含まれていませんでした。」 – lrvick

「Reasonixはプレフィックスキャッシュの再利用に重点を置いているため、興味深い比較対象になるかもしれません。」 – swiftcoder

これらのコメントは、軽量なプロンプト、オープンソースの再現性、および元のマトリックスに含まれていない代替の超軽量エージェントの存在の重要性を強調しています。


ベンチマークの再現

実験全体は chad リポジトリでスクリプト化されています。

# リポジトリをクローン
git clone https://github.com/nathansutton/chad.git
cd chad

# 依存関係をインストール (uv推奨)
uv run python benchmarks/matrix/run.py setup   # モデルをダウンロード、llama.cppをビルド
uv run python benchmarks/matrix/run.py smoke   # スモークテスト
uv run python benchmarks/matrix/run.py llama   # llama.cppサーバー経由ですべてのハーネスを実行
uv run python benchmarks/matrix/run.py mlx     # MLXバックエンドでchadを実行
uv run python benchmarks/matrix/run.py table   # 上記のマークダウンテーブルを生成

すべての生データ (grid.jsonturns.jsonl) はベンチマークコードとともにコミットされており、単一の行から集計された数値までの完全なトレーサビリティが保証されます。


まとめ

クラウドLLMをラップトップ上のローカルモデルに置き換える場合、支配的な要因はプロンプトサイズによるプリフィルレイテンシーです。事実上無料のクラウドプリフィル用に設計されたハーネス (大きなシステムプロンプト、多数のツールスキーマ) は、ローカルでは使用できなくなります。規律ある最小限のプロンプトと永続的なプレフィックスキャッシュ、可能であればプロセス内モデルループを組み合わせることで、コンシューマーハードウェアでクラウドに近いスループットを実現できます。

Sources

関連