搞大模型推理的人,最近很难绕开一个词:vLLM。尤其是当你准备把Qwen3这样的开源模型真正跑起来对外提供服务时,社区里几乎所有教程、生产方案、排障帖子最后都会指向同一个关键词——vLLM。这篇是vLLM系列的第一篇,我先不急着甩一堆命令,而是把vLLM到底是什么、它凭什么能扛住高并发、以及在部署时会踩到的那些坑,一次讲清楚。
如果你是第一次接触vLLM,或者已经看过几篇零散的部署文章但始终没建立起整体认知,这篇文章就是给你准备的。我会从原理讲到实操,从Qwen3部署讲到多卡并行,再穿插一些社区里高频出现的报错和优化问题,尽量让你看完之后能直接动手,而不是收藏了一堆命令却不知道自己在做什么。
1. vLLM到底是什么:从一次Qwen3部署说起
1.1 是“框架”还是“服务”:先把概念边界说清楚
很多人第一次搜“vLLM部署Qwen3”的时候,会以为vLLM是一个模型,或者是一个类似Ollama那样的傻瓜式聊天软件。其实vLLM是UCSD等机构开源的一个大模型推理引擎,你可以把它理解成一套专门负责“让大模型跑得更快、更省显存、能对外提供API”的服务层。
模型本身只是权重文件,好比一大堆菜谱和食材;vLLM则是后厨里的整套加工流水线。它负责把模型加载进显存、处理并发请求、管理KV Cache、做完解码优化,最后把结果以OpenAI兼容的接口吐给客户端。所以你会看到大家说得最多的一句话是“用vLLM部署大模型”,而不是“部署vLLM模型”。
这个区分特别重要。因为在部署之前,你首先要明确:我要跑的是Qwen3-8B、Qwen3-27B还是35B-A3B这种MoE结构;而vLLM是跟硬件、CUDA、显存打交道的执行层。两者缺一不可,但职责完全不同。
1.2 为什么大模型服务都绕不开vLLM
我最早用HuggingFace Transformers跑Qwen的小模型时,一个并发请求就能把GPU占得死死的,再来一个请求就得排队。本质原因是Transformers默认把输入拼成一个batch,全部prefill之后再逐个decode,每个新请求到来时,已经算好的KV Cache可能还要重新算,显存浪费非常大。
vLLM解决的就是这个痛点。它通过PagedAttention管理KV Cache,通过Continuous Batching让GPU一直在算,而不是干等慢请求;再加上量化、前缀缓存、多卡并行这些机制,吞吐量能比Naive推理高出几倍到几十倍。于是社区里形成了一个共识:想要对外提供服务,vLLM基本是首选,SGLang在某些场景下有优势,但vLLM的生态最完整、踩坑资料最多,也最适合作为第一套生产方案。
很多热词其实都是在问同一个问题:怎么把这个引擎真正用起来。比如“vllm如何优化大模型的缓存命中率”“vllm首字慢”“vllm本地部署3.8 27B”,这些不是独立孤立的问题,而是vLLM的核心机制在生产环境中暴露出来的真实挑战。理解了机制,很多坑你就能自己绕开。
2. 核心技术点拆解:PagedAttention、动态调度与缓存命中率
2.1 PagedAttention:把KV Cache当成内存页来管
Transformer推理时,每个请求的注意力计算结果会保存为KV Cache。请求变多、序列变长,KV Cache会像滚雪球一样吃掉大量显存。传统做法是预先为每个请求分配一整块连续显存,长度按最大可能值预留,结果大多数时候这块显存利用率不到一半,浪费得很。
vLLM的核心创新是PagedAttention。它把KV Cache切分成固定大小的块,按需分配,而不是一次性给足。这个思路跟操作系统里的虚拟内存页非常像:你写文档时,系统不会因为你要写10万字就把10万字的物理内存全部锁给你,而是你写哪页就加载哪页。
这样做带来的直接收益有两个:一是显存碎片化明显减少,能同时容纳的并发请求数量变多;二是长上下文场景下,不再因为某个请求突然变长而触发OOM。你去看vLLM的显存占用曲线时会发现,它比原生推理平滑很多,原因就在这里。
2.2 Continuous Batching:让GPU一直忙起来
早期推理服务经常出现这种情况:一个请求生成了很长一段内容,后面十几个短请求全在排队。GPU的计算单元大部分时间是空的,因为所有请求被绑在一个固定的batch里,必须等整个batch结束后才能重新组batch。
vLLM的Continuous Batching(连续批处理)打破了这种绑定。它在每个解码步都会检查当前有哪些请求已经结束、哪些新请求可以插队进来,然后动态重组batch。只要显存还放得下,短请求就能跟长请求一起算,GPU利用率自然拉满。
这也是为什么社区里有人会聊“vllm自己写调度器”。vLLM内部有一个Scheduler模块,专门负责任务级和序列级的调度决策:什么时候允许一个新请求进入、prefill和decode如何分时复用GPU、KV Cache块满了之后应该淘汰谁。你如果只是普通使用,不需要自己写调度器;但如果想优化线上效果,分析Scheduler日志是很有效的切入点。
2.3 缓存命中率优化:前缀缓存与RadixAttention
热词里出现“vllm如何优化大模型的缓存命中率”,这是个非常实战的问题。vLLM对KV Cache有缓存机制,如果两个请求的prompt前缀完全一致,后一个请求可以直接复用前面算好的KV Cache,跳过重复的prefill计算,这个能力叫Prefix Caching。
但默认情况下前缀缓存不一定命中得漂亮,因为prompt里的system prompt、工具定义、对话历史如果顺序稍有变化,前缀就断了。我常见的做法是:把固定的system prompt和工具说明放在prompt最前面,并且保证每次拼接时完全一致;多轮对话时,把公共的历史对话单独缓存,避免每次请求都重新拼长上下文。
vLLM较新版本还引入了更复杂的RadixAttention,它把KV Cache组织成前缀树,支持更细粒度的复用。这意味着你不需要让整段前缀完全一致,部分公共路径也能命中。我对大多数项目的建议很简单:先开启--enable-prefix-caching,然后从prompt结构上保证前缀稳定,缓存命中率通常能到70%以上。命中率上去了,“首字慢”的问题会明显缓解。
3. 部署实操:从Qwen3到多卡、Docker与Windows
3.1 最小可用部署:一条命令跑起Qwen3 8B
先给一个最小可用的方案。假设你有一张大概24GB显存的显卡,想跑Qwen3-8B,步骤非常简单:
conda create -n vllm python=3.10 -y conda activate vllm pip install vllm vllm serve Qwen/Qwen3-8B --gpu-memory-utilization 0.9 --max-model-len 8192等模型下载完,vLLM会在8000端口启动一个OpenAI兼容服务。你可以直接请求http://localhost:8000/v1/chat/completions,用浏览器的/docs页面就能看到接口列表。
这里有几个参数值得解释一下。--gpu-memory-utilization 0.9意思是允许vLLM使用90%的显存,剩下的留给CUDA context和其他进程,新手不建议设成1.0;--max-model-len 8192是限制最大上下文长度,如果设得太大,KV Cache会提前把显存占满,导致并发数上不去。你要是看到社区里有人跑Qwen3-8B时总报OOM,多半就是这里没控制住。
3.2 多卡部署:--dp、张量并行与专家并行怎么选
模型一大,单卡根本塞不下,比如Qwen3-27B或者35B-A3B这种MoE模型。这时你需要多卡部署。热词里提到“启动参数中添加--dp参数”,这确实是很多人容易忽略的点。
vLLM里常见的并行方式有几种:--tensor-parallel-size(TP)把模型层切到多张卡上,每张卡只放模型的一部分;--pipeline-parallel-size(PP)按层切分;--data-parallel-size(DP)则是把请求复制到多组模型上,每组独立处理一部分请求。一般来说,TP适合单机多卡,能降低显存压力但通信开销大;DP能提升吞吐,适合并发高的场景。
对MoE模型来说,还有一个更重要的概念是专家并行(EP)。因为MoE模型里每个token只会激活部分专家,如果把专家均匀分配到各卡上,计算和通信就能更均衡。vLLM在较新版本里对Qwen3-MoE这类模型支持得比较成熟,通常你只需要设--tensor-parallel-size 4这类参数,框架会自动处理专家分布。我不建议一上来就同时叠加TP、PP、DP三个参数,先跑一个TP,跑通了再逐步加。
3.3 生产环境部署:Docker Compose还是裸进程
“当前的vllm必须使用docker加载模型吗”这个问题我几乎每周都会看到。答案是不必须。vLLM作为一个Python包,完全可以在你的conda环境里直接运行。但生产环境我更多推荐Docker,因为vLLM对CUDA、PyTorch、FlashAttention的版本组合非常敏感,Docker镜像帮你锁好了运行时环境。
一个典型的生产部署命令长这样:
docker run --rm --gpus all -p 8000:8000 \ -v /data/models:/models \ -e HF_HOME=/models \ vllm/vllm-openai:latest \ --model Qwen/Qwen3-8B \ --served-model-name qwen3-8b \ --host 0.0.0.0 \ --port 8000--served-model-name特别有用,它让你在API里用自己定义的模型名,而不是克隆下来的HuggingFace仓库名。如果你用docker-compose管理服务,只需要把命令参数写进command字段,再做健康检查挂载即可。
有人会问:直接跑进程不行吗?行,但如果你的服务器要升级CUDA、换显卡驱动,或者换Python版本,裸进程大概率会翻车。Docker镜像最少能保证“在我这里能跑,在你那里也能跑”。
3.4 低显存与Windows:2080 Ti和ModelScope实战
再聊几个偏门但确实存在的场景。热词里有“vllm 2080 ti definitive edition”,说的其实是2080 Ti显存改装版,22GB甚至24GB的版本。这块卡虽然老,但支持FP16推理,跑Qwen3-8B或者经过AWQ/GPTQ量化后的27B模型没问题。如果你用的是改装版2080 Ti,建议优先考虑量化模型,并配合--quantization awq参数启动,显存占用能降很多。
Windows下的部署要稍微费点劲。vLLM官方对Windows原生的支持不算好,我踩过几次坑之后,更推荐两条路:一是直接使用WSL2,在Ubuntu环境里按Linux方式装;二是如果一定要在Windows里试,可以用Docker Desktop并开启GPU支持。热词里的“windows vllm modelscope”说明很多人希望从ModelScope下载模型,这在国内很方便。
pip install modelscope modelscope download --model Qwen/Qwen3-8B --local_dir /models/Qwen3-8B vllm serve /models/Qwen3-8BModelScope的好处是下载速度快,不需要额外配置网络。把模型先下到本地,再用本地路径传给vLLM,既省时间也稳定,比运行时临时从HuggingFace拉权重省心得多。
4. 服务化接入与生态对接:OpenAI兼容是硬通货
4.1 OpenAI兼容API:LangChain、Ollama、CodeBuddy如何接入
vLLM启动后提供的API协议与OpenAI基本一致,这是一个特别聪明的设计。你不需要为每个模型写一套新的客户端,直接改base_url就能接入。比如在LangChain里:
from langchain_openai import ChatOpenAI llm = ChatOpenAI( model="qwen3-8b", base_url="http://localhost:8000/v1", api_key="EMPTY" )热词里同时出现了“vllm ollama openai langchain”,我猜不少人搞不清楚这几者关系。Ollama更适合本地轻量使用,它也可以把服务暴露成OpenAI兼容接口;但如果你要追求高吞吐、动态batching、多卡并行,vLLM是更硬核的选择。LangChain不关心背后是哪套引擎,只要接口长成OpenAI风格,它就能正常调用。
CodeBuddy这类IDE代码助手接入本地模型也是类似逻辑。它一般会要求你填一个“OpenAI兼容服务地址”和“api key”,你填http://localhost:8000/v1就行。真正容易卡住的是工具调用相关配置,这就引出下面这个问题。
4.2 tool-call-parser到底填什么:Qwen3工具调用配置
热词里有一个很具体的问题:“codebuddy 接入本地 vllm 部署 qwen3 tool-call-parser填什么”。这其实是vLLM新版本新增工具调用支持后,不少人被拦住的点。
vLLM在serve命令里提供了--tool-call-parser参数,用来告诉框架模型输出tool call的格式怎么解析。Qwen3使用的是类似Qwen原生的function calling格式,通常填--tool-call-parser hermes或者厂商为特定模型内置的parser。具体要看vLLM版本里支持了哪些parser,可以用vllm serve --help查看。
实际操作中,我建议先在vLLM的GitHub文档里查一下当前版本是否已经内置Qwen的tool call支持。如果支持,直接填对应的parser名字;如果不支持,可以先把工具调用功能关掉,让CodeBuddy走普通对话模式。否则可能出现“理想中模型会输出工具调用JSON,实际上输出了一堆废话”的尴尬情况。
4.3 Jetson Thor等边缘场景:vLLM不是只能跑在服务器上
关于“jetson thor vllm”,一开始我也有点意外。Jetson Thor是英伟达面向机器人、边缘计算推出的硬件平台,vLLM在边缘设备上确实有人在做适配,但和服务器显卡的体验有很大差别。
在Jetson上跑vLLM,核心限制是显存/内存带宽和驱动库。你不能直接拿服务器版CUDA wheel来装,通常需要用JetPack自带的PyTorch容器,再在容器里编译vLLM。性能上,Jetson更适合跑轻量模型,比如Qwen3-1B、4B这档,跑8B以上会很吃力。
如果你确实想在Jetson Thor上尝试,我的建议是先不要追求最高吞吐,把目标定在“启动稳定、延迟可接受”上。关闭连续batching之外的花哨功能,减少--max-model-len,优先保证服务能长时间运行。边缘设备上,稳定压倒一切。
5. 常见问题与性能排查实录:首字慢、chunk_size bug与调优思路
5.1 “vLLM首字慢”到底卡在哪一环
“vllm 首字慢”这个热词出现频率极高。首字延迟(Time to First Token)指从发出请求到收到第一个token的时间。很多时候它慢,不是因为vLLM不行,而是配置问题。
我排查时一般按这个顺序来:先看--max-model-len是不是设得太大,过长的prefill会让第一批token迟迟出不来;再看--enable-prefix-caching有没有开,如果没开,公共前缀每次都要重新算;接着看prompt里是不是有一段很长的系统提示词,每轮请求都带着它,自然慢。还有一个经常被忽略的:模型从HuggingFace或ModelScope下载到本地后,首次加载权重也需要时间,这时候你以为服务在卡,其实是在读盘。
解决思路很直接:把公共prefix固定住,开启前缀缓存,缩短max-model-len,以及做好模型预加载。上面这套做下来,很多人的首字延迟能从十几秒降到一两秒。
5.2 vLLM 0.23.0的chunk_size bug与版本管理教训
热词里提到“vllm 0.23.0 chunk_size bug”,估计是真的有朋友被坑到了。vLLM更新速度非常快,某些版本会引入新功能或重构调度逻辑,同时带来一些边缘case下的bug。chunk_size通常是控制prefill阶段按多大chunk切分输入长度的参数,它在显存预分配、KV Cache管理上牵一发动全身。如果版本存在bug,可能出现显存分配异常、长序列下decode变慢等奇怪现象。
我的建议是:不要永远追最新版。vLLM的版本策略更像“激进迭代”,生产环境最好锁定一个经过验证的版本,比如你能确认跑通Qwen3和OpenAI接口的版本,再把vllm==某个版本写进requirements或Docker镜像里。
遇到怀疑是版本bug的问题,先做最小化验证:换一个官方示例模型,用同一套命令启动,看是否还能复现。如果官方模型没问题,大概率是你模型配置文件或参数的问题;如果官方模型同样报错,就去GitHub Issues里搜版本号加报错关键字,通常能找到临时规避方案。
5.3 “vllm expecting value”与其它高频报错速查
“vllm expecting value”是我见过非常典型的报错。它本质上是某个字段没拿到预期值,常见触发点有三个:一是模型配置文件里的quantization_config字段缺失或格式不对,vLLM解析时读不到量化参数;二是API请求格式不对,比如messages里没有content字段;三是权重文件不完整,下载中断后部分分片缺失。
我把这类高频问题整理成一张速查表,方便你排查:
| 报错现象 | 最常见原因 | 解决建议 |
|---|---|---|
| expecting value | 模型config字段缺失/请求格式错误 | 检查配置文件字段,校验API请求结构 |
| CUDA OOM | max-model-len过大,KV Cache预分配过多 | 调低max-model-len,降低gpu-memory-utilization |
| 找不到vllm_flash_attn | 安装版本与CUDA/PyTorch不匹配 | 使用官方Docker镜像或重新安装匹配版本 |
| 服务启动后访问超时 | 模型权重首次加载中,或下载不完整 | 提前下载权重到本地,检查模型完整性 |
| Tool call输出异常 | tool-call-parser配置不当 | 按版本文档选用对应parser,必要时关闭工具调用 |
这张表适用面很广,我每次给团队做vLLM排障培训都会拿它当起点。真正复杂的线上问题,90%都能归到上面某一类。
5.4 吞吐量与延迟的调优思路:从2080 Ti到A100
调优前先分清目标:你是想要更高吞吐量,还是更低延迟?这两者在参数上是打架的。追求吞吐量,可以让vLLM尽量多地同时处理请求,适当增大--max-num-seqs;追求低延迟,则要控制并发,留着更多显存给单个请求快速decode。
对显存紧张的2080 Ti这类卡,我建议优先开量化模型,用AWQ或者GPTQ,把--quantization参数配好,再把--max-num-seqs调到8-16之间,不要贪多。对A100或H100这类大显存卡,可以放宽到32甚至更高,前提是KV Cache放得下。
还有一个参数值得单独提:--gpu-memory-utilization。默认值一般在0.9左右,但如果你的显卡同时要跑其他服务,就得调低。调优时每次只改一个参数,改完压测一轮,记录下来,再改下一个。别一次性把所有参数都堆上去,否则出了问题你根本不知道是哪个改动引起的。
结语:先跑通,再谈优化
我在实际部署vLLM的过程中,最大的体会是:vLLM确实快,但它不是魔法。你把Qwen3用vllm serve拉起来很容易,真正难的是理解显存、KV Cache、调度器这些底层机制如何影响线上表现。第二点想说的是,社区里的热词背后几乎都是真问题。首字慢、缓存命中率、chunk_size bug,这些词你看着零散,实际上每一项都对应着一个可定位、可优化的环节。
所以我的建议是:第一次接触时,按文章里最小部署步骤跑通一个8B模型,然后依次打开前缀缓存、调整并发参数、做简单压测,一步步把现象和参数对应起来。后续如果遇到报错,先按速查表排查,再决定是否升级版本。vLLM这个工具,你越是理解它的工作方式,越能少走弯路。