vLLM部署实战:搭建OpenAI兼容推理服务

admin 2026-07-22 06:15:31 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细介绍了vLLM在生产环境部署OpenAI兼容推理服务的完整流程,强调不能仅靠单条启动命令。核心要点包括:确定服务边界(vLLM负责推理,Nginx提供入口)、检查主机GPU与容器运行时、验证模型目录完整性、确认镜像参数、以前台方式最小启动、使用DockerCompose固化部署、逐层验证API(健康检查、模型列表、非流式与流式请求)、用SDK验证兼容性、处理聊天模板与Base模型、以及进行并发与显存基线测试。文章提供了大量可执行的bash脚本和配置示例,并指出了常见陷阱如健康通过不代表推理正确、gpu-memory-utilization过高可能导致OOM等。 综合评分: 87 文章分类: AI安全,安全工具,技术标准,解决方案


cover_image

vLLM 部署实战:搭建 OpenAI 兼容推理服务

点击关注 👉 点击关注 👉

马哥Linux运维

2026年7月19日 18:00 广东

在小说阅读器读本章

去阅读

vLLM 部署实战:搭建 OpenAI 兼容推理服务

vLLM 能快速把本地模型转换为 OpenAI 兼容 API,但生产部署不能只停留在一条启动命令。驱动与镜像是否兼容、模型文件是否完整、KV Cache 如何规划、并发是否超过显存边界、流式响应是否被代理缓存、故障能否定位、版本能否回滚,都会决定服务是否真正可用。

本文以 Linux、NVIDIA GPU、Docker Compose 和 vllm serve 为主。<模型目录>、、<对外模型名>、 等是占位符,必须替换为实际值。vLLM 版本迭代快,执行前必须通过目标镜像的 –help 核对参数,生产镜像应固定版本或 digest。

一、先确定服务边界

vLLM 负责模型加载、批处理、KV Cache 和推理接口,不应直接承担公网 TLS、企业认证、租户配额和跨实例负载均衡。通常让 vLLM 监听回环或内网地址,由 Nginx/API Gateway 提供统一入口。

上线前确认模型精度、最大上下文、GPU 数量、张量并行度、最大并发、输入输出上限、模型逻辑名、聊天模板和访问范围。没有这些边界,显存与并发参数无法合理配置。

二、检查主机、GPU 与容器运行时

bash

#!/usr/bin/env bash
set&nbsp;-euo pipefail

MODEL_DIR="<模型目录>"

nvidia-smi --query-gpu=index,uuid,name,driver_version,memory.total,memory.used &nbsp; --format=csv
docker version
docker compose version
df&nbsp;-h&nbsp;"$MODEL_DIR"
du&nbsp;-sh&nbsp;"$MODEL_DIR"

宿主机 nvidia-smi 成功不代表容器能访问 GPU。使用企业已验证的 CUDA 镜像做最小验证。

bash

docker run --rm&nbsp;--gpus all <受信任CUDA镜像> &nbsp; nvidia-smi --query-gpu=index,uuid,name,driver_version --format=csv

容器失败时优先检查 NVIDIA Container Toolkit、Docker device request 和守护进程日志,不要先修改 vLLM 参数。

三、验证模型目录

bash

MODEL_DIR="<模型目录>"

test&nbsp;-r&nbsp;"$MODEL_DIR/config.json"
find&nbsp;"$MODEL_DIR"&nbsp;-maxdepth 1 -type&nbsp;f &nbsp; \( -name&nbsp;'*.safetensors'&nbsp;-o -name&nbsp;'*.safetensors.index.json'&nbsp; &nbsp; &nbsp; -o -name&nbsp;'tokenizer.json'&nbsp;-o -name&nbsp;'tokenizer_config.json'&nbsp;\) &nbsp; -printf&nbsp;'%f\n'&nbsp;|&nbsp;sort

读取模型架构、dtype 和上下文配置,确认没有指向错误目录。

bash

jq&nbsp;'{architectures,model_type,torch_dtype,max_position_embeddings}'&nbsp; &nbsp;<模型目录>/config.json
jq&nbsp;'{model_max_length,chat_template}'&nbsp; &nbsp;<模型目录>/tokenizer_config.json
findmnt -T <模型目录> -o TARGET,SOURCE,FSTYPE,OPTIONS

共享存储加载慢时,应结合 iostat、存储吞吐和容器日志判断;GPU 利用率低并不等于模型进程卡死。

四、确认镜像和参数

bash

IMAGE="<vLLM镜像>:<vLLM镜像标签>"

