Metadata-Version: 2.4
Name: dgxllm
Version: 0.1.0
Summary: Run large LLMs across two NVIDIA DGX Sparks with vLLM — model picker, one-command start/stop, and an Anthropic-compatible endpoint for Claude Code.
Project-URL: Homepage, https://github.com/javasparrows/dgxllm
Project-URL: Issues, https://github.com/javasparrows/dgxllm/issues
Author: Yuki Kashiwada
License-Expression: MIT
License-File: LICENSE
Keywords: claude-code,dgx-spark,gb10,llm,tensor-parallel,vllm
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
Requires-Python: >=3.10
Requires-Dist: httpx>=0.28
Requires-Dist: pyyaml>=6.0
Requires-Dist: questionary>=2.0
Requires-Dist: rich>=13.9
Requires-Dist: typer>=0.15
Description-Content-Type: text/markdown

# dgxllm

Run large LLMs across **two NVIDIA DGX Sparks** with vLLM — pick a model from a menu,
start it with one command, and use it from **Claude Code**.

```
$ dgxllm start

? 使用するモデルを選択してください:
❯ deepseek-v4-flash-0731  (TP=2, 1M ctx, KV fp8_ds_mla, spec k=5)
  qwen3.6-27b-nvfp4       (TP=2, 32K ctx)
  gemma-4-31b-it-nvfp4    (TP=2, 32K ctx)

deepseek-v4-flash-0731 を 2 ノードで起動します...
```

```
$ dgxllm claude      # Claude Code をこのモデルで起動
```

---

## なぜ必要か

DGX Spark を 2 台つないで大きなモデルを動かすのは、やってみると設定の落とし穴が多い。
`dgxllm` は実機で踏んだ落とし穴を最初から回避する。

| 落とし穴 | dgxllm の対処 |
|---|---|
| **1 つの物理 QSFP ポートが 2 つの論理 IF に見える。** 片方だけ `NCCL_IB_HCA` に渡すと約 100 Gbit/s で頭打ち | `phys_port_name` で同一物理ポートのレールを全部集めて渡す |
| **RoCEv2 の GID index はノードごとに違い、再起動でずれる。** 固定値を共有すると NCCL が初期化時に固まる | IPv4 が埋まった GID を sysfs から毎回解決する |
| **`--master-addr` を渡さないと `127.0.0.1` になりワーカーが繋がらない** | 常に明示的に渡す |
| **ヘッドを先に起動すると `mp` 初期化のレースで掴み損ねる** | ワーカーを先に起動する |
| **API キーを渡し忘れると LAN 上の誰でもアクセスできる** | キーを自動生成し、起動後に「キー無しで 401 か」を検証する |
| **ログインシェルが fish だと `ssh host "bash構文"` が壊れる** | スクリプトを常に stdin から渡す (`ssh host bash -ls`) |
| **GB10 は GPUDirect RDMA 非対応。** ログに GDRDMA が出なくても正常 | `NCCL_NET_GDR_LEVEL=0` を明示 |
| **同梱の NCCL プラグインが GB10 で RoCE を使っているように見えて性能が出ない** | `NCCL_NET_PLUGIN=none` |

---

## インストール

```bash
uv tool install dgxllm
```

試すだけなら:

```bash
uvx dgxllm --help
```

Mac / Linux のどちらからでも使える。**ノード側に入れる必要はない** — SSH 経由で操作する。

---

## セットアップ

### 1. クラスタを登録する

```bash
dgxllm init
```

SSH ホストとノード間リンクの IP を訊かれる。実機を見に行って GPU・メモリ・
ファブリック構成（物理ポートと論理 IF の対応、RoCE デバイス、MTU）を検出し、
API キーを生成する。

前提: 各ノードへ**パスワードなし SSH** が通り、`docker` が使えること。

### 2. モデルを登録する

```bash
dgxllm model add deepseek-ai/DeepSeek-V4-Flash-0731 \
  --name deepseek-v4-flash-0731 \
  --max-model-len 1048576 \
  --kv-cache-dtype fp8_ds_mla \
  --speculative-tokens 5
```

引数を省略すると対話的に訊かれる。

> **モデル名にスラッシュは使えない。** vLLM の served model name がそのまま
> Claude Code の model picker に出るため。

### 3. 起動する

```bash
dgxllm start          # 選択画面が出る
dgxllm start <name>   # 直接指定
```

起動前に「SSH で届くか」「イメージがあるか」「モデルがキャッシュにあるか」を
全ノードで確認してから始める。

### モデルを切り替える

```bash
dgxllm switch         # 選択画面。起動中のモデルには [起動中] と出る
dgxllm switch <name>
```

