From 24ea3f3e25643f95dd4905d14d8550b598ffe4d0 Mon Sep 17 00:00:00 2001 From: koide Date: Thu, 19 Feb 2026 01:33:04 +0000 Subject: [PATCH] =?UTF-8?q?Refactor:=20=E8=A8=98=E4=BA=8B=E3=82=92?= =?UTF-8?q?=E3=83=87=E3=82=A3=E3=83=AC=E3=82=AF=E3=83=88=E3=83=AA=E6=A7=8B?= =?UTF-8?q?=E6=88=90=E3=81=AB=E5=A4=89=E6=9B=B4=E3=80=81=E3=82=BB=E3=83=83?= =?UTF-8?q?=E3=83=88=E3=82=A2=E3=83=83=E3=83=97=E3=82=B9=E3=82=AF=E3=83=AA?= =?UTF-8?q?=E3=83=97=E3=83=88=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../README.md | 0 dgx-spark-minimax/scripts/dgx-spark-serve.sh | 144 ++++++++ dgx-spark-minimax/scripts/dgx-spark-setup.sh | 343 ++++++++++++++++++ 3 files changed, 487 insertions(+) rename dgx-spark-minimax-m25-reap.md => dgx-spark-minimax/README.md (100%) create mode 100755 dgx-spark-minimax/scripts/dgx-spark-serve.sh create mode 100755 dgx-spark-minimax/scripts/dgx-spark-setup.sh diff --git a/dgx-spark-minimax-m25-reap.md b/dgx-spark-minimax/README.md similarity index 100% rename from dgx-spark-minimax-m25-reap.md rename to dgx-spark-minimax/README.md diff --git a/dgx-spark-minimax/scripts/dgx-spark-serve.sh b/dgx-spark-minimax/scripts/dgx-spark-serve.sh new file mode 100755 index 0000000..68c36bf --- /dev/null +++ b/dgx-spark-minimax/scripts/dgx-spark-serve.sh @@ -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 [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 "$@" diff --git a/dgx-spark-minimax/scripts/dgx-spark-setup.sh b/dgx-spark-minimax/scripts/dgx-spark-setup.sh new file mode 100755 index 0000000..59bcff5 --- /dev/null +++ b/dgx-spark-minimax/scripts/dgx-spark-setup.sh @@ -0,0 +1,343 @@ +#!/bin/bash +# +# DGX Spark デュアル構成セットアップスクリプト +# Usage: curl -sL | 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 + +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 "$@"