Refactor: 記事をディレクトリ構成に変更、セットアップスクリプト追加

This commit is contained in:
2026-02-19 01:33:04 +00:00
parent 30761d2e4f
commit 24ea3f3e25
3 changed files with 487 additions and 0 deletions
+453
View File
@@ -0,0 +1,453 @@
# DGX Sparkで最新LLM「MiniMax-M2.5-REAP-172B」を動かしてみた
## はじめに
NVIDIA DGX Sparkは、デスクトップサイズでありながら最大200Bパラメータ(デュアル構成で405B)のAIモデルを動かせる小型AIワークステーションです。今回は、2026年2月にリリースされたばかりの最新モデル「**MiniMax-M2.5-REAP-172B-A10B**」をDGX Sparkデュアル構成(256GB統合メモリ)で動かす手順を紹介します。
---
## MiniMax-M2.5-REAP-172Bとは?
MiniMax-M2.5は、中国MiniMax社が開発したコーディング・エージェント特化のMixture-of-ExpertsMoE)モデルです。Cerebras社がREAPRouter-weighted Expert Activation Pruning)技術を適用し、オリジナルの230Bパラメータから172Bに軽量化したものが本モデルです。
### スペック
| 項目 | 値 |
|------|-----|
| 総パラメータ数 | 172B |
| アクティブパラメータ数 | 10B/トークン |
| レイヤー数 | 62 |
| エキスパート数 | 192(256から25%削減)|
| コンテキスト長 | 196,608トークン |
| ライセンス | Modified MIT |
### なぜREAP版がおすすめ?
- **ほぼ無劣化で25%軽量化**: HumanEval等のベンチマークでオリジナルと同等性能
- **vLLMネイティブ対応**: パッチ不要でそのまま動く
- **256GBメモリで十分動作**: オリジナル230B版では厳しいVRAM要件をクリア
---
## 動作環境
### DGX Spark デュアル構成
| 項目 | スペック |
|------|----------|
| アーキテクチャ | NVIDIA Grace Blackwell (GB10) |
| GPU | Blackwell GPU × 2 |
| メモリ | 256GB統合メモリ(LPDDR5x|
| ストレージ | 4TB NVMe × 2 |
| ネットワーク | ConnectX-7 (QSFP接続) |
---
## 事前準備:ネットワーク構成
デュアルSpark構成では、2台のノードをQSFPケーブルで物理接続する必要があります。
詳細な手順は公式ドキュメントを参照してください:
👉 [Connect two Sparks](https://build.nvidia.com/spark/connect-two-sparks)
### ネットワーク構成の理解(重要)
DGX Sparkデュアル構成では、**2種類のネットワーク**を使い分けます。公式ドキュメントではこの点が分かりにくいため、詳しく解説します。
```
┌─────────────────────┐ QSFP (200GbE) ┌─────────────────────┐
│ DGX Spark 1 │◄────────────────────────────►│ DGX Spark 2 │
│ │ 192.168.100.10/11 │ │
│ enp1s0f1np1 │ (クラスター通信) │ enp1s0f1np1 │
│ ┌───────────────┐ │ │ ┌───────────────┐ │
│ │ NCCL/Ray通信 │ │ │ │ NCCL/Ray通信 │ │
│ └───────────────┘ │ │ └───────────────┘ │
├─────────────────────┤ ├─────────────────────┤
│ eth0 (10.0.0.10) │◄──── 通常LAN ──────────────►│ eth0 (10.0.0.11) │
│ ┌───────────────┐ │ │ ┌───────────────┐ │
│ │ 管理/API公開 │ │ │ │ 管理のみ │ │
│ └───────────────┘ │ │ └───────────────┘ │
└─────────────────────┘ └─────────────────────┘
クライアントからの
APIリクエスト
http://10.0.0.10:8000
```
| ネットワーク | 用途 | インターフェース例 | IP例 |
|-------------|------|-------------------|------|
| **クラスター通信** | NCCL、Ray、テンソル並列 | enp1s0f1np1 (QSFP) | 192.168.100.x |
| **管理/API公開** | SSH、APIエンドポイント | eth0 (通常LAN) | 10.0.0.x |
### なぜ分離が必要?
1. **セキュリティ**: クラスター通信は暗号化されていないため、プライベートネットワーク推奨
2. **パフォーマンス**: NCCL通信は200GbE QSFPを使用し、APIトラフィックと分離
3. **柔軟性**: APIエンドポイントを外部公開しつつ、クラスター通信は内部に閉じる
### QSFPインターフェースへのIP割り当て(手動)
**重要**: QSFPインターフェースにはIPアドレスが自動で割り当てられません。手動設定が必要です。
```bash
# 両ノードで実行
# 1. インターフェース確認
ibdev2netdev
# 出力例: mlx5_0 port 1 ==> enp1s0f1np1 (Up)
# mlx5_1 port 1 ==> enp1s0f0np0 (Down)
# "(Up)" と表示されるインターフェースを使用
# 2. IPアドレス設定
# Node 1
sudo ip addr add 192.168.100.10/24 dev enp1s0f1np1
sudo ip link set enp1s0f1np1 up
# Node 2
sudo ip addr add 192.168.100.11/24 dev enp1s0f1np1
sudo ip link set enp1s0f1np1 up
# 3. 疎通確認(Node 1から)
ping 192.168.100.11
```
### 永続化(netplan使用)
再起動後もIPを維持するには、netplanで設定します。
```bash
# /etc/netplan/99-qsfp.yaml を作成
sudo tee /etc/netplan/99-qsfp.yaml << 'EOF'
network:
version: 2
ethernets:
enp1s0f1np1:
addresses:
- 192.168.100.10/24 # Node 2は192.168.100.11/24
EOF
sudo netplan apply
```
### パスワードなしSSHの設定
```bash
# Node 1で
ssh-keygen -t ed25519 -N ""
ssh-copy-id [email protected]
# Node 2でも同様に設定(逆方向)
```
---
## 環境構築
### 1. Dockerグループの設定(初回のみ)
```bash
sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker
```
### 2. NGC公式vLLMイメージの取得
DGX Sparkではドライバー互換性の関係で、**必ずNGC公式イメージを使用**してください。
```bash
# 両方のノードで実行
docker pull nvcr.io/nvidia/vllm:25.11-py3
export VLLM_IMAGE=nvcr.io/nvidia/vllm:25.11-py3
```
### 3. クラスターデプロイスクリプトの取得
```bash
# 両方のノードで実行
wget https://raw.githubusercontent.com/vllm-project/vllm/refs/heads/main/examples/online_serving/run_cluster.sh
chmod +x run_cluster.sh
```
---
## Rayクラスターの起動
### Node 1(ヘッドノード)の起動
```bash
# Node 1で実行
# 高速インターフェースのIPアドレスを取得
# ibdev2netdev で "(Up)" と表示されるインターフェースを使用
export MN_IF_NAME=enp1s0f1np1
export VLLM_HOST_IP=$(ip -4 addr show $MN_IF_NAME | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
echo "Using interface $MN_IF_NAME with IP $VLLM_HOST_IP"
bash run_cluster.sh $VLLM_IMAGE $VLLM_HOST_IP --head ~/.cache/huggingface \
-e VLLM_HOST_IP=$VLLM_HOST_IP \
-e UCX_NET_DEVICES=$MN_IF_NAME \
-e NCCL_SOCKET_IFNAME=$MN_IF_NAME \
-e OMPI_MCA_btl_tcp_if_include=$MN_IF_NAME \
-e GLOO_SOCKET_IFNAME=$MN_IF_NAME \
-e TP_SOCKET_IFNAME=$MN_IF_NAME \
-e RAY_memory_monitor_refresh_ms=0 \
-e MASTER_ADDR=$VLLM_HOST_IP
```
### Node 2(ワーカーノード)の起動
```bash
# Node 2で実行
export MN_IF_NAME=enp1s0f1np1
export VLLM_HOST_IP=$(ip -4 addr show $MN_IF_NAME | grep -oP '(?<=inet\s)\d+(\.\d+){3}')
# 重要:Node 1のIPアドレスを設定
# Node 1で `echo $VLLM_HOST_IP` を実行して確認
export HEAD_NODE_IP=<NODE_1_IP_ADDRESS>
echo "Worker IP: $VLLM_HOST_IP, connecting to head node at: $HEAD_NODE_IP"
bash run_cluster.sh $VLLM_IMAGE $HEAD_NODE_IP --worker ~/.cache/huggingface \
-e VLLM_HOST_IP=$VLLM_HOST_IP \
-e UCX_NET_DEVICES=$MN_IF_NAME \
-e NCCL_SOCKET_IFNAME=$MN_IF_NAME \
-e OMPI_MCA_btl_tcp_if_include=$MN_IF_NAME \
-e GLOO_SOCKET_IFNAME=$MN_IF_NAME \
-e TP_SOCKET_IFNAME=$MN_IF_NAME \
-e RAY_memory_monitor_refresh_ms=0 \
-e MASTER_ADDR=$HEAD_NODE_IP
```
### クラスター状態の確認
```bash
# Node 1で実行
export VLLM_CONTAINER=$(docker ps --format '{{.Names}}' | grep -E '^node-[0-9]+$')
echo "Found container: $VLLM_CONTAINER"
docker exec $VLLM_CONTAINER ray status
```
2ノードが認識され、GPUリソースが利用可能と表示されればOKです。
---
## モデルのダウンロード
### Hugging Faceへのログイン
```bash
# Node 1のコンテナ内で実行
export VLLM_CONTAINER=$(docker ps --format '{{.Names}}' | grep -E '^node-[0-9]+$')
docker exec -it $VLLM_CONTAINER /bin/bash
# コンテナ内で
huggingface-cli login
```
### MiniMax-M2.5-REAP-172Bのダウンロード
```bash
# コンテナ内で(約330GB、時間がかかります)
huggingface-cli download cerebras/MiniMax-M2.5-REAP-172B-A10B
```
---
## 推論サーバーの起動
### MiniMax-M2.5-REAP-172B用の起動コマンド
```bash
# Node 1で実行
export VLLM_CONTAINER=$(docker ps --format '{{.Names}}' | grep -E '^node-[0-9]+$')
docker exec -it $VLLM_CONTAINER /bin/bash -c '
vllm serve cerebras/MiniMax-M2.5-REAP-172B-A10B \
--tensor-parallel-size 2 \
--tool-call-parser minimax_m2 \
--reasoning-parser minimax_m2_append_think \
--trust-remote-code \
--enable-auto-tool-choice \
--max-model-len 65536 \
--gpu-memory-utilization 0.90'
```
### パラメータ解説
| パラメータ | 説明 |
|-----------|------|
| `--tensor-parallel-size 2` | 2台のSparkでテンソル並列 |
| `--tool-call-parser minimax_m2` | MiniMax専用ツールコールパーサー |
| `--reasoning-parser minimax_m2_append_think` | thinking出力を履歴に保持 |
| `--max-model-len 65536` | 最大コンテキスト長(64K|
| `--gpu-memory-utilization 0.90` | メモリ使用率上限 |
### メモリ節約版(長文不要な場合)
```bash
docker exec -it $VLLM_CONTAINER /bin/bash -c '
vllm serve cerebras/MiniMax-M2.5-REAP-172B-A10B \
--tensor-parallel-size 2 \
--tool-call-parser minimax_m2 \
--reasoning-parser minimax_m2_append_think \
--trust-remote-code \
--enable-auto-tool-choice \
--max-model-len 16384 \
--max-num-seqs 32 \
--gpu-memory-utilization 0.85'
```
### APIエンドポイントを別IPで公開する
クラスター通信(QSFP)とAPIエンドポイント(通常LAN)を分離したい場合:
```bash
docker exec -it $VLLM_CONTAINER /bin/bash -c '
vllm serve cerebras/MiniMax-M2.5-REAP-172B-A10B \
--tensor-parallel-size 2 \
--host 0.0.0.0 \
--port 8000 \
...'
```
| オプション | 説明 |
|-----------|------|
| `--host 0.0.0.0` | 全インターフェースでリッスン(推奨) |
| `--host 10.0.0.10` | 特定IPのみでリッスン |
| `--port 8000` | APIポート番号 |
**ポイント**: `VLLM_HOST_IP`環境変数はクラスター内部通信用であり、`--host`オプションはAPIエンドポイント用です。これらは独立して設定できます。
---
## 動作確認
### OpenAI互換APIでテスト
```bash
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "cerebras/MiniMax-M2.5-REAP-172B-A10B",
"messages": [
{"role": "user", "content": "Pythonでフィボナッチ数列を生成する関数を書いて"}
],
"temperature": 1.0,
"top_p": 0.95,
"top_k": 40
}'
```
### ヘルスチェック
```bash
curl http://localhost:8000/health
```
---
## 監視とデバッグ
### Rayダッシュボード
```
http://<head-node-ip>:8265
```
### GPU使用状況の確認
```bash
# 両ノードで
nvidia-smi
# コンテナ内から
export VLLM_CONTAINER=$(docker ps --format '{{.Names}}' | grep -E '^node-[0-9]+$')
docker exec $VLLM_CONTAINER nvidia-smi --query-gpu=memory.used,memory.total --format=csv
```
---
## トラブルシューティング
### OOM(メモリ不足)エラー
```bash
# 対策1: シーケンス数を減らす
--max-num-seqs 32
# 対策2: コンテキスト長を短く
--max-model-len 16384
# 対策3: メモリ使用率を下げる
--gpu-memory-utilization 0.80
```
### CUDA illegal memory access エラー
```bash
# CUDAグラフモードを変更
--compilation-config "{\"cudagraph_mode\": \"PIECEWISE\"}"
```
### Rayクラスターに接続できない
```bash
# ネットワークインターフェースの確認
ibdev2netdev
# ファイアウォールの確認
sudo ufw status
# Rayポートの開放(必要な場合)
sudo ufw allow 6379
sudo ufw allow 8265
```
---
## 重要な注意点
### Thinkingトークンの扱い
MiniMax-M2.5は **interleaved thinking model** です。出力に含まれる `<think>...</think>` タグは、履歴として次のリクエストに含める必要があります。
```python
# ❌ NGパターン:thinkingを削除
content = response.replace(r'<think>.*?</think>', '')
# ✅ OKパターン:そのまま保持
history.append({"role": "assistant", "content": response})
```
### 推奨推論パラメータ
```json
{
"temperature": 1.0,
"top_p": 0.95,
"top_k": 40
}
```
---
## まとめ
DGX Sparkデュアル構成(256GB)とNGC公式vLLMイメージを使えば、最新のMiniMax-M2.5-REAP-172Bが快適に動作します。オープンソースモデルとしてはトップクラスのコーディング・エージェント性能を持つこのモデル、ぜひ試してみてください。
### 参考リンク
- [MiniMax-M2.5-REAP-172B (Hugging Face)](https://huggingface.co/cerebras/MiniMax-M2.5-REAP-172B-A10B)
- [DGX Spark vLLM公式ガイド](https://build.nvidia.com/spark/vllm/stacked-sparks)
- [MiniMax-M2 公式リポジトリ (GitHub)](https://github.com/MiniMax-AI/MiniMax-M2)
- [vLLM MiniMax-M2 デプロイガイド](https://docs.vllm.ai/projects/recipes/en/latest/MiniMax/MiniMax-M2.html)
- [REAP論文 (arXiv)](https://arxiv.org/abs/2510.13999)
---
*この記事は2026年2月時点の情報です。最新情報は各公式ドキュメントをご確認ください。*
+144
View File
@@ -0,0 +1,144 @@
#!/bin/bash
#
# DGX Spark vLLM モデル起動スクリプト
# Usage: ./dgx-spark-serve.sh [model] [options...]
#
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# デフォルト設定
DEFAULT_TP_SIZE=2
DEFAULT_MAX_MODEL_LEN=65536
DEFAULT_GPU_MEM_UTIL=0.90
DEFAULT_HOST="0.0.0.0"
DEFAULT_PORT=8000
# プリセットモデル
declare -A MODEL_PRESETS=(
["minimax-m25"]="cerebras/MiniMax-M2.5-REAP-172B-A10B --tool-call-parser minimax_m2 --reasoning-parser minimax_m2_append_think --enable-auto-tool-choice"
["minimax-m2"]="cerebras/MiniMax-M2-REAP-172B-A10B --tool-call-parser minimax_m2 --reasoning-parser minimax_m2_append_think --enable-auto-tool-choice"
["llama-70b"]="nvidia/Llama-3.3-70B-Instruct-NVFP4"
["qwen-32b"]="nvidia/Qwen3-32B-NVFP4"
["gpt-oss-120b"]="openai/gpt-oss-120b"
)
show_presets() {
echo "利用可能なプリセット:"
for key in "${!MODEL_PRESETS[@]}"; do
echo " $key"
done
}
find_container() {
docker ps --format '{{.Names}}' | grep -E '^node-[0-9]+$' | head -1
}
main() {
local model="${1:-}"
shift || true
if [[ -z "$model" || "$model" == "help" || "$model" == "--help" ]]; then
cat << 'EOF'
DGX Spark vLLM モデル起動スクリプト
Usage:
dgx-spark-serve.sh <model|preset> [options...]
Presets:
minimax-m25 MiniMax-M2.5-REAP-172B (推奨)
minimax-m2 MiniMax-M2-REAP-172B
llama-70b Llama-3.3-70B-Instruct-NVFP4
qwen-32b Qwen3-32B-NVFP4
gpt-oss-120b GPT-OSS-120B
Options (vllm serveに渡される):
--tensor-parallel-size N テンソル並列数 (default: 2)
--max-model-len N 最大コンテキスト長 (default: 65536)
--host IP APIホスト (default: 0.0.0.0)
--port N APIポート (default: 8000)
Examples:
# プリセット使用
./dgx-spark-serve.sh minimax-m25
# カスタムモデル
./dgx-spark-serve.sh my-org/my-model --max-model-len 32768
# メモリ節約モード
./dgx-spark-serve.sh minimax-m25 --max-model-len 16384 --max-num-seqs 32
EOF
echo ""
show_presets
exit 0
fi
# コンテナ確認
local container
container=$(find_container)
if [[ -z "$container" ]]; then
log_error "vLLMコンテナが見つかりません"
log_info "先に 'dgx-spark-setup.sh cluster' でクラスターを起動してください"
exit 1
fi
log_ok "コンテナ検出: $container"
# プリセット展開
local model_args=""
if [[ -n "${MODEL_PRESETS[$model]:-}" ]]; then
model_args="${MODEL_PRESETS[$model]}"
log_info "プリセット使用: $model"
model=$(echo "$model_args" | awk '{print $1}')
model_args=$(echo "$model_args" | cut -d' ' -f2-)
fi
# デフォルトオプション構築
local has_tp=false has_len=false has_host=false has_port=false has_trust=false
for arg in "$@"; do
case "$arg" in
--tensor-parallel-size*) has_tp=true ;;
--max-model-len*) has_len=true ;;
--host*) has_host=true ;;
--port*) has_port=true ;;
--trust-remote-code*) has_trust=true ;;
esac
done
local defaults=""
$has_tp || defaults+=" --tensor-parallel-size $DEFAULT_TP_SIZE"
$has_len || defaults+=" --max-model-len $DEFAULT_MAX_MODEL_LEN"
$has_host || defaults+=" --host $DEFAULT_HOST"
$has_port || defaults+=" --port $DEFAULT_PORT"
$has_trust || defaults+=" --trust-remote-code"
defaults+=" --gpu-memory-utilization $DEFAULT_GPU_MEM_UTIL"
# コマンド構築
local cmd="vllm serve $model $model_args $defaults $*"
log_info "起動コマンド:"
echo " $cmd"
echo ""
read -rp "実行しますか? [Y/n]: " confirm
if [[ "${confirm,,}" == "n" ]]; then
log_warn "キャンセルしました"
exit 0
fi
log_info "モデルを起動中..."
docker exec -it "$container" /bin/bash -c "$cmd"
}
main "$@"
+343
View File
@@ -0,0 +1,343 @@
#!/bin/bash
#
# DGX Spark デュアル構成セットアップスクリプト
# Usage: curl -sL <url> | bash -s -- [command]
#
# Commands:
# network - QSFPインターフェースのIP設定
# ssh - 対向ノードへのSSH鍵配布
# docker - Docker権限設定
# vllm-pull - vLLMイメージ取得
# cluster - vLLMクラスター起動
# all - 全セットアップ実行
#
set -euo pipefail
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# QSFPインターフェース検出
detect_qsfp_interface() {
local iface
iface=$(ibdev2netdev 2>/dev/null | grep "(Up)" | awk '{print $5}' | head -1)
if [[ -z "$iface" ]]; then
# フォールバック: enp1s0f で始まるインターフェースを探す
iface=$(ip link show | grep -oP 'enp1s0f[0-9]+np[0-9]+' | head -1)
fi
echo "$iface"
}
# 現在のIPアドレス取得
get_current_ip() {
local iface=$1
ip -4 addr show "$iface" 2>/dev/null | grep -oP '(?<=inet\s)\d+(\.\d+){3}' | head -1
}
# ネットワーク設定
cmd_network() {
log_info "QSFPインターフェースを検出中..."
local iface
iface=$(detect_qsfp_interface)
if [[ -z "$iface" ]]; then
log_error "QSFPインターフェースが見つかりません"
log_info "ibdev2netdev の出力を確認してください"
exit 1
fi
log_ok "検出: $iface"
# 現在のIP確認
local current_ip
current_ip=$(get_current_ip "$iface")
if [[ -n "$current_ip" ]]; then
log_info "現在のIP: $current_ip"
read -rp "このIPを使用しますか? [Y/n]: " use_current
if [[ "${use_current,,}" != "n" ]]; then
log_ok "設定完了: $iface = $current_ip"
return 0
fi
fi
# IP入力
echo ""
log_info "このノードのIPアドレスを設定します"
log_info "例: Node 1 = 192.168.100.10, Node 2 = 192.168.100.11"
read -rp "IPアドレス (例: 192.168.100.10): " new_ip
read -rp "サブネットマスク [24]: " subnet
subnet=${subnet:-24}
# 設定適用
log_info "IPアドレスを設定中..."
sudo ip addr flush dev "$iface" 2>/dev/null || true
sudo ip addr add "${new_ip}/${subnet}" dev "$iface"
sudo ip link set "$iface" up
log_ok "設定完了: $iface = $new_ip/$subnet"
# 永続化確認
read -rp "netplanで永続化しますか? [Y/n]: " persist
if [[ "${persist,,}" != "n" ]]; then
local netplan_file="/etc/netplan/99-dgx-spark-qsfp.yaml"
log_info "netplan設定を作成中..."
sudo tee "$netplan_file" > /dev/null << EOF
network:
version: 2
ethernets:
${iface}:
addresses:
- ${new_ip}/${subnet}
EOF
sudo netplan apply
log_ok "永続化完了: $netplan_file"
fi
# 環境変数出力
echo ""
log_info "以下の環境変数をエクスポートしてください:"
echo "export MN_IF_NAME=$iface"
echo "export VLLM_HOST_IP=$new_ip"
}
# SSH鍵配布
cmd_ssh() {
log_info "SSH鍵の設定を開始します"
# 鍵がなければ生成
if [[ ! -f ~/.ssh/id_ed25519 ]]; then
log_info "SSH鍵を生成中..."
ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519
log_ok "鍵生成完了"
else
log_ok "既存の鍵を使用: ~/.ssh/id_ed25519"
fi
# 対向ノードのIP入力
read -rp "対向ノードのIPアドレス: " remote_ip
read -rp "対向ノードのユーザー名 [$USER]: " remote_user
remote_user=${remote_user:-$USER}
log_info "対向ノードに公開鍵を配布中..."
log_warn "パスワードを求められます"
ssh-copy-id "${remote_user}@${remote_ip}"
log_ok "SSH鍵配布完了"
# 疎通確認
log_info "接続テスト中..."
if ssh -o BatchMode=yes -o ConnectTimeout=5 "${remote_user}@${remote_ip}" "echo OK" &>/dev/null; then
log_ok "パスワードなしSSH接続成功"
else
log_error "接続に失敗しました"
exit 1
fi
}
# Docker権限設定
cmd_docker() {
log_info "Docker権限を設定中..."
if groups | grep -q docker; then
log_ok "既にdockerグループに所属しています"
else
sudo groupadd docker 2>/dev/null || true
sudo usermod -aG docker "$USER"
log_ok "dockerグループに追加しました"
log_warn "変更を反映するには再ログインするか 'newgrp docker' を実行してください"
fi
# NVIDIA Container Toolkit確認
if docker run --rm --gpus all nvidia/cuda:13.0.1-devel-ubuntu24.04 nvidia-smi &>/dev/null; then
log_ok "NVIDIA Container Toolkit正常動作"
else
log_error "GPU付きコンテナが起動できません"
log_info "NVIDIA Container Toolkitをインストールしてください"
exit 1
fi
}
# vLLMイメージ取得
cmd_vllm_pull() {
local image="nvcr.io/nvidia/vllm:25.11-py3"
log_info "vLLMイメージを取得中..."
log_info "Image: $image"
docker pull "$image"
log_ok "取得完了"
echo ""
echo "export VLLM_IMAGE=$image"
}
# vLLMクラスター起動
cmd_cluster() {
log_info "vLLMクラスターを起動します"
# 環境変数確認
if [[ -z "${VLLM_IMAGE:-}" ]]; then
export VLLM_IMAGE="nvcr.io/nvidia/vllm:25.11-py3"
log_warn "VLLM_IMAGE未設定、デフォルト使用: $VLLM_IMAGE"
fi
if [[ -z "${VLLM_HOST_IP:-}" ]]; then
local iface
iface=$(detect_qsfp_interface)
if [[ -n "$iface" ]]; then
VLLM_HOST_IP=$(get_current_ip "$iface")
fi
if [[ -z "${VLLM_HOST_IP:-}" ]]; then
read -rp "このノードのクラスター通信用IP: " VLLM_HOST_IP
fi
fi
if [[ -z "${MN_IF_NAME:-}" ]]; then
MN_IF_NAME=$(detect_qsfp_interface)
fi
log_info "設定:"
echo " VLLM_IMAGE: $VLLM_IMAGE"
echo " VLLM_HOST_IP: $VLLM_HOST_IP"
echo " MN_IF_NAME: $MN_IF_NAME"
echo ""
# ノードタイプ選択
echo "このノードの役割を選択してください:"
echo " 1) ヘッドノード (Node 1)"
echo " 2) ワーカーノード (Node 2)"
read -rp "選択 [1/2]: " node_type
# run_cluster.sh取得
if [[ ! -f ./run_cluster.sh ]]; then
log_info "run_cluster.sh をダウンロード中..."
wget -q https://raw.githubusercontent.com/vllm-project/vllm/refs/heads/main/examples/online_serving/run_cluster.sh
chmod +x run_cluster.sh
fi
local head_ip
if [[ "$node_type" == "1" ]]; then
head_ip="$VLLM_HOST_IP"
log_info "ヘッドノードとして起動中..."
bash run_cluster.sh "$VLLM_IMAGE" "$head_ip" --head ~/.cache/huggingface \
-e VLLM_HOST_IP="$VLLM_HOST_IP" \
-e UCX_NET_DEVICES="$MN_IF_NAME" \
-e NCCL_SOCKET_IFNAME="$MN_IF_NAME" \
-e OMPI_MCA_btl_tcp_if_include="$MN_IF_NAME" \
-e GLOO_SOCKET_IFNAME="$MN_IF_NAME" \
-e TP_SOCKET_IFNAME="$MN_IF_NAME" \
-e RAY_memory_monitor_refresh_ms=0 \
-e MASTER_ADDR="$head_ip"
else
read -rp "ヘッドノード(Node 1)のIP: " head_ip
log_info "ワーカーノードとして起動中..."
bash run_cluster.sh "$VLLM_IMAGE" "$head_ip" --worker ~/.cache/huggingface \
-e VLLM_HOST_IP="$VLLM_HOST_IP" \
-e UCX_NET_DEVICES="$MN_IF_NAME" \
-e NCCL_SOCKET_IFNAME="$MN_IF_NAME" \
-e OMPI_MCA_btl_tcp_if_include="$MN_IF_NAME" \
-e GLOO_SOCKET_IFNAME="$MN_IF_NAME" \
-e TP_SOCKET_IFNAME="$MN_IF_NAME" \
-e RAY_memory_monitor_refresh_ms=0 \
-e MASTER_ADDR="$head_ip"
fi
}
# 全セットアップ
cmd_all() {
log_info "=== DGX Spark デュアル構成 フルセットアップ ==="
echo ""
cmd_docker
echo ""
cmd_network
echo ""
read -rp "対向ノードへのSSH鍵配布を行いますか? [Y/n]: " do_ssh
if [[ "${do_ssh,,}" != "n" ]]; then
cmd_ssh
echo ""
fi
cmd_vllm_pull
echo ""
log_ok "セットアップ完了!"
echo ""
log_info "次のステップ:"
echo " 1. 対向ノードでも同じスクリプトを実行"
echo " 2. 両ノードで 'dgx-spark-setup.sh cluster' を実行"
echo " 3. ヘッドノードでモデルを起動"
}
# ヘルプ
cmd_help() {
cat << 'EOF'
DGX Spark デュアル構成セットアップスクリプト
Usage:
dgx-spark-setup.sh <command>
Commands:
network QSFPインターフェースのIP設定
ssh 対向ノードへのSSH鍵配布
docker Docker権限設定
vllm-pull vLLMイメージ取得
cluster vLLMクラスター起動
all 全セットアップ実行(推奨)
Examples:
# フルセットアップ
./dgx-spark-setup.sh all
# ネットワークのみ設定
./dgx-spark-setup.sh network
# クラスター起動
export VLLM_HOST_IP=192.168.100.10
export MN_IF_NAME=enp1s0f1np1
./dgx-spark-setup.sh cluster
ワンライナー実行:
curl -sL https://example.com/dgx-spark-setup.sh | bash -s -- all
EOF
}
# メイン
main() {
local cmd="${1:-help}"
case "$cmd" in
network) cmd_network ;;
ssh) cmd_ssh ;;
docker) cmd_docker ;;
vllm-pull) cmd_vllm_pull ;;
cluster) cmd_cluster ;;
all) cmd_all ;;
help|--help|-h) cmd_help ;;
*)
log_error "Unknown command: $cmd"
cmd_help
exit 1
;;
esac
}
main "$@"