针对场景4进行修改

This commit is contained in:
2026-01-16 16:05:52 +08:00
parent fb473dcf1a
commit 5d1c02fb5b
602 changed files with 15130 additions and 3169 deletions

477
start_all_vllm.sh Executable file
View File

@@ -0,0 +1,477 @@
#!/bin/bash
# ==============================================================================
# 无人机自然语言控制项目 - vLLM 一键启动脚本(新增,不修改 start_all.sh
# ==============================================================================
# 功能启动所有必需的服务vLLM推理模型、llama-server embedding模型、FastAPI后端
# 用法:./start_all_vllm.sh [选项]
# ==============================================================================
set -e # 遇到错误立即退出
# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# 默认配置(可通过环境变量覆盖)
PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
LLAMA_SERVER_DIR="${LLAMA_SERVER_DIR:-~/llama.cpp/build/bin}"
INFERENCE_MODEL_DIR="${INFERENCE_MODEL_DIR:-/home/a/models/AWQ/Qwen3-4B-Instruct-2507}"
VLLM_CONDA_ENV="${VLLM_CONDA_ENV:-VLLM}"
VLLM_SPECULATIVE_CONFIG="${VLLM_SPECULATIVE_CONFIG:-}"
VLLM_GPU_MEMORY_UTILIZATION="${VLLM_GPU_MEMORY_UTILIZATION:-0.6}"
VLLM_SERVED_MODEL_NAME="${VLLM_SERVED_MODEL_NAME:-local-model}"
EMBEDDING_MODEL="${EMBEDDING_MODEL:-~/models/gguf/Qwen3/Qwen3-Embedding-4B/Qwen3-Embedding-4B-Q5_K_M.gguf}"
VENV_PATH="${VENV_PATH:-${PROJECT_ROOT}/backend_service/venv}"
LOG_DIR="${LOG_DIR:-${PROJECT_ROOT}/logs}"
PID_FILE="${LOG_DIR}/services_vllm.pid"
# 端口配置
INFERENCE_PORT="${INFERENCE_PORT:-8081}"
EMBEDDING_PORT="${EMBEDDING_PORT:-8090}"
API_PORT="${API_PORT:-8000}"
# 创建日志目录
mkdir -p "${LOG_DIR}"
# ==============================================================================
# 辅助函数
# ==============================================================================
print_info() {
echo -e "${BLUE}[INFO]${NC} $1"
}
print_success() {
echo -e "${GREEN}[SUCCESS]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
print_error() {
echo -e "${RED}[ERROR]${NC} $1"
}
# 检查命令是否存在
check_command() {
if ! command -v "$1" &> /dev/null; then
print_error "$1 命令未找到,请先安装"
return 1
fi
return 0
}
# 检查端口是否被占用
check_port() {
local port=$1
if lsof -Pi :${port} -sTCP:LISTEN -t >/dev/null 2>&1 ; then
return 0 # 端口被占用
else
return 1 # 端口空闲
fi
}
# 等待服务就绪
wait_for_service() {
local url=$1
local service_name=$2
local max_attempts=30
local attempt=0
print_info "等待 ${service_name} 启动..."
while [ $attempt -lt $max_attempts ]; do
if curl -s "${url}" > /dev/null 2>&1; then
print_success "${service_name} 已就绪"
return 0
fi
attempt=$((attempt + 1))
sleep 1
done
print_error "${service_name} 启动超时"
return 1
}
# 停止所有服务
stop_services() {
print_info "正在停止所有服务..."
if [ -f "${PID_FILE}" ]; then
while read -r pid; do
if ps -p "$pid" > /dev/null 2>&1; then
print_info "停止进程 PID: $pid"
kill "$pid" 2>/dev/null || true
fi
done < "${PID_FILE}"
rm -f "${PID_FILE}"
fi
# 尝试通过端口停止服务
for port in ${INFERENCE_PORT} ${EMBEDDING_PORT} ${API_PORT}; do
if check_port ${port}; then
local pid
pid=$(lsof -ti:${port})
if [ -n "$pid" ]; then
print_info "停止占用端口 ${port} 的进程 (PID: $pid)"
kill $pid 2>/dev/null || true
fi
fi
done
print_success "所有服务已停止"
}
# 清理函数(脚本退出时调用)
cleanup() {
if [ "$?" -ne 0 ]; then
print_error "启动过程中发生错误,正在清理..."
fi
# 注意:这里不自动停止服务,让用户手动控制
}
trap cleanup EXIT
# ==============================================================================
# 主函数
# ==============================================================================
start_services() {
print_info "=========================================="
print_info " 无人机自然语言控制项目 - vLLM 服务启动"
print_info "=========================================="
echo ""
# 检查必要的命令
print_info "检查必要的命令..."
check_command "python3" || exit 1
check_command "curl" || exit 1
check_command "lsof" || print_warning "lsof 未安装,将无法检查端口占用"
check_command "conda" || exit 1
echo ""
# 检查端口占用
print_info "检查端口占用..."
if check_port ${INFERENCE_PORT}; then
print_warning "端口 ${INFERENCE_PORT} 已被占用,推理模型可能已在运行"
fi
if check_port ${EMBEDDING_PORT}; then
print_warning "端口 ${EMBEDDING_PORT} 已被占用Embedding模型可能已在运行"
fi
if check_port ${API_PORT}; then
print_error "端口 ${API_PORT} 已被占用,请先停止占用该端口的服务"
exit 1
fi
echo ""
# 检查llama-server用于 embedding展开路径中的 ~
local llama_server_dir_expanded
llama_server_dir_expanded=$(eval echo "${LLAMA_SERVER_DIR}")
local llama_server="${llama_server_dir_expanded}/llama-server"
if [ ! -f "${llama_server}" ]; then
print_error "llama-server 未找到: ${llama_server}"
print_info "请设置 LLAMA_SERVER_DIR 环境变量指向正确的路径"
print_info "当前路径: ${LLAMA_SERVER_DIR}"
print_info "展开后路径: ${llama_server_dir_expanded}"
exit 1
fi
print_success "找到 llama-server: ${llama_server}"
echo ""
# 检查模型路径
local inference_model_dir_expanded
inference_model_dir_expanded=$(eval echo "${INFERENCE_MODEL_DIR}")
local embedding_model_expanded
embedding_model_expanded=$(eval echo "${EMBEDDING_MODEL}")
if [ ! -e "${inference_model_dir_expanded}" ]; then
print_error "推理模型目录未找到: ${inference_model_dir_expanded}"
print_info "请设置 INFERENCE_MODEL_DIR 环境变量指向正确的模型目录"
exit 1
fi
if [ ! -d "${inference_model_dir_expanded}" ]; then
print_warning "推理模型路径不是目录: ${inference_model_dir_expanded}"
print_warning "vLLM 通常需要传入模型目录(包含 config/tokenizer/权重等)"
fi
print_success "找到推理模型目录: ${inference_model_dir_expanded}"
if [ ! -f "${embedding_model_expanded}" ]; then
print_error "Embedding模型文件未找到: ${embedding_model_expanded}"
print_info "请设置 EMBEDDING_MODEL 环境变量指向正确的模型路径"
exit 1
fi
print_success "找到Embedding模型: ${embedding_model_expanded}"
echo ""
# 检查ROS2环境
local ros2_setup="${PROJECT_ROOT}/install/setup.bash"
if [ ! -f "${ros2_setup}" ]; then
print_warning "ROS2 setup文件未找到: ${ros2_setup}"
print_warning "如果项目已与ROS2解耦可以忽略此警告"
else
print_success "找到ROS2 setup文件: ${ros2_setup}"
fi
echo ""
# 检查venv虚拟环境
local venv_path_expanded
venv_path_expanded=$(eval echo "${VENV_PATH}")
print_info "检查venv虚拟环境: ${venv_path_expanded}"
if [ ! -d "${venv_path_expanded}" ]; then
print_error "venv虚拟环境目录不存在: ${venv_path_expanded}"
print_info "请先创建venv环境: python3 -m venv ${venv_path_expanded}"
print_info "然后安装依赖: ${venv_path_expanded}/bin/pip install -r backend_service/requirements.txt"
exit 1
fi
if [ ! -f "${venv_path_expanded}/bin/activate" ]; then
print_error "venv激活脚本不存在: ${venv_path_expanded}/bin/activate"
print_error "这看起来不是一个有效的venv环境"
exit 1
fi
print_success "venv虚拟环境存在: ${venv_path_expanded}"
echo ""
# 初始化PID文件
> "${PID_FILE}"
# ==========================================================================
# 启动推理模型服务vLLM
# ==========================================================================
print_info "启动推理模型服务(vLLM) (端口 ${INFERENCE_PORT})..."
bash -lc "
set -e
CONDA_BASE=\$(conda info --base)
if [ -z \"\${CONDA_BASE}\" ] || [ ! -f \"\${CONDA_BASE}/etc/profile.d/conda.sh\" ]; then
echo \"[ERROR] conda.sh 未找到:\${CONDA_BASE}/etc/profile.d/conda.sh\" 1>&2
exit 1
fi
source \"\${CONDA_BASE}/etc/profile.d/conda.sh\"
conda activate \"${VLLM_CONDA_ENV}\"
export VLLM_USE_V1=1
echo \"[DEBUG] conda_env=${VLLM_CONDA_ENV}\"
echo \"[DEBUG] CONDA_PREFIX=\${CONDA_PREFIX}\"
echo \"[DEBUG] which python: \$(which python)\"
echo \"[DEBUG] which vllm: \$(which vllm)\"
vllm --version || true
python -c \"import sys; print('[DEBUG] python:', sys.version)\" || true
python -c \"import torch; print('[DEBUG] torch:', torch.__version__, 'cuda:', torch.version.cuda, 'available:', torch.cuda.is_available())\" || true
SPEC_ARGS=()
if [ -n \"${VLLM_SPECULATIVE_CONFIG}\" ]; then
SPEC_ARGS+=(--speculative-config \"${VLLM_SPECULATIVE_CONFIG}\")
fi
exec vllm serve \"${inference_model_dir_expanded}\" \
--host=0.0.0.0 \
--port=${INFERENCE_PORT} \
--dtype=auto \
--max-num-seqs=1 \
--max-model-len=16384 \
\"\${SPEC_ARGS[@]}\" \
--served-model-name \"${VLLM_SERVED_MODEL_NAME}\" \
--trust-remote-code \
--gpu-memory-utilization=${VLLM_GPU_MEMORY_UTILIZATION} \
--uvicorn-log-level=debug \
--max-parallel-loading-workers 4
" > "${LOG_DIR}/vllm_inference_model.log" 2>&1 &
local inference_pid=$!
echo $inference_pid >> "${PID_FILE}"
print_success "推理模型服务(vLLM)已启动 (PID: $inference_pid)"
print_info "日志文件: ${LOG_DIR}/vllm_inference_model.log"
echo ""
# ==========================================================================
# 启动Embedding模型服务llama-server
# ==========================================================================
print_info "启动Embedding模型服务 (端口 ${EMBEDDING_PORT})..."
cd "${llama_server_dir_expanded}"
nohup ./llama-server \
-m "${embedding_model_expanded}" \
--gpu-layers 36 \
--port ${EMBEDDING_PORT} \
--embeddings \
--pooling last \
--host 0.0.0.0 \
> "${LOG_DIR}/embedding_model.log" 2>&1 &
local embedding_pid=$!
echo $embedding_pid >> "${PID_FILE}"
print_success "Embedding模型服务已启动 (PID: $embedding_pid)"
print_info "日志文件: ${LOG_DIR}/embedding_model.log"
echo ""
# ==========================================================================
# 等待模型服务就绪
# ==========================================================================
print_info "等待模型服务就绪..."
sleep 3 # 给服务一些启动时间
# 等待推理模型服务vLLM
if ! wait_for_service "http://localhost:${INFERENCE_PORT}/health" "推理模型服务(vLLM)"; then
if ! wait_for_service "http://localhost:${INFERENCE_PORT}/v1/models" "推理模型服务(vLLM)"; then
print_warning "推理模型服务(vLLM)可能未完全就绪,但将继续启动"
fi
fi
# 等待Embedding模型服务
if ! wait_for_service "http://localhost:${EMBEDDING_PORT}/health" "Embedding模型服务"; then
if ! wait_for_service "http://localhost:${EMBEDDING_PORT}/v1/models" "Embedding模型服务"; then
print_warning "Embedding模型服务可能未完全就绪但将继续启动"
fi
fi
echo ""
# ==========================================================================
# 启动FastAPI后端服务
# ==========================================================================
print_info "启动FastAPI后端服务 (端口 ${API_PORT})..."
cd "${PROJECT_ROOT}"
# 尝试找到 dot 命令 (Graphviz) 路径,以便后端生成图片
DOT_PATH=$(which dot || true)
DOT_DIR=""
if [ -n "$DOT_PATH" ]; then
DOT_DIR=$(dirname "$DOT_PATH")
print_info "找到 Graphviz dot 命令: $DOT_PATH"
else
print_warning "未找到 dot 命令,可视化图片生成可能会失败"
# 尝试默认的 miniconda 路径作为备选
if [ -f "/home/a/miniconda3/bin/dot" ]; then
DOT_DIR="/home/a/miniconda3/bin"
print_info "使用备选路径: $DOT_DIR"
fi
fi
bash -c "
# 激活ROS2环境如果存在
if [ -f '${ros2_setup}' ]; then
source '${ros2_setup}'
fi
# 确保 Graphviz 在 PATH 中
if [ -n '${DOT_DIR}' ]; then
export PATH=\"${DOT_DIR}:\$PATH\"
fi
# 让后端默认使用 vLLM 暴露的模型名,避免请求 local-model 导致 404
OPENAI_MODEL=\"\${OPENAI_MODEL:-${VLLM_SERVED_MODEL_NAME}}\" \
CLASSIFIER_MODEL=\"\${CLASSIFIER_MODEL:-${VLLM_SERVED_MODEL_NAME}}\" \
SIMPLE_MODEL=\"\${SIMPLE_MODEL:-${VLLM_SERVED_MODEL_NAME}}\" \
COMPLEX_MODEL=\"\${COMPLEX_MODEL:-${VLLM_SERVED_MODEL_NAME}}\" \
cd '${PROJECT_ROOT}/backend_service' && \
'${venv_path_expanded}/bin/python' -m uvicorn src.main:app --host 0.0.0.0 --port ${API_PORT}
" > "${LOG_DIR}/fastapi.log" 2>&1 &
local api_pid=$!
echo $api_pid >> "${PID_FILE}"
print_success "FastAPI服务已启动 (PID: $api_pid)"
print_info "日志文件: ${LOG_DIR}/fastapi.log"
echo ""
# 等待FastAPI服务就绪
sleep 3
if wait_for_service "http://localhost:${API_PORT}/docs" "FastAPI服务"; then
print_success "所有服务已成功启动!"
else
print_warning "FastAPI服务可能未完全就绪请检查日志: ${LOG_DIR}/fastapi.log"
fi
echo ""
# 显示服务访问信息
print_info "=========================================="
print_info " 服务启动完成!"
print_info "=========================================="
print_info "推理模型API(vLLM): http://localhost:${INFERENCE_PORT}/v1"
print_info "Embedding模型API: http://localhost:${EMBEDDING_PORT}/v1"
print_info "FastAPI后端: http://localhost:${API_PORT}"
print_info "API文档: http://localhost:${API_PORT}/docs"
print_info ""
print_info "日志文件位置:"
print_info " - 推理模型(vLLM): ${LOG_DIR}/vllm_inference_model.log"
print_info " - Embedding模型: ${LOG_DIR}/embedding_model.log"
print_info " - FastAPI服务: ${LOG_DIR}/fastapi.log"
print_info ""
print_info "按 Ctrl+C 停止所有服务"
print_info "=========================================="
echo ""
# 设置信号处理确保Ctrl+C时能清理
trap 'print_info "\n正在停止服务..."; stop_services; exit 0' INT TERM
# 保持脚本运行
print_info "所有服务正在运行中,查看日志请使用:"
print_info " tail -f ${LOG_DIR}/*.log"
echo ""
wait
}
# ==============================================================================
# 脚本入口
# ==============================================================================
case "${1:-start}" in
start)
start_services
;;
stop)
stop_services
;;
restart)
stop_services
sleep 2
start_services
;;
status)
print_info "检查服务状态..."
if [ -f "${PID_FILE}" ]; then
print_info "已记录的服务进程:"
while read -r pid; do
if ps -p "$pid" > /dev/null 2>&1; then
print_success "PID $pid: 运行中"
else
print_warning "PID $pid: 已停止"
fi
done < "${PID_FILE}"
else
print_info "未找到PID文件服务可能未启动"
fi
echo ""
print_info "端口占用情况:"
for port in ${INFERENCE_PORT} ${EMBEDDING_PORT} ${API_PORT}; do
if check_port ${port}; then
local pid
pid=$(lsof -ti:${port})
print_success "端口 ${port}: 被占用 (PID: $pid)"
else
print_warning "端口 ${port}: 空闲"
fi
done
;;
*)
echo "用法: $0 {start|stop|restart|status}"
echo ""
echo "命令说明:"
echo " start - 启动所有服务(默认)"
echo " stop - 停止所有服务"
echo " restart - 重启所有服务"
echo " status - 查看服务状态"
echo ""
echo "环境变量配置:"
echo " LLAMA_SERVER_DIR - llama-server所在目录 (默认: ~/llama.cpp/build/bin)"
echo " INFERENCE_MODEL_DIR - 推理模型目录(vLLM) (默认: /home/a/models/safetensors/Qwen3-4B)"
echo " VLLM_CONDA_ENV - conda环境名 (默认: vllm如果你的是 VLLM请设置为 VLLM)"
echo " VLLM_SPECULATIVE_CONFIG - vLLM speculative JSON可选例如: {\"model\":\"/home/a/qwen3-4b-eagle3\",\"num_speculative_tokens\":5,\"method\":\"eagle3\"}"
echo " VLLM_GPU_MEMORY_UTILIZATION - vLLM 显存占用比例 (默认: 0.85)"
echo " VLLM_SERVED_MODEL_NAME - vLLM 对外模型名 (默认: local-model后端也会用它作为默认模型名)"
echo " EMBEDDING_MODEL - Embedding模型路径 (默认: ~/models/gguf/Qwen3/Qwen3-Embedding-4B/Qwen3-Embedding-4B-Q5_K_M.gguf)"
echo " VENV_PATH - venv虚拟环境路径 (默认: \${PROJECT_ROOT}/backend_service/venv)"
echo " LOG_DIR - 日志目录 (默认: \${PROJECT_ROOT}/logs)"
echo " INFERENCE_PORT - 推理端口 (默认: 8081)"
echo " EMBEDDING_PORT - embedding端口 (默认: 8090)"
echo " API_PORT - 后端端口 (默认: 8000)"
exit 1
;;
esac