はじめに
本記事は、DGX SparkでローカルLLMをClaude Codeのバックエンドとして使う検証シリーズの第3弾です。
- 第1弾: DGX Spark 2台スタッキングでQwen3.5-397BをClaude Codeのバックエンドにしてみた — 397Bモデルを2台のテンソル並列で動かしたが、コンテキスト長8,192トークンの壁でタスク成功率40%
- 第2弾: Qwen3.5-122Bでリベンジ — コンテキスト長が全てだった — 122Bモデルに切り替え、コンテキスト262Kトークンで成功率100%を達成
2つの検証を通じて、「ローカルLLMでClaude Codeを動かす」こと自体は十分に実用的だとわかりました。しかし、毎回手動でプロキシスクリプトを起動し、環境変数を設定し、Claude Codeを立ち上げる……という手順は煩雑でした。
そこで、この一連の作業をワンコマンドで完結させるCLIツールを作りました。
GitHub: https://github.com/amu815/claude-local
なぜツール化したか
第2弾の検証で使っていた構成は、以下のようなものでした。
# 1. vLLMコンテナを起動(spark-vllm-docker)
cd ~/spark-vllm-docker && python3 run-recipe.py qwen3.5-122b-int4-solo --solo -d
# 2. プロキシスクリプトを起動
python3 ~/.local/bin/vllm-proxy.py &
# 3. 環境変数を設定してClaude Codeを起動
ANTHROPIC_BASE_URL=http://127.0.0.1:8081 \
ANTHROPIC_AUTH_TOKEN=local \
claude --model Intel/Qwen3.5-122B-A10B-int4-AutoRound
これを毎回打つのは面倒ですし、DGX Sparkの2台構成だとリモートノードへのSSHも必要です。さらに、同僚や友人にこの手順を共有しようとしたとき、「DGX Sparkを持っていない人はどうすればいい?」という問題に気づきました。
macOSのApple SiliconマシンならMLXで、WindowsやLinuxならOllamaで、同じ体験ができるはずです。プラットフォームごとのバックエンドの差異を吸収し、どの環境でも同じコマンドで動くツールが必要でした。
claude-local とは
pip install claude-local
claude-local setup # ハードウェア検出 → モデル推奨 → 設定生成
claude-local start # バックエンド起動 → プロキシ起動 → Claude Code起動
3コマンドで、ローカルLLMを使ったClaude Code環境が立ち上がります。
アーキテクチャ
┌──────────────┐ ┌──────────────────┐ ┌───────────────────┐
│ Claude Code │─────▶│ Failover Proxy │─────▶│ Backend (vLLM) │
│ │ │ :8081 │ ┌──▶│ Node 1 :8000 │
│ 環境変数: │ │ │ │ └───────────────────┘
│ BASE_URL= │ │ ヘルスチェック │──┤
│ :8081 │ │ 安全圧縮 │ │ ┌───────────────────┐
│ │ │ max_tokens制限 │ └──▶│ Backend (vLLM) │
└──────────────┘ └──────────────────┘ │ Node 2 :8000 │
└───────────────────┘
Claude CodeはOpenAI互換APIを通じてプロキシと通信し、プロキシが裏側のLLMバックエンドにリクエストを転送します。バックエンドが複数ある場合はフェイルオーバーで冗長化されます。
技術的な工夫
1. ハードウェア自動検出
claude-local setup を実行すると、OS・CPU・GPU・メモリを自動検出し、最適なバックエンドとモデルを推奨します。
def detect_platform() -> PlatformInfo:
"""Detect OS, arch, memory, GPU, DGX Spark status."""
os_name = _detect_os()
arch = _detect_arch()
memory_gb = _detect_memory_gb(os_name)
is_dgx_spark = _detect_dgx_spark()
gpu_type, gpu_vram_gb = _detect_gpu()
is_uma = os_name == "darwin" or is_dgx_spark
...
DGX Sparkの検出は3段階で行います。
-
/etc/dgx-releaseファイルに "spark" or "gx10" が含まれるか -
dpkg -l dgx-otaパッケージが存在し、かつアーキテクチャがARM64か - デバイスツリー
/sys/firmware/devicetree/base/modelに "gx10" が含まれるか
Apple Siliconは darwin + arm64 の組み合わせで判定します。いずれも UMA(統合メモリ)として扱い、GPU VRAMではなくシステムメモリ全体をモデルの載せられるメモリとして計算します。
2. メモリベースのモデル推奨
利用可能なメモリ量に応じて、最適なモデルとコンテキスト長を自動選択します。
| メモリ | 推奨モデル | コンテキスト長 |
|---|---|---|
| 128 GB (UMA) | Qwen3.5-122B-INT4 | 262,144 |
| 96 GB (UMA) | Qwen3.5-122B-INT4 | 131,072 |
| 80 GB (VRAM) | Qwen3.5-122B-INT4 | 65,536 |
| 64 GB | Qwen3-32B-INT4 | 131,072 |
| 32 GB | Qwen3-32B-INT4 | 32,768 |
| 24 GB | Qwen3-32B-INT4 | 16,384 |
モデルレジストリは configs/models.yaml にYAMLで定義されており、コミュニティが新しいモデルを追加しやすい構造になっています。
models:
- id: qwen3.5-122b-int4
name: Qwen3.5-122B-A10B-INT4
repo: Intel/Qwen3.5-122B-A10B-int4-AutoRound
weight_size_gb: 63
memory_gb: 80
context_by_memory:
112: 262144
96: 131072
80: 65536
backends:
vllm-spark:
recipe: qwen3.5-122b-int4-solo
mlx:
repo: mlx-community/Qwen3.5-122B-A10B-int4-AutoRound
ollama:
tag: qwen3.5-coder:122b
context_by_memory が重要なポイントです。同じモデルでも、メモリ量によってKVキャッシュに割ける容量が変わるため、コンテキスト長を段階的に調整しています。第1弾の検証で学んだ「コンテキスト長が全て」という教訓がここに反映されています。
3. フェイルオーバープロキシ
プロキシは標準ライブラリのみで実装しています(外部依存なし)。
# proxy.py
MAX_OUTPUT_TOKENS = 16384
SAFETY_CHAR_LIMIT = 900_000 # ~225K tokens
def _safety_compress(data: dict) -> dict:
"""Drop oldest non-system messages if over SAFETY_CHAR_LIMIT."""
messages = data.get("messages")
if not messages:
return data
while _estimate_chars(data) > SAFETY_CHAR_LIMIT and len(messages) > 1:
if messages[0].get("role") == "system":
if len(messages) > 2:
messages.pop(1)
else:
break
else:
messages.pop(0)
return data
設計上のポイントは3つです。
- フルパススルー: 第1弾で行ったsystemプロンプトの圧縮やtools削減は一切行わない。第2弾で「圧縮しないほうがいい」と学んだため
- 安全弁のみ: リクエストが90万文字(約225Kトークン)を超えた場合だけ、古いメッセージを落とす。これはモデルのコンテキスト上限を超えてOOMを起こすのを防ぐための最後の砦
-
フェイルオーバー: 複数のupstreamを順番に試し、503やタイムアウトなら次のノードへ。全ノードが落ちていれば
503 All upstreams unavailableを返す
4. KVキャッシュ最適化の自動適用
Claude Codeはデフォルトで「Attribution Header」というヘッダーをリクエストに付加します。APIサーバーでは無視される無害なヘッダーですが、ローカルLLMではこのヘッダーがKVキャッシュを毎回無効化し、推論速度が約90%低下します(Unslothの報告)。
厄介なことに、export CLAUDE_CODE_ATTRIBUTION_HEADER=0 では効きません。~/.claude/settings.json の env セクションに書く必要があります。
claude-localは setup 時にこの設定を自動的に適用します。
_LOCAL_ENV_DEFAULTS = {
"CLAUDE_CODE_ATTRIBUTION_HEADER": "0",
}
def _optimize_claude_settings() -> None:
"""Ensure ~/.claude/settings.json has env vars for local inference."""
settings: dict = {}
if os.path.exists(_CLAUDE_SETTINGS_PATH):
with open(_CLAUDE_SETTINGS_PATH) as f:
settings = json.load(f)
env = settings.setdefault("env", {})
for key, value in _LOCAL_ENV_DEFAULTS.items():
if env.get(key) != value:
env[key] = value
# ... save to settings.json
setup を実行するだけで、KVキャッシュの最適化が有効になります。手動で settings.json を編集する必要はありません。
5. プラグインアーキテクチャ
バックエンドは抽象基底クラス Backend を継承する形で実装されています。
class Backend(abc.ABC):
@abc.abstractmethod
def install(self) -> None: ...
@abc.abstractmethod
def start(self, model: dict, port: int = 8000) -> None: ...
@abc.abstractmethod
def stop(self) -> None: ...
@abc.abstractmethod
def status(self) -> BackendStatus: ...
現在の実装:
| バックエンド | 対象環境 | 特徴 |
|---|---|---|
vllm-spark |
DGX Spark | spark-vllm-dockerのレシピを呼び出し、マルチノードSSH起動にも対応 |
mlx |
macOS (Apple Silicon) | mlx-lmのサーバーモードを起動、モデルの自動ダウンロードにも対応 |
ollama |
Windows / Linux | Ollamaのモデルpull & serve |
新しいバックエンド(例: llama.cpp, SGLang)を追加する場合、Backend を継承した1ファイルを追加し、models.yaml にエントリを足すだけで対応できます。
対応プラットフォームとバックエンド選択
recommend_backend() のロジックはシンプルです。
def recommend_backend(info: PlatformInfo) -> str:
if info.is_dgx_spark:
return "vllm-spark"
if info.os == "darwin" and info.gpu_type == "apple_silicon":
return "mlx"
if info.gpu_type == "nvidia":
return "vllm"
return "ollama"
| 判定条件 | バックエンド | 理由 |
|---|---|---|
| DGX Spark | vllm-spark | 専用Dockerコンテナ経由が最も安定 |
| macOS + Apple Silicon | mlx | Metal最適化、統合メモリを最大限活用 |
| NVIDIA GPU (非Spark) | vllm | CUDAによる高速推論 |
| その他 | ollama | セットアップが最も簡単、幅広いハードウェアに対応 |
使い方
macOS (Apple Silicon) の場合
$ pip install claude-local
$ claude-local setup
Detecting platform...
OS: darwin (arm64)
Memory: 96 GB (unified)
GPU: apple_silicon
Recommended backend: mlx
Recommended model: Qwen3.5-122B-A10B-INT4
Parameters: 122B, Quantization: INT4
Weight size: ~63 GB
Max context: 131,072 tokens
Proceed with this configuration? [Y/n]: Y
Download model weights? [Y/n]: Y
Configuration saved to ~/.claude-local/config.yaml
Claude Code settings optimized (KV cache fix applied).
$ claude-local start
Starting mlx backend...
Backend started.
Starting proxy on 127.0.0.1:8081...
Proxy running on 127.0.0.1:8081
Launching Claude Code with model: mlx-community/Qwen3.5-122B-A10B-int4-AutoRound
DGX Spark(マルチノード構成)の場合
$ claude-local setup
Detecting platform...
OS: linux (arm64)
Memory: 128 GB (unified)
GPU: nvidia
Platform: NVIDIA DGX Spark
Recommended backend: vllm-spark
Recommended model: Qwen3.5-122B-A10B-INT4
Parameters: 122B, Quantization: INT4
Weight size: ~63 GB
Max context: 262,144 tokens
Proceed with this configuration? [Y/n]: Y
Enter node IPs (comma-separated) [192.168.100.1,192.168.100.2]:
Configuration saved to ~/.claude-local/config.yaml
Claude Code settings optimized (KV cache fix applied).
$ claude-local start
Starting vllm-spark backend...
Starting vLLM with recipe: qwen3.5-122b-int4-solo
Starting vLLM on 192.168.100.2...
http://192.168.100.1:8000 is READY
http://192.168.100.2:8000 is READY
Backend started.
Starting proxy on 127.0.0.1:8081...
Proxy running on 127.0.0.1:8081
Launching Claude Code with model: Intel/Qwen3.5-122B-A10B-int4-AutoRound
2台のDGX Sparkがそれぞれ独立してvLLMを起動し、プロキシがフェイルオーバーを管理します。片方のノードが再起動中でも、もう一方で推論を継続できます。
ステータス確認
$ claude-local status
Backend: vllm-spark
Model: Qwen3.5-122B-A10B-INT4
Context: 262,144 tokens
http://192.168.100.1:8000: UP
http://192.168.100.2:8000: UP
Proxy: http://127.0.0.1:8081 (UP)
シリーズを通じて学んだこと
3つの記事を通じて、ローカルLLM × Claude Codeの知見が積み上がりました。
| 検証 | 学び |
|---|---|
| 第1弾 (397B) | モデルの賢さよりコンテキスト長が重要。プロキシでの過剰な圧縮は逆効果 |
| 第2弾 (122B) | 262Kコンテキストなら成功率100%。prefix cacheで24%高速化 |
| 第3弾 (本記事) | 検証の知見をツール化し、DGX Spark以外の環境にも展開 |
特に「フルパススルー + 安全弁だけ」というプロキシ設計は、第1弾で過剰圧縮に苦しみ、第2弾で圧縮を外して成功した経験から導き出されたものです。最初からこの設計にはたどり着けませんでした。
今後の展望
-
PyPI公開:
pip install claude-localで誰でもインストールできるようにする - バックエンド追加: llama.cpp、SGLang への対応
- モデルカタログの拡充: Qwen以外のモデル(Llama、DeepSeek等)のサポート
-
ベンチマーク自動化:
claude-local benchコマンドで性能を計測
まとめ
DGX Sparkでの検証を出発点に、「ローカルLLMでClaude Codeを動かす」体験をどのプラットフォームでも再現できるツールを作りました。
核心は4つです。
- ハードウェア自動検出 — setup一発で最適なモデルとバックエンドを選択
- フェイルオーバープロキシ — 過剰な圧縮はせず、安全弁だけを提供
- KVキャッシュ最適化 — Attribution Headerによる90%速度低下を自動で回避
- プラグインアーキテクチャ — バックエンドを1ファイル追加するだけで新環境に対応
コードは全てMITライセンスで公開しています。
GitHub: https://github.com/amu815/claude-local
ローカルLLMでClaude Codeを試したい方、DGX Sparkを持て余している方、ぜひ使ってみてください。Issue・PRも歓迎です。