
vLLM 是本地部署大模型时最值得优先试的一个推理框架。它解决的核心问题不是“单次生成能压到多快”而是“多个请求同时进来时GPU 为什么一直在空转、显存为什么一直不够用”。官方基准里vLLM 在部分任务上比常规 PyTorch 推理快出 8 倍左右实际能达到多少取决于你跑的模型、输入长度、并发数和 GPU 显存。这篇文章按我自己的实测路径拆一遍先理解 PagedAttention 和连续批处理再把 qwen3-8b 这类模型用 OpenAI 兼容 API 部署起来最后说清楚遇到首字慢、卡顿、显存不足时应该怎么排查。如果你正准备把本地模型接入 Python 应用、LangChain 或 CodeBuddy 这类工具或者正在纠结“为什么别人说很快我部署完却感觉很卡”这篇文章会比较有用。1. 先搞清楚 vLLM 快在哪不是把单次推理压到极限而是把排队和显存碎片清掉很多人第一次接触 vLLM以为它像编译优化一样把单条 prompt 的生成速度提升了多少倍。这个理解不能说错但容易跑偏。vLLM 最大的提升发生在“同时有很多请求”的场景比如你写了一个 Python Web 服务多个用户同时在提问或者你在 LangChain 里批量处理任务。这时候普通推理框架的短板会非常明显GPU 利用率低排队越来越长显存还可能被碎片占满。1.1 一句话理解 vLLM 的加速思路大模型推理时每生成一个 token都要依赖之前所有 token 的 Key 和 Value 缓存这套缓存叫 KV Cache。常规推理框架会为每个请求预先分配一大块连续显存可实际用到的往往只是一部分剩下的空间既不能给别的请求用还会因为请求结束而留下碎片。vLLM 的思路很简单把 KV Cache 切成小块像操作系统管理内存一样按需分配。请求需要多少就分配多少生成完了立刻回收。显存利用率高了能同时跑在 GPU 上的请求就多了吞吐量自然上去了。吞吐量上去了用户体验就变成“响应变快了”。这和你一个人跑单条 prompt 的体感不完全一样。单条 prompt 下 vLLM 也有收益但真正拉开差距的是持续请求场景。1.2 PagedAttention 到底解决什么问题PagedAttention 是 vLLM 的核心机制对应标题里那个“Paged”。它的灵感来自操作系统的虚拟内存分页物理内存不连续没关系只要页表能把这些不连续的块映射到逻辑地址上就行。在 vLLM 里KV Cache 被分成固定大小的块常见是 16 个 token 一个 block每个请求不再占用一整段连续显存而是由多个块拼起来。生成过程中新 token 需要缓存时动态挂载新的块。请求结束时这些块全部释放可以留给下一个请求复用。这个设计带来的实际收益有三个显存碎片大幅减少极端情况下能把可用显存利用率拉到接近 90%。多个请求可以共享同一份 KV Cache比如多条 prompt 前缀相同时公共前缀只需要缓存一份。请求的实际长度和预分配长度不再绑定内存浪费变少。这就是为什么 vLLM 能做到“同样一块 GPU能同时跑的请求数变多”。不是把每个 token 的生成速度提升多少倍而是同一时间窗口内处理的总 token 数上去了。1.3 连续批处理如何影响线上吞吐传统推理框架处理多个请求时常见做法是固定 batch把请求攒够一批要么一起跑要么等前面一批完全跑完再处理后面。问题是同一个批次里不同请求的生成长度不一样。短的很快就结束了但 GPU 还在等长的生成完这段时间就是浪费。连续批处理Continuous Batching解决的就是这个浪费。vLLM 允许请求粒度更细地进入和退出某个请求生成完了立刻从当前批次中移除新请求马上补位还在生成中的请求继续跑不需要重新组成一个大 batch。对于实际服务来说这个机制意味着什么用户不会因为“前面有个长回答还没跑完”而一直排队。短请求不会被长请求长期阻塞。服务端的吞吐更稳定队列积压风险更低。不过注意一点连续批处理是 vLLM 默认开启的调度机制普通用户不需要额外“设置”。你真正需要做的是把并发请求数、批大小、显存利用率这些参数调合适让调度器有足够空间发挥作用。我的建议第一次跑的验证目标不应该是“首 token 有多快”而是“连续发 20 个请求时服务还能不能稳”。这才是 vLLM 的主场。2. 在我跑通之前先把部署条件核对一遍vLLM 不是纯 Python 库它依赖 CUDA、GPU 驱动、PyTorch 和一系列编译好的算子。环境不对启动时会出现各种莫名其妙的问题。我踩过的坑里有一半以上不是模型问题而是 Python 版本、CUDA 版本或显存估算出了偏差。2.1 硬件和 GPU 显存怎么估以 qwen3-8b 这类 8B 级别模型为例FP16 精度下权重文件本身接近 16GB。除了权重推理过程中还要给 KV Cache 预留显存输入上下文越长KV Cache 占得越多。如果再加并发请求每个请求都会占用独立的 KV Cache 空间。所以我的经验是8B 模型用 FP16 部署建议至少 24GB 显存比如 RTX 3090、4090、L20 这种级别。如果只有 16GB 显存优先考虑 7B 模型加 AWQ 或 GPTQ 量化版本或者用 vLLM 的 FP8 支持来降显存。如果跑 27B 级别模型比如 qwen3-27b单卡 24GB 会非常紧张这时候要么量化要么张量并行到两张卡。低显存机器也能启动 8B 模型但并发能力会明显下降更容易出现 OOM 或首字变慢。还有一个容易忽略的点显存和内存不是一回事。vLLM 启动时会把模型权重载入显存如果显存不够它会尝试把部分层放到内存里这时性能会断崖式下降。所以不能只看“模型能不能启动”要看“是全部在显存里跑还是有 offload”。2.2 Python、CUDA 和依赖版本怎么选vLLM 对 Python 版本有要求不同版本支持的 CUDA 也不一样。落地时不要直接pip install vllm了事先确认三件事Python 版本。建议用 3.10 或 3.11兼容性比较稳妥。3.12 用新版 vLLM 一般也没问题但如果你要配合 TensorFlow 或其他老库提前确认。CUDA 版本。vLLM 编译好的 wheel 通常要求 CUDA 11.8 或 12.1 以上。用nvidia-smi看驱动支持的 CUDA 版本驱动版本可以比 CUDA 运行时更高但不能低。是否要装配套的 flash attention。有些安装方式会要求单独装 flash-attn装不上会看到编译错误或运行时报缺库。如果是在 Linux 服务器上操作建议先建独立 Python 虚拟环境。Windows 也能跑 vLLM但官方支持优先级在 Linux遇到奇怪问题先考虑是不是系统差异。2.3 安装 vLLM 的三种方式最常规的方式是 pip 安装pip install vllm装完后验证一下python -c import vllm; print(vllm.__version__)能打印出版本号说明基础环境没问题。如果机器上有 Docker 和 NVIDIA Container Toolkit我更推荐直接用官方镜像省去编译和 CUDA 配置docker pull vllm/vllm-openai:latest这种方式对生产环境更友好模型目录可以挂载到容器内端口也容易映射。第三种方式是从源码编译除非要改内核代码或适配特殊硬件否则不建议新手尝试。源码编译时间比较长出错后排查成本也高。注意先确认依赖版本再跑重型 Demo。很多部署失败案例是 vLLM 和 PyTorch、CUDA 版本不匹配而不是模型本身有问题。3. 用 qwen3-8b 跑通一次完整部署环境准备好之后第一次部署不要直接上生产配置先跑通一个最小可用服务。我建议以 qwen3-8b 为例原因很简单8B 级别模型在常见 24GB 显存单卡上能跑权重适中下载时间可以接受而且 qwen3 本身支持工具调用、编码、文本生成很多场景适合后续做功能扩展。3.1 模型下载自动拉取还是先手动下载vLLM 支持直接从 HuggingFace 或 ModelScope 拉取模型。国内网络环境下从 HuggingFace 拉取可能很慢可以用 ModelScope 镜像或者先手动把模型下载到本地目录再让 vLLM 加载本地路径。手动下载的好处是模型文件复用度高同一个模型你部署、微调、导出 ONNX 都会用到。避免每次启动都去检查远程仓库。下载后目录结构大概是/models/qwen3-8b/ ├── config.json ├── model-00001-of-00004.safetensors ├── model-00002-of-00004.safetensors ├── ... ├── tokenizer.json └── tokenizer_config.json启动 vLLM 时把--model指向这个本地目录就不会每次读取网络。3.2 单卡启动命令和关键参数用新版 vLLM直接vllm serve启动 OpenAI 兼容服务vllm serve /models/qwen3-8b \ --served-model-name qwen3-8b \ --gpu-memory-utilization 0.9 \ --max-model-len 8192 \ --port 8000几个参数我解释一下--served-model-name对外暴露的模型名称。客户端调用时要指定这个值不设置的话默认使用模型目录名。在一个服务里部署多个模型时这个参数很重要。--gpu-memory-utilizationvLLM 最多可以占用多少比例的显存。0.9 表示最多用 90%留一部分给模型加载过程中的临时开销。如果你在跑 Web 服务或者还开着浏览器可以降到 0.8。--max-model-len最大上下文长度等于输入加输出 token 总数。设得越高KV Cache 预留空间越大能同时处理的请求越少。如果只是测试8192 够用生产环境要根据业务平均输入输出长度调。--port默认 8000。如果你本机已经有服务占用了 8000改掉就行。启动日志里出现Application startup complete或类似字样说明服务已经起来了。此时不要急着并发压测先看看日志里的显存占用、加载时间再发一个测试请求。3.3 OpenAI 兼容 API 怎么调用服务启动后请求地址是http://localhost:8000/v1它兼容 OpenAI 的 chat completions 接口所以用 OpenAI SDK 可以直接接只需要改base_url。官方文档推荐的调用方式from openai import OpenAI client OpenAI( base_urlhttp://localhost:8000/v1, api_keyEMPTY, ) resp client.chat.completions.create( modelqwen3-8b, messages[ {role: user, content: 用一句话解释 PagedAttention} ], max_tokens128, temperature0.7, ) print(resp.choices[0].message.content)这里有几个细节要注意api_key可以随便填字符串比如EMPTY因为本地服务不做鉴权。如果你在公网部署后面要加网关层做访问控制。model必须和启动时的--served-model-name一致。不一致时 OpenAPI 会报 model not found。如果客户端报连接失败先确认端口是否监听、防火墙是否放行、服务日志里有没有异常。没有安装 OpenAI SDK 的话用 curl 也能验证curl http://localhost:8000/v1/chat/completions \ -H Content-Type: application/json \ -H Authorization: Bearer EMPTY \ -d { model: qwen3-8b, messages: [{role: user, content: 你好}], max_tokens: 64 }返回 JSON 里choices[0].message.content就是模型回答。3.4 用 Python 做一次简单性能验证单条请求通了之后我一般会写个小脚本测试连续请求的表现而不是直接压测框架。目的是先确认服务在多次调用下行为稳定import time from openai import OpenAI client OpenAI(base_urlhttp://localhost:8000/v1, api_keyEMPTY) total_time 0 for i in range(10): start time.time() resp client.chat.completions.create( modelqwen3-8b, messages[{role: user, content: f请输出第 {i} 次测试内容}], max_tokens64, ) cost time.time() - start total_time cost print(frequest {i}: {cost:.2f}s) print(favg: {total_time / 10:.2f}s)这次测试不要只看平均时间还要看这 10 次的波动。如果第一次和第二三次相差很大可能和输入长度、GPU 预热、显存分配策略有关不一定代表服务坏了。如果你发现单请求都要很久并且 GPU 利用率很低先回看日志确认模型是否全部加载进显存有没有 CPU offload 的告警。4. 连续批处理和 PagedAttention 不是自动就能吃满参数要自己调服务能跑通只是第一步。真正影响线上体验的是并发请求来了之后服务会不会崩、会不会排队、会不会越跑越慢。vLLM 的连续批处理和 PagedAttention 是自动机制但参数没有给够空间它们也发挥不出来。4.1 影响吞吐的三个核心参数实际部署中我先看三个参数--max-num-seqs最大并发序列数。默认值在某些场景下偏保守。调高之后GPU 可以同时处理更多请求但显存和算力消耗也更大。--max-num-batched-tokens一个 batch 最多能积累多少 token。这个值决定连续批处理的上限调太高会增大单次处理的延迟调太低会浪费 GPU 并行能力。--gpu-memory-utilization显存利用率上限。它决定了 KV Cache 可以占多少显存KV Cache 越大能容纳的并发序列越多。这三个参数是互相牵制的。显存利用率固定后max-num-seqs 调得越大KV Cache 被分得越碎单个请求能用的缓存空间越小遇到长上下文时可能出现请求被拒绝或触发长度限制。4.2 max-model-len 和显存利用率怎么配很多新手上来就把--max-model-len调成模型支持的极限长度比如 32k、128k然后发现显存不够用启动直接 OOM。这里要先理解一个关系max-model-len 不只是限制单次对话长度它还会影响 KV Cache 的预留空间。vLLM 会按照最大长度给每个序列预规划缓存max-model-len 设得越高每条请求占用的潜在显存越多能并发跑的序列数就越少。我的建议是先看业务真实需求。普通知识问答、代码生成输入输出加起来 8k 通常够用。只有明确需要处理长文档、长代码库时才调高到 32k 或更长。如果显存只有 24GB又必须跑长上下文优先用量化版本或者降低并发数。有一种常见组合错误max-model-len 拉到 32k又把 gpu-memory-utilization 拉到 0.95还希望并发很高。这在 24GB 显卡上基本不可能。eChO—— 想做高并发就要在上下文长度、显存利用率、批大小之间做取舍。4.3 并发和排队为什么有时候“更快”反而更卡连续批处理提升的是吞吐不代表延迟一定降低。如果你给客户端设置了并发请求但服务端实际只能同时处理少量序列那么多余的请求只能排队。排队时间长用户感知就是“变卡了”。这时候不要急着骂框架先查几个数据GPU 利用率是否已经接近 100%。请求队列里积压了多少条。单次请求从进入到开始生成首字符的时间首 token 延迟。如果 GPU 已经满载说明参数不是不够激进而是负载已经超过单卡能力。这时候再调并发参数只会让延迟更高。更合适的做法是增加一张卡做张量并行或者把部分流量切到另一个服务。如果 GPU 利用率只有 40%但请求还是很慢那多半是 CPU 端的数据预处理、tokenizer、调度器成了瓶颈或者单条生成时 GPU 没有足够多的并行请求来填满 batch。排查优先级先看显存有没有不足再看 GPU 利用率和请求排队数最后才改启动参数。参数是最后一步不是第一步。5. 部署 qwen3-27b 或更大模型时的注意点如果你从 8B 升级到 27B或者准备在生产环境部署更大的模型会碰到一些新问题。最典型的是显存不够、双卡启动失败、量化格式怎么选、生产环境要不要用 Docker Compose。5.1 FP8、AWQ 等量化和显存平衡qwen3 系列在 vLLM 里经常提到的量化方式有 FP8 和 AWQ。FP8 在 Hopper 架构显卡上有硬件加速AWQ 是更通用的激活感知量化。具体数据我不展开因为不同版本差异很大落地时要注意这几点量化后的模型文件大小明显变小但依然要预留 KV Cache 显存。部署 FP8 版本要用 vLLM 对应的量化参数启动常见的写法是--quantization fp8但具体取值取决于模型文件是已经量化好的还是需要动态量化。如果你的显卡不支持相关硬件加速量化不一定带来预期的速度提升可能只是降低了显存占用。网上有些说法是“量化一定更快更省”真实情况是量化能省显存速度提升不一定有时候反而会稍微降低推理质量。所以我的态度是显存不够时用量化显存够用时不折腾。如果搜索材料里体现的“qwen3.8-27b-fp8 部署感觉经常有延迟”我建议先确认当前模型文件确实加载的量化权重再用nvidia-smi查看显存占用。如果显存没占满但延迟高说明问题不在显存在 batch 大小、输入长度和并发策略。5.2 多卡跑 tensor-parallel-size 的正确姿势vLLM 多卡部署常见参数是vllm serve /models/qwen3-27b \ --tensor-parallel-size 2 \ --gpu-memory-utilization 0.9 \ --max-model-len 8192--tensor-parallel-size 2表示把模型权重平均分到两张 GPU 上两张卡协同计算。这个方案能把显存和算力都翻倍但有一个前提两张卡之间的通信要快。如果是同一台机器上的两张卡一般没问题如果跨节点跨机箱通信开销可能吃掉大部分收益。有用户反馈 L20 双卡运行模型失败这类问题通常是没有启用 NCCL 需要的权限比如容器里没有正确限制 GPU。两张卡之间的 P2P 或 NVLink 不可用导致初始化失败。驱动的通信版本不支持当前 CUDA 请求。遇到这种问题先不要直接怀疑 vLLM 不能双卡。先用nvidia-smi topo -m查看硬件拓扑再跑一次 vLLM 官方的单卡测试确认模型本身能加载最后再试双卡。5.3 什么情况适合配 Docker Compose 生产环境热词里提到了 docker-compose 生产环境部署 vLLM。如果你是个人开发机直接用vllm serve更省事。如果服务要长期运行要随机器启动自动拉起还要和 Nginx、网关、监控一起编排那 Docker Compose 更合适。一个最小样例services: vllm: image: vllm/vllm-openai:latest command: - --model - /models/Qwen3-8B - --served-model-name - qwen3-8b - --gpu-memory-utilization - 0.9 ports: - 8000:8000 volumes: - /data/models:/models environment: - HF_HOME/models deploy: resources: reservations: devices: - driver: nvidia count: 1 capabilities: [gpu] restart: unless-stopped使用 Docker Compose 时注意三件事volumes 挂载目录要有模型文件的读取权限路径不一致是启动失败最常见原因。GPU 资源限制依赖 NVIDIA Container Toolkit要先安装配置好否则容器内看不到 GPU。生产环境不要用latest镜像固定到一个已验证过的 vLLM 版本避免镜像更新带来行为差异。6. 部署后最容易踩的坑首字慢、卡顿、无输出模型能启动API 能返回不意味着线上没问题。实际使用中用户反馈最多的几个现象是首字慢、响应卡顿、输出为空或者输出格式不对。这些问题的排查链路我自己会按“输入、资源、参数、框架限制”的顺序走。6.1 首字延迟高先看哪里首字延迟高就是用户发完 prompt 后等了很久才开始看到第一个 token。这个问题在长上下文场景特别明显因为模型要先并行处理全部输入 token这个阶段叫 prefill。prefill 耗时和输入长度、GPU 算力强相关。先看这几项输入 prompt 是不是特别长。超过 2k、4k 之后prefill 时间明显上涨。如果业务只是简单问答不需要把历史全量塞给模型。并发请求是否很多。连续批处理下新请求要等 GPU 完成当前 batch 的一部分才能插入。GPU 利用率。如果利用率接近 99%说明计算已经满载首字延迟高是算力瓶颈。有没有 CPU offload。某些情况下模型层被放到 CPU每个 token 计算前要把数据搬回 GPU延迟会成倍增加。如果只是单用户测试也首字很慢优先怀疑输入长度、模型量化和离线 offload。6.2 响应卡顿先查资源占用和日志响应卡顿或者说整个服务变慢通常不是算法问题而是资源或配置问题。我排查时先执行nvidia-smi free -h看显存剩余、内存占用、GPU 利用率。如果显存已经接近上限说明并发请求把 KV Cache 吃满了部分请求可能在等待分配缓存表现为响应慢或偶发超时。然后看日志。vLLM 默认会打调度器、显存分配和请求处理日志。日志里如果频繁出现 OOM、retry、序列被终止的提示基本可以确定是并发参数或者上下文长度设置不合理。再看 Python 进程的 CPU 占用。如果模型在 GPU 上跑CPU 占用不应该长期接近 100%否则可能是 tokenizer、API 序列化或日志打印成了瓶颈。这一轮排查不需要改代码。先定位瓶颈在 CPU、GPU 还是显存再决定调参方向。6.3 检查输出质量和工具调用输出为空常见原因有两个max_tokens 设成 0或者模型在当前上下文里生成了空内容。先看客户端请求的max_tokens参数再看服务日志里的 finish_reason。如果 finish_reason 是 length说明输出被截断如果是 stop说明模型正常结束。qwen3 系列支持工具调用接入 CodeBuddy 或 LangChain 时经常会遇到 tool-call-parser 配置问题。在 vLLM 的 OpenAI 兼容服务里如果要启用自动工具调用一般需要加上--enable-auto-tool-choice --tool-call-parser qwen但具体 parser 取值要以你使用的 vLLM 版本和 qwen3 官方适配为准。不同版本支持的 parser 名称可能不一样。遇到 tool_call_parser 填不对的问题先去 vLLM 文档确认当前版本支持哪些 parser再决定填 qwen、hermes 还是其他名称。不要直接照抄旧版本的参数。OpenAI 兼容接口只保证基础 chat 接口稳定工具调用这块各家模型适配差异很大。7. 从“能跑”到“能用”接口化、监控和调度的一些思路本地 Demo 跑通很简单难的是一套稳定的服务能长期跑。最后这部分说几个从“能跑”到“能用”的关键点。7.1 自写调度器和默认调度的边界搜索热词里有人问“vllm 自己写调度器”。我的理解是vLLM 默认的连续批处理调度器已经负责请求的插入、抢占、缓存管理普通用户不需要也不应该直接改调度器。如果你确实需要特殊调度策略比如优先级队列、按用户限制并发数、限制某个模型的最大并发这些逻辑应该写在 vLLM 外面的 API 网关或 Python 服务层。vLLM 暴露的 OpenAI 兼容端口只负责接收请求你可以在它前面加一层 FastAPI 服务外部请求先到达你的 API 服务。API 服务做鉴权、限流、优先级排序。再把合规请求转发到 vLLM 的 8000 端口。这样做的原因很简单vLLM 的调度器面向的是“尽量高效利用 GPU”而不是“满足业务账户体系和优先级规则”。两者目标不同别混在一起改。7.2 接入 LangChain 等框架时的参数对齐用 vLLM 的 OpenAI 兼容接口接 LangChain很容易犯一个错误在 LangChain 里也设置了一堆 vLLM 专属参数结果这些参数没有被转发到 vLLM或者被 LangChain 默认值覆盖。我的经验是语言链侧只保留模型名、openai_api_base、openai_api_key其他推理参数通过model_kwargs传并且确认这些参数 vLLM 服务端真的认识。比如temperature、max_tokens这种标准参数没问题但top_p、repetition_penalty这类参数要看 vLLM 版本是否支持。如果出现问题先用 curl 直接调 vLLM 接口排掉框架侧缓存和参数干扰。curl 能正常返回再去排查 LangChain 的配置问题。7.3 监控指标和线上排障vLLM 默认暴露/metrics接口输出 Prometheus 格式的监控指标。即使你不想搭完整监控也可以用它快速判断服务状态curl http://localhost:8000/metrics | grep vllm常见的指标大概包括这几类请求数量、失败数量。生成 token 总数、平均生成速度。显存使用情况包括 KV Cache 使用量。当前运行的序列数量。线上排障时我一般先看三组数据显存使用率。接近上限时优先降并发或减小 max-model-len。请求失败数和错误类型。如果 OOM 相关错误增加说明 KV Cache 分配不足。平均生成速度和吞吐。如果下降而显存没满再查 GPU 利用率、输入长度和网络延迟。我自己会把日志按时间切分排障时能很快找到某个请求在哪个阶段耗时最长。这个习惯在调试 AI 服务时比调参更重要因为很多问题不是“参数不对”而是“某个阶段的资源被耗尽”。最后留几句实在话如果你只想要一个快速结论单卡 24GB先跑 qwen3-8b用vllm serve默认参数加--gpu-memory-utilization 0.9客户端接 OpenAI SDK就能得到一条可用的本地大模型服务。然后再按业务需求调并发、上下文长度和量化。如果只是学习默认配置完全够用。如果要生产化值得投入时间的不是反复改参数而是把日志、监控、鉴权、失败重试这些工程基本功补齐。踩过几次坑之后会发现很多部署问题不是 vLLM 不行而是环境、输入格式和资源评估没有做干净。