Skip to main content

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 --helpdgxllm 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-commandcat ~/.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 を動かす方法は、以下の先行事例に多くを負っている。

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

ライセンス

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dgxllm-0.3.0.tar.gz (313.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dgxllm-0.3.0-py3-none-any.whl (165.3 kB view details)

Uploaded Python 3

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

Hashes for dgxllm-0.3.0.tar.gz
Algorithm Hash digest
SHA256 5b54b195e985d7f63b5d7e2c4a176a27e62e680dd874babfc2dd72c10d338aa1
MD5 257923f3d21a15a024c0d3f706e708ce
BLAKE2b-256 2733408cdac084d6f848113011b73f4497618130c7187b7123120245d2eb500f

See more details on using hashes here.

Provenance

The following attestation bundles were made for dgxllm-0.3.0.tar.gz:

Publisher: publish.yml on javasparrows/dgxllm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for dgxllm-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e36793d2d8bfbe29ba375df668b49ca0b496c4cb22e8dc9b65b33818d7f0461b
MD5 970067ae2c48b9ccf77d13974c8fe1ee
BLAKE2b-256 e82880d5ebfbde4efc3d6c51f3be54da3cf1cc6291a48305bbe87e954def13fe

See more details on using hashes here.

Provenance

The following attestation bundles were made for dgxllm-0.3.0-py3-none-any.whl:

Publisher: publish.yml on javasparrows/dgxllm

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page