docker image inspect&nbsp;"$IMAGE"&nbsp; &nbsp;--format&nbsp;'Id={{.Id}} Digests={{json .RepoDigests}}'
docker run --rm&nbsp;--entrypoint vllm&nbsp;"$IMAGE"&nbsp;--version
docker run --rm&nbsp;--entrypoint vllm&nbsp;"$IMAGE"&nbsp;serve --help&nbsp;| less

重点确认 host、port、served-model-name、tensor-parallel-size、dtype、max-model-len、gpu-memory-utilization 和 max-num-seqs。参数不存在时应按当前版本替换,不能混用不同版本命令。

五、以前台方式最小启动

以下示例使用 GPU 0、1,TP 为 2,模型以 BF16 加载。GPU 数量必须与 TP 一致,模型与硬件必须支持所选 dtype。

bash

docker run --rm&nbsp;--gpus&nbsp;'"device=0,1"'&nbsp;--ipc=host --shm-size=32g \
&nbsp; -p 8000:8000 -v&nbsp;"<模型目录>:/models/model:ro"&nbsp;\
&nbsp; --entrypoint vllm <vLLM镜像>:<vLLM镜像标签> \
&nbsp; serve /models/model \
&nbsp; --served-model-name <对外模型名> --host 0.0.0.0 --port 8000 \
&nbsp; --tensor-parallel-size 2 --dtype bfloat16 --max-model-len 8192 \
&nbsp; --gpu-memory-utilization 0.85 --max-num-seqs 32

gpu-memory-utilization 影响模型与 KV Cache 的显存规划,不是“业务最多用多少显存”的绝对限制。值过高可能在 CUDA Graph、通信缓冲或峰值请求时 OOM。trust-remote-code 会执行模型仓库代码,只有经过审查且模型确实要求时才启用。

六、使用 Compose 固化部署

yaml

services:
&nbsp;&nbsp;vllm-api:
&nbsp; &nbsp;&nbsp;image:&nbsp;<vLLM镜像>:<vLLM镜像标签>
&nbsp; &nbsp;&nbsp;container_name:&nbsp;vllm_api_8000
&nbsp; &nbsp;&nbsp;restart:&nbsp;unless-stopped
&nbsp; &nbsp;&nbsp;ipc:&nbsp;host
&nbsp; &nbsp;&nbsp;shm_size:&nbsp;32g
&nbsp; &nbsp;&nbsp;ports:
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;"127.0.0.1:8000:8000"
&nbsp; &nbsp;&nbsp;environment:
&nbsp; &nbsp; &nbsp;&nbsp;CUDA_VISIBLE_DEVICES:&nbsp;"0,1"
&nbsp; &nbsp;&nbsp;volumes:
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;<模型目录>:/models/model:ro
&nbsp; &nbsp;&nbsp;deploy:
&nbsp; &nbsp; &nbsp;&nbsp;resources:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;reservations:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;devices:
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;driver:&nbsp;nvidia
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;device_ids:&nbsp;["0",&nbsp;"1"]
&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp;capabilities:&nbsp;[gpu]
&nbsp; &nbsp;&nbsp;entrypoint:&nbsp;["vllm",&nbsp;"serve"]
&nbsp; &nbsp;&nbsp;command:
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;/models/model
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;--served-model-name
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;<对外模型名>
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;--host
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;0.0.0.0
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;--port
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;"8000"
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;--tensor-parallel-size
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;"2"
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;--dtype
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;bfloat16
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;--max-model-len
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;"8192"
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;--gpu-memory-utilization
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;"0.85"
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;--max-num-seqs
&nbsp; &nbsp; &nbsp;&nbsp;-&nbsp;"32"
&nbsp; &nbsp;&nbsp;healthcheck:
&nbsp; &nbsp; &nbsp;&nbsp;test:&nbsp;["CMD-SHELL",&nbsp;"python3 -c \"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health', timeout=3)\""]
&nbsp; &nbsp; &nbsp;&nbsp;interval:&nbsp;30s
&nbsp; &nbsp; &nbsp;&nbsp;timeout:&nbsp;5s
&nbsp; &nbsp; &nbsp;&nbsp;retries:&nbsp;5
&nbsp; &nbsp; &nbsp;&nbsp;start_period:&nbsp;600s
&nbsp; &nbsp;&nbsp;logging:
&nbsp; &nbsp; &nbsp;&nbsp;driver:&nbsp;json-file
&nbsp; &nbsp; &nbsp;&nbsp;options:
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;max-size:&nbsp;100m
&nbsp; &nbsp; &nbsp; &nbsp;&nbsp;max-file:&nbsp;"5"

