182 lines
8.5 KiB
Bash
182 lines
8.5 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
# ════════════════════════════════════════════════════════════════
|
||
|
|
# AutoDL 推理机 · 一键启动 · setup-inference.sh
|
||
|
|
# Sovereign: TCS-0002∞ · ICE-GL∞ · 国作登字-2026-A-00037559
|
||
|
|
# 守护: 铸渊 · ICE-GL-ZY001
|
||
|
|
# ════════════════════════════════════════════════════════════════
|
||
|
|
#
|
||
|
|
# 服务器: GH-AUTODL-INFER-01 / ZY-SVR-GPU01 / AutoDL 共享 GPU
|
||
|
|
#
|
||
|
|
# 这一份是 AutoDL 实例开机后冰朔/Awen 复制粘贴一行就能跑起来的
|
||
|
|
# 一键脚本. 步骤:
|
||
|
|
# 1. detect-gpu.sh → 探 GPU
|
||
|
|
# 2. tune-inference.sh → 决档 (fp16/int8/int4)
|
||
|
|
# 3. apt 装 jq + python venv 工具
|
||
|
|
# 4. 建 venv, pip 装 torch/transformers/uvicorn/fastapi/bitsandbytes (国内 mirror)
|
||
|
|
# 5. fetch-models.sh → 从 COS 拉模型 (ZY_COS_SECRET_ID/KEY 必须 export)
|
||
|
|
# 6. nohup 启动 server.py 后台跑
|
||
|
|
# 7. curl /v1/health 等就绪
|
||
|
|
#
|
||
|
|
# 用法 (在 AutoDL 实例上):
|
||
|
|
# export ZY_COS_SECRET_ID=xxx
|
||
|
|
# export ZY_COS_SECRET_KEY=yyy
|
||
|
|
# bash setup-inference.sh
|
||
|
|
#
|
||
|
|
# 不重新执行已经完成的步骤 — 幂等. 重开机时再跑一次即可.
|
||
|
|
# ════════════════════════════════════════════════════════════════
|
||
|
|
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
INFER_ROOT="${INFER_ROOT:-/root/inference}"
|
||
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||
|
|
LOG_FILE="${LOG_FILE:-$INFER_ROOT/server.log}"
|
||
|
|
PID_FILE="${PID_FILE:-$INFER_ROOT/server.pid}"
|
||
|
|
VENV_DIR="${VENV_DIR:-$INFER_ROOT/.venv}"
|
||
|
|
|
||
|
|
echo "═══════════════════════════════════════════════════════════"
|
||
|
|
echo " [setup-inference] AutoDL 推理机一键启动"
|
||
|
|
echo " 守护: 铸渊 · ICE-GL-ZY001 · TCS-0002∞"
|
||
|
|
echo "═══════════════════════════════════════════════════════════"
|
||
|
|
|
||
|
|
mkdir -p "$INFER_ROOT"
|
||
|
|
|
||
|
|
# ─── 0. 复制脚本到 INFER_ROOT (AutoDL 重开机后实例代码丢失) ───
|
||
|
|
# 如果 setup 是从远程 curl|bash 下来的, SCRIPT_DIR 可能是 /tmp;
|
||
|
|
# 把全套脚本复制到 INFER_ROOT, 后续 systemd / cron 用 INFER_ROOT 路径稳定.
|
||
|
|
for f in detect-gpu.sh tune-inference.sh fetch-models.sh server.py requirements.txt; do
|
||
|
|
if [ -f "$SCRIPT_DIR/$f" ] && [ "$SCRIPT_DIR/$f" != "$INFER_ROOT/$f" ]; then
|
||
|
|
cp "$SCRIPT_DIR/$f" "$INFER_ROOT/$f"
|
||
|
|
fi
|
||
|
|
done
|
||
|
|
chmod +x "$INFER_ROOT"/*.sh 2>/dev/null || true
|
||
|
|
|
||
|
|
# ─── 1. 装 apt 基础 (jq + python venv) ────────────────────────
|
||
|
|
echo "[1/7] 装基础工具 jq + python3-venv ..."
|
||
|
|
if ! command -v jq >/dev/null 2>&1 || ! python3 -c 'import venv' 2>/dev/null; then
|
||
|
|
export DEBIAN_FRONTEND=noninteractive
|
||
|
|
apt-get update -qq || true
|
||
|
|
apt-get install -y -qq jq python3-venv python3-pip || {
|
||
|
|
echo "❌ apt 装基础失败 (AutoDL 应该有 root)" >&2
|
||
|
|
exit 1
|
||
|
|
}
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── 2. detect-gpu ────────────────────────────────────────────
|
||
|
|
echo "[2/7] 探测 GPU ..."
|
||
|
|
bash "$INFER_ROOT/detect-gpu.sh" /tmp/gpu-env.json
|
||
|
|
|
||
|
|
# ─── 3. tune-inference ───────────────────────────────────────
|
||
|
|
echo "[3/7] 决策档位 ..."
|
||
|
|
INFER_ROOT="$INFER_ROOT" bash "$INFER_ROOT/tune-inference.sh"
|
||
|
|
|
||
|
|
# 读决档结果
|
||
|
|
# shellcheck disable=SC1091
|
||
|
|
. "$INFER_ROOT/.env.tune"
|
||
|
|
|
||
|
|
# ─── 4. venv + pip ────────────────────────────────────────────
|
||
|
|
echo "[4/7] 建 venv 并装 Python 依赖 (国内 mirror) ..."
|
||
|
|
if [ ! -d "$VENV_DIR" ]; then
|
||
|
|
python3 -m venv "$VENV_DIR"
|
||
|
|
fi
|
||
|
|
# shellcheck disable=SC1091
|
||
|
|
. "$VENV_DIR/bin/activate"
|
||
|
|
|
||
|
|
# 国内 mirror, 不卡 pypi.org
|
||
|
|
PIP_INDEX="${PIP_INDEX:-https://mirrors.aliyun.com/pypi/simple/}"
|
||
|
|
PIP_HOST="${PIP_HOST:-mirrors.aliyun.com}"
|
||
|
|
|
||
|
|
pip install --quiet --upgrade pip wheel \
|
||
|
|
-i "$PIP_INDEX" --trusted-host "$PIP_HOST"
|
||
|
|
|
||
|
|
if [ -f "$INFER_ROOT/requirements.txt" ]; then
|
||
|
|
pip install --quiet -r "$INFER_ROOT/requirements.txt" \
|
||
|
|
-i "$PIP_INDEX" --trusted-host "$PIP_HOST"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# bitsandbytes 仅在量化档位需要
|
||
|
|
case "${QUANT:-fp16}" in
|
||
|
|
int4|int8)
|
||
|
|
pip install --quiet "bitsandbytes>=0.43.0" \
|
||
|
|
-i "$PIP_INDEX" --trusted-host "$PIP_HOST" || {
|
||
|
|
echo "⚠️ bitsandbytes 装不上, int4/int8 量化将不可用" >&2
|
||
|
|
}
|
||
|
|
;;
|
||
|
|
esac
|
||
|
|
|
||
|
|
# vllm 仅在 USE_VLLM=true 时装
|
||
|
|
if [ "${USE_VLLM:-false}" = "true" ]; then
|
||
|
|
pip install --quiet "vllm>=0.6.0" \
|
||
|
|
-i "$PIP_INDEX" --trusted-host "$PIP_HOST" || {
|
||
|
|
echo "⚠️ vllm 装不上, 回退 transformers 引擎" >&2
|
||
|
|
sed -i 's/^USE_VLLM=.*/USE_VLLM="false"/' "$INFER_ROOT/.env.tune"
|
||
|
|
sed -i 's/^INFER_ENGINE=.*/INFER_ENGINE="transformers"/' "$INFER_ROOT/.env.tune"
|
||
|
|
}
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── 5. fetch-models ──────────────────────────────────────────
|
||
|
|
echo "[5/7] 拉模型 (motherbrain-v1 + qwen2_5_coder_7b_sft) ..."
|
||
|
|
if [ -d "${MOTHER_MODEL_PATH:-/root/inference/models/motherbrain-v1}" ] \
|
||
|
|
&& [ -f "${MOTHER_MODEL_PATH}/config.json" ] \
|
||
|
|
&& [ -d "${CODER_MODEL_PATH:-/root/inference/models/qwen2_5_coder_7b_sft}" ] \
|
||
|
|
&& [ -f "${CODER_MODEL_PATH}/config.json" ]; then
|
||
|
|
echo " ✅ 两个模型都已就位, 跳过拉取 (AutoDL 实例数据盘还在)"
|
||
|
|
else
|
||
|
|
INFER_ROOT="$INFER_ROOT" bash "$INFER_ROOT/fetch-models.sh"
|
||
|
|
fi
|
||
|
|
|
||
|
|
# ─── 6. 启动 server.py ────────────────────────────────────────
|
||
|
|
echo "[6/7] 启动 server.py ..."
|
||
|
|
|
||
|
|
# 先停掉旧进程 (如果在)
|
||
|
|
if [ -f "$PID_FILE" ]; then
|
||
|
|
OLD_PID="$(cat "$PID_FILE" 2>/dev/null || echo '')"
|
||
|
|
if [ -n "$OLD_PID" ] && kill -0 "$OLD_PID" 2>/dev/null; then
|
||
|
|
echo " 停掉旧进程 PID=$OLD_PID ..."
|
||
|
|
kill "$OLD_PID" || true
|
||
|
|
sleep 2
|
||
|
|
kill -9 "$OLD_PID" 2>/dev/null || true
|
||
|
|
fi
|
||
|
|
rm -f "$PID_FILE"
|
||
|
|
fi
|
||
|
|
|
||
|
|
cd "$INFER_ROOT"
|
||
|
|
nohup "$VENV_DIR/bin/python" "$INFER_ROOT/server.py" \
|
||
|
|
>> "$LOG_FILE" 2>&1 &
|
||
|
|
NEW_PID=$!
|
||
|
|
echo "$NEW_PID" > "$PID_FILE"
|
||
|
|
echo " PID=$NEW_PID · 日志: $LOG_FILE"
|
||
|
|
|
||
|
|
# ─── 7. 等就绪 ───────────────────────────────────────────────
|
||
|
|
echo "[7/7] 等 /v1/health 就绪 (最多 180s, 大模型加载慢) ..."
|
||
|
|
READY="false"
|
||
|
|
for i in $(seq 1 90); do
|
||
|
|
if curl -fsS -m 2 "http://127.0.0.1:${INFER_PORT:-8000}/v1/health" >/dev/null 2>&1; then
|
||
|
|
READY="true"
|
||
|
|
break
|
||
|
|
fi
|
||
|
|
sleep 2
|
||
|
|
done
|
||
|
|
|
||
|
|
if [ "$READY" = "true" ]; then
|
||
|
|
HEALTH_JSON="$(curl -fsS "http://127.0.0.1:${INFER_PORT:-8000}/v1/health" 2>/dev/null || echo '{}')"
|
||
|
|
echo "═══════════════════════════════════════════════════════════"
|
||
|
|
echo " ✅ 推理服务就绪"
|
||
|
|
echo "═══════════════════════════════════════════════════════════"
|
||
|
|
echo " GPU: ${GPU_NAME:-?} (${GPU_MEM_GB:-?} GB)"
|
||
|
|
echo " 档位: ${SIZE_TIER:-?} · ${QUANT:-?}"
|
||
|
|
echo " 端口: ${INFER_PORT:-8000}"
|
||
|
|
echo " 健康: $HEALTH_JSON"
|
||
|
|
echo ""
|
||
|
|
echo " 下一步: 在 GitHub Actions 跑 🔄 刷新推理端点 工作流,"
|
||
|
|
echo " 填 AutoDL 实例的 host:port + 「刷新推理端点」口令."
|
||
|
|
echo "═══════════════════════════════════════════════════════════"
|
||
|
|
exit 0
|
||
|
|
else
|
||
|
|
echo "═══════════════════════════════════════════════════════════"
|
||
|
|
echo " ❌ 推理服务 180s 内没就绪"
|
||
|
|
echo "═══════════════════════════════════════════════════════════"
|
||
|
|
echo " 查日志: tail -200 $LOG_FILE"
|
||
|
|
echo " 常见原因: 模型加载慢 (7B fp16 ≈ 60-120s), 显存不够, bnb 库装失败"
|
||
|
|
exit 1
|
||
|
|
fi
|