動いているモデルを止めてから新しいモデルを起動する。
2 台の合計メモリに 1 モデルしか載らないため、同時起動はできない。

> Claude Code を開いている場合は、モデル名が変わるので `dgxllm claude` で
> 起動し直す必要がある。

---

## コマンド

| コマンド | 説明 |
|---|---|
| `dgxllm init` | クラスタを対話的に設定する |
| `dgxllm start [name]` | 起動する。名前を省略すると選択画面 |
| `dgxllm switch [name]` | 動いているモデルを止めて別のモデルに切り替える |
| `dgxllm stop` | 停止し、メモリと GPU 電力を表示する |
| `dgxllm status` | ノード状態と API の疎通・認証を確認する |
| `dgxllm logs [-f]` | ヘッドノードのログ |
| `dgxllm claude` | Claude Code をこの endpoint で起動する |
| `dgxllm model list` | 登録済みモデル一覧 |
| `dgxllm model add` | モデルを追加する |
| `dgxllm model remove` | モデルを削除する |

---

## Claude Code から使う

```bash
dgxllm claude
```

vLLM は **v0.11.1 から Anthropic 互換の `/v1/messages` を持つ**ので、
LiteLLM や claude-code-router のような変換プロキシは要らない。

`dgxllm claude` は「その起動だけ」に環境変数を効かせるため、
**別ターミナルで動いている通常の claude.ai セッションには影響しない。**

### 制約

- **動作中のセッションを `/model` で切り替えることはできない。** `ANTHROPIC_BASE_URL` は
  プロセス起動時に一度だけ読まれる
- **Anthropic モデルとの同一セッション併用は不可。** `ANTHROPIC_BASE_URL` を設定すると
  Anthropic モデルは無効になる
- Anthropic は非 Claude モデルを gateway 経由で使う構成を**公式にはサポートしていない**

---

## 設定ファイル

`~/.config/dgxllm/config.yaml`

```yaml
nodes:
  - ssh_host: dgx-spark-1
    fabric_ip: 192.168.100.10
  - ssh_host: dgx-spark-2
    fabric_ip: 192.168.100.11
models:
  - name: deepseek-v4-flash-0731
    hf_repo: deepseek-ai/DeepSeek-V4-Flash-0731
    tensor_parallel: 2
    max_model_len: 1048576
    kv_cache_dtype: fp8_ds_mla
    speculative_tokens: 5
default_image: ghcr.io/anemll/dspark-vllm-gx10:0.1.1
port: 8888
```

API キーは `~/.config/dgxllm/api-key` (mode 600) に分離してある。
設定ファイルをそのまま共有しても鍵は漏れない。

---

## セキュリティ

- API キーは自動生成され、**起動後に「キー無しアクセスが 401 になるか」を必ず検証する**。
  ならなければエラーで終了する
- 既定の bind は `0.0.0.0`。LAN 内から使う想定
- **ルーターでポートを開けないこと。** vLLM は単一の静的キーしか持たず、
  レート制限も監査ログもない。外から使うなら Tailscale か SSH トンネルを使う

---

## 動作確認済みの構成

- 2× DGX Spark (GB10, sm_121, ARM64, Ubuntu 24.04)
- 1 本の 200GbE ConnectX-7 QSFP DAC 直結
- `deepseek-ai/DeepSeek-V4-Flash-0731` を TP=2、1M コンテキスト

実測 (64K コンテキスト): TTFT 34.05 秒 / prefill 1,926 tok/s / decode 61.8 tok/s

> ベンチマークを取るときは **必ず 2 回以上回して 2 回目以降を採用する**こと。
> vLLM は初回リクエストで Triton カーネルを JIT コンパイルすることがあり、
> TTFT が 8 倍以上変わる。ログの `jit_monitor` 警告で確認できる。

---

## 謝辞

2 ノード DGX Spark で DeepSeek-V4-Flash を動かす方法は、以下の先行事例に多くを負っている。

- [MiaAI-Lab/DeepSeek-v4-Flash-DSpark-2x-DGX-Spark](https://github.com/MiaAI-Lab/DeepSeek-v4-Flash-DSpark-2x-DGX-Spark)
- [tonyd2wild/DeepSeek-v4-Flash-0731-DSpark-1M-NVFP4-KV-2x-DGX-Spark](https://github.com/tonyd2wild/DeepSeek-v4-Flash-0731-DSpark-1M-NVFP4-KV-2x-DGX-Spark)
- [NVIDIA/dgx-spark-playbooks](https://github.com/NVIDIA/dgx-spark-playbooks)

`dgxllm` はこれらのレシピを、モデルを差し替えられる形の CLI にまとめ直したもの。

## ライセンス

MIT
