#!/usr/bin/env bash
# Start the SGLang server for Qwen3-235B-A22B on Kunlun P800 (TP=8, 8x XPU).
#
# Adapted from the proven qwen3-8b TP=1 launch on this image. Differences:
#   - new container exposing all 8 /dev/xpu* devices (the 8b container only had xpu0)
#   - --tp-size 8, XPU_VISIBLE_DEVICES=0..7
#   - context-length 8192 (8b used 4096)
#
# All XPU-specific env vars (XSGL_*, XMLIR_*) are taken verbatim from the 8b run.
set -Eeuo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
EXPERIMENT_NAME="$(basename "$SCRIPT_DIR")"

# shellcheck source=/dev/null
source "${SCRIPT_DIR}/../../../scripts/common/lib.sh"
# Source config.env BEFORE platform.sh so our CONTAINER_NAME default wins
# over the platform's "sglang-dsv4-flash" default (avoids name collisions
# with the dsv4 experiments, which would docker rm -f this container).
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/config.env"
# shellcheck source=/dev/null
source "${SCRIPT_DIR}/../../../scripts/common/platform.sh"

RUN_ID="${RUN_ID:-$(date '+%Y%m%d-%H%M%S')}"
RESULT_ROOT="${RESULT_ROOT:-${SCRIPT_DIR}/results/${RUN_ID}}"
LOG_DIR="${RESULT_ROOT}/logs"
mkdir -p "$LOG_DIR"

log_init "${LOG_DIR}/start_server.log"
log "starting server for ${EXPERIMENT_NAME}"
log "model: ${MODEL_PATH}"
log "tp: ${TP}  xpu: ${XPU_VISIBLE_DEVICES}"
log "port: ${PORT}"
log "container: ${CONTAINER_NAME}"
log "image: ${DOCKER_IMAGE}"

SERVER_LOG="${LOG_DIR}/server.log"

# --- 1. Ensure the container is running with all 8 XPU devices -------------
ensure_container() {
  if docker inspect "$CONTAINER_NAME" >/dev/null 2>&1; then
    log "container ${CONTAINER_NAME} exists; ensuring it is running"
    docker start "$CONTAINER_NAME" >/dev/null 2>&1 || true
  else
    log "creating container ${CONTAINER_NAME} from ${DOCKER_IMAGE}"
    local device_args=""
    local i
    for i in 0 1 2 3 4 5 6 7; do
      device_args="${device_args} --device /dev/xpu${i}:/dev/xpu${i}"
    done
    device_args="${device_args} --device /dev/xpuctrl:/dev/xpuctrl"

    docker run -d \
      --name "$CONTAINER_NAME" \
      --privileged \
      --network host \
      --ipc host \
      --security-opt label=disable \
      --shm-size 1g \
      ${device_args} \
      -v /data1:/data1 \
      -e XPU_VISIBLE_DEVICES="${XPU_VISIBLE_DEVICES}" \
      -e CUDA_VISIBLE_DEVICES="${CUDA_VISIBLE_DEVICES}" \
      -e TZ=Asia/Shanghai \
      --entrypoint /bin/bash \
      "$DOCKER_IMAGE" \
      -lc "source /etc/profile >/dev/null 2>&1 || true; exec sleep infinity"
  fi
}

