2026-08-27 14:25:34 +08:00

390 lines
13 KiB
Bash
Executable File

#!/usr/bin/env bash
# Deploy Kimi-K3 standard PD on 601-608. Speculative decoding stays disabled.
set -Eeuo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/config.env"
ACTION="${1:-status}"
RUN_ID="${RUN_ID:-kimi3-pd-standard-$(date '+%Y%m%d-%H%M%S')}"
RESULT_ROOT="${RESULT_ROOT:-${SCRIPT_DIR}/results/${RUN_ID}}"
SUDO_PASSWORD_FILE="${SUDO_PASSWORD_FILE:-/data/hzy/.sudo_password}"
P_CONTAINER_PREFIX="${EXPERIMENT}_prefill"
D_CONTAINER_PREFIX="${EXPERIMENT}_decode"
MC_CONTAINER="${EXPERIMENT}_mc_master"
ROUTER_CONTAINER="${EXPERIMENT}_router"
mkdir -p "${RESULT_ROOT}"/{commands,logs,metadata,bench}
log() {
printf '[%s] %s\n' "$(date '+%F %T')" "$*" | tee -a "${RESULT_ROOT}/orchestrator.log"
}
require_password() {
if [[ -z "${SUDO_PASSWORD:-}" ]]; then
[[ -r "${SUDO_PASSWORD_FILE}" ]] || {
echo "ERROR: set SUDO_PASSWORD or create ${SUDO_PASSWORD_FILE}" >&2
exit 2
}
IFS= read -r SUDO_PASSWORD <"${SUDO_PASSWORD_FILE}"
fi
}
is_local() {
[[ "$1" == "${P_HEAD}" ]]
}
remote() {
local host="$1"
shift
if is_local "$host"; then
"$@"
return
fi
local command
printf -v command '%q ' "$@"
ssh "${SSH_OPTIONS[@]}" "${SSH_USER}@${host}" "${command}"
}
sudo_host() {
local host="$1"
shift
require_password
if is_local "$host"; then
printf '%s\n' "${SUDO_PASSWORD}" | sudo -S -p '' -- "$@"
return
fi
local command
printf -v command '%q ' "$@"
printf '%s\n' "${SUDO_PASSWORD}" | ssh "${SSH_OPTIONS[@]}" \
"${SSH_USER}@${host}" "sudo -S -p '' -- ${command}"
}
node_suffix() {
printf '%s' "${1##*.}"
}
container_name() {
local group="$1" rank="$2"
if [[ "$group" == "p" ]]; then
printf '%s_node%s' "${P_CONTAINER_PREFIX}" "$rank"
else
printf '%s_node%s' "${D_CONTAINER_PREFIX}" "$rank"
fi
}
build_image_on_host() {
local host="$1" suffix build_dir
suffix="$(node_suffix "$host")"
build_dir="/tmp/${EXPERIMENT}_build"
log "staging PD image build context on node ${suffix}"
if is_local "$host"; then
rm -rf "$build_dir"
mkdir -p "$build_dir/patches"
cp "${SCRIPT_DIR}/Dockerfile.pd" "$build_dir/Dockerfile"
cp "${SCRIPT_DIR}/patches/31869.patch" "$build_dir/patches/31869.patch"
cp "${SCRIPT_DIR}/patches/32797.patch" "$build_dir/patches/32797.patch"
cp "${MOONCAKE_WHEEL}" "$build_dir/mooncake.whl"
else
ssh "${SSH_OPTIONS[@]}" "${SSH_USER}@${host}" \
"rm -rf ${build_dir} && mkdir -p ${build_dir}/patches"
scp "${SSH_OPTIONS[@]}" "${SCRIPT_DIR}/Dockerfile.pd" \
"${SSH_USER}@${host}:${build_dir}/Dockerfile"
scp "${SSH_OPTIONS[@]}" "${SCRIPT_DIR}/patches/31869.patch" \
"${SSH_USER}@${host}:${build_dir}/patches/31869.patch"
scp "${SSH_OPTIONS[@]}" "${SCRIPT_DIR}/patches/32797.patch" \
"${SSH_USER}@${host}:${build_dir}/patches/32797.patch"
scp "${SSH_OPTIONS[@]}" "${MOONCAKE_WHEEL}" \
"${SSH_USER}@${host}:${build_dir}/mooncake.whl"
fi
sudo_host "$host" docker build \
--build-arg "BASE_IMAGE=${BASE_IMAGE}" \
-t "${PD_IMAGE}" "$build_dir" \
>"${RESULT_ROOT}/logs/build_node${suffix}.log" 2>&1
log "PD image built on node ${suffix}"
}
build_image_all() {
local host
local -a build_nodes
read -r -a build_nodes <<<"${BUILD_NODES_OVERRIDE:-${ALL_NODES[*]}}"
for host in "${build_nodes[@]}"; do
build_image_on_host "$host"
done
}
preflight() {
require_password
local host suffix
: >"${RESULT_ROOT}/metadata/preflight.tsv"
printf 'node\timage\tmodel\trdma\tgpu_processes\tports\n' \
>>"${RESULT_ROOT}/metadata/preflight.tsv"
for host in "${ALL_NODES[@]}"; do
suffix="$(node_suffix "$host")"
log "preflight node ${suffix}"
sudo_host "$host" docker image inspect "$PD_IMAGE" >/dev/null
remote "$host" test -d "$MODEL_PATH"
remote "$host" test -e /dev/infiniband/uverbs0
local gpu_pids ports
gpu_pids="$(remote "$host" nvidia-smi --query-compute-apps=pid --format=csv,noheader | xargs || true)"
ports="$(remote "$host" sh -c "ss -lnt | grep -E ':(20000|28800|30000|31000|50051)[[:space:]]' || true")"
if [[ -n "$gpu_pids" ]]; then
echo "ERROR: node ${suffix} still has GPU processes: ${gpu_pids}" >&2
return 1
fi
if [[ -n "$ports" ]]; then
echo "ERROR: node ${suffix} has occupied PD ports: ${ports}" >&2
return 1
fi
printf '%s\tok\tok\tok\t0\tfree\n' "$suffix" \
>>"${RESULT_ROOT}/metadata/preflight.tsv"
done
local hash_cmd
hash_cmd="sha256sum /sgl-workspace/sglang/python/sglang/srt/disaggregation/prefill.py /sgl-workspace/sglang/python/sglang/srt/disaggregation/decode.py /sgl-workspace/sglang/python/sglang/srt/disaggregation/utils.py /sgl-workspace/sglang/python/sglang/srt/managers/scheduler_pp_mixin.py /sgl-workspace/sglang/python/sglang/srt/managers/scheduler.py; python3 -m pip list | grep -E 'sglang|flashinfer|mooncake-transfer-engine'"
sudo_host "$P_HEAD" docker run --rm --entrypoint bash "$PD_IMAGE" -lc "$hash_cmd" \
>"${RESULT_ROOT}/metadata/pd_image_versions.txt"
grep -q 'mooncake-transfer-engine-cuda13.*0.3.12.post1' \
"${RESULT_ROOT}/metadata/pd_image_versions.txt"
log "preflight passed on all 8 nodes"
}
common_docker_args() {
local host="$1"
local suffix
suffix="$(node_suffix "$host")"
COMMON_DOCKER_ARGS=(
--gpus all --network host --ipc=host --ulimit memlock=-1
--device /dev/infiniband --shm-size 32g --entrypoint bash
-v "${MODEL_PATH}:${MODEL_PATH}:ro"
-e CUDA_VISIBLE_DEVICES=0,1,2,3,4,5,6,7
-e "SGLANG_HOST_IP=174.1.60.${suffix}"
-e GLOO_SOCKET_IFNAME=bond0
-e NCCL_SOCKET_IFNAME=bond1
-e "NCCL_IB_HCA=${NCCL_IB_HCAS}"
-e NCCL_IB_GID_INDEX=3
-e NCCL_IB_TIMEOUT=22
-e NCCL_IB_RETRY_CNT=7
-e NCCL_CUMEM_ENABLE=1
-e "MOONCAKE_MASTER=${MC_MASTER}"
-e MOONCAKE_PROTOCOL=rdma
-e SGLANG_ENABLE_TP_MEMORY_INBALANCE_CHECK=0
-e SGLANG_MOE_FUSED_GATE_RADIX=1
-e FLASHINFER_DISABLE_JIT=1
-e FLASHINFER_DISABLE_VERSION_CHECK=1
)
}
start_node() {
local group="$1" rank="$2" host name bootstrap
local -a nodes launch_args cmd
if [[ "$group" == "p" ]]; then
nodes=("${P_NODES[@]}")
host="${nodes[$rank]}"
name="$(container_name p "$rank")"
launch_args=(
--model-path "$MODEL_PATH" --served-model-name "$SERVED_MODEL_NAME"
--tp-size "$P_TP" --pp-size "$P_PP" --ep-size "$P_EP"
--nnodes 4 --node-rank "$rank" --dist-init-addr "${P_HEAD}:${P_DIST_PORT}"
--trust-remote-code --moe-runner-backend flashinfer_mxfp4
--chunked-prefill-size "$P_CHUNKED_PREFILL_SIZE"
--page-size "$P_PAGE_SIZE"
--mem-fraction-static "$P_MEM_FRACTION_STATIC"
--cuda-graph-max-bs-decode 16
--mamba-radix-cache-strategy extra_buffer_lazy
--disable-radix-cache --dist-timeout 3600
--mamba-full-memory-ratio "$P_MAMBA_FULL_MEMORY_RATIO"
--disaggregation-transfer-backend mooncake
--disaggregation-bootstrap-port "$P_BOOTSTRAP_PORT"
--disaggregation-ib-device "$DISAGG_IB_DEVICES"
--disaggregation-mode prefill
--host 0.0.0.0 --port "$P_PORT"
)
else
nodes=("${D_NODES[@]}")
host="${nodes[$rank]}"
name="$(container_name d "$rank")"
launch_args=(
--model-path "$MODEL_PATH" --served-model-name "$SERVED_MODEL_NAME"
--tp-size "$D_TP" --pp-size "$D_PP" --ep-size "$D_EP"
--nnodes 4 --node-rank "$rank" --dist-init-addr "${D_HEAD}:${D_DIST_PORT}"
--trust-remote-code --moe-runner-backend marlin
--mem-fraction-static "$D_MEM_FRACTION_STATIC"
--cuda-graph-max-bs-decode 16
--mamba-radix-cache-strategy extra_buffer_lazy
--disable-radix-cache --dist-timeout 3600
--mamba-full-memory-ratio "$D_MAMBA_FULL_MEMORY_RATIO"
--page-size "$D_PAGE_SIZE"
--disaggregation-transfer-backend mooncake
--disaggregation-bootstrap-port "$P_BOOTSTRAP_PORT"
--disaggregation-ib-device "$DISAGG_IB_DEVICES"
--disaggregation-mode decode
--host 0.0.0.0 --port "$D_PORT"
)
fi
printf -v bootstrap '%q ' python3 -m sglang.launch_server "${launch_args[@]}"
bootstrap="exec ${bootstrap}"
common_docker_args "$host"
cmd=(docker run -d --name "$name" "${COMMON_DOCKER_ARGS[@]}" "$PD_IMAGE" -lc "$bootstrap")
printf '%q ' "${cmd[@]}" >"${RESULT_ROOT}/commands/${group}_node${rank}.cmd.txt"
printf '\n' >>"${RESULT_ROOT}/commands/${group}_node${rank}.cmd.txt"
sudo_host "$host" docker rm -f "$name" >/dev/null 2>&1 || true
sudo_host "$host" "${cmd[@]}" >/dev/null
log "started ${group} rank ${rank} on node $(node_suffix "$host")"
}
wait_health() {
local label="$1" host="$2" port="$3" elapsed
for ((elapsed = 1; elapsed <= HEALTH_WAIT_S; elapsed++)); do
if curl --fail --silent --max-time 5 "http://${host}:${port}/health" >/dev/null 2>&1; then
log "${label} healthy after ${elapsed}s"
return 0
fi
if (( elapsed % 30 == 0 )); then
log "waiting for ${label}, elapsed=${elapsed}s"
collect_group_logs "${label}_starting"
if grep -Eiq 'Traceback|CUDA out of memory|NCCL.*(error|failed)|AssertionError|RuntimeError' \
"${RESULT_ROOT}/logs/${label}_starting_"*.log 2>/dev/null; then
log "ERROR: fatal pattern found while starting ${label}"
return 1
fi
fi
sleep 1
done
return 1
}
start_group() {
local group="$1" rank
for rank in 1 2 3; do
start_node "$group" "$rank"
done
sleep 5
start_node "$group" 0
if [[ "$group" == "p" ]]; then
wait_health p "$P_HEAD" "$P_PORT"
else
wait_health d "$D_HEAD" "$D_PORT"
fi
collect_group_logs "${group}_healthy"
}
start_mc_master() {
sudo_host "$P_HEAD" docker rm -f "$MC_CONTAINER" >/dev/null 2>&1 || true
sudo_host "$P_HEAD" docker run -d --name "$MC_CONTAINER" --network host --gpus all \
--entrypoint /usr/local/bin/mooncake_master "$PD_IMAGE" >/dev/null
for _ in $(seq 1 30); do
remote "$P_HEAD" sh -c "ss -lnt | grep -q ':50051 '" && {
log "Mooncake master listening on 50051"
return 0
}
sleep 1
done
return 1
}
start_router() {
local -a cmd=(
docker run -d --name "$ROUTER_CONTAINER" --network host
--entrypoint python3 "$PD_IMAGE" -m sglang_router.launch_router
--pd-disaggregation --mini-lb
--prefill "http://${P_HEAD}:${P_PORT}" "$P_BOOTSTRAP_PORT"
--decode "http://${D_HEAD}:${D_PORT}"
--host 0.0.0.0 --port "$ROUTER_PORT"
)
printf '%q ' "${cmd[@]}" >"${RESULT_ROOT}/commands/router.cmd.txt"
printf '\n' >>"${RESULT_ROOT}/commands/router.cmd.txt"
sudo_host "$ROUTER_HOST" docker rm -f "$ROUTER_CONTAINER" >/dev/null 2>&1 || true
sudo_host "$ROUTER_HOST" "${cmd[@]}" >/dev/null
wait_health router "$ROUTER_HOST" "$ROUTER_PORT"
}
collect_group_logs() {
local label="$1" group rank host name
for group in p d; do
for rank in 0 1 2 3; do
if [[ "$group" == "p" ]]; then
host="${P_NODES[$rank]}"
else
host="${D_NODES[$rank]}"
fi
name="$(container_name "$group" "$rank")"
sudo_host "$host" docker logs "$name" \
>"${RESULT_ROOT}/logs/${label}_${group}_node${rank}.log" 2>&1 || true
done
done
sudo_host "$P_HEAD" docker logs "$MC_CONTAINER" \
>"${RESULT_ROOT}/logs/${label}_mc_master.log" 2>&1 || true
sudo_host "$ROUTER_HOST" docker logs "$ROUTER_CONTAINER" \
>"${RESULT_ROOT}/logs/${label}_router.log" 2>&1 || true
}
status_all() {
local host
for host in "${ALL_NODES[@]}"; do
echo "===== node $(node_suffix "$host") ====="
sudo_host "$host" docker ps --format '{{.Names}}|{{.Image}}|{{.Status}}' \
--filter "name=${EXPERIMENT}"
remote "$host" nvidia-smi --query-gpu=index,memory.used,utilization.gpu \
--format=csv,noheader
done
for endpoint in \
"p=http://${P_HEAD}:${P_PORT}/health" \
"d=http://${D_HEAD}:${D_PORT}/health" \
"router=http://${ROUTER_HOST}:${ROUTER_PORT}/health"; do
local name="${endpoint%%=*}" url="${endpoint#*=}"
if curl --fail --silent --max-time 5 "$url" >/dev/null 2>&1; then
echo "${name}=healthy"
else
echo "${name}=down"
fi
done
}
stop_all() {
local rank host name
sudo_host "$ROUTER_HOST" docker rm -f "$ROUTER_CONTAINER" >/dev/null 2>&1 || true
for rank in 0 1 2 3; do
host="${D_NODES[$rank]}"
name="$(container_name d "$rank")"
sudo_host "$host" docker rm -f "$name" >/dev/null 2>&1 || true
done
for rank in 0 1 2 3; do
host="${P_NODES[$rank]}"
name="$(container_name p "$rank")"
sudo_host "$host" docker rm -f "$name" >/dev/null 2>&1 || true
done
sudo_host "$P_HEAD" docker rm -f "$MC_CONTAINER" >/dev/null 2>&1 || true
log "standard PD service stopped"
}
start_all() {
preflight
start_mc_master
start_group p
start_group d
start_router
collect_group_logs ready
status_all | tee "${RESULT_ROOT}/metadata/status_ready.txt"
log "standard PD service ready at http://${ROUTER_HOST}:${ROUTER_PORT}"
}
case "$ACTION" in
build-image) build_image_all ;;
preflight) preflight ;;
start) start_all ;;
stop) stop_all ;;
restart) stop_all; start_all ;;
status) status_all ;;
logs) collect_group_logs manual ;;
*)
echo "Usage: $0 {build-image|preflight|start|stop|restart|status|logs}" >&2
exit 2
;;
esac