nccl.sh 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. # NCCL setup shared by the head and the generated worker Bash script.
  2. # Ring transport/overlay integration derives from PR #3 by @Saolence and is
  3. # extended here with current-main compatibility, all-rank preflight fixtures,
  4. # safer quoting/path mapping, and local-weight validation.
  5. # The underlying NCCL transport patch is from FujitsuPolycom/sparkring.
  6. # Sourced by start.sh; no probing or side effects at import time.
  7. nccl_library() {
  8. local dir="$1" name
  9. for name in libnccl.so.2.30.7 libnccl.so.2; do
  10. if [[ -f "$dir/$name" && -r "$dir/$name" ]]; then
  11. printf '%s\n' "$dir/$name"
  12. return 0
  13. fi
  14. done
  15. return 1
  16. }
  17. nccl_mount_args() {
  18. local -n mounts=$1
  19. local library
  20. if library=$(nccl_library "$NCCL_HOST_DIR"); then
  21. if [[ "$NCCL_OVERLAY_PIP" == 1 ]]; then
  22. mounts+=(-v "$library:$NCCL_PIP_SO:ro")
  23. else
  24. mounts+=(-v "$NCCL_HOST_DIR:$NCCL_CONTAINER_DIR:ro"
  25. -e "LD_LIBRARY_PATH=$NCCL_CONTAINER_DIR")
  26. fi
  27. elif [[ "$NCCL_OVERLAY_PIP" == 1 || "${NCCL_SWITCHLESS_RING_ONLY:-0}" == 1 ]]; then
  28. echo "NCCL: no readable library in $NCCL_HOST_DIR" >&2
  29. return 1
  30. fi
  31. }
  32. switchless_ring_args() {
  33. [[ "${NCCL_SWITCHLESS_RING_ONLY:-0}" == 1 ]] || return 0
  34. local -n ring_args=$1
  35. ring_args+=(-e NCCL_SWITCHLESS_RING_ONLY=1
  36. -e "NCCL_ALGO=${NCCL_ALGO:-Ring}"
  37. -e "NCCL_SKIP_TREE_CONNECT=${NCCL_SKIP_TREE_CONNECT:-1}"
  38. -e "NCCL_IB_SUBNET_PREFIX_LEN=${NCCL_IB_SUBNET_PREFIX_LEN:-24}"
  39. -e "NCCL_MIN_NCHANNELS=${NCCL_MIN_NCHANNELS:-4}"
  40. -e "NCCL_P2P_LEVEL=${NCCL_P2P_LEVEL:-SYS}")
  41. }
  42. nccl_validate_config() {
  43. local flag
  44. for flag in NCCL_SWITCHLESS_RING_ONLY NCCL_OVERLAY_PIP DSV41_SERIAL_WEIGHT_LOAD; do
  45. case "${!flag:-0}" in 0|1) ;; *) echo "$flag must be 0 or 1" >&2; return 1;; esac
  46. done
  47. if [[ -n "${NCCL_WORKER_DIR:-}" && "$NCCL_WORKER_DIR" != /* ]]; then
  48. echo 'NCCL_WORKER_DIR must be absolute' >&2
  49. return 1
  50. fi
  51. [[ "${NCCL_SWITCHLESS_RING_ONLY:-0}" == 1 ]] || return 0
  52. # The ring spans the tensor-parallel group, so NNODES = TP_SIZE = 4. ./start.sh still
  53. # requires EP_SIZE=4. start-tp4.sh (DSV41_LAUNCHER=tp4) also accepts 2 and 1: EP only
  54. # groups the MoE experts inside the ring, and the TP4 production line runs EP 1.
  55. local ep_ok=0 ep_msg="EP_SIZE=4"
  56. if [[ "$EP_SIZE" == 4 ]]; then
  57. ep_ok=1
  58. elif [[ "${DSV41_LAUNCHER:-}" == tp4 && "$EP_SIZE" =~ ^[12]$ ]]; then
  59. ep_ok=1
  60. ep_msg="EP_SIZE 1, 2 or 4"
  61. fi
  62. if [[ "$NNODES" != 4 || "$TP_SIZE" != 4 || "$ep_ok" != 1 ||
  63. "$NCCL_OVERLAY_PIP" != 1 || "${NCCL_ALGO:-Ring}" != Ring ||
  64. "$NCCL_NET" != IB || "$NCCL_IB_DISABLE" != 0 ||
  65. "${NCCL_IB_SUBNET_AWARE_ROUTING:-1}" != 1 ]]; then
  66. echo "Ring requires NNODES=TP_SIZE=4, $ep_msg, NCCL_OVERLAY_PIP=1, NCCL_ALGO=Ring, NCCL_NET=IB, NCCL_IB_DISABLE=0 and subnet-aware routing=1" >&2
  67. return 1
  68. fi
  69. local minimum="${NCCL_MIN_NCHANNELS:-4}" maximum="${NCCL_MAX_NCHANNELS:-32}"
  70. if [[ ! "$minimum" =~ ^[1-9][0-9]{0,2}$ || ! "$maximum" =~ ^[1-9][0-9]{0,2}$ ]] ||
  71. (( minimum > maximum )); then
  72. echo 'Ring requires positive NCCL_MIN_NCHANNELS <= NCCL_MAX_NCHANNELS' >&2
  73. return 1
  74. fi
  75. }
  76. # MANAGEMENT IPs need not appear in the CX7 GID table. Find a common, nonzero
  77. # IPv4 RoCE v2 index on all explicitly selected ports, or validate the override.
  78. # The optional sysfs root is used by host-side fixtures.
  79. ring_gid_index() {
  80. local sysfs="${1:-/sys/class/infiniband}" entry hca port state base g idx valid gid
  81. local -a entries=() ports=() candidates=()
  82. IFS=, read -r -a entries <<<"${IB_HCA#=}"
  83. for entry in "${entries[@]}"; do
  84. if [[ ! "$entry" =~ ^[a-zA-Z0-9_]+(:[1-9][0-9]*)?$ ]]; then
  85. echo 'Ring IB_HCA must list exact HCA names, optionally with :port' >&2
  86. return 1
  87. fi
  88. hca="${entry%%:*}"; port=1
  89. [[ "$entry" != *:* ]] || port="${entry##*:}"
  90. base="$sysfs/$hca/ports/$port"
  91. state=$(cat "$base/state" 2>/dev/null) || state=''
  92. if [[ "$state" != '4: ACTIVE' ]]; then
  93. echo "Ring: $hca:$port is not ACTIVE" >&2
  94. return 1
  95. fi
  96. ports+=("$base")
  97. done
  98. [[ ${#ports[@]} -gt 0 ]] || { echo 'Ring: IB_HCA is empty' >&2; return 1; }
  99. if [[ -n "${NCCL_IB_GID_INDEX:-}" ]]; then
  100. [[ "$NCCL_IB_GID_INDEX" =~ ^[0-9]+$ ]] || return 1
  101. candidates=("$NCCL_IB_GID_INDEX")
  102. else
  103. for g in "${ports[0]}"/gids/*; do candidates+=("${g##*/}"); done
  104. fi
  105. for idx in "${candidates[@]}"; do
  106. valid=1
  107. for base in "${ports[@]}"; do
  108. if [[ "$(cat "$base/gid_attrs/types/$idx" 2>/dev/null)" != 'RoCE v2' ]]; then
  109. valid=0; break
  110. fi
  111. gid=$(cat "$base/gids/$idx" 2>/dev/null) || gid=''
  112. if [[ ! "$gid" =~ ^0000:0000:0000:0000:0000:ffff:[[:xdigit:]]{4}:[[:xdigit:]]{4}$ ||
  113. "$gid" == *:0000:0000 ]]; then
  114. valid=0; break
  115. fi
  116. done
  117. if [[ "$valid" == 1 ]]; then printf '%s\n' "$idx"; return 0; fi
  118. done
  119. echo "Ring: no common IPv4 RoCE v2 GID on $IB_HCA (override: ${NCCL_IB_GID_INDEX:-auto})" >&2
  120. return 1
  121. }
  122. nccl_preflight() {
  123. local library
  124. if [[ "$NCCL_OVERLAY_PIP" == 1 || "${NCCL_SWITCHLESS_RING_ONLY:-0}" == 1 ]]; then
  125. library=$(nccl_library "$NCCL_HOST_DIR") || {
  126. echo "NCCL: no readable library in $NCCL_HOST_DIR" >&2; return 1;
  127. }
  128. if [[ "${NCCL_SWITCHLESS_RING_ONLY:-0}" == 1 ]] && ! grep -qa SWITCHLESS_RING_ONLY "$library"; then
  129. echo "NCCL: $library lacks SWITCHLESS_RING_ONLY; install the patched build" >&2
  130. return 1
  131. fi
  132. [[ "$NCCL_PIP_SO" == /* && "$NCCL_PIP_SO" != *:* && "$NCCL_HOST_DIR" == /* && "$NCCL_HOST_DIR" != *:* ]] || {
  133. echo 'NCCL overlay requires an absolute container path and paths without colons' >&2; return 1;
  134. }
  135. docker run --rm --pull=never --network none --entrypoint /bin/sh "$IMAGE" \
  136. -c 'test -f "$1"' sh "$NCCL_PIP_SO" || {
  137. echo "NCCL: $IMAGE lacks the pip library at $NCCL_PIP_SO" >&2; return 1;
  138. }
  139. fi
  140. if [[ "${NCCL_SWITCHLESS_RING_ONLY:-0}" == 1 ]]; then ring_gid_index || return 1; fi
  141. return 0
  142. }
  143. # Home-relative head paths follow the worker's HOME, including custom subdirs.
  144. # An explicit NCCL_WORKER_DIR (absolute) overrides that mapping on every worker.
  145. nccl_worker_settings() {
  146. if [[ -n "${NCCL_WORKER_DIR:-}" ]]; then
  147. printf 'NCCL_HOST_DIR=%q\n' "$NCCL_WORKER_DIR"
  148. elif [[ "$NCCL_HOST_DIR" == "$HOME/"* ]]; then
  149. printf 'NCCL_HOST_DIR="$HOME"/%q\n' "${NCCL_HOST_DIR#"$HOME/"}"
  150. else
  151. printf 'NCCL_HOST_DIR=%q\n' "$NCCL_HOST_DIR"
  152. fi
  153. local key
  154. for key in NCCL_OVERLAY_PIP NCCL_PIP_SO NCCL_CONTAINER_DIR NCCL_SWITCHLESS_RING_ONLY IB_HCA IMAGE NCCL_IB_GID_INDEX; do
  155. printf '%s=%q\n' "$key" "${!key:-}"
  156. done
  157. declare -f nccl_library nccl_mount_args ring_gid_index nccl_preflight
  158. }