yy-fighting 3f28edd1a6 feat(p800): add Qwen3-235B-A22B SGLang TP=8 benchmark experiment
Deploy Qwen3-235B-A22B on 8x Kunlun P800 XPU (TP=8) via sglang, adapted
from the proven qwen3-8b launch (attention-backend kunlun, dtype float16,
mem-fraction-static 0.9, XSGL_* XPU env vars). TP bumped to 8 with all 8
/dev/xpu* devices; context-length 8192 (the 8b used 4096 which truncated
2 outputs at 2k+2k).

Patch qwen3_moe.py in the sglang image: load_weights assigned to the
read-only @property routed_experts_weights_of_layer, raising
AttributeError for any qwen3 MoE model (Qwen3-8B is dense, so unaffected).
Fix: assign to the private _routed_experts_weights_of_layer, applied
idempotently in start_server.sh on every start.

bench_serving: isl=2048 osl=2048 concurrency=16 num_prompts=160.
160/160 success in 771s. Summary in results/qwen3_235b_tp8_run1/report.md.
Server on port 30010 (30000 held by the lingering qwen3_8b_bench_tp1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 06:34:57 +00:00

152 lines
5.8 KiB
Bash
Executable File

#!/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