启动前渲染配置、检查端口和 GPU 占用。Compose 子命令使用 service 名 vllm-api,不是 container_name。

bash

COMPOSE_FILE="<Compose文件路径>"

docker compose -f&nbsp;"$COMPOSE_FILE"&nbsp;config
ss -lntp | grep&nbsp;':8000 '&nbsp;||&nbsp;true
nvidia-smi --query-compute-apps=gpu_uuid,pid,process_name,used_memory &nbsp; --format=csv ||&nbsp;true
bash

docker compose -f <Compose文件路径> up -d vllm-api
docker compose -f <Compose文件路径> ps vllm-api
docker compose -f <Compose文件路径> logs -f --tail=300 vllm-api

七、逐层验证 API

先确认监听和健康端点。健康通过只代表进程可响应,不代表推理正确。

bash

ss -lntp | grep&nbsp;':8000 '
curl -fsS --connect-timeout 2 --max-time 10 &nbsp; http://127.0.0.1:8000/health

确认模型列表中的 ID 与 served-model-name 相同。

bash

curl -fsS http://127.0.0.1:8000/v1/models | jq .

非流式 Chat Completions 用于验证响应结构、finish_reason 和 usage。

bash

curl -fsS http://127.0.0.1:8000/v1/chat/completions &nbsp; -H&nbsp;'Content-Type: application/json'&nbsp; &nbsp;-d&nbsp;'{
&nbsp; &nbsp; "model":"<对外模型名>",
&nbsp; &nbsp; "messages":[{"role":"user","content":"只回复 ready"}],
&nbsp; &nbsp; "temperature":0,
&nbsp; &nbsp; "max_tokens":16,
&nbsp; &nbsp; "stream":false
&nbsp; }'&nbsp;| jq .

流式请求使用 curl -N,确认数据逐段到达而不是被中间代理缓存。

bash

curl -N -fsS http://127.0.0.1:8000/v1/chat/completions &nbsp; -H&nbsp;'Content-Type: application/json'&nbsp; &nbsp;-d&nbsp;'{
&nbsp; &nbsp; "model":"<对外模型名>",
&nbsp; &nbsp; "messages":[{"role":"user","content":"列出三项上线检查。"}],
&nbsp; &nbsp; "temperature":0.2,
&nbsp; &nbsp; "max_tokens":128,
&nbsp; &nbsp; "stream":true
&nbsp; }'

八、用 SDK 验证兼容性

python

import&nbsp;os
from&nbsp;openai&nbsp;import&nbsp;OpenAI

client = OpenAI(
&nbsp; &nbsp; base_url=os.environ.get("LLM_BASE_URL",&nbsp;"http://127.0.0.1:8000/v1"),
&nbsp; &nbsp; api_key=os.environ.get("LLM_API_KEY",&nbsp;"local-not-checked"),
)

response = client.chat.completions.create(
&nbsp; &nbsp; model="<对外模型名>",
&nbsp; &nbsp; messages=[{"role":&nbsp;"user",&nbsp;"content":&nbsp;"返回当前请求是否成功。"}],
&nbsp; &nbsp; temperature=0,
&nbsp; &nbsp; max_tokens=64,
)
print(response.choices[0].message.content)

API Key 应由环境变量或密钥系统注入,不应写入仓库。验收还要覆盖超长输入、错误结构、流式结束、客户端中断和并发行为。

九、聊天模板与 Base 模型

Base 模型或 tokenizer 缺失 chat_template 时,Chat Completions 可能启动失败或格式错误。模板必须来自模型官方或内部验证版本,并纳入版本控制。

bash

jq -r&nbsp;'.chat_template // "NO_CHAT_TEMPLATE"'&nbsp; &nbsp;<模型目录>/tokenizer_config.json
sha256sum&nbsp;<聊天模板文件>

修改模板会改变全部真实 prompt,属于模型行为变更,必须回归测试并保留旧模板回滚。

十、并发与显存基线

max-model-len 越大,单请求潜在 KV Cache 越高;max-num-seqs 越大,并发能力和排队行为变化。以下脚本只做短请求稳定性检查,不替代专业基准。

bash

#!/usr/bin/env bash
set&nbsp;-euo pipefail

URL="http://127.0.0.1:8000/v1/chat/completions"
MODEL="<对外模型名>"
TOTAL="32"
CONCURRENCY="8"
TMP_DIR="$(mktemp -d)"
trap&nbsp;'rm -rf "$TMP_DIR"'&nbsp;EXIT