# --- 2. Launch sglang.launch_server inside the container -------------------
launch_sglang() {
  log "launching sglang launch_server (TP=${TP}) inside ${CONTAINER_NAME}"
  # Kill any prior sglang process inside the container.
  docker exec "$CONTAINER_NAME" bash -lc 'pkill -9 -f sglang.launch_server 2>/dev/null || true' || true

  docker exec -d "$CONTAINER_NAME" bash -lc "
    source /root/miniconda/etc/profile.d/conda.sh
    conda activate python310_torch25_cuda
    export XPU_VISIBLE_DEVICES=${XPU_VISIBLE_DEVICES}
    export CUDA_VISIBLE_DEVICES=${CUDA_VISIBLE_DEVICES}
    export XSGL_INTERTYPE_BFP16=1
    export XSGL_FAST_SWIGLU=1
    export XMLIR_FORCE_USE_XPU_GRAPH=1
    export XSGL_FUSE_SPLIT_NORM_ROPE_NEOX=1
    export SGLANG_DISAGGREGATION_BOOTSTRAP_TIMEOUT=3000000
    export SGLANG_DISAGGREGATION_WAITING_TIMEOUT=600
    exec python -m sglang.launch_server \
      --model-path ${MODEL_PATH} \
      --host 0.0.0.0 \
      --port ${PORT} \
      --tp-size ${TP} \
      --context-length ${CONTEXT_LENGTH} \
      --max-running-requests ${MAX_RUNNING_REQUESTS} \
      --attention-backend ${ATTENTION_BACKEND} \
      --trust-remote-code \
      --disable-radix-cache \
      --disable-custom-all-reduce \
      --chunked-prefill-size -1 \
      --page-size ${PAGE_SIZE} \
      --mem-fraction-static ${MEM_FRACTION_STATIC} \
      --max-prefill-tokens ${MAX_PREFILL_TOKENS} \
      --dtype ${DTYPE} \
      --cuda-graph-max-bs ${CUDA_GRAPH_MAX_BS} \
      --cuda-graph-bs ${CUDA_GRAPH_BS} \
      --watchdog-timeout 3000000 \
      > ${SERVER_LOG} 2>&1
  "
}

ensure_container

# --- 1b. Patch qwen3_moe.py bug in the image -------------------------------
# The Kunlun sglang image's qwen3_moe.py:1829 assigns to the read-only
# @property `routed_experts_weights_of_layer` instead of the private
# `_routed_experts_weights_of_layer`, raising
#   AttributeError: can't set attribute 'routed_experts_weights_of_layer'
# for any qwen3 MoE model (e.g. Qwen3-235B-A22B). Qwen3-8B is dense, so the
# proven 8b run never hit this path. Fix: assign to the private attr.
# Idempotent - safe to run on every start.
patch_qwen3_moe() {
  log "applying qwen3_moe.py patch in ${CONTAINER_NAME}"
  docker exec "$CONTAINER_NAME" bash -lc '
    F=/root/miniconda/envs/python310_torch25_cuda/lib/python3.10/site-packages/sglang/srt/models/qwen3_moe.py
    if grep -q "self\.routed_experts_weights_of_layer = LazyValue(" "$F"; then
      cp "$F" "$F.bak.qwen3moe" 2>/dev/null || true
      sed -i "s/self\.routed_experts_weights_of_layer = LazyValue(/self._routed_experts_weights_of_layer = LazyValue(/" "$F"
      echo "PATCHED qwen3_moe.py (routed_experts_weights_of_layer -> _routed_experts_weights_of_layer)"
    else
      echo "qwen3_moe.py already patched"
    fi
  '
}
patch_qwen3_moe

launch_sglang

# 235B weights (~470GB) + cuda-graph capture for 16 batch sizes take a while.
HEALTH_TIMEOUT="${HEALTH_TIMEOUT:-1200}"
log "waiting for health on http://127.0.0.1:${PORT}/health (timeout=${HEALTH_TIMEOUT}s)"
if health_check 127.0.0.1 "$PORT" "$HEALTH_TIMEOUT"; then
  log "sglang server is READY at http://127.0.0.1:${PORT}"
  log "server log: ${SERVER_LOG}  (or: docker logs -f ${CONTAINER_NAME})"
  exit 0
else
  log "ERROR: server not healthy after ${HEALTH_TIMEOUT}s"
  log "----- last 200 lines of server log -----"
  tail -200 "$SERVER_LOG" 2>/dev/null || docker logs --tail 200 "$CONTAINER_NAME" 2>&1 | tail -200
  exit 1
fi
