ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

大模型推理服务vLLM部署实战:从环境配置到性能优化

大模型推理服务vLLM部署实战:从环境配置到性能优化 先放个结论vLLM 是目前把大模型跑成 OpenAI 兼容推理服务最省事的方案之一而且没有“之一”也问题不大。我最早在本地用 Transformers 的 generate 接口跑 Qwen8B 模型单次请求都要等好几秒并发两个请求直接 OOM后来切到 vLLM同一张卡吞吐量翻了几倍不说还能直接拉起一个 8000 端口的标准接口把之前的业务脚本无缝接过来。这篇内容是我从零到一跑通 vLLM 的完整笔记覆盖三种常见部署路径、Qwen3 8B/27B 的实际启动参数、缓存命中率优化以及几个我实测踩过的版本级坑。适合刚开始接触大模型推理部署、想把模型快速变成一个可用 API 的读者。1. 跑通 vLLM 前先分清三条路pip、WSL 与 Docker1.1 pip 安装最直接但不是零门槛vLLM 的 pip 安装本身不复杂真正麻烦的地方在于环境版本匹配。官方要求 Python 3.9 以上新版本已经偏向 3.10CUDA 11.8 或 12.1 以上GPU 架构建议是 Volta 及以上。如果你手里是 Ampere、Ada、Hopper 这些新卡直接装官方 wheel 就行如果是老卡比如 Turing、Volta部分功能会受限或者需要源码编译这点后面硬件章节我会单独展开。我现在的习惯是先用 uv 建一个干净的虚拟环境uv venv vllm-env --python 3.11 source vllm-env/bin/activate uv pip install vllm装完以后用一个最短命令验证启动是否正常python -m vllm.entrypoints.openai.api_server \ --model Qwen/Qwen3-8B-Instruct \ --dtype bfloat16第一次跑会自动下载模型权重如果你的网络环境访问 Hugging Face 不顺畅可以把模型先拉到本地用本地路径替换模型名。ModelScope 上也有官方镜像直接命令行下载pip install modelscope modelscope download --model Qwen/Qwen3-8B-Instruct --local_dir /models/Qwen3-8B-Instruct然后把--model指向/models/Qwen3-8B-Instruct即可这属于我实测下来最稳妥的离线部署姿势。1.2 Docker 部署生产环境最稳的选择热词里那条命令很典型docker run --rm --gpus all -p 8000:8000 \ -v /data/models:/models \ vllm/vllm-openai:latest \ --model /models/Qwen3-8B-Instruct简单拆一下--rm容器退出自动删除--gpus all把宿主机 GPU 全部透传进容器-p 8000:8000暴露推理端口-v /data/models:/models把宿主机模型目录挂进容器避免镜像体重到几十 GB。镜像里已经内置了 vLLM 的 OpenAI 兼容服务入口所以最后的参数直接是模型启动参数不用再手动跑 Python。Docker 的价值在于环境隔离。我遇到过不止一次本地 pip 环境因为某个 CUDA 库版本冲突vLLM 装了两天没装上换成 Docker 十分钟解决。官方镜像与 vLLM 版本同步更新不会出现“源码和 wheel 不一致”的玄学问题。有个细节latest标签对应的是开发版镜像不建议生产环境用。应该锁定具体的 release 标签比如vllm/vllm-openai:v0.8.4这样可复现、可回滚。我一般会在.env文件里记录镜像版本方便后面做环境审计。提示Docker 容器里的 CUDA 版本由镜像决定宿主机只需要有 NVIDIA Container Toolkit 即可。检查方式nvidia-smi能看到容器内 GPU 信息说明透传成功。1.3 Windows 下的“伪原生”方案WSL2vLLM 官方不支持 Windows 原生运行因为底层依赖 CUDA 的某些库和 Linux 内核特性。Windows 用户最常规的做法是 WSL2 Ubuntu 22.04。流程wsl --install wsl --set-default-version 2进入 WSL 后安装 CUDA Toolkit 的 WSL 版本然后和 Linux 环境一样用 pip 安装即可。实测下来WSL2 里跑 vLLM 的性能和原生 Linux 差距很小前提是模型文件放在 Linux 文件系统~/models而不是/mnt/c/...。放在 Win 挂载盘上模型加载时会因为跨文件系统 IO 变得很慢甚至出现卡顿。这是个折腾过的人才懂的坑。2. 本地部署 Qwen3 8B/27B从启动服务到第一个请求2.1 模型选型和显存估算决定你能否单卡跑起来部署 Qwen3-8B-Instruct 和 27B 之前先做显存规划。以 BF16 精度为例8B 模型权重约 16GB27B 约 54GB。这只是权重还没算 KV cache 和激活值。所以单张 24GB 卡跑 8B 没问题还能剩一部分做 KV cache27B 的 BF16 权重就超过 54GB单卡 48GB 也很勉强通常要 2 张 A6000/A100或者直接用 4bit 量化版权重。我的建议是如果只有单张 24GB 卡又想跑 27B直接用 AWQ 量化版官方和社区都有现成权重。27B-AWQ 量化后权重约 15GB 左右单卡 24GB 能跑只是 max-model-len 要控制在 16k 以内。启动命令里最重要的三个参数python -m vllm.entrypoints.openai.api_server \ --model /models/Qwen3-27B-AWQ \ --served-model-name qwen3-27b \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.90 \ --max-model-len 32768--tensor-parallel-size张量并行数多卡部署时必须指定一般等于你分配给模型的 GPU 数--gpu-memory-utilization模型权重之外预留给 KV cache 的显存比例默认 0.9建议不要大于 0.95否则 CUDA 在极端情况容易 OOM--max-model-len最大上下文长度它直接决定 KV cache 的预分配大小。长度翻倍KV cache 几乎翻倍这是单卡部署时最先需要妥协的参数。--served-model-name是 API 层的模型别名。客户端请求时指定的模型名和它一致就行不必是真实权重目录名。2.2 启动后的第一个请求用 curl 或 openai SDK 验证服务起来以后看一眼日志里的INFO: Started server process再用一个小请求验证curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -d { model: qwen3-27b, messages: [{role: user, content: 用一句话介绍 vLLM}] }Python 侧更常用 openai SDKfrom openai import OpenAI client OpenAI(base_urlhttp://localhost:8000/v1, api_keyEMPTY) resp client.chat.completions.create( modelqwen3-27b, messages[{role: user, content: 用一句话介绍 vLLM}], temperature0.7, max_tokens512, ) print(resp.choices[0].message.content)注意vLLM 默认对 OpenAI SDK 兼容得比较完整但api_key随便填个字符串就行它不校验。如果请求返回 401多半是你把 base_url 写成了http://localhost:8000而不是http://localhost:8000/v1这是我见过最常见的低级错误。2.3 日志里的关键信息启动阶段要看什么服务启动时日志会打印模型配置、GPU 显存分布、KV cache 分配情况。重点看GPU KV cache size和Maximum concurrency这两个字段。前者决定了你能支撑多长的并发上下文后者是当前配置下的理论最大并发数。我经常在群里看到有人问“我的 4090 为什么跑 32K 上下文就 OOM”实际上去看日志里 KV cache 占了多少就知道了。单卡 24GB 跑 8B 模型如果 max-model-len 设置成 32KKV cache 可能吃掉 6-8GB另一个 8GB 被权重占用剩下还要放激活值系统几乎没有余量。这时候不是代码问题是配置没给系统留余地。3. SGLang 与 vLLM同样内核下的不同取舍3.1 两者到底在竞争什么SGLang 和 vLLM 都是当前大模型推理服务的主流框架目标高度重合高性能、OpenAI 兼容、支持连续批处理。vLLM 的核心是 PagedAttention把 KV cache 做成类似操作系统的分页提升显存利用率和吞吐SGLang 的核心是 RadixAttention通过树状前缀复用让多轮对话和类似 prompt 场景下的前缀缓存命中率更高。一开始很多人以为 SGLang 是 vLLM 的替代品实际更准确的说法是“在部分场景下比 vLLM 更激进、更极致”。它的 RadixAttention 天然适合 prompt 前缀高度重复的业务比如 Agent 场景下所有人共享同一段长 system prompt或者 RAG 场景下固定的指令模板 变化的检索内容。3.2 我的选型建议别只盯着“谁快”我实际对比过一轮结论是场景推荐框架原因长上下文高并发vLLM生态成熟Bug 修复快社区资料多大量重复前缀的 Agent/RAGSGLangRadixAttention 前缀复用效率更高多轮对话两者皆可差别不大取决于具体负载快速上线优先vLLM部署资料多有问题容易搜到深度定制调度SGLang代码架构更激进但维护成本更高一台 8 卡机器上我用同样的 Qwen3-8B 跑 20 路并发两个框架的吞吐曲线都很稳差距在个位数百分比。真正拉开差距的是长 prompt 高并发场景vLLM 的显存控制和超时处理更成熟。这也是我生产环境仍然主力用 vLLM 的原因之一宁可稍微慢一点不能出了奇怪问题找不到人问。3.3 快速切换不必改业务代码两个框架都实现了 OpenAI 兼容接口所以切换的成本很低。假设你现在是 vLLM 在 8000 端口SGLang 起在 8001 端口业务代码只需要改base_url字符级都不用动client OpenAI(base_urlhttp://localhost:8001/v1, api_keyEMPTY)不要被框架绑架。它是一个服务层组件底层换了接口不变业务就还在可控范围内。4. 缓存命中率优化与 chunk_size Bug 复盘4.1 自动前缀缓存为什么它决定响应延迟vLLM 的缓存命中率核心是指 KV cache 的前缀复用。比如用户请求里有一段很长的 system prompt服务端已经算过这段 token 的 KV 值下一次请求来了如果前缀完全一致就不需要重复计算直接从缓存里读取。启用方式是在启动参数加--enable-prefix-caching。实测里这个开关对“共享 system prompt”的场景收益非常明显一个 16k char 的 system prompt关闭前缀缓存时每次请求都要先花 4-5 秒“预填充”前 2k token开启后这部分时间几乎归零。但有个前提缓存是按 token 级别匹配的要求前缀 token 完全一致。任何一处修改比如给 system prompt 加了时间戳、日志里塞了个随机 ID都会导致整条前缀失效。所以优化命中率的第一步不是调参数而是规范 prompt 模板把变化的部分放在后缀。4.2 如何把命中率从 30% 拉到 90%我总结的操作序列如下固定 system prompt 模板不拼接动态内容动态信息通过用户消息传进去统一 chat template不要每台机器各自改 jinja 模板打开--enable-prefix-caching对于多轮对话尽量让历史消息原样传递不要截断最前面的 system prompt监控日志里的prefix cache hit ratevLLM 的 metrics 里有对应指标可以接到 Prometheus 里看趋势。真实案例之前有个 Agent 服务每次用户请求都会把一段 4000 token 的工具说明拼在消息前缓存命中率只有 35% 左右。排查后发现是每条消息拼接时加了request_id去掉之后命中率提升到 90% 以上TTFT首 token 延迟从 3 秒降到 0.4 秒。改动成本极低收益却非常大。4.3 vLLM 0.23.0 chunk_size Bug一次完整的问题排查说回热词里的vllm 0.23.0 chunk_size bug这版本确实在圈内讨论过。chunked prefill 是 vLLM 处理长 prompt 的一种机制把长序列切块预填充和 decode 请求交错执行避免一个长请求把整卡资源占满。0.23.0 版本在特定配置下chunk_size 处理异常表现为长 prompt 请求后显存持续增长最终 OOM。最典型的现象是短请求一切正常长请求一来几次之后显存就满了但任务本身并没有大并发。我当时是这样排查的先用nvidia-smi盯显存确认是缓慢爬升而不是瞬时暴增看 vLLM 日志里 KV cache 分配记录发现每次请求后缓存都没有正确释放把问题抽象成最小复现固定模型、固定 prompt 长度、固定并发数连续发 20 次请求显存曲线稳定上升用同一份配置回退到上一个 release0.22.x问题消失——确认是版本回归不是硬件或模型问题去 GitHub 搜 issue找到对应讨论后临时方案是把--chunked-prefill-max-tokens调大或直接关闭 chunked prefill最终通过升级版本彻底解决。这个经历给我的教训很明确遇到 vLLM 诡异行为第一反应不是怀疑模型而是先对比版本变更。推理框架迭代很快回归 Bug 并不少见锁版本 定期升级才是生产环境的正解。5. 硬件边界从 Jetson Thor 到 2080 Ti 的部署经验5.1 老卡和小卡的挣扎显存不够时怎么办热词里的Jetson Thor、2080 Ti definitive edition其实指向同一个问题vLLM 并不只跑在云端数据中心里边缘设备和老卡上也有大量部署需求。Jetson 系列是 ARM 架构用 JetPack 自带的 CUDA 环境不能直接 pip 装 vLLM 的 x86 wheel需要源码编译2080 Ti 是 Turing 架构sm_75虽然能装官方 wheel但 FlashAttention 的支持和 Ampere 相比有差距。对于显存紧张的场景我推荐的操作顺序是先量化后缩上下文最后才降低精度。量化优先用 AWQ。Qwen3-8B 的 AWQ 4bit 权重只要 5-6GB比 BF16 版本直接少掉 10GBKV cache 的空间一下子就宽裕了。2080 Ti 22GB 魔改版上跑 Qwen3-8B-AWQmax-model-len 调到 16Kgpu-memory-utilization 设 0.92实测可以稳定支撑 4-6 路并发速度完全可用。5.2 源码编译Jetson 上绕不开的环节Jetson 上 pip install 会直接失败因为官方没有预编译 ARM wheel。必须源码编译整个流程包括git clone --branch v0.8.4 https://github.com/vllm-project/vllm.git cd vllm python -m pip install -r requirements-cuda.txt python setup.py build_ext --inplace pip install -e .在 Jetson Orin 上完整构建一次大约需要 2-3 小时因为 JIT 要编译大量 CUDA kernel如果 FlashAttention 也需要额外编译的话时间更长。构建时间本身就是热词里会问到“vllm构建需要多长时间”的原因。给个经验值平台构建方式耗时x86 Ampere GPUpip wheel5-10 分钟x86 Turing GPUpip wheel JIT10-20 分钟Jetson OrinARM源码编译2-3 小时Jetson Thor早期开发板源码编译3 小时以上如果只是想在 Jetson 上快速验证模型我建议先用官方的 Jetson 容器镜像里面已经预编译好大部分组件能省掉最痛苦的 CUDA 环境配置阶段。5.3 显存不够时的兜底方案如果你的卡实在装不下某个模型还有两个方案可以兜底换更小的量化模型Qwen3-8B 换成 Qwen3-4B27B 换成 14B-AWQ用 API 网关把负载转发到远端推理服务本地只做预处理和缓存这个属于架构层面的妥协但在边缘设备上非常常见。老卡跑 vLLM 还有个共同坑默认开启的 CUDA graph 在部分 Turing 架构上会触发非法内存访问。遇到这个问题启动时加--enforce-eager可以规避代价是吞吐量显著下降但至少服务是稳定的。等确认模型和环境没问题后再尝试逐步开启 CUDA graph。6. 结尾一点个人经验跑通 vLLM 这件事最难的地方往往不是框架本身而是环境依赖、显存规划和版本管理。我的建议很简单本地验证用 pip生产环境用 DockerWindows 用户老老实实 WSL2模型权重尽量提前下载到本地别等着服务启动时现拉日志里关于 KV cache 的行文一定不要跳过那是判断配置是否合理的直接证据。如果你的机器显存不大优先用 AWQ 量化模型其次压缩 max-model-len。最后再分享一个小技巧遇到 vLLM 的怪问题第一件事是去 GitHub Releases 页面看这个版本的已知 issue八成你能找到别人已经踩过的同款坑。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进