request_one() {
&nbsp;&nbsp;id="$1"
&nbsp; curl -sS -o&nbsp;"$TMP_DIR/$id.json"&nbsp; &nbsp; &nbsp;-w&nbsp;"id=$id&nbsp;code=%{http_code} total=%{time_total}\n"&nbsp; &nbsp; &nbsp;-H&nbsp;'Content-Type: application/json'&nbsp; &nbsp; &nbsp;-d&nbsp;"{\"model\":\"$MODEL\",\"messages\":[{\"role\":\"user\",\"content\":\"返回编号&nbsp;$id\"}],\"temperature\":0,\"max_tokens\":32}"&nbsp; &nbsp; &nbsp;"$URL"
}
export&nbsp;-f request_one
export&nbsp;URL MODEL TMP_DIR
seq&nbsp;1&nbsp;"$TOTAL"&nbsp;| xargs -P&nbsp;"$CONCURRENCY"&nbsp;-I{} bash -c&nbsp;'request_one "$@"'&nbsp;_ {}

压测时同步采集 GPU 和服务指标。vLLM Prometheus 指标名称以实际服务暴露为准,不同版本前缀与标签可能变化。

bash

nvidia-smi dmon -s pucvmet -d 1 -o DT
curl -fsS http://127.0.0.1:8000/metrics | sed -n&nbsp;'1,100p'

吞吐不再上升而排队时延持续增加,说明实例越过合理并发边界,应限流或扩实例,而不是继续增大 max-num-seqs。

十一、启动失败与 OOM

容器退出时先看退出码、OOM 标志、日志和实际命令,不要反复重启覆盖首个错误。

bash

CONTAINER="vllm_api_8000"

docker inspect&nbsp;"$CONTAINER"&nbsp; &nbsp;--format&nbsp;'Status={{.State.Status}} Exit={{.State.ExitCode}} OOM={{.State.OOMKilled}} Error={{.State.Error}}'
docker logs --timestamps --tail=400&nbsp;"$CONTAINER"
docker inspect&nbsp;"$CONTAINER"&nbsp;--format&nbsp;'{{json .Config.Cmd}}'&nbsp;| jq .

CUDA OOM 时核对每张卡的全部进程和容器映射。

bash

nvidia-smi --query-gpu=index,uuid,memory.total,memory.used,memory.free &nbsp; --format=csv
nvidia-smi --query-compute-apps=gpu_uuid,pid,process_name,used_memory &nbsp; --format=csv
docker inspect vllm_api_8000 &nbsp; --format&nbsp;'{{json .HostConfig.DeviceRequests}}'&nbsp;| jq .

降低 gpu-memory-utilization、max-model-len 或 max-num-seqs时一次只改一个变量,并重新完成同样压测。

十二、多卡与 NCCL 排障

多卡初始化卡住时检查 TP、可见 GPU、拓扑、进程与 Xid。

bash

nvidia-smi topo -m
ps -eo pid,ppid,stat,etime,cmd | grep -E&nbsp;'[v]llm|[t]orch'
journalctl -k --since&nbsp;'-30 min'&nbsp;--no-pager &nbsp; | grep -Ei&nbsp;'NVRM|Xid|nvlink|pcie|oom'&nbsp;||&nbsp;true

NCCL 调试只在复现窗口临时启用,避免长期产生大量日志。

bash

NCCL_DEBUG=INFO NCCL_DEBUG_SUBSYS=INIT,GRAPH CUDA_VISIBLE_DEVICES=0,1 vllm serve <模型目录> &nbsp; --tensor-parallel-size 2 &nbsp; --served-model-name <对外模型名> &nbsp; --port 8000

十三、通过 Nginx 暴露服务

SSE 流式响应必须关闭代理缓冲,读取超时应覆盖业务最长生成时间。

nginx

upstream&nbsp;vllm_backend {
&nbsp; &nbsp;&nbsp;server&nbsp;127.0.0.1:8000;
&nbsp; &nbsp;&nbsp;keepalive&nbsp;64;
}

location&nbsp;/v1/ {
&nbsp; &nbsp;&nbsp;proxy_pass&nbsp;http://vllm_backend;
&nbsp; &nbsp;&nbsp;proxy_http_version&nbsp;1.1;
&nbsp; &nbsp;&nbsp;proxy_set_header&nbsp;Connection&nbsp;"";
&nbsp; &nbsp;&nbsp;proxy_set_header&nbsp;X-Request-ID&nbsp;$request_id;
&nbsp; &nbsp;&nbsp;proxy_set_header&nbsp;X-Forwarded-For&nbsp;$proxy_add_x_forwarded_for;
&nbsp; &nbsp;&nbsp;proxy_connect_timeout&nbsp;3s;
&nbsp; &nbsp;&nbsp;proxy_send_timeout&nbsp;60s;
&nbsp; &nbsp;&nbsp;proxy_read_timeout&nbsp;600s;
&nbsp; &nbsp;&nbsp;proxy_request_buffering&nbsp;off;
&nbsp; &nbsp;&nbsp;proxy_buffering&nbsp;off;
&nbsp; &nbsp;&nbsp;gzip&nbsp;off;
}

