stop.sh 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. #!/usr/bin/env bash
  2. # stop.sh — tear down DeepSeek-V4.1-Flash SGLang on all 3 Sparks.
  3. #
  4. # Stops:
  5. # - dsv41-head on spark1 (rank 0 + API :8888)
  6. # - dsv41-worker on spark2 and spark3
  7. # - log-tail helper
  8. # - leftover sglang.launch_server in those containers
  9. #
  10. # Keeps:
  11. # - checkpoint on spark1
  12. # - overlay image dsv41-3x-spark:local
  13. # - shared NFSv4 exporter (vllm-fn-nfs) — Qwen/GLM still use it
  14. # - docker volume dsv41-weights unless you pass --unmount
  15. #
  16. # Usage:
  17. # ./stop.sh stop serve on all 3 nodes
  18. # ./stop.sh --unmount also drop worker NFS volumes (weights stay on spark1)
  19. # ./start.sh stop same
  20. #
  21. set -uo pipefail
  22. ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  23. cd "$ROOT"
  24. UNMOUNT="${UNMOUNT:-0}"
  25. while [[ $# -gt 0 ]]; do
  26. case "$1" in
  27. --unmount|-u) UNMOUNT=1 ;;
  28. -h|--help)
  29. sed -n '2,20p' "$0" | sed 's/^# \?//'
  30. exit 0
  31. ;;
  32. *)
  33. echo "unknown arg: $1 (try ./stop.sh --help)" >&2
  34. exit 1
  35. ;;
  36. esac
  37. shift
  38. done
  39. ENV_FILE="${ENV_FILE:-$ROOT/.env}"
  40. if [[ -f "$ENV_FILE" ]]; then
  41. set -a
  42. # shellcheck disable=SC1091
  43. source "$ENV_FILE"
  44. set +a
  45. fi
  46. _abs() { readlink -f "$1" 2>/dev/null || echo "$1"; }
  47. HEAD_CTN="${HEAD_CTN:-dsv41-head}"
  48. WORKER_CTN="${WORKER_CTN:-dsv41-worker}"
  49. PORT="${PORT:-8888}"
  50. _list() { tr ',' ' ' <<<"$1"; }
  51. if [[ -n "${WORKER_IPS:-}" ]]; then
  52. read -r -a WORKER_IPS <<<"$(_list "$WORKER_IPS")"
  53. else
  54. WORKER_IPS=("${WORKER1_IP:-10.0.0.2}" "${WORKER2_IP:-10.0.0.3}")
  55. fi
  56. if [[ -n "${WORKER_HOSTS:-}" ]]; then
  57. read -r -a WORKER_HOSTS <<<"$(_list "$WORKER_HOSTS")"
  58. else
  59. WORKER_HOSTS=()
  60. for _i in "${!WORKER_IPS[@]}"; do
  61. _v="WORKER$((_i + 1))_HOST"
  62. WORKER_HOSTS+=("${!_v:-${WORKER_IPS[$_i]}}")
  63. done
  64. fi
  65. WORKER_USER="${WORKER_USER:-zurih}"
  66. SSH_IDENTITY="$(_abs "${SSH_IDENTITY:-$HOME/.ssh/id_ed25519_shared}")"
  67. NFS_VOLUME="${NFS_VOLUME:-dsv41-weights}"
  68. IMAGE="${IMAGE:-dsv41-3x-spark:local}"
  69. REMOTE_PY="${REMOTE_PY:-$ROOT/scripts/remote.py}"
  70. LOG_DIR="${LOG_DIR:-$ROOT/logs}"
  71. RM_TIMEOUT="${RM_TIMEOUT:-30}"
  72. GREEN=$'\033[0;32m'; YELLOW=$'\033[1;33m'; RED=$'\033[0;31m'; NC=$'\033[0m'
  73. info() { echo "${GREEN}[+]${NC} $*"; }
  74. warn() { echo "${YELLOW}[!]${NC} $*"; }
  75. err() { echo "${RED}[x]${NC} $*" >&2; }
  76. remote_on() {
  77. local host="$1"; shift
  78. local env_args=()
  79. [[ -f "$ENV_FILE" ]] && env_args=(--env-file "$ENV_FILE")
  80. python3 "$REMOTE_PY" "${env_args[@]}" \
  81. --host "$host" --user "$WORKER_USER" \
  82. --identity "$SSH_IDENTITY" \
  83. --timeout "${REMOTE_TIMEOUT:-60}" \
  84. "bash -lc $(printf '%q' "$*")"
  85. }
  86. _rm_ctn() {
  87. local name="$1"
  88. timeout "$RM_TIMEOUT" docker rm -f "$name" >/dev/null 2>&1 || true
  89. }
  90. _stop_sglang_in() {
  91. local ctn="$1"
  92. docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$ctn" || return 0
  93. docker exec "$ctn" bash -lc '
  94. pkill -TERM -f "[s]glang.launch_server" >/dev/null 2>&1 || true
  95. pkill -TERM -f "[s]glang.srt" >/dev/null 2>&1 || true
  96. sleep 1
  97. pkill -KILL -f "[s]glang.launch_server" >/dev/null 2>&1 || true
  98. ' 2>/dev/null || true
  99. }
  100. info "=== stop DeepSeek-V4.1-Flash (3× Spark SGLang) ==="
  101. if [[ -f "$LOG_DIR/logtail.pid" ]]; then
  102. kill "$(cat "$LOG_DIR/logtail.pid")" 2>/dev/null || true
  103. rm -f "$LOG_DIR/logtail.pid"
  104. fi
  105. pkill -f "docker logs -f ${HEAD_CTN}" >/dev/null 2>&1 || true
  106. info "head: SIGTERM sglang in $HEAD_CTN, then remove"
  107. _stop_sglang_in "$HEAD_CTN"
  108. _rm_ctn "$HEAD_CTN"
  109. # anything else this recipe named
  110. ids=$(docker ps -aq --filter "name=dsv41-" 2>/dev/null || true)
  111. if [[ -n "$ids" ]]; then
  112. # shellcheck disable=SC2086
  113. timeout "$RM_TIMEOUT" docker rm -f $ids >/dev/null 2>&1 || true
  114. fi
  115. for h in "${WORKER_HOSTS[@]}"; do
  116. info "worker $WORKER_USER@$h: stop $WORKER_CTN"
  117. if remote_on "$h" "
  118. if docker ps --format '{{.Names}}' | grep -qx $(printf '%q' "$WORKER_CTN"); then
  119. docker exec $(printf '%q' "$WORKER_CTN") bash -lc '
  120. pkill -TERM -f \"[s]glang.launch_server\" >/dev/null 2>&1 || true
  121. sleep 1
  122. pkill -KILL -f \"[s]glang.launch_server\" >/dev/null 2>&1 || true
  123. ' 2>/dev/null || true
  124. fi
  125. timeout ${RM_TIMEOUT} docker rm -f $(printf '%q' "$WORKER_CTN") >/dev/null 2>&1 || docker rm -f $(printf '%q' "$WORKER_CTN") >/dev/null 2>&1 || true
  126. ids=\$(docker ps -aq --filter name=dsv41- 2>/dev/null || true)
  127. [ -n \"\$ids\" ] && docker rm -f \$ids >/dev/null 2>&1 || true
  128. echo STOPPED_$h
  129. " 2>/dev/null | grep -q "STOPPED_$h"; then
  130. info " $h: container gone"
  131. else
  132. warn " $h: SSH/docker cleanup failed (node unreachable?). GPU there may still be busy."
  133. fi
  134. if [[ "$UNMOUNT" == "1" ]]; then
  135. info " $h: drop NFS volume $NFS_VOLUME"
  136. remote_on "$h" "docker volume rm $(printf '%q' "$NFS_VOLUME") >/dev/null 2>&1 || true" || true
  137. fi
  138. done
  139. echo
  140. if curl -sf --max-time 2 "http://127.0.0.1:${PORT}/v1/models" >/dev/null 2>&1 \
  141. || curl -sf --max-time 2 "http://127.0.0.1:${PORT}/health" >/dev/null 2>&1; then
  142. warn "something is still answering on :${PORT}"
  143. else
  144. info "API down on :${PORT}"
  145. fi
  146. left=$(docker ps --format '{{.Names}}' 2>/dev/null | grep -E '^dsv41-' || true)
  147. if [[ -n "$left" ]]; then
  148. warn "still running on head: $left"
  149. else
  150. info "no dsv41-* containers on head"
  151. fi
  152. info "kept: spark1 weights, $IMAGE overlay, vllm-fn-nfs exporter"
  153. [[ "$UNMOUNT" == "1" ]] || info "worker NFS volume $NFS_VOLUME kept (./stop.sh --unmount to drop it)"
  154. info "start again with: ./start.sh"