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 description
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 |
インストール
uv tool install dgxllm
試すだけなら:
uvx dgxllm --help
Mac / Linux のどちらからでも使える。ノード側に入れる必要はない — SSH 経由で操作する。
セットアップ
1. クラスタを登録する
dgxllm init
SSH ホストとノード間リンクの IP を訊かれる。実機を見に行って GPU・メモリ・ ファブリック構成(物理ポートと論理 IF の対応、RoCE デバイス、MTU)を検出し、 API キーを生成する。
前提: 各ノードへパスワードなし SSH が通り、docker が使えること。
2. モデルを登録する
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. 起動する
dgxllm start # 選択画面が出る
dgxllm start <name> # 直接指定
起動前に「SSH で届くか」「イメージがあるか」「モデルがキャッシュにあるか」を 全ノードで確認してから始める。
モデルを切り替える
dgxllm switch # 選択画面。起動中のモデルには [起動中] と出る
dgxllm switch <name>
対象 deployment を止めてから新しいモデルを起動する。2 ノードとも使う 大きいモデル1つだけの構成なら、これで「今動いているモデル」を丸ごと 入れ替えることになる (同じノード集合を使う限り同時起動はできない)。 ノードを分けて複数のモデルを並行して動かす場合は、後述の 複数の deployment を同時に動かす を参照。
Claude Code を開いている場合は、モデル名が変わるので
dgxllm claudeで 起動し直す必要がある。
コマンド
複数の deployment (起動中のモデルの組) を同時に扱える前提のコマンド体系になっている。
ps/stop [name]/switch [name]/claude [--name] はどれも「どの deployment を
対象にするか」を選ぶ操作で、名前を省略すると対話的なピッカーが出る
(deployment が1つしか無ければピッカー無しでそれを使う)。
| コマンド | 説明 |
|---|---|
dgxllm init |
クラスタを対話的に設定する |
dgxllm start [name] [--nodes ...] [--name ...] |
deployment を起動する (モデル選択 → ノード選択 → 起動) |
dgxllm ps |
起動中の deployment を一覧する |
dgxllm stop [name] |
deployment を停止する |
dgxllm switch [name] |
deployment を選び、モデルとノードを選び直して切り替える |
dgxllm logs [name] [-f] |
deployment のヘッドノードのログを表示する |
dgxllm claude [--name/-n name] |
この endpoint を backend にして Claude Code を起動する |
dgxllm dashboard [--host] [--port] [--open] |
Web ダッシュボードを起動する |
dgxllm model list |
登録済みモデルプロファイルを一覧する |
dgxllm model add [hf_repo] |
モデルプロファイルを追加する |
dgxllm model remove <name> |
モデルプロファイルを削除する |
dgxllm statusは廃止された。 単一 deployment 前提のコマンドだったが、 複数 deployment を扱えるようにした際にdgxllm psへ統合した (psは「どの deployment がどのノードで動いているか」を表で見せる)。 呼ぶとその場でエラーになり、psを使うよう案内が出る。
各コマンドの正確なオプションは --help で確認するのが確実
(例: dgxllm start --help、dgxllm model add --help)。
Claude Code から使う
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
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
min_nodes: 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_base: 8888
master_port_base: 25000
tensor_parallelという設定キーは存在しない。 TP サイズはモデルごとに 固定するものではなく、dgxllm startのたびに選んだノード数からその場で 決まる (2 ノードなら TP=2)。モデル側が持つのは「これより少ないノードでは 載らない」という下限のmin_nodesだけ。port/master_portも v0.1.0 時点のキー名で、port_base/master_port_baseに改名されている (複数 deployment を同時に扱うようになり、実際に使うポートはport_base/master_port_baseを起点に deployment ごとへずらして 割り当てるため)。旧キーで書かれた設定ファイルはそのまま読める — 読み込み時に自動で新しいキー名へ読み替えるので、手で書き換える必要はない。
API キーは ~/.config/dgxllm/api-key (mode 600) に分離してある。
設定ファイルをそのまま共有しても鍵は漏れない。
複数の deployment を同時に動かす
dgxllm はノードの部分集合ごとに独立した deployment を起動できる。
たとえば 2 ノードを全部使う大きいモデルを動かす代わりに、ノードを 1 台ずつに
分けて別々の小さいモデルを載せる、といった使い方ができる。
dgxllm start deepseek-v4-flash-0731 --nodes dgx-spark-1,dgx-spark-2 --name big
dgxllm start qwen3.6-27b-nvfp4 --nodes dgx-spark-1 --name small
- 同じノードを複数の deployment で取り合うことはできない (使用中のノードを 選ぶとエラーになる)
--nameを省略すると deployment 名はモデル名になり、衝突時は-2/-3が 付くdgxllm psは全 deployment を一覧する。dgxllm stop [name]/switch [name]/claude --nameは名前で対象を選ぶ (deployment が1つしか無ければ名前無しで そのまま使える)
Claude Code の endpoint は deployment ごとに別ポートで立つため、 どの deployment 向けに
dgxllm claudeを起動したかを覚えておくこと。
Web ダッシュボード
dgxllm dashboard # http://127.0.0.1:8000 で待受
dgxllm dashboard --open # 起動後にブラウザで自動的に開く
ノードのリソース使用状況 (GPU/CPU/メモリ/温度) と、動いているモデルの
メトリクス (KV キャッシュ使用率、トークン生成レート等) をブラウザで見られる。
一覧・グラフ表示だけでなく、その場で deployment の起動・停止・切替もできる
(dgxllm start/stop/switch を CLI から打たなくてよい)。
- ノード側から2秒間隔でテレメトリを収集し、SSE (Server-Sent Events) で ブラウザへ配信する。タブを開いたままにしておけば自動的に更新され続ける
- 起動・停止・切替は SSE でログをリアルタイムに追える (
dgxllm startの 出力をブラウザで見ている状態に近い) - ノードが1台落ちても、そのノードのカードだけ「到達不能」と表示され、 残りのノードの更新は止まらない
セキュリティモデル
ダッシュボードは操作ひとつで動作中のクラスタを止められるため、CLI とは 別に3層の防御を持つ。
- 起動ごとに使い捨てのトークンを発行する。
dgxllm dashboardの出力にhttp://127.0.0.1:8000/?token=...という URL が出るので、これをそのまま ブラウザにコピペして開く。この URL に含まれるトークンはプロセスを 再起動すると変わる (Jupyter Notebook と同じ方式) - トップページを開くと Cookie が発行され、以後 URL のトークンは不要になる。
Cookie を受け取った時点でクエリ無しの
/へリダイレクトするため、 トークンがブラウザのアドレスバー・履歴に残り続けることもない - 既定では
127.0.0.1(ループバック) にしかバインドしない。 同じマシン 以外からは端から接続できない --host 0.0.0.0で LAN に公開できるが、これを指定するとHost/Originヘッダの検証が両方とも無効になる。 ワイルドカード bind では「正規の 接続元がどの LAN アドレス/ホスト名で来るか」を事前に確定できないため (DHCP で変わる、.local名で来る、コンテナ越し等)、この2層は最初から 検証しようがない。その状態ではトークンだけがクラスタを守る唯一の層に なる ので、--host 0.0.0.0は信頼できる LAN でのみ使うこと。ダッシュボード 自身もこの旨を起動時にログへ警告として出す
リモートから使う
dgxllm dashboard は既定では 127.0.0.1 にしかバインドしないため、Mac の
外 (スマホ・別の端末) からは見えない。Mac がスリープしていても常に見える
形で運用したい場合は、ヘッドノード上で dgxllm dashboard を systemd
ユーザーユニットとして常駐させ、tailscale serve の背後に置く構成が使える。
ノード常駐の具体的な手順とハマりどころは
docs/deploy/node-setup.md にまとめてある。
ここでは、その構成を理解するのに必要な2つの起動オプションと、
やらないと決めたことを書く。
--public-origin と --token-command
--public-origin:tailscale serve(や他のリバースプロキシ) は TLS を終端したあと平文 HTTP に変換してアプリへ転送し、Hostヘッダも書き換 えない。つまりアプリはリクエストそのものからは「自分が https で外部公開 されているか」を知りようがない。この判断材料を起動時の設定として明示的 に渡すのが--public-origin(例:https://<head>.<tailnet>.ts.net) で あり、これによってHost/Originの許可集合と Cookie のSecure属性 が決まる。--token-command: 既定の起動トークンは毎回ランダム生成される。 フォアグラウンドで都度起動するだけなら問題ないが、systemd で常駐させる と再起動のたびにトークンが変わり、運用者が保存したブックマークが黙って 死ぬ。--token-commandはcat ~/.config/dgxllm/dashboard-tokenのよう な外部コマンドの標準出力を起動トークンとして使う口で、これを使えば トークンを固定できる。コマンドの実行に失敗した場合はランダムトークン へフォールバックせず、起動そのものを中止する (黙って別のトークンで 動き続けるより、気付ける形で止まるほうを選んでいる)。
HTTPS 証明書の有効化は不可逆
tailscale serve を使うには Tailscale 管理コンソールでそのノードの HTTPS
証明書を先に有効化しておく必要がある。この有効化は取り消せない —
有効化した時点で、そのノードのマシン名が Certificate Transparency (CT)
ログに永久公開される。個人が特定できるマシン名を付けている場合は、
有効化する前に見直すこと。
tailscale funnel は使わない
funnel は tailnet の外・インターネット全体に公開する機能であり、この
dashboard は起動ひとつでクラスタを止められる操作をトークン1枚で守っている
ため、到達範囲を tailnet メンバーに限定する設計を最初から前提にしている。
funnel を使うとその前提そのものが崩れるため使わない。
dgxllm から tailscale は呼ばない
dgxllm のコードから tailscale コマンドを直接呼ぶ実装はしない。VPN
ベンダ1社のツールをコード側に焼き込むことになるため、この判断は変えない
(後から「自動化しよう」と言い出さないための記録)。
セキュリティ (vLLM API)
- 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
- tonyd2wild/DeepSeek-v4-Flash-0731-DSpark-1M-NVFP4-KV-2x-DGX-Spark
- NVIDIA/dgx-spark-playbooks
dgxllm はこれらのレシピを、モデルを差し替えられる形の CLI にまとめ直したもの。
ライセンス
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dgxllm-0.3.0.tar.gz.
File metadata
- Download URL: dgxllm-0.3.0.tar.gz
- Upload date:
- Size: 313.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5b54b195e985d7f63b5d7e2c4a176a27e62e680dd874babfc2dd72c10d338aa1
|
|
| MD5 |
257923f3d21a15a024c0d3f706e708ce
|
|
| BLAKE2b-256 |
2733408cdac084d6f848113011b73f4497618130c7187b7123120245d2eb500f
|
Provenance
The following attestation bundles were made for dgxllm-0.3.0.tar.gz:
Publisher:
publish.yml on javasparrows/dgxllm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dgxllm-0.3.0.tar.gz -
Subject digest:
5b54b195e985d7f63b5d7e2c4a176a27e62e680dd874babfc2dd72c10d338aa1 - Sigstore transparency entry: 2335457425
- Sigstore integration time:
-
Permalink:
javasparrows/dgxllm@d26a796ac85ad9f2ea5676d843292049ad607f01 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/javasparrows
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d26a796ac85ad9f2ea5676d843292049ad607f01 -
Trigger Event:
push
-
Statement type:
File details
Details for the file dgxllm-0.3.0-py3-none-any.whl.
File metadata
- Download URL: dgxllm-0.3.0-py3-none-any.whl
- Upload date:
- Size: 165.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e36793d2d8bfbe29ba375df668b49ca0b496c4cb22e8dc9b65b33818d7f0461b
|
|
| MD5 |
970067ae2c48b9ccf77d13974c8fe1ee
|
|
| BLAKE2b-256 |
e82880d5ebfbde4efc3d6c51f3be54da3cf1cc6291a48305bbe87e954def13fe
|
Provenance
The following attestation bundles were made for dgxllm-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on javasparrows/dgxllm
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dgxllm-0.3.0-py3-none-any.whl -
Subject digest:
e36793d2d8bfbe29ba375df668b49ca0b496c4cb22e8dc9b65b33818d7f0461b - Sigstore transparency entry: 2335457450
- Sigstore integration time:
-
Permalink:
javasparrows/dgxllm@d26a796ac85ad9f2ea5676d843292049ad607f01 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/javasparrows
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@d26a796ac85ad9f2ea5676d843292049ad607f01 -
Trigger Event:
push
-
Statement type: