はじめに
前回の記事では、DGX Spark 2台スタッキングでQwen3.5-397Bを動かし、Claude Codeのバックエンドとして検証しました。結果はタスク成功率40%(5タスク中2タスク成功)。モデルの賢さは十分でしたが、8192トークンのコンテキスト制約がエージェントとしての性能を大きく制限していました。
前回の結論は「モデルの限界ではなくインフラの限界」でした。
今回、その仮説を検証します。Qwen3.5-122B(1220億パラメータ)を各ノード独立で動かし、コンテキスト長を262,144トークンに拡大。まったく同じ5タスクで再テストしました。
前回からの変更点
アーキテクチャの刷新
前回(397B)と今回(122B)で、構成が根本的に変わっています。
前回: 397B(2台スタッキング)
今回: 122B(独立並列)
| 項目 | 前回(397B) | 今回(122B) |
|---|---|---|
| モデル | Qwen3.5-397B-A17B(MoE, int4) | Qwen3.5-122B-A10B(MoE, int4) |
| パラメータ数 | 3970億(活性17B) | 1220億(活性10B) |
| 構成 | 2台スタッキング(Tensor Parallel) | 各ノード独立(Solo) |
| コンテキスト長 | 8,192 tokens | 262,144 tokens |
| プロキシ | v1: 重度の圧縮 | v2: フルパススルー |
| システムプロンプト | 1文に圧縮 | 全文そのまま |
| ツール定義 | 6個に削減 | 全ツール通過 |
| メッセージ履歴 | 直近6件のみ | 全履歴保持 |
| 冗長性 | なし(単一障害点) | Failover(2ノード) |
最大の変化はプロキシの役割です。v1はClaude Codeのコンテキストを8Kトークンに「押し込む」ための激しい圧縮を行っていました。v2は262Kのコンテキスト長のおかげで、ほぼ何もしません。Claude Codeが送信するシステムプロンプト、20以上のツール定義、メッセージ履歴をそのままvLLMに転送するだけです。
なぜ122Bなのか
397Bはメモリが足りず、コンテキスト長が8Kに制限されていました。
DGX Spark 2台 = 統合メモリ 256GB
- 397Bモデルの重み: ~200GB
- 残りのKVキャッシュ用メモリ: ~56GB → 8Kコンテキストが限界
122Bなら1台で動きます。
DGX Spark 1台 = 統合メモリ 128GB
- 122Bモデルの重み: ~60GB
- 残りのKVキャッシュ用メモリ: ~68GB → 262Kコンテキストが可能
モデルの重みが軽い分、KVキャッシュにメモリを回せます。そして2台あるのでそれぞれ独立に動かし、プロキシでFailoverするという設計にしました。
ベンチマーク結果
前回とまったく同じ5タスクを実行しました。
総合スコア
| 指標 | Opus 4.6 | Qwen3.5-122B | Qwen3.5-397B(前回) |
|---|---|---|---|
| タスク成功率 | 5/5 (100%) | 5/5 (100%) | 2/5 (40%) |
| 合計時間 | 112.6s | 175.9s | 249.2s |
| 平均時間/タスク | 22.5s | 35.2s | 49.8s |
| ファイル生成成功 | 3/3 | 3/3 | 1/3 |
| バグ修正率 | 3/3 | 3/3 | 1/3 |
タスク別結果
| # | タスク | Opus 4.6 | Qwen3.5-122B | Qwen3.5-397B(前回) | |||
|---|---|---|---|---|---|---|---|
| 時間 | 結果 | 時間 | 結果 | 時間 | 結果 | ||
| 1 | 知識Q&A | 13.1s | 正確 | 31.4s | 正確 | 56.9s | 正確 |
| 2 | ファイル読取 | 14.0s | 完全な分析 | 18.2s | 完全な分析 | 32.1s | max-turns超過 |
| 3 | コード生成 | 25.0s | 正常動作 | 35.8s | 正常動作 | 62.9s | 正常動作 |
| 4 | バグ修正 | 29.5s | 3/3修正 | 43.4s | 3/3修正 | 54.9s | 1/3のみ |
| 5 | マルチステップ | 31.0s | 9/9合格 | 47.0s | 9/9合格 | 42.3s | 部分完了 |
タスク別の詳細
Task 1: 知識Q&A — 全モデル成功
Opus 4.6(13.1s):
Pythonのデコレータは、関数やクラスを修飾して機能を追加・変更するための構文で、
@decoratorの形式で対象の直前に記述します。内部的には、対象の関数を引数として受け取り、新しい関数を返す高階関数です。ログ記録、認証チェック、キャッシュなど、横断的な関心事を既存コードを変更せずに付加する用途で広く使われます。
Qwen3.5-122B(31.4s):
Pythonのデコレータは、関数やメソッドの動作を変更・拡張するための高階関数で、
@decorator構文を使って簡単に適用できます。元の関数をラップして、実行前後で追加処理(ログ出力、認証、タイム計測など)を追加する際に使用されます。関数を引数に取り、ラップした新しい関数を返す関数として実装され、コードの再利用性と可読性を向上させます。
両方とも正確で質に差はありません。122Bの回答は397Bのときより速く(56.9s → 31.4s)、前回のような「回答後もAPIコールを繰り返す」挙動は見られませんでした。
Task 2: ファイル読取 — 122Bが前回の失敗をリカバリ
前回の397B: Readツールの使い方がわからず、max-turnsを超過して失敗。
今回の122B(18.2s): Readツールを正しく使い、行数(49行)、関数一覧(5関数)、要約を完璧に回答。Opusとほぼ同等の速度です。
これがフルコンテキストパススルーの効果です。Claude Codeのシステムプロンプトにはツールの使い方の詳細な説明が含まれており、それをそのまま受け取った122Bは迷わずツールを使えています。
Task 3: コード生成 — 両方成功
両モデルとも正しいFizzBuzz関数を生成し、ファイル保存と実行確認まで完了。
# 両モデルとも標準的なFizzBuzzを生成
def fizzbuzz(n):
result = []
for i in range(1, n + 1):
if i % 15 == 0:
result.append('FizzBuzz')
elif i % 3 == 0:
result.append('Fizz')
elif i % 5 == 0:
result.append('Buzz')
else:
result.append(str(i))
return result
Task 4: バグ修正 — 122Bが劇的改善
3つのバグ(median, variance, percentile)をすべて正しく特定・修正。
| バグ | Opus 4.6 | Qwen3.5-122B | Qwen3.5-397B(前回) |
|---|---|---|---|
| median: mid+1 → mid-1 | 修正 | 修正 | 修正 |
| variance: n-1 → n | 修正 | 修正 | 未検出 |
| percentile: len → len-1 | 修正 | 修正 | 未検出 |
| 別ファイルへの保存 | 正しい | 正しい | 元ファイルを上書き |
前回の397Bは、圧縮されたツール定義から正しい操作手順を推測する必要があり、1つ直しただけで5ターンを消費していました。122Bはフルのツール定義を受け取っているため、効率的にRead→分析→Writeを連携できています。
Task 5: マルチステップ — 完全成功
ディレクトリ作成 → コード生成 → テスト作成 → テスト実行 → 結果報告の5ステップを完遂。
122B: 5ステップすべて完了し、9/9テスト合格。
前回の397B: ディレクトリ作成とコード生成までで5ターンを消費。テストファイルの作成に到達できず。
何が変わったのか
コンテキスト長が全てだった
結果を一言でまとめると、コンテキスト長が8K → 262Kに増えたことで、エージェント性能が40% → 100%に跳ね上がったということです。
前回: 397Bの脳 + 8Kの視野 → 2/5 (40%)
今回: 122Bの脳 + 262Kの視野 → 5/5 (100%)
397Bのほうが3倍以上大きなモデルですが、成績は122Bの方が圧倒的に良い。これは直感に反する結果ですが、理由は明確です。
Claude Codeは内部で以下のようなコンテキストを送信しています。
| コンポーネント | 推定トークン数 |
|---|---|
| システムプロンプト | ~15,000 |
| ツール定義(20+個) | ~10,000 |
| メッセージ履歴(数ターン分) | ~5,000+ |
| 合計 | ~30,000+ |
8Kコンテキストでは、これらを激しく圧縮する必要がありました。
8K = 圧縮プロキシが必須
→ システムプロンプトを1文に → ツールの使い方がわからない
→ ツール定義を6個に絞る → 必要なツールが使えない
→ メッセージ履歴を6件に → 文脈を忘れる
262Kなら、すべてがそのまま収まります。
262K = パススルーで十分
→ システムプロンプト全文 → 正確にツールを使える
→ ツール定義20+個 → すべてのツールにアクセス
→ メッセージ履歴全件 → 文脈を完全に保持
プロキシの役割の変化
# v1(397B向け): 必死の圧縮
COMPACT_SYSTEM = "You are an AI coding assistant. Use the provided tools..." # 1文
ESSENTIAL_TOOLS = [...] # 6個だけ
messages = messages[-6:] # 直近6件のみ
# v2(122B向け): ほぼ何もしない
def safety_compress(data):
if estimate_chars(data) <= SAFETY_CHAR_LIMIT: # 900K文字 ≈ 225Kトークン
return data # 圧縮不要!
v2プロキシの主な仕事は、2ノード間のFailoverと安全弁(262Kに近づいたときだけ圧縮)だけです。実質的にはただのリバースプロキシです。
パラメータ数 vs コンテキスト長
今回の結果は、LLMの性能指標について重要な示唆を与えます。
| 397B (8K ctx) | 122B (262K ctx) | |
|---|---|---|
| 単体の知能(Q&A) | 高い | ほぼ同等 |
| エージェント性能 | 低い(40%) | 高い(100%) |
「大きなモデル = 高性能」という直感は、エージェント用途では成り立ちません。エージェントにとっての「性能」は、ツール定義を正しく理解できるか、前の操作結果を覚えているか、複数ステップを計画的に実行できるか — つまりコンテキスト内の情報をどれだけ活用できるかにかかっています。
これはちょうど、「IQは高いが目が悪くて手元の資料が読めない人」と「IQはやや低いが資料を全部読める人」の違いに似ています。
Opus 4.6との比較
122Bは全タスクを成功させましたが、Opusとはまだ差があります。
| 観点 | Opus 4.6 | Qwen3.5-122B |
|---|---|---|
| タスク成功率 | 100% | 100% |
| 速度 | 1.0x | 1.56x遅い |
| 回答品質 | より洗練 | 十分に実用的 |
| コンテキスト長 | 200K | 262K |
| コスト | API課金 | 無料 |
| データの所在 | Anthropicのサーバー | ローカルのみ |
速度差の要因はネットワークではなく、純粋な推論速度の違いです(Anthropicのクラウドインフラ vs DGX Sparkのローカル推論)。ただし、前回の397B(2.0x遅い)と比べると大幅に改善しています。122Bのほうが活性パラメータが少なく、MoEの効率が良いためです。
副産物: Failoverアーキテクチャ
122Bが1台で動くということは、2台のDGX Sparkにそれぞれ独立したvLLMを立てられるということです。
ここで言う「独立並列」は**ロードバランシング(作業分担)ではなく、Failover(障害時切替)**です。通常時はspark-1だけがリクエストを処理し、spark-2はスタンバイとして待機しています。spark-1が応答しなくなったときに初めて、プロキシが自動的にspark-2へ切り替えます。
片方のノードが落ちても、もう片方が自動で引き継ぎます。397Bのスタッキング構成では、片方が落ちるとシステム全体が停止していました。冗長性の観点でも122B独立並列のほうが優れています。
262Kコンテキストに必要なVRAM
「コンテキスト長が重要」とわかったところで、実際にどれだけのVRAMが必要なのかを確認しておきます。vLLMの起動ログから、メモリの内訳が正確にわかります。
メモリ内訳(実測値)
| コンポーネント | 使用量 |
|---|---|
| モデルの重み(int4) | 62.65 GiB |
| KVキャッシュ(262K対応) | 32.34 GiB |
| CUDAグラフ | ~1.10 GiB |
| 合計 | ~96 GiB |
現在の設定は gpu_memory_utilization: 0.85 で、DGX Sparkの119.6 GiB統合メモリのうち約101.7 GiBを利用しています。
KVキャッシュの容量は352,128トークン分あり、262Kトークンのリクエストを最大5.11件同時に処理できます。つまり、262Kを1リクエスト分だけ確保するなら、KVキャッシュは約24 GiB程度で十分です。
他のGPUで動かすには
| 構成 | 必要VRAM | 代表的なGPU |
|---|---|---|
| 262K x 1リクエスト | ~88 GiB | DGX Spark、A100 80GB(ギリギリ不足) |
| 262K x 5リクエスト | ~96 GiB | DGX Spark(現在の構成) |
| モデルロードのみ(KVなし) | ~64 GiB | A100 80GB、H100 80GB |
262Kフルコンテキストを使うには、90 GiBクラス以上のVRAMが必要です。A100/H100の80GBではモデルは載りますが、KVキャッシュが足りず262Kには届きません。DGX SparkのGB10(119.6 GiB統合メモリ)は、大きなKVキャッシュを確保できるという点で、この用途に適したハードウェアです。
まとめ
| 用途 | 推奨 |
|---|---|
| 本格的な開発作業(速度重視) | Opus 4.6 — 1.5倍速い |
| 本格的な開発作業(コスト重視) | Qwen3.5-122B — 同等の成功率で無料 |
| シンプルなQ&A・コード生成 | Qwen3.5-122B — 十分な品質 |
| 機密コードの取り扱い | Qwen3.5-122B — データがローカルに留まる |
| 高可用性が必要 | Qwen3.5-122B — 2ノードFailover |
前回の結論を更新します: ローカルLLMでClaude Codeのバックエンドを実用的に動かすことは可能です。ただし、パラメータ数よりもコンテキスト長を優先すべきです。
397B(8K ctx)→ 122B(262K ctx)の変更で:
- タスク成功率: 40% → 100%
- 速度: 2.0x → 1.56x(Opus比)
- 冗長性: なし → Failover
モデルを小さくしたのに、性能が上がった。 コンテキスト長が全てでした。
なお、今回は2台のDGX SparkをFailover構成で使いましたが、122Bモデル自体は1台で完結します。DGX Spark 1台(128GB統合メモリ)はもちろん、Mac Studio(M4 Ultra/M3 Ultra、128GB以上の統合メモリ) でも同様に動作するはずです。2台構成は冗長性のためであり、必須ではありません。手元に128GB以上のメモリを持つマシンが1台あれば、この記事と同じ体験が得られます。
追記: Attribution Header無効化でさらに高速化(2026-03-19)
本記事の公開後、読者様からUnslothのドキュメントについてご指摘をいただきました。結論から言うと、本記事のベンチマークではこの最適化は適用されていませんでした。 適用後に再ベンチマークを行った結果、単一ターンタスクで最大4倍の高速化を確認しました。
問題: prefix cacheが機能していなかった
Claude Codeはリクエストごとに「Attribution Header」(帰属情報ヘッダー)を付加します。このヘッダーの内容がリクエストのたびに変わるため、vLLMのprefix cachingが無効化されていました。
Claude Codeのリクエストには約25,000トークンの共通プレフィックス(システムプロンプト + ツール定義)が含まれます。prefix cachingが機能すれば、このプレフィックスのKV計算結果は初回のみ必要で、2回目以降はキャッシュから即座に読み込まれます。しかし、Attribution Headerがプレフィックスを変えてしまうため、毎回全計算が走っていたのです。
最適化なし:
Request 1: [Header_A][System][Tools][Msg] → 全プレフィックス計算 (40s)
Request 2: [Header_B][System][Tools][Msg] → 全プレフィックス計算 (40s) ← Header変化でキャッシュ無効
最適化あり:
Request 1: [System][Tools][Msg] → 全プレフィックス計算 (28s)
Request 2: [System][Tools][Msg] → キャッシュヒット (10s) ← プレフィックス一致
Request 3: [System][Tools][Msg] → キャッシュヒット (8s) ← プレフィックス一致
解決策
~/.claude/settings.json に以下を追加します。
{
"env": {
"CLAUDE_CODE_ATTRIBUTION_HEADER": "0",
"CLAUDE_CODE_ENABLE_TELEMETRY": "0",
"CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1"
},
"attribution": {
"commit": "",
"pr": ""
}
}
注意: export CLAUDE_CODE_ATTRIBUTION_HEADER=0 のように環境変数で設定しても効きません。settings.json の env ブロック内に記述する必要があります。
加えて、vLLM側で --enable-prefix-caching が有効であることを確認してください(本記事の構成では有効になっています)。
検証結果: 単一ターンQ&A(3回反復)
最もクリーンな比較のため、同一タスク(Task 1: Pythonデコレータの説明)を各設定で3回ずつ実行しました。
| Run 1 | Run 2 | Run 3 | 平均 | |
|---|---|---|---|---|
| 最適化なし | 41.1s | 41.9s | 38.3s | 40.4s |
| 最適化あり | 28.6s(cold) | 11.9s | 8.2s | 10.0s(warm) |
最適化なしでは3回とも安定して40秒前後。prefix cacheが効いていないため、毎回同じ計算量です。
最適化ありのRun 1(28.6s)はprefix cacheがまだ空の「cold start」。プレフィックスの全計算が走りますが、結果がキャッシュされます。Run 2以降はキャッシュヒットにより8〜12秒に短縮。
warm cache時の高速化: 4.0倍
検証結果: フルベンチマーク
5タスクすべてを含むフルベンチマーク結果です。prefix cacheが暖機された状態で実行しました。
| # | タスク | Opus 4.6 | 122B(最適化なし) | 122B(最適化あり) |
|---|---|---|---|---|
| 1 | 知識Q&A | 13.5s | 44.2s | 12.3s |
| 2 | ファイル読取 | 15.0s | 58.6s | 22.4s |
| 3 | コード生成 | 23.6s | 33.9s | 24.1s |
| 4 | バグ修正 | 36.1s | 34.5s | 79.3s* |
| 5 | マルチステップ | 33.8s | 47.9s | 51.5s |
| 合計 | 122.0s | 219.0s | 189.6s |
* Task 4は修正版ファイルが正しく保存されており品質は向上したが、モデルがより多くのターンを使ったため時間が増加。
注意: マルチターンタスク(Task 3-5)はモデルの応答パスのランダム性により、実行ごとの分散が大きいです。各ターンのprefill時間はprefix cacheで短縮されますが、タスク全体の時間はターン数の変動に支配されます。単一ターンのTask 1が最も信頼性の高い比較ポイントです。
vLLM prefix cacheメトリクス
vLLMの内部メトリクスからも効果を確認できます。
prefix_cache_queries_total: 975,002 tokens
prefix_cache_hits_total: 632,992 tokens
Hit rate: 64.9%
全クエリの65%がキャッシュヒット。約25,000トークンのプレフィックス部分が確実に再利用されています。
Opusに追いつく
最も重要な発見は、最適化ありのQwen3.5-122Bが単一ターンタスクでOpus 4.6と同等の速度を達成したことです。
| タスク | Opus 4.6 | 122B(最適化あり) |
|---|---|---|
| Task 1: 知識Q&A | 13.5s | 12.3s ← Opusより速い |
| Task 3: コード生成 | 23.6s | 24.1s ← ほぼ同等 |
本文の結論では「Opusの1.56倍遅い」としていましたが、prefix cacheが機能する状態ではOpusとほぼ互角です。差があるのは、prefix cacheの恩恵が薄いマルチターンタスク(ターン数の変動が支配的)のみです。
更新された推奨
ローカルLLMをClaude Codeのバックエンドとして使う場合、Attribution Header無効化は必須の設定です。
| 設定 | 効果 |
|---|---|
CLAUDE_CODE_ATTRIBUTION_HEADER=0 |
prefix cacheを有効化(4x高速化) |
--enable-prefix-caching(vLLM側) |
KVキャッシュの再利用を許可 |
この2つの組み合わせにより、25,000トークンのプレフィックス計算が初回のみとなり、2回目以降のリクエストが劇的に高速化されます。
環境情報
| 項目 | 値 |
|---|---|
| ハードウェア | DGX Spark x2(独立並列) |
| GPU | GB10 (Blackwell) x2 |
| 統合メモリ | 128GB x2(各ノード独立) |
| 推論エンジン | vLLM 0.17.1(spark-vllm-docker) |
| モデル | Intel/Qwen3.5-122B-A10B-int4-AutoRound |
| コンテキスト長 | 262,144 tokens |
| Claude Code | v2.1.78 (Opus 4.6) |
| テスト日 | 2026-03-19 |
参考リンク
- 前回の記事: Claude CodeのバックエンドをQwen3.5-397Bに差し替えて性能比較
- 前々回の記事: DGX Spark 2台でQwen3.5-397Bを動かす
- Claude Code — Anthropic公式ドキュメント
- spark-vllm-docker — DGX Spark向けvLLMクラスタ構築ツール
- Intel/Qwen3.5-122B-A10B-int4-AutoRound — 使用したモデル