Vllm LINUX部署Qwen3.8-27B多模态支持视频图片模型全流程(8张L20卡)
一.环境准备
1.安装python虚拟环境,安装VLLM
sudo dnf install -y gcc openssl-devel bzip2-devel libffi-devel xz-devel sqlite-devel sudo dnf install python3-devel libxml2-devel libxslt-devel zlib-devel pcre-devel openssl-devel sudo wget https://www.python.org/ftp/python/3.11.6/Python-3.11.6.tgz tar xvf Python-3.11.6.tgz cd Python-3.11.6 ./configure --enable-optimizations make -j$(nproc) sudo make install #验证 /usr/local/bin/python3.11 --version #创建虚拟环境 python3 -m venv vllm_env #激活虚拟环境 source vllm_env/bin/activate #==================== #安装 vllm pip3 install vllm==0.25.02.安装英伟达显卡驱动
wget https://mirrors.tencentyun.com/install/GPU/NVIDIA-Linux-x86_64-580.82.07.run chmod +x NVIDIA-Linux-x86_64-580.82.07.run ./NVIDIA-Linux-x86_64-580.82.07.run -x cd NVIDIA-Linux-x86_64-580.82.07 ./nvidia-installer #查看显卡 lspci | grep -i nvidia #查看驱动英伟达显卡 nvidia-smi #实时监控 watch -n 1 nvidia-smi #toolkit ✅ 安装到 /data 目录 步骤 1:创建目标目录 bash sudo mkdir -p /data/cuda-root 步骤 2:使用 --installroot 安装到 /data bash sudo yum install -y --installroot=/data/cuda-root cuda-toolkit-12-5 步骤 3:安装完成后,CUDA 文件在 /data/cuda-root/usr/local/cuda-12.5 bash ls -la /data/cuda-root/usr/local/ 步骤 4:创建软链接 bash sudo ln -sf /data/cuda-root/usr/local/cuda-12.5 /data/cuda-12.5 步骤 5:设置环境变量 bash echo 'export CUDA_HOME=/data/cuda-12.5' >> ~/.bashrc echo 'export PATH=$CUDA_HOME/bin:$PATH' >> ~/.bashrc source ~/.bashrc 步骤 6:验证 bash /data/cuda-12.5/bin/nvcc --version3.模型下载
#魔塔社区下载模型 pip3 install modelscope modelscope download --model Qwen/Qwen3.8-27B --local_dir /data/app/model/Qwen3.8-27B #huggingface下载模型 # 设置环境变量使用 HF 镜像 pip install -U huggingface_hub export HF_TOKEN="hf_your_token_here" export HF_ENDPOINT=https://hf-mirror.com hf download tencent/Qwen3.8-27B \ --local-dir /data/app/model/Hy/Qwen3.8-27B4.启动脚本编写
启动脚本(3卡实例不支持改模型)
#!/bin/bash # ============================================ # vLLM 生产级部署脚本 - v0.25.0 # 模型: Qwen3.8-27B # 使用: ./deploy.sh [single|dual|3card|4card|auto] # ============================================ set -uo pipefail # ==================== 信号处理 ==================== trap 'log_info "收到退出信号,正在清理..."; kill $(jobs -p) 2>/dev/null; exit' SIGTERM SIGINT # ==================== 缓存目录设置 ==================== export VLLM_CACHE_ROOT=/run/vllm_cache export TMPDIR=/run/tmp mkdir -p /run/vllm_cache /run/tmp chmod 1777 /run/tmp SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # ==================== CUDA 环境变量 ==================== export CUDA_HOME=/data/cuda-12.5 export PATH=$CUDA_HOME/bin:$PATH # ==================== 核心配置 ==================== MODEL_PATH="/data/app/model/Qwen/Qwen3.8-27B" SERVED_NAME="Qwen3.8-27B" VENV_PATH="/data/app/vllm_env" # ==================== 性能调优参数 ==================== MAX_MODEL_LEN=102400 MAX_NUM_SEQS=24 GPU_MEMORY_UTIL=0.85 KV_CACHE_DTYPE="fp8" ENABLE_CHUNKED_PREFILL=false # ==================== MTP 配置 ==================== ENABLE_MTP=true MTP_NUM_SPECULATIVE_TOKENS=3 # ==================== 路径配置 ==================== LOG_DIR="${SCRIPT_DIR}/vllm_logs" PID_DIR="${SCRIPT_DIR}/vllm_pids" mkdir -p "$LOG_DIR" "$PID_DIR" # ==================== 颜色输出 ==================== GREEN='\033[0;32m' RED='\033[0;31m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' log_info() { echo -e "${GREEN}[INFO]${NC} $1"; } log_error() { echo -e "${RED}[ERROR]${NC} $1"; } log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; } log_model() { echo -e "${BLUE}[MODEL]${NC} $1"; } # ==================== 获取部署模式名称 ==================== get_deploy_mode_name() { local tp=$1 case $tp in 1) echo "单卡" ;; 2) echo "双卡" ;; 3) echo "三卡" ;; 4) echo "四卡" ;; *) echo "${tp}卡" ;; esac } # ==================== 解析部署模式 ==================== parse_deploy_mode() { local mode="${1:-auto}" case "$mode" in single) NUM_INSTANCES=8 TENSOR_PARALLEL_SIZE=1 BASE_PORT=8000 log_info "🔧 部署模式: 单卡 (8实例 × 1卡, 端口8000-8007)" ;; dual) NUM_INSTANCES=4 TENSOR_PARALLEL_SIZE=2 BASE_PORT=8000 log_info "🔧 部署模式: 双卡 (4实例 × 2卡, 端口8000-8003)" ;; 3card) NUM_INSTANCES=2 TENSOR_PARALLEL_SIZE=3 BASE_PORT=8000 log_info "🔧 部署模式: 三卡 (2实例 × 3卡, 端口8000-8001)" ;; 4card) NUM_INSTANCES=2 TENSOR_PARALLEL_SIZE=4 BASE_PORT=8000 log_info "🔧 部署模式: 四卡 (2实例 × 4卡, 端口8000-8001)" ;; auto|*) # 自动检测:根据 GPU 数量决定 local gpu_count=$(nvidia-smi --query-gpu=name --format=csv,noheader 2>/dev/null | wc -l) if [ $gpu_count -ge 8 ]; then NUM_INSTANCES=8 TENSOR_PARALLEL_SIZE=1 log_info "🔧 自动检测: 8+ GPU,使用单卡模式" elif [ $gpu_count -ge 4 ]; then NUM_INSTANCES=4 TENSOR_PARALLEL_SIZE=2 log_info "🔧 自动检测: 4-7 GPU,使用双卡模式" else log_error "GPU 数量不足 (需要至少4张): $gpu_count" exit 1 fi BASE_PORT=8000 ;; esac local total_gpus=$((NUM_INSTANCES * TENSOR_PARALLEL_SIZE)) log_info " 实例数: $NUM_INSTANCES" log_info " TP大小: $TENSOR_PARALLEL_SIZE" log_info " 总GPU: $total_gpus" } # ==================== 激活虚拟环境 ==================== activate_venv() { if [ ! -d "$VENV_PATH" ]; then log_error "虚拟环境不存在: $VENV_PATH" exit 1 fi source "$VENV_PATH"/bin/activate log_info "已激活虚拟环境: $VENV_PATH" if ! command -v nvcc &> /dev/null; then log_error "nvcc 未找到,请检查 CUDA 安装" exit 1 fi log_info "CUDA 版本: $(nvcc --version | grep release | awk '{print $6}' | sed 's/,//')" } # ==================== 等待服务启动 ==================== wait_for_service() { local port=$1 local instance_name=$2 local max_wait=600 local wait_time=0 local interval=5 log_info "等待 $instance_name (端口 $port) 服务启动..." while [ $wait_time -lt $max_wait ]; do if netstat -tln 2>/dev/null | grep -q ":$port"; then local http_code=$(curl -s -o /dev/null -w "%{http_code}" "http://127.0.0.1:$port/v1/models" 2>/dev/null) if [ "$http_code" = "200" ]; then log_info " ✅ $instance_name 已就绪 (耗时 ${wait_time}s)" return 0 fi fi sleep $interval wait_time=$((wait_time + interval)) [ $((wait_time % 30)) -eq 0 ] && log_info " 等待 $instance_name ... (已等待 ${wait_time}s)" done log_error "$instance_name 等待超时 (${max_wait}s)" return 1 } # ==================== 启动单个实例 ==================== start_instance() { local instance_id=$1 local port=$((BASE_PORT + instance_id)) # 计算 GPU 分配 local gpu_start=$((instance_id * TENSOR_PARALLEL_SIZE)) local gpu_end=$((gpu_start + TENSOR_PARALLEL_SIZE - 1)) local gpu_list=$(seq -s ',' "$gpu_start" "$gpu_end") local LOG_FILE="$LOG_DIR/instance_${instance_id}.log" local PID_FILE="$PID_DIR/instance_${instance_id}.pid" log_model "启动实例 $instance_id: GPU=$gpu_list, 端口=$port" log_info " 日志: $LOG_FILE" # MTP 参数 local MTP_ARG="" if [ "$ENABLE_MTP" = true ]; then MTP_ARG="--speculative-config {\"method\":\"mtp\",\"num_speculative_tokens\":$MTP_NUM_SPECULATIVE_TOKENS}" log_info " MTP 已启用: num_speculative_tokens=$MTP_NUM_SPECULATIVE_TOKENS" fi # 启动进程 CUDA_VISIBLE_DEVICES="$gpu_list" \ vllm serve \ --model "$MODEL_PATH" \ --served-model-name "$SERVED_NAME" \ --tensor-parallel-size "$TENSOR_PARALLEL_SIZE" \ --max-model-len "$MAX_MODEL_LEN" \ --max-num-seqs "$MAX_NUM_SEQS" \ --enable-auto-tool-choice \ --tool-call-parser hermes \ --gpu-memory-utilization "$GPU_MEMORY_UTIL" \ ${KV_CACHE_DTYPE:+--kv-cache-dtype "$KV_CACHE_DTYPE"} \ $( [ "$ENABLE_CHUNKED_PREFILL" = true ] && echo "--enable-chunked-prefill" ) \ --port "$port" \ --host 0.0.0.0 \ --enable-log-requests \ ${MTP_ARG} \ >> "$LOG_FILE" 2>&1 & local pid=$! echo "$pid" > "$PID_FILE" log_info " PID: $pid" sleep 2 } # ==================== 启动所有实例 ==================== start_instances() { log_info "==========================================" log_info "启动 $NUM_INSTANCES 个 vLLM 实例..." log_info "模型: $SERVED_NAME" log_info "==========================================" for ((i=0; i<NUM_INSTANCES; i++)); do start_instance "$i" done log_info "" sleep 2 } # ==================== 验证所有实例 ==================== verify_instances() { log_info "==========================================" log_info "等待所有实例启动完成..." log_info "注意: 首次启动可能需要 5-10 分钟进行 torch.compile" log_info "==========================================" local all_ready=true for ((i=0; i<NUM_INSTANCES; i++)); do local port=$((BASE_PORT + i)) if ! wait_for_service "$port" "实例$((i+1))"; then all_ready=false log_error "实例 $((i+1)) (端口 $port) 启动失败" log_info "查看日志: tail -100 $LOG_DIR/instance_${i}.log" fi done echo "" if [ "$all_ready" = true ]; then log_info "==========================================" log_info "✅ 所有实例已成功启动!" log_info "==========================================" log_info "" log_model "=== $SERVED_NAME ===" log_info " 部署模式: $(get_deploy_mode_name $TENSOR_PARALLEL_SIZE)" log_info " 实例数: $NUM_INSTANCES" log_info " TP 大小: $TENSOR_PARALLEL_SIZE" log_info " 总 GPU: $((NUM_INSTANCES * TENSOR_PARALLEL_SIZE)) 张" log_info "" for ((i=0; i<NUM_INSTANCES; i++)); do local port=$((BASE_PORT + i)) local gpu_start=$((i * TENSOR_PARALLEL_SIZE)) local gpu_end=$((gpu_start + TENSOR_PARALLEL_SIZE - 1)) log_info " 端口 $port: GPU $gpu_start-$gpu_end" done log_info "" log_info "测试命令:" for ((i=0; i<NUM_INSTANCES; i++)); do log_info " curl http://127.0.0.1:$((BASE_PORT + i))/v1/models" done log_info "==========================================" return 0 else log_error "==========================================" log_error "❌ 部分实例启动失败,请检查日志" log_error "==========================================" return 1 fi } # ==================== 主流程 ==================== main() { # 解析部署模式(从命令行参数) parse_deploy_mode "${1:-auto}" log_info "==========================================" log_info "vLLM 0.25.0 单模型多实例部署" log_info " 模型: $SERVED_NAME" log_info " 模式: $(get_deploy_mode_name $TENSOR_PARALLEL_SIZE)" log_info " 实例: $NUM_INSTANCES 个 (TP=$TENSOR_PARALLEL_SIZE)" log_info " 端口: $BASE_PORT-$((BASE_PORT + NUM_INSTANCES - 1))" log_info "==========================================" activate_venv start_instances verify_instances } # ============================================================ # 🚀 主进程保持运行,等待所有子进程 # ============================================================ main "$@" JOBS=$(jobs -p) if [ -n "$JOBS" ]; then log_info "脚本进入守护模式,等待 $NUM_INSTANCES 个 vLLM 进程..." log_info "子进程 PIDs: $JOBS" wait log_warn "所有 vLLM 子进程已退出,脚本结束" else log_error "没有后台子进程,脚本退出" exit 1 fi停止脚本
#!/bin/bash # ============================================ # vLLM 生产级停止脚本(彻底版) # ============================================ set -e SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PID_DIR="${SCRIPT_DIR}/vllm_pids" log_info() { echo -e "\033[0;32m[INFO]\033[0m $1"; } log_warn() { echo -e "\033[1;33m[WARN]\033[0m $1"; } log_error() { echo -e "\033[0;31m[ERROR]\033[0m $1"; } stop_instances() { log_info "正在停止所有 vLLM 实例..." # 1. 通过 PID 文件停止主进程 if [ -d "$PID_DIR" ]; then for pid_file in $PID_DIR/*.pid; do if [ -f "$pid_file" ]; then PID=$(cat $pid_file) if kill -0 $PID 2>/dev/null; then log_info "停止主进程 PID: $PID" # 先发 SIGTERM 让进程自己清理 kill -TERM $PID 2>/dev/null || kill -9 $PID 2>/dev/null else log_warn "进程 $PID 已不存在" fi rm -f $pid_file fi done fi # 2. 等待主进程退出 sleep 2 # 3. 清理所有 vLLM 相关进程(关键!) log_info "清理所有 vLLM 相关进程..." # vLLM Worker 进程(最常见残留) local worker_count=$(ps aux | grep -c "VLLM::Worker" 2>/dev/null || echo 0) if [ $worker_count -gt 0 ]; then pkill -9 -f "VLLM::Worker" 2>/dev/null && log_info " ✅ 已杀死 Worker 进程" || true fi # EngineCore 进程 pkill -9 -f "EngineCore" 2>/dev/null && log_info " ✅ 已杀死 EngineCore 进程" || true # multiprocessing resource_tracker pkill -9 -f "resource_tracker" 2>/dev/null && log_info " ✅ 已杀死 resource_tracker" || true # vLLM 主进程(二次清理) pkill -9 -f "vllm serve" 2>/dev/null && log_info " ✅ 已杀死 vLLM 主进程" || true # 其他 vLLM Python 进程 pkill -9 -f "vllm_env.*vllm" 2>/dev/null && log_info " ✅ 已杀死其他 vLLM Python 进程" || true # 4. 等待进程退出 sleep 2 # 5. 检查残留 local remaining=$(ps aux | grep -E "vllm|VLLM::Worker|EngineCore|resource_tracker" | grep -v grep | wc -l) if [ $remaining -eq 0 ]; then log_info "✅ 所有进程已清理干净" else log_warn "⚠️ 还有 $remaining 个残留进程:" ps aux | grep -E "vllm|VLLM::Worker|EngineCore|resource_tracker" | grep -v grep fi log_info "所有实例已停止。" } # 强制停止模式 force_stop() { log_warn "执行强制停止..." # 杀死所有相关进程 pkill -9 -f "vllm serve" 2>/dev/null || true pkill -9 -f "VLLM::Worker" 2>/dev/null || true pkill -9 -f "EngineCore" 2>/dev/null || true pkill -9 -f "resource_tracker" 2>/dev/null || true pkill -9 -f "vllm_env.*vllm" 2>/dev/null || true rm -rf $PID_DIR 2>/dev/null || true sleep 2 log_info "✅ 强制停止完成" # 检查残留 local remaining=$(ps aux | grep -E "vllm|VLLM::Worker|EngineCore" | grep -v grep | wc -l) if [ $remaining -eq 0 ]; then log_info "✅ 所有进程已清理干净" else log_warn "⚠️ 还有 $remaining 个残留进程:" ps aux | grep -E "vllm|VLLM::Worker|EngineCore" | grep -v grep fi } # 主逻辑 case "$1" in -f|--force) force_stop ;; *) stop_instances ;; esac