最近两个月我一直在折腾模型部署这件事,从最开始一个个命令手动敲,到后来把整套流程收拢成自动化脚本,中间踩的坑比预想多得多。今天这篇就把这段经历完整写下来,从选型思路到脚本设计,再到报错排查,把能直接照抄的东西都放出来。
先说清楚这篇文章解决什么问题。如果你手头有一台带显卡的机器(或者没有显卡也行),想把某个开源大模型跑起来做成一个可用的推理服务,比如给内部工具接一个对话接口,或者给 AI Agent 提供一个底座,又或者只是想在本地验证一下某个模型的效果——那么你需要的不只是“会跑通一次”,而是“能稳定、可重复地跑通无数次”。这正是自动化脚本的价值所在。适合来看这篇内容的人有两类:一类是刚接触本地模型部署、被环境问题折磨得想放弃的新手;另一类是自己部署过几次、但每次都要手动敲一长串命令、换台机器就得重新踩一遍坑的工程师。
1. 为什么“部署”这件事值得写成脚本
1.1 手动部署的真实痛点
先还原一个最典型的场景。你要在服务器上部署一个 7B 参数的大语言模型,手动流程大致是:装 Python 虚拟环境、装 CUDA 或 CPU 依赖、装推理框架、下载模型权重(几个 GB 到几十个 GB 不等)、写启动命令、确认端口起来、拿 curl 测一下接口通不通。
这一套流程每一步看着都不难,但串起来就非常折磨人。我第一次部署时,光是环境就折腾了大半天:conda 里 Python 版本不对,pip 装 torch 时装成了 CPU 版,vLLM 和 CUDA 版本不兼容,启动时直接报算子编译错。这些坑单看任何一个都很低级,但组合在一起足以让人崩溃。
更麻烦的是,手动部署过程是不可复现的。你在机器 A 上辛辛苦苦调通了,到了机器 B 上,显卡不一样、驱动版本不一样、磁盘路径不一样,所有参数又要重新试一遍。如果团队里有三个人要各自部署一套同样的模型,那就是三倍的重复劳动,三倍的机会踩同样的坑。
还有参数遗忘的问题。fp16、bf16、tf32 这些精度格式到底该用哪个?max-model-len 设多大?gpu-memory-utilization 保留多少显存给 KV cache?这些参数不是每次都能记住的,更不是每台机器都适用同一套值。手动部署意味着每次都要重新查资料、重新试错。
1.2 脚本化部署带来的实际改变
我把这套流程写成脚本后,最直观的感受是:部署一个新模型从“半天起步”变成了“跑一条命令”。
脚本带来的核心收益是可复现性。同一份脚本在 A 机器和 B 机器上跑出来的结果一致,环境变量控制差异,参数集中管理,不存在“上次明明是这么配的怎么这次不行了”的问题。
其次是可迁移性。换机器、换显卡时,不需要改脚本逻辑,只需要改配置项。比如从 4090 换到 A100,调整 tensor-parallel-size 和 gpu-memory-utilization 这两个配置就行。
然后是降低犯错率。脚本把容易出错的步骤——版本选择、依赖锁定、路径处理——固化下来,人工干预越少,出错概率越低。
对团队来说还有一个隐性收益:部署不再依赖“某个会部署的人”。新同事拿到脚本就能自己把服务跑起来,这对交付效率的提升是非常明显的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 部署前先想清楚:模型选型与推理引擎
2.1 模型选型:不是越大越好
很多人一上来就问“哪个模型最强”,但我做自动化部署之后的第一条经验是:先搞清楚你的场景到底需要什么。
开发调试期,我强烈建议先用 7B 到 14B 的中小模型。这个量级的模型在单卡 24GB 显存下能跑得比较舒服,迭代速度快,改 prompt、调参数的成本低。等你确认了方案,再上更大的模型不迟。
选模型还要看任务类型。如果你做的是通用对话,那主流的指令微调模型(比如 Qwen 系列、DeepSeek 系列的开源版本)都够用。如果你做的是检索增强(RAG),那需要一个好的嵌入模型,比如 BGE-M3,它的多语言能力很强,而且能处理最长 8K 的文本。如果你做的是目标检测,那 YOLOv5 这类专用模型反而比大语言模型更合适——你总不可能用 70B 的对话模型去识别图片里的物体。
不同架构的模型部署方式差异很大,这也是脚本需要覆盖不同场景的原因。我在脚本里分了三个模板:大语言模型(LLM)、嵌入模型(Embedding)、视觉检测模型(YOLO 类),各自用不同的推理框架和启动方式。
2.2 推理引擎四选一:vLLM、Ollama、TGI、SGLang
模型选完之后,接下来要选“用什么引擎跑”。这一步很多人会忽略,觉得随便拿 PyTorch 跑一下不就行了,但实际做服务化部署时,引擎选错了后面的麻烦非常多。
我用的比较多的是这几个:
| 引擎 | 核心优势 | 显存优化手段 | 适用场景 |
|---|---|---|---|
| vLLM | 吞吐高,支持连续批处理和 PagedAttention | KV cache 分页管理,显存利用率高 | 高并发 API 服务、生产环境 |
| Ollama | 上手最简单,模型管理一条命令搞定 | 自带量化层,自动管理模型 | 个人开发机、快速验证 |
| TGI | Hugging Face 官方出品,企业级特性齐全 | 支持模型并行、量化加载 | 需要跟 HF 生态深度结合的场景 |
| SGLang | 新秀,RadixAttention 可以复用公共前缀 | 对多轮对话和多 Agent 场景优化好 | 复杂 prompt 结构、Agent 应用 |
选型逻辑其实很简单:先看场景,再看生态,最后看自己的机器。
如果你要做的是内部 API 服务,同事要并发调用,那 vLLM 是最稳的选择,它的吞吐量在几个引擎里是最高的。它把显存里的 KV cache 分页管理,可以同时处理更多请求。我做 AI Agent 开发时也倾向用 vLLM,因为 Agent 场景往往需要频繁的流式输出和多轮交互,vLLM 对这部分的支持比较成熟。
如果你只是在自己电脑上验证一下模型效果,或者 MacBook 上想跑个 7B 模型试试水,那直接用 Ollama 就好。它是不折腾的选择,一条命令下载模型,一条命令启动服务,还能自动处理量化和显存不足的问题。热词里提到很多人问 Ollama 部署本地模型,我猜大部分是个人开发场景,那 Ollama 就是正确答案。
如果你用的显卡比较老,或者需要跟 Hugging Face 的工具链深度配合,TGI 值得看看。最后 SGLang 目前还在快速迭代期,如果你不是想尝鲜,我建议等它生态再成熟一些再说。
2.3 精度格式:fp32、fp16、bf16、tf32 到底怎么选
这个知识点几乎是每个做部署的人都会被问到的,我也把它固化进了脚本的参数注释里。精度选择直接影响显存占用、推理速度和数值稳定性,属于部署里绕不开的核心决策。
简单梳理一下:
- fp32:PyTorch 默认精度,32 位浮点数。显存占用最大,一个 7B 模型光权重就 28GB,普通单卡基本顶不住。好处是数值最稳定,基本不会出现精度溢出问题。
- fp16:半精度,16 位浮点数。显存占用比 fp32 直接减半,是过去几年 GPU 推理的主流选择。它的坑在于数值范围比较小,某些训练或推理场景下可能出现溢出。
- bf16:同样是 16 位,但指数位跟 fp32 一样多,数值范围比 fp16 大得多,不用太担心溢出。NVIDIA Ampere 架构(30 系、A100 等)开始原生支持,是目前大模型推理的最优选择之一。
- tf32:这个比较特殊。它不是独立的数据格式,而是 Tensor Core 在 fp32 计算时启用的一种“截断精度”模式,用 19 位有效位做运算,速度和显存占用比 fp32 好,精度又比 fp16 更接近 fp32。适合不想降精度但又想提速的场景。
| 格式 | 位宽 | 指数位 | 尾数位 | 显存占用(7B 模型) | 适用场景 |
|---|---|---|---|---|---|
| fp32 | 32 | 8 | 23 | 28GB | 数值敏感场景 |
| fp16 | 16 | 5 | 10 | 14GB | 老 GPU、显存有限 |
| bf16 | 16 | 8 | 7 | 14GB | Ampere 以上 GPU 首选 |
| tf32 | 32(计算时截断) | 8 | 10 | 同 fp32 | 不想降精度又想提速 |
我的建议很简单:如果你的卡是 NVIDIA 30 系之后(Ampere 架构及以上),部署推理服务直接用 bf16 是最稳的选择;如果是老卡,那就用 fp16;如果显存非常紧张,直接上 4bit 量化模型,比如 GPTQ 或 AWQ 格式的版本,7B 模型能做到 4GB 左右占用,效果虽有一点损失但完全可接受。
顺带说一个显存估算的公式:显存占用 ≈ 模型参数量 × 每个参数的字节数 + 激活值 + KV cache。7B 模型用 bf16 的话,权重大约 14GB,再加上 KV cache 和推理中间变量,单卡 24GB 是比较稳妥的起步配置。如果你的 max-model-len 开得很大,KV cache 占比会明显上升,这时候就需要调低 gpu-memory-utilization 给运行时留出空间。
3. 自动化脚本的核心设计
3.1 脚本整体流程设计
我设计的部署脚本不是一个“一键装好一切”的黑盒,而是分成六个阶段,每个阶段都有独立的日志和失败重试机制。这样跑的时候你能清楚知道卡在哪一步,避免出了问题还要从头看日志。
六个阶段分别是:
- 环境探测:检测操作系统、GPU 型号、驱动版本、显存大小、Python 版本。
- 依赖装填:创建虚拟环境,安装对应版本的 PyTorch、推理框架及其它依赖。
- 模型准备:下载模型权重,校验文件完整性,配置缓存目录。
- 参数生成:根据探测到的硬件信息自动计算启动参数。
- 服务启动:启动推理服务进程,落盘日志,记录 PID。
- 健康检查:轮询服务接口,确认服务真正可用后输出访问信息。
这个设计的核心思路是“把每一步都变成可检查、可重跑、可输出日志的独立单元”。比如模型下载到一半断了,重跑脚本时应该能跳过已下载的部分,而不是从头再来。健康检查这一步很多人会省掉,但我觉得恰恰不能省——进程起来了不代表模型加载完了,模型加载完成也不代表接口能正常响应,只有 curl 返回 200 才叫真正部署成功。
3.2 环境探测与依赖装填
环境探测是整个脚本的地基。我的脚本里用一段 bash 先拿到显卡信息,再决定后面的参数怎么定。
bash复制#!/bin/bash
# 探测 GPU 信息和驱动版本
if command -v nvidia-smi &> /dev/null; then
GPU_NAME=$(nvidia-smi --query-gpu=name --format=csv,noheader | head -n 1)
DRIVER_VERSION=$(nvidia-smi --query-gpu=driver_version --format=csv,noheader | head -n 1)
GPU_MEMORY=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader | head -n 1)
echo "[INFO] GPU: $GPU_NAME, Driver: $DRIVER_VERSION, Memory: $GPU_MEMORY"
else
echo "[WARN] 未检测到 NVIDIA GPU,将使用 CPU 模式部署"
fi
拿到 GPU 型号和驱动版本后,脚本会自动选择合适的 PyTorch 安装源。这里有一个非常关键的细节:PyTorch 的 CUDA 版本必须跟驱动兼容。驱动是向下兼容的,比如驱动版本支持 CUDA 12.1,那你装 PyTorch 的 cu121 版本就没问题;如果驱动太老只支持 CUDA 11.8,那就得装 cu118 的包,否则装的时候不报错,一跑就报 CUDA driver version is insufficient。
依赖装填我建议用 conda 或 venv 隔离环境,不要直接装进系统 Python。我用的是 Python 自带的 venv,因为它在没有 conda 的服务器上也通用。脚本会自动创建虚拟环境并激活,然后按需安装依赖。
bash复制python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
接着根据框架选择安装 PyTorch 和推理引擎。vLLM 为例:
bash复制pip install torch --index-url https://download.pytorch.org/whl/cu121
pip install vllm
需要说明的是,这里 vllm 的安装版本最好锁定一个已知稳定的版本号,而不是每次都用最新版。原因我在后面的报错章节会具体讲,简而言之就是 vLLM 版本迭代太快,新版经常要配合更高版本的 CUDA 和 PyTorch,锁版本是为了可控。
3.3 模型下载与缓存策略
模型下载是整个部署过程中最耗时的一步,尤其是一次性拉几十 GB 的权重文件,网速稍微不稳就容易中断。脚本里我用 Hugging Face Hub 的 snapshot_download 来做,它支持断点续传,默认从缓存目录继续下载,不会因为中断而前功尽弃。
bash复制python3 - <<'EOF'
from huggingface_hub import snapshot_download
import os
model_id = os.environ.get("MODEL_ID", "Qwen/Qwen2.5-7B-Instruct")
cache_dir = os.environ.get("CACHE_DIR", "./models")
snapshot_download(
repo_id=model_id,
local_dir=cache_dir,
local_dir_use_symlinks=False,
resume_download=True,
)
print(f"[INFO] 模型下载完成: {model_id}")
EOF
关于下载源,我也在脚本里留了一个可切换的开关。Hugging Face 的源在部分网络环境下速度不理想,所以我预留了 ModelScope 的下载逻辑——只需要改一个环境变量就能切换。这个设计不是针对哪个平台好或不好,单纯是从国内实际下载速度出发做的兼容处理。
下载完成之后,脚本会对关键权重文件做一次大小校验,防止下载不完整。模型服务启动失败很多时候不是代码问题,而是权重文件坏了或没下全。这一步校验虽然简单,但能省掉大量排查时间。
缓存目录也要统一管理。我的习惯是设置一个固定的模型缓存根目录,比如 ./models,同时给每个模型建一个子目录,避免多个模型混在一起。这样后续换模型、清理磁盘都方便。
3.4 服务启动、健康检查与优雅关闭
启动服务这一步设计的关键是“参数自动生成”。很多人直接在命令行里手写 --max-model-len 8192、--tensor-parallel-size 1,但换个机器就得重写。我的做法是根据探测到的显存大小自动计算推荐值。
bash复制# 根据显存大小自动计算 vLLM 参数
GPU_MEM_GB=$(echo $GPU_MEMORY | awk '{print int($1/1024)}')
if [ $GPU_MEM_GB -ge 40 ]; then
TENSOR_PARALLEL=1
MAX_MODEL_LEN=32768
GPU_UTIL=0.90
elif [ $GPU_MEM_GB -ge 24 ]; then
TENSOR_PARALLEL=1
MAX_MODEL_LEN=16384
GPU_UTIL=0.90
elif [ $GPU_MEM_GB -ge 16 ]; then
TENSOR_PARALLEL=1
MAX_MODEL_LEN=8192
GPU_UTIL=0.85
else
TENSOR_PARALLEL=1
MAX_MODEL_LEN=4096
GPU_UTIL=0.80
fi
然后启动 vLLM 服务:
bash复制nohup python3 -m vllm.entrypoints.openai.api_server \
--model "$MODEL_DIR" \
--served-model-name "$SERVED_MODEL_NAME" \
--tensor-parallel-size "$TENSOR_PARALLEL" \
--max-model-len "$MAX_MODEL_LEN" \
--gpu-memory-utilization "$GPU_UTIL" \
--port "$PORT" \
> "$LOG_FILE" 2>&1 &
echo $! > "$PID_FILE"
用 nohup 启动是为了让进程在脚本退出后继续存活,PID 文件则用于后续的停止和重启操作。
健康检查的脚本是这样的:
bash复制# 健康检查:等待服务真正可用
for i in $(seq 1 60); do
HEALTH_STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:$PORT/health)
if [ "$HEALTH_STATUS" = "200" ]; then
echo "[INFO] 服务健康检查通过,部署成功"
break
fi
echo "[INFO] 等待服务启动... ($i/60)"
sleep 5
done
这里有一个经验点:大模型加载到显存再完成预热,需要的时间可能是几十秒到几分钟不等,取决于模型大小和磁盘速度。健康检查的超时时间一定要给足,我第一次写脚本时只给了 30 秒,结果 7B 模型从磁盘加载模型权重加编译就花了快两分钟,脚本早早就报失败了。后来改成了 5 分钟的超时窗口,这个才稳定下来。
优雅关闭同样要处理。粗暴地 kill -9 可能导致显存没释放、临时文件残留。脚本里我用 kill -TERM 先让进程自己清理退出,等几秒确认进程结束后再做状态清理。
bash复制if [ -f "$PID_FILE" ]; then
PID=$(cat "$PID_FILE")
echo "[INFO] 停止服务进程 PID=$PID"
kill -TERM "$PID" 2>/dev/null
sleep 5
if kill -0 "$PID" 2>/dev/null; then
echo "[WARN] 进程未退出,强制结束"
kill -9 "$PID"
fi
rm -f "$PID_FILE"
fi
4. 可直接落地的脚本实例
4.1 场景 A:vLLM 部署大语言模型
这里给一个我实际在用的完整脚本,注释都写在里面了。适用场景是单卡 24GB 及以上,部署一个 7B 到 14B 级别的通用对话模型。
bash复制#!/bin/bash
set -e
# ========== 配置区 ==========
MODEL_ID="${MODEL_ID:-Qwen/Qwen2.5-7B-Instruct}"
SERVED_MODEL_NAME="${SERVED_MODEL_NAME:-local-qwen}"
PORT="${PORT:-8000}"
CACHE_DIR="${CACHE_DIR:-./models}"
LOG_FILE="${LOG_FILE:-./logs/server.log}"
PID_FILE="${PID_FILE:-./logs/server.pid}"
# ============================
mkdir -p logs
mkdir -p "$CACHE_DIR"
# 1. 环境探测
if command -v nvidia-smi &> /dev/null; then
GPU_MEMORY=$(nvidia-smi --query-gpu=memory.total --format=csv,noheader | head -n 1)
echo "[INFO] GPU 显存: $GPU_MEMORY"
else
echo "[ERROR] 未检测到 NVIDIA GPU,vLLM 需要 CUDA 环境"
exit 1
fi
# 2. 创建虚拟环境
if [ ! -d ".venv" ]; then
echo "[INFO] 创建 Python 虚拟环境"
python3 -m venv .venv
fi
source .venv/bin/activate
# 3. 安装依赖
pip install --quiet --upgrade pip
pip install --quiet torch vllm huggingface_hub
# 4. 下载模型
echo "[INFO] 开始下载模型: $MODEL_ID"
python3 - <<EOF
from huggingface_hub import snapshot_download
import os
snapshot_download(
repo_id=os.environ["MODEL_ID"],
local_dir=os.environ["CACHE_DIR"] + "/" + os.environ["MODEL_ID"].replace("/", "--"),
resume_download=True,
)
EOF
# 5. 根据显存计算参数
GPU_MEM_GB=$(echo $GPU_MEMORY | awk '{print int($1/1024)}')
if [ $GPU_MEM_GB -ge 40 ]; then
MAX_MODEL_LEN=32768
GPU_UTIL=0.90
elif [ $GPU_MEM_GB -ge 24 ]; then
MAX_MODEL_LEN=16384
GPU_UTIL=0.90
else
MAX_MODEL_LEN=8192
GPU_UTIL=0.85
fi
# 6. 启动服务
echo "[INFO] 启动 vLLM 服务,端口 $PORT,模型名 $SERVED_MODEL_NAME"
MODEL_PATH="$CACHE_DIR/$(echo $MODEL_ID | tr '/' '--')"
nohup python3 -m vllm.entrypoints.openai.api_server \
--model "$MODEL_PATH" \
--served-model-name "$SERVED_MODEL_NAME" \
--tensor-parallel-size 1 \
--max-model-len "$MAX_MODEL_LEN" \
--gpu-memory-utilization "$GPU_UTIL" \
--port "$PORT" \
> "$LOG_FILE" 2>&1 &
echo $! > "$PID_FILE"
# 7. 健康检查
for i in $(seq 1 60); do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:$PORT/health || true)
if [ "$STATUS" = "200" ]; then
echo "[INFO] 服务启动成功!"
echo "[INFO] 访问地址: http://127.0.0.1:$PORT"
echo "[INFO] 模型名称: $SERVED_MODEL_NAME"
exit 0
fi
echo "[INFO] 正在等待服务启动...($i/60)"
sleep 5
done
echo "[ERROR] 服务启动超时,请检查日志: $LOG_FILE"
exit 1
这个脚本的核心参数有三个:--served-model-name 是给客户端看的模型 ID,--max-model-len 控制上下文长度,--gpu-memory-utilization 控制显存预留比例。这三个参数是我调脚本时打交道最多的,也是最容易出问题的位置。
4.2 场景 B:Ollama 轻量部署
如果场景只是个人开发机或者 MacBook 上快速跑一个模型,我推荐用 Ollama。它把下载、部署、服务化全部都封装好了,脚本可以做得非常简单。
bash复制#!/bin/bash
set -e
MODEL_NAME="${1:-qwen2.5:7b}"
PORT="${OLLAMA_PORT:-11434}"
echo "[INFO] 检查 Ollama 是否已安装"
if ! command -v ollama &> /dev/null; then
echo "[INFO] 安装 Ollama"
curl -fsSL https://ollama.com/install.sh | sh
fi
echo "[INFO] 拉取模型: $MODEL_NAME"
ollama pull "$MODEL_NAME"
echo "[INFO] 启动 Ollama 服务(常驻)"
nohup ollama serve > ./logs/ollama.log 2>&1 &
echo "[INFO] 等待服务启动..."
for i in $(seq 1 30); do
if curl -s http://127.0.0.1:$PORT &> /dev/null; then
echo "[INFO] Ollama 服务已就绪"
break
fi
sleep 2
done
echo "[INFO] 验证模型可访问"
ollama list
Ollama 的优势在于模型管理。MODEL_NAME 里带 :7b 这样的标签,你甚至不需要记住完整的模型 ID。而且它默认就带一定程度的量化加载,显存不够时会自动想办法,对于轻度使用者来说是很省心的。
4.3 场景 C:嵌入模型 BGE-M3
做 RAG 的时候嵌入模型是必需品。BGE-M3 是我目前在用的,它支持最长 8192 token 的输入,多语言效果不错。部署嵌入模型有两个选择:一是直接用 Hugging Face 的 sentence-transformers,适合离线批处理场景;二是用 TEI(Text Embeddings Inference)起一个 API 服务,适合在线调用。
我脚本里用的是 TEI 起服务的方式,因为它在显存优化和并发上比直接调 Python 库更靠谱。启动命令也很简单:
bash复制#!/bin/bash
set -e
MODEL_ID="${1:-BAAI/bge-m3}"
PORT="${TEI_PORT:-8080}"
echo "[INFO] 启动 TEI 服务加载嵌入模型: $MODEL_ID"
nohup docker run -d --name tei-bge \
-p $PORT:80 \
-v ./data:/data \
ghcr.io/huggingface/text-embeddings-inference:latest \
--model-id "$MODEL_ID" \
--max-batch-tokens 16384 \
> ./logs/tei.log 2>&1 &
echo "[INFO] 等待 TEI 服务启动..."
for i in $(seq 1 60); do
STATUS=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:$PORT/health)
if [ "$STATUS" = "200" ]; then
echo "[INFO] 嵌入模型服务已就绪"
exit 0
fi
sleep 5
done
用 Docker 的好处是不需要担心 Python 环境冲突,TEI 的容器镜像把依赖都打包好了。如果你的服务器上不方便跑 Docker,也可以直接用 text-embeddings-inference 的 pip 包装命令启动,只是要注意 CUDA 版本匹配。
4.4 实际运行效果记录
我在一台 24GB 显存的机器上用上述脚本部署了 Qwen2.5-7B-Instruct,整个流程从零开始大约耗时 30 分钟,其中模型下载占了 20 分钟(权重约 15GB),环境安装 5 分钟,服务加载和健康检查 5 分钟。第二次在这台机器上重新部署时,模型已经在缓存里,总耗时只有不到 3 分钟。
服务起来后用 curl 验证接口:
bash复制curl -s http://127.0.0.1:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "local-qwen",
"messages": [{"role": "user", "content": "你好,请做一下自我介绍"}],
"max_tokens": 256
}'
返回结果是正常的 JSON 响应,这基本就可以确认部署链路是通的。之后不管是接 Spring AI 这类后端框架,还是接 AI Agent 框架,都只要把 base URL 指向 http://127.0.0.1:8000 就行。
5. 高频报错与排查实录
5.1 模型名报错:明明填对了为什么提示不存在
这个是我碰到过最多的一个问题,也是很多刚接触本地部署的人会卡住的地方。现象是:你在客户端工具里填了本地模型的名称,报错信息类似:
code复制there's an issue with the selected model (deepseek-v4-flash-0731). it may not exist or you may not have access to it. run /model to pick a different model.
看到这个报错,很多人第一反应是“我模型名填错了”,然后反复检查、反复重填,但问题依旧。我排过几次之后发现,真正的坑往往不在“名称本身”,而在“名称不匹配”。
具体来说,有三种可能:
第一种,客户端里填的模型 ID 和服务端实际注册的模型名不一致。 vLLM 启动的时候有个参数叫 --served-model-name,客户端调用时填的 model 字段必须和它一致。比如你 vLLM 里设的是 local-qwen,客户端填的是 deepseek-v4-flash-0731,那就必然报错。解决方法是把两边的名字对齐,要么改 vLLM 的 --served-model-name,要么改客户端配置。
第二种,中间走了网关或代理,网关没映射好。 很多 AI 开发工具支持你配一个自定义接口,但这个接口背后可能还有一个网关层在做模型名路由。客户端把请求发给网关,网关再转发给本地 vLLM,这个过程中模型名需要两级同时匹配。如果有人改了 vLLM 的名字但没改网关的映射表,就会看到这种“名称明明对了但报不存在”的诡异问题。
第三种,模型确实没加载成功但服务进程还在。 这种情况比较隐蔽。vLLM 启动时如果模型加载失败,进程可能不会立刻退出,但 /v1/models 接口返回的模型列表是空的或者没有你期望的模型 ID。客户端自然报“模型不存在”。
排查这个问题的正确顺序是:先直接 curl http://127.0.0.1:8000/v1/models 看服务端真正注册了哪些模型 ID,再对照客户端配置。确认服务端没问题之后,再检查网关映射。这个思路能覆盖绝大多数“模型名报错”的场景。
5.2 显存不足与 OOM
显存不足是我在部署路上遇到次数最多的另一个问题,报错信息一般是 CUDA out of memory 或者干脆进程被 kill。
这类问题有几个常见原因:
max-model-len设置过大。上下文长度越长,KV cache 占用的显存越多。如果 7B 模型你硬开 32K 上下文,24GB 显存真的吃不住。我调试的时候通常先用 4K 跑通,再逐步往上涨。gpu-memory-utilization设置过高。这个参数控制 vLLM 最多使用多少比例的显存。设置成 0.95 乍一看很合理,但如果你还有别的进程在占用显存,就会 OOM。我一般默认 0.85 到 0.90,留出安全余量。- 并发请求太多。每增加一个并发请求,KV cache 占用都会涨。如果 openai 接口的并发数设置太高,显存撑不住。
排查时先用 nvidia-smi 看当前显存占用和进程,确认是不是有别的进程占着显存。然后逐步调小 max-model-len 和 gpu-memory-utilization,找到稳定运行的临界点。注意,这两个参数要在服务启动前设置好,运行中改不了。
5.3 端口冲突与健康检查超时
端口冲突的问题比较直白,启动脚本时报 Address already in use,或者健康检查一直失败但服务日志里没有任何报错。我的处理习惯是启动前先检查端口是否被占用。
bash复制if lsof -i :$PORT &> /dev/null; then
echo "[ERROR] 端口 $PORT 已被占用,请更换端口或释放占用"
lsof -i :$PORT
exit 1
fi
健康检查超时则要区分两种情况:一种是服务真的没起来,日志里有异常;另一种是模型在加载中,时间比较长。我的建议是日志和健康检查配合看,不要因为一次超时就判定失败。把健康检查的超时窗口设置得宽一些(比如 3 到 5 分钟),同时把服务日志实时输出到文件,这样你心里有数。
5.4 工具链版本不匹配
版本不匹配是部署时最耗人耐心的坑。典型情况是:vLLM 装上了,torch 也装上了,但一启动就报算子编译错误或者 undefined symbol 之类的错。
我的建议是锁定版本组合。每次部署我都记录当前用的 Python 版本、PyTorch 版本、vLLM 版本、CUDA 版本,这样四个维度对齐之后,换机器部署才能真正可复现。比如我现在常用的组合是 Python 3.10 + PyTorch 2.1.2 + vLLM 0.5.4 + CUDA 12.1。
在脚本里锁版本就一行:
bash复制pip install torch==2.1.2 vllm==0.5.4
不要用 pip install vllm 直接装最新版。新版虽然功能多,但往往要求更高的 CUDA 版本和更旧的 Python 版本,如果你对这套工具链的适配关系不熟,锁版本是最稳妥的策略。
6. 部署脚本的进阶玩法与经验沉淀
6.1 开机自启与守护进程
脚本部署完之后还有个问题:机器重启了怎么办?手动重新跑一遍脚本当然可以,但既然做了自动化,不如一步到位用 systemd 把服务管起来。
写一个 systemd service 文件:
ini复制[Unit]
Description=Local LLM Service
After=network.target
[Service]
Type=simple
User=deploy
WorkingDirectory=/opt/llm
ExecStart=/opt/llm/.venv/bin/python3 -m vllm.entrypoints.openai.api_server \
--model /opt/llm/models/Qwen--Qwen2.5-7B-Instruct \
--served-model-name local-qwen \
--tensor-parallel-size 1 \
--max-model-len 16384 \
--gpu-memory-utilization 0.90 \
--port 8000
Restart=always
RestartSec=10
StandardOutput=append:/opt/llm/logs/server.log
StandardError=append:/opt/llm/logs/server.log
[Install]
WantedBy=multi-user.target
Restart=always 的意思是进程非正常退出时自动拉起,配合 RestartSec 防止疯狂重启。这样服务崩溃了也能自动恢复,基本不用人工干预。
6.2 低成本设备部署经验
别以为只有大显卡才能玩模型部署。我实测过树莓派 5 和 MacBook Air M3 这类低资源设备,结论是:能跑,但要选对模型和框架。
树莓派 5 上跑 YOLOv5 目标检测是可以的,关键在于推理框架的选择。直接在 PyTorch 上跑非常勉强,我建议转成 NCNN 或 TFLite 格式,推理速度能提升数倍。我部署的一组实测数据是:YOLOv5s 模型在树莓派 5 上通过 NCNN 推理,单帧检测大约 300ms 左右,虽然不算快,但对原型验证足够了。
MacBook Air M3 16G 的体验就更好一些。Ollama 对 Apple Silicon 的支持很成熟,可以用 Metal 加速,跑 7B 量级的量化模型完全没有问题。我拿它跑过 7B 模型的对话,速度体感跟云上 GPU 实例差距没有想象中那么大。如果你手头只有 MacBook,想体验本地模型开发,Ollama 是最好的起点。
6.3 一套脚本管多个模型
部署脚本做到最后,我发现真正效率高的方式不是“一个模型一套脚本”,而是“一个配置管所有模型”。用环境变量或 .env 文件定义模型清单,脚本循环启动多个服务,端口自动分配,日志按模型名分开存储。
bash复制# example.env
MODEL_A=Qwen/Qwen2.5-7B-Instruct:8001
MODEL_B=BAAI/bge-m3:8080
MODEL_C=deepseek-ai/DeepSeek-R1-Distill-Qwen-7B:8002
脚本启动时读取这个配置,逐条拉起服务,并在最后输出一个服务总览表格。这样不管是给团队共享开发环境,还是自己本机管理多个实验模型,都只需要改配置文件,不用碰脚本逻辑。
这之后接 Spring AI 这类框架就非常顺畅了。Spring AI 支持配置多个模型提供者的 base URL,你只要把各个服务的地址和模型名对齐,就能在应用里自由切换。我实际接过的场景是:一个对话模型负责聊天,一个嵌入模型负责向量化,一个 Rerank 模型负责检索精排,三个服务都是这套脚本管起来的,稳定性很好。
最后再分享一个实操中的小技巧:脚本里每一步都打上带时间戳的日志。一开始我嫌日志啰嗦,后来发现部署出问题时,完整的日志链就是最强排查依据。哪一步慢、哪一步报错、哪一步重试了,一眼就能定位。日志是自动化的副产品,但也是自动化的生命线。
