File size: 6,922 Bytes
aa975a2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | #!/usr/bin/env bash
set -euo pipefail
############################################
# vLLM(Qwen-VL) + 批处理流水线(防截断·参数加强)
# - 上下文窗放大:--max-model-len 40960(Qwen3 默认)
# - 显存利用提升:--gpu-memory-utilization 0.95
# - 并发适度下降:--max-num-seqs 8(给长输出更稳定)
# - TP 自适配(整除 attention heads)
# - setsid 进程组 + 组杀,退出优雅清理
############################################
# ======== 可改参数 ========
CSV_PATH="${CSV_PATH:-/mnt/data_nas/guanfb/Edit/CoT_Edit/8BTest_CoT_box/data/Test.csv}"
OUTDIR="${OUTDIR:-./outputs}"
MODEL_SIZE="${MODEL_SIZE:-8b}" # 8b | 32b
API_PORT="${API_PORT:-8007}" # 8B默认8007,32B可用8032
RESULTS_CSV="${RESULTS_CSV:-$OUTDIR/results.csv}"
DRY_RUN="${DRY_RUN:-0}"
RESUME="${RESUME:-1}"
WAIT_SECS="${WAIT_SECS:-600}"
# === 客户端并发(传给 run.py) ===
CLIENT_HTTP_WORKERS="${CLIENT_HTTP_WORKERS:-2}" # 建议 2~4
CLIENT_HTTP_TIMEOUT="${CLIENT_HTTP_TIMEOUT:-1200}" # 单请求超时
CLIENT_MAX_TOKENS="${CLIENT_MAX_TOKENS:-4096}" # 生成上限(配合防截断)
# GPU 绑定(多卡逗号分隔)
GPU_IDS="${GPU_IDS:-0,1,2,3}"
export CUDA_VISIBLE_DEVICES="${GPU_IDS}"
IFS=',' read -ra _g <<< "$GPU_IDS"
NUM_GPUS="${#_g[@]}"
# vLLM 服务参数(防截断关键项)
GPU_UTIL="${GPU_UTIL:-0.95}"
MAX_NUM_SEQS="${MAX_NUM_SEQS:-8}"
MAX_MODEL_LEN="${MAX_MODEL_LEN:-40960}"
SERVED_NAME="${SERVED_NAME:-qwen_vl}"
# 模型权重
if [[ "$MODEL_SIZE" == "32b" ]]; then
MODEL_NAME="${MODEL_NAME:-/path/to/Qwen3-VL-32B-Instruct}"
API_PORT="${API_PORT:-8032}"
else
MODEL_NAME="${MODEL_NAME:-/mnt/data_nas/guanfb/Edit/CoT_Edit/8BTest_CoT_box/Qwen3-VL-8B-Instruct}"
fi
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LOG_DIR="$OUTDIR/logs"
mkdir -p "$LOG_DIR" "$OUTDIR"
echo "[1/6] Installing Python deps if needed..."
pip install -r "$ROOT_DIR/requirements.txt" >/dev/null || true
# ===== TP 自适配(读取 num_attention_heads,整除选 TP) =====
echo "[2/6] Probing model heads & selecting tensor parallel size..."
NUM_HEADS=$(python - <<'PY'
import json, os
m=os.environ.get("MODEL_NAME","")
cfg=os.path.join(m,"config.json")
h=None
try:
with open(cfg,"r",encoding="utf-8") as f:
j=json.load(f)
h=j.get("num_attention_heads") or j.get("n_head") or j.get("num_key_value_heads")
except Exception:
pass
if h is None:
h = 64 if any(s in m.lower() for s in ("32","32b")) else 32
print(h)
PY
)
pick_tp() { local heads="$1"; local gpus="$2"; for ((d=gpus; d>=1; d--)); do (( heads % d == 0 )) && { echo "$d"; return; }; done; echo 1; }
TP_SIZE="${TP_SIZE:-$(pick_tp "$NUM_HEADS" "$NUM_GPUS")}"
echo " - CUDA_VISIBLE_DEVICES=${GPU_IDS} (num_gpus=${NUM_GPUS})"
echo " - num_attention_heads=${NUM_HEADS} => tensor_parallel_size=${TP_SIZE}"
API_BASE="http://127.0.0.1:${API_PORT}/v1"
echo "[3/6] Starting vLLM server: $MODEL_NAME on port $API_PORT (served as '$SERVED_NAME')"
# 端口占用兜底清理
if lsof -iTCP:${API_PORT} -sTCP:LISTEN -Pn >/dev/null 2>&1; then
echo "[CLEANUP] Port ${API_PORT} is in use. Killing existing server..."
old_pids=$(lsof -iTCP:${API_PORT} -sTCP:LISTEN -t || true)
if [[ -n "$old_pids" ]]; then
kill $old_pids 2>/dev/null || true
sleep 2
for p in $old_pids; do ps -p $p >/dev/null 2>&1 && kill -9 $p 2>/dev/null || true; done
fi
fi
VLLM_PID_FILE="$LOG_DIR/vllm_${MODEL_SIZE}.pid"
VLLM_PGID_FILE="$LOG_DIR/vllm_${MODEL_SIZE}.pgid"
# setsid -> 独立进程组,方便组杀
setsid bash -c "
nohup python -m vllm.entrypoints.openai.api_server \
--model \"$MODEL_NAME\" \
--served-model-name \"$SERVED_NAME\" \
--trust-remote-code \
--dtype bfloat16 \
--tensor-parallel-size \"$TP_SIZE\" \
--gpu-memory-utilization \"$GPU_UTIL\" \
--max-num-seqs \"$MAX_NUM_SEQS\" \
--max-model-len \"$MAX_MODEL_LEN\" \
--port \"$API_PORT\" \
>\"$LOG_DIR/vllm_${MODEL_SIZE}.log\" 2>&1 &
echo \$! > \"$VLLM_PID_FILE\"
sleep 0.5
" >/dev/null 2>&1 &
sleep 1
VLLM_PID="$(cat "$VLLM_PID_FILE" 2>/dev/null || true)"
if [[ -n "${VLLM_PID:-}" ]]; then
VLLM_PGID="$(ps -o pgid= "$VLLM_PID" 2>/dev/null | tr -d ' ' || true)"
[[ -n "${VLLM_PGID:-}" ]] && echo "$VLLM_PGID" > "$VLLM_PGID_FILE"
fi
cleanup() {
set +e
echo "[CLEANUP] Triggered. Cleaning vLLM..."
if [[ -f "$VLLM_PGID_FILE" ]]; then
pgid="$(cat "$VLLM_PGID_FILE" 2>/dev/null || true)"
if [[ -n "${pgid:-}" ]]; then
echo "[CLEANUP] kill -TERM -- -$pgid"
kill -TERM -- "-$pgid" 2>/dev/null || true
for _ in {1..15}; do lsof -iTCP:${API_PORT} -sTCP:LISTEN -Pn >/dev/null 2>&1 || break; sleep 1; done
if lsof -iTCP:${API_PORT} -sTCP:LISTEN -Pn >/dev/null 2>&1; then
echo "[CLEANUP] kill -KILL -- -$pgid"
kill -KILL -- "-$pgid" 2>/dev/null || true
fi
fi
fi
if [[ -f "$VLLM_PID_FILE" ]]; then
pid="$(cat "$VLLM_PID_FILE" 2>/dev/null || true)"
if [[ -n "${pid:-}" ]] && ps -p "$pid" >/dev/null 2>&1; then
kill -TERM "$pid" 2>/dev/null || true
for _ in {1..5}; do ps -p "$pid" >/dev/null 2>&1 || break; sleep 1; done
ps -p "$pid" >/dev/null 2>&1 && kill -KILL "$pid" 2>/dev/null || true
fi
fi
if lsof -iTCP:${API_PORT} -sTCP:LISTEN -Pn >/dev/null 2>&1; then
old_pids=$(lsof -iTCP:${API_PORT} -sTCP:LISTEN -t || true)
[[ -n "$old_pids" ]] && kill $old_pids 2>/dev/null || true
fi
rm -f "$VLLM_PID_FILE" "$VLLM_PGID_FILE"
}
trap 'cleanup; exit 130' INT
trap 'cleanup' TERM EXIT
# 等待就绪
echo "[4/6] Waiting for vLLM to be ready at ${API_BASE} (timeout=${WAIT_SECS}s) ..."
ready=0
for (( i=1; i<=WAIT_SECS; i++ )); do
if curl -m 2 -s "${API_BASE}/models" >/dev/null 2>&1; then ready=1; break; fi
sleep 1
done
[[ $ready -eq 1 ]] || { echo "ERROR: vLLM not ready after ${WAIT_SECS}s. See $LOG_DIR/vllm_${MODEL_SIZE}.log"; exit 1; }
echo "vLLM is ready."
# 健康检查(标准 text)
if ! curl -m 5 -s -X POST "${API_BASE}/chat/completions" \
-H 'Content-Type: application/json' \
-d "{\"model\":\"${SERVED_NAME}\",\"messages\":[{\"role\":\"user\",\"content\":[{\"type\":\"text\",\"text\":\"ping\"}]}],\"max_tokens\":4}" \
| grep -q '"choices"'; then
echo "WARNING: /v1/chat/completions 返回异常。确认 OpenAI 兼容入口与依赖是否齐全。"
fi
# 跑流水线(把 served-model-name 传入)
echo "[5/6] Running pipeline..."
CMD=(python "$ROOT_DIR/run.py"
--csv "$CSV_PATH"
--outdir "$OUTDIR"
--api-base "$API_BASE"
--model "$SERVED_NAME"
--results-csv "$RESULTS_CSV"
)
# 只有等于 "1" 才加对应 flag;"0" 或空都不加
if [[ "${RESUME:-0}" == "1" ]]; then
CMD+=("--resume")
fi
if [[ "${DRY_RUN:-0}" == "1" ]]; then
CMD+=("--dry-run")
fi
echo "Command: ${CMD[*]}"
"${CMD[@]}"
echo "[6/6] Done."
echo "结果汇总 CSV: $RESULTS_CSV"
echo "日志目录 : $LOG_DIR"
|