配置修改采用备份、语法检查、reload、验证闭环。

bash

sudo&nbsp;cp&nbsp;-a <Nginx配置文件> <Nginx配置文件>.bak.$(date&nbsp;+%Y%m%d-%H%M%S)
sudo&nbsp;nginx -t
sudo&nbsp;systemctl reload nginx
curl -fsS https://<API域名>/v1/models | jq .

vLLM 端口不应直接暴露公网。鉴权、TLS、限流、请求体限制和审计应由受控网关承担,日志不要记录完整 prompt 和 Authorization。

十四、升级和回滚

bash

#!/usr/bin/env bash
set&nbsp;-euo pipefail

BACKUP_DIR="<备份目录>/vllm-$(date +%Y%m%d-%H%M%S)"
COMPOSE_FILE="<Compose文件路径>"

install -d -m 0700&nbsp;"$BACKUP_DIR"
cp&nbsp;-a&nbsp;"$COMPOSE_FILE"&nbsp;"$BACKUP_DIR/"
docker compose -f&nbsp;"$COMPOSE_FILE"&nbsp;config >&nbsp;"$BACKUP_DIR/compose.rendered.yaml"
docker image inspect <vLLM镜像>:<旧镜像标签> >&nbsp;"$BACKUP_DIR/image.inspect.json"
sha256sum&nbsp;<模型目录>/config.json >&nbsp;"$BACKUP_DIR/model-config.sha256"

新版本应在新端口启动,完成非流式、流式、长上下文和灰度后切换。失败时先回切网关,再停止新实例。

bash

sudo&nbsp;cp&nbsp;-a <旧Nginx配置备份> <Nginx配置文件>
sudo&nbsp;nginx -t &&&nbsp;sudo&nbsp;systemctl reload nginx
curl -fsS https://<API域名>/v1/models | jq .
docker compose -f <新版本Compose文件> stop vllm-api

停止容器会中断在途请求,因此应先摘流和排空。回滚成功的标准是入口、模型名、聊天模板、核心请求、流式输出、错误率和时延全部恢复。

十五、日常巡检

bash

#!/usr/bin/env bash
set&nbsp;-euo pipefail

CONTAINER="vllm_api_8000"
BASE_URL="http://127.0.0.1:8000"
EXPECTED_MODEL="<对外模型名>"

[[&nbsp;"$(docker inspect -f '{{.State.Running}}'&nbsp;"$CONTAINER")"&nbsp;==&nbsp;"true"&nbsp;]]
curl -fsS --max-time 5&nbsp;"$BASE_URL/health"&nbsp;>/dev/null
MODELS="$(curl -fsS --max-time 10&nbsp;"$BASE_URL/v1/models")"
jq -e --arg model&nbsp;"$EXPECTED_MODEL"&nbsp; &nbsp;'.data[] | select(.id == $model)'&nbsp;<<<"$MODELS"&nbsp;>/dev/null
nvidia-smi --query-gpu=index,utilization.gpu,memory.used,memory.total,temperature.gpu &nbsp; --format=csv

自动重启前仍需区分模型加载慢、OOM、NCCL、Xid、网关故障与过载。可靠上线还要覆盖镜像 digest、只读模型、GPU/TP 一致、上下文与并发边界、SSE、鉴权、日志脱敏、监控、限流和回滚。

文末阅读福利

仅目前来说,无论是运维人转型提升,还是零基础想转行IT,最好的岗位就是云计算运维&SRE岗位。

为了帮助大家早日快速入门云计算运维领域,给大家整理了一套【最新运维资料】高级运维工程师必备技能资料包(文末一键免费领取),内容有多详实丰富看下图!

1.38张最全工程师技能图谱

2.面试大礼包

3.Linux书籍

内容比较多,就不一一展示了

以上所有资料获取请扫码:

识别上方二维码

备注:2026最新运维资料

100%免费领取

(是扫码领取,不是在公众号后台回复,别看错了哦)


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:马哥Linux运维 点击关注 👉 点击关注 👉《vLLM 部署实战:搭建 OpenAI 兼容推理服务》

评论:0   参与:  0