1. 为什么是这个组合:vLLM、DeepSeek与显存焦虑
我知道很多人都是从Ollama或者LM Studio开始玩本地大模型的,那玩意儿确实方便,点两下就能跑起来一个Chat接口。但你一旦想把它放到生产环境、想让并发请求别卡死、想真正吃满一张卡而不是看着显存疯狂碎片化,就会碰壁。这时候vLLM几乎是必选项。我的第一套vLLM部署纯粹是被显存逼出来的——当时用transformers直接跑一个70B量化模型,单请求都要四五秒,两个请求一起来直接OOM,气得我差点摔键盘。
后来换成vLLM,同样的卡,同样的模型,并发上去了不说,显存占用反而降下来了。这背后的核心是它把KV Cache用PagedAttention管理成了一个个“显存页”,像操作系统的虚拟内存一样按需分配,而不是像transformers那样一次性预留整块显存。这个概念你不需要背,你只需要知道:用vLLM跑生成模型,同样的硬件能塞下更长的上下文、扛住更高的并发。
所以这篇文章我计划从零开始,带着你走一遍完整的路:环境准备、安装、启动服务、显存调优,最后把我踩过的几个深坑也摊开讲,省得你再摔一遍。不管你是想本地部署DeepSeek,还是想用Docker拉起一个OpenAI兼容接口,看完这一篇应该都能跑通。
要注意的是,这里讲的不只是“敲几条命令”,更多是背后的判断逻辑——为什么选这个镜像版本、为什么参数这样配、为什么显存这么调。毕竟网上教程版本五花八门,抄错了项目就歇菜。
2. 环境准备:GPU驱动、CUDA、Python版本三个坑位
2.1 GPU驱动和CUDA:先确认你的卡能干什么
vLLM目前的主力还是NVIDIA的CUDA环境,AMD的ROCm和华为的昇腾也逐步支持了,但你要是新手,建议老老实实用CUDA。你的显卡建议至少8GB显存。8GB也能玩,但只能跑很小尺寸的模型,比如7B量化版,上下文一长还是悬。我的主力是RTX 4090 24GB,基本能跑DeepSeek-R1的AWQ量化版,再大就得靠多卡了。
安装之前先用nvidia-smi看三样东西:驱动版本、CUDA版本、显存大小。你不需要精确记住每个CUDA对应哪个驱动,只需要保证驱动版本不低于你选择的CUDA运行时所需要的最低驱动。
提示:vLLM官方发布的wheel包通常对应某一版CUDA,比如cu12.1或cu12.4。如果你机器上的驱动版本太老,vLLM会提示缺少CUDA动态库,直接加不了载。我见过一个朋友拿2019年的老驱动跑v0.6.x,折腾一晚上没跑起来,实际就是驱动不认PTX。
2.2 Python版本:别用客户端尝鲜版
vLLM对Python版本有明确要求,一般来说3.10到3.12比较稳。我个人习惯用Anaconda管理环境,因为分开环境真的能救命。你要是同时搞Ollama、transformers、Torch项目,互相依赖冲突是家常便饭。我是这么建的:
conda create -n vllm_env python=3.10 -y conda activate vllm_env为什么卡在3.10?因为我吃过多版本兼容的亏。3.12虽然新,但某些编译型依赖(比如flash-attn)可能来不及出对应wheel,而3.10基本是各个大模型框架的“黄金版本”。你要是想用3.11或3.12也可以,但出问题先别怪vLLM,先查依赖兼容性。
2.3 虚拟环境里的Torch选择
装vLLM之前,你机器上可能已经有PyTorch了。但注意,vLLM对Torch的版本约束很紧,它要的torch版本是经过它测试并编译的,你手头升级过的torch没准会导致它加载时直接报一堆符号错误。稳妥做法是:在干净的conda环境里安装vLLM,让pip自动解析依赖,它会给你装一个特定版本的torch,别用你自己的环境强行融合。
这里要给新手一个心理准备:vLLM安装过程中会拉下来一堆编译好的二进制包,看起来占空间,但这是正常的。它不像transformers那样纯Python调库,vLLM有大量CUDA扩展,安装体积大,加载也慢,但换来的是推理吞吐。
3. 安装vLLM:pip、源码、Docker三条路
3.1 pip直接装,最省心的选择
如果你的环境干净,最推荐的安装方式就是pip。简单到令人发指:
pip install vllm但这里有一个细节:默认的pypi包会跟着vLLM团队的发布节奏走,你要指定版本的话就加后缀,比如:
pip install vllm==0.6.1.post1版本号里.post1这种就是修复了某个bug后的补丁版。我建议你先查一下官方GitHub的Release说明,别直接装最新未稳定版本,尤其在生产环境。vLLM迭代速度真的太快,我见过0.5.x到0.6.x就把命令行参数改得全家不认识的。
如果你想用最新的CUDA优化特性,也可以指定源:
pip install vllm --extra-index-url https://download.pytorch.org/whl/cu124这样能把配套的torch也定位到CUDA 12.4版本。
3.2 源码编译:只有特殊需求才走这条路
源码编译能让你改vLLM内核代码或者适配特殊硬件,但对大多数人来说性价比极低。我编译过一次,光等flash-attention的编译就午饭外卖都到了,而且编译失败率不低。除非你是想给某个架构打补丁,或者要用最新的未发版功能,不然直接pip装吧,真的。
3.3 Docker安装:最推荐的老手方案
这次我要特别强调Docker,因为热搜词里出现了“docker vllm/vllm-openai:v0.27.1加载qwen3-embedding-0.6b”。用Docker的好处是把CUDA环境、Python版本、依赖全部封装在镜像里,宿主机只需要装好NVIDIA Container Toolkit。我之前在网吧式电脑上装驱动搞得满屏黑块,后来干脆服务器上全用Docker,再也没担心过系统环境被搞坏。
常用的官方镜像长这样:
docker pull vllm/vllm-openai:v0.27.1注意这个版本号不是vLLM的版本,而是vLLM官方镜像的发布版本。你需要去查看镜像tag对应关系,比如v0.27.1这个镜像内置的可能是v0.6.x的vLLM。如果你要加载qwen3-embedding-0.6b这类嵌入模型,也得确保镜像版本足够新。
启动一个vLLM服务容器,最简单是这样:
docker run --gpus all \ -v ~/models:/models \ -p 8000:8000 \ vllm/vllm-openai:v0.27.1 \ --model /models/qwen3-embedding-0.6b \ --task embed注意我加了--task embed,这是加载嵌入模型的必需参数。如果还是当生成模型启动,它会尝试访问不存在的大模型配置文件,然后bang。这个坑我在后面专门说。
4. 启动推理服务:命令行参数里的门道
4.1 最简单的一行:跑通一个生成模型
先把最基础的启动学会。假设你拉了一个DeepSeek模型,放在本地路径/models/deepseek-r1-7b-awq,启动服务:
vllm serve /models/deepseek-r1-7b-awq \ --served-model-name deepseek \ --max-model-len 8192 \ --gpu-memory-utilization 0.9这条命令直接打开一个OpenAI兼容的HTTP服务,默认监听http://0.0.0.0:8000。你把--served-model-name改成一个响亮的名字,后面请求时model字段就填这个名字。
--max-model-len是上下文长度上限,8192就是最多允许8192个token的输入+输出。这个参数直接决定KV Cache占多少显存,设太大模型没跑起来就爆显存了。我一般设置一个期望值,再根据实际显存余量反推,后面细说。
--gpu-memory-utilization表示vLLM最多能用多少比例的显存,0.9就是90%。剩下10%留给模型权重和CUDA上下文。别设成1.0,你不想系统画UI都卡成PPT吧。
4.2 并发与并行:别以为默认就够用
vLLM默认是连续批处理(continuous batching),意思是多个请求进来,它会动态把它们拼成一个批次,每个token生成完就退出,新的请求再补进来,这样吞吐最大化。但并发数到底能开多大,由显存和max-model-len共同决定。
你可以显式加--max-num-seqs控制最大并发序列数。比如:
--max-num-seqs 3232并发的意思是,同时最多积压32个对话请求。设太高会疯狂挤占KV Cache,进而导致OOM或请求变慢。设太低浪费吞吐。我一般做法:先把并发调到最小,跑几个请求看显存和延迟,再逐步往上顶。
如果你有两张或多张卡,可以用--tensor-parallel-size 2来张量并行,把一张卡放不下的模型切到两张卡上。这是分布式推理的基础用法,前提是你有PCIe连接多张卡。注意这个参数改动后每个请求的调度方式都会变,显存分配逻辑也不一样,后面调优时得很小心。
4.3 加载Embedding模型的特殊姿势
热搜词里“docker vllm/vllm-openai:v0.27.1加载qwen3-embedding-0.6b”这个使用场景很多人碰到。vLLM不只跑生成模型,从0.6.x开始,也能跑embedding模型。但你得显式给它说明任务类型,不然vLLM以为你的模型是个decoder-only大模型,一顿狂加载然后报错。
具体启动方式:
vllm serve /models/qwen3-embedding-0.6b \ --task embed \ --served-model-name qwen3-embedding \ --max-model-len 4096 \ --gpu-memory-utilization 0.8启动之后,你用OpenAI的embeddings接口调用:
curl http://localhost:8000/v1/embeddings \ -H "Content-Type: application/json" \ -d '{"model": "qwen3-embedding", "input": "你好,世界"}'让它返回向量。注意embedding模型通常对max-model-len很敏感,太短的上下文可能导致句子被截断,太长又浪费显存。实际业务里建议按文本分布的中位数设定。
5. 显存调优实战:从OOM到优雅奔跑
5.1 先搞懂显存到底花在哪
这可能是整篇文章最值得你反复看的部分。vLLM请求显存大致分三块:模型权重、KV Cache、运行时开销(CUDA上下文、激活值等)。
其中KV Cache是动态的,也是调优重点。它的计算逻辑里藏着两个关键参数:max-model-len和gpu-memory-utilization。vLLM会在启动时根据这两个值估算KV Cache能开多大,然后给每一层预留若干块。你给模型设的上下文越长,KV Cache总容量越紧张,能支持的并发请求越少。
一个常见现象是:你刚启动服务时看着显存余量挺大,一跑长上下文对话就OOM。原因是vLLM预留的KV Cache块被长序列吃光了,新的请求无法分配新块。错误信息往往是Request exceeds available block capacity。
调这个问题的思路很直白:要么缩短上下限,要么调高显存利用率,要么开prefix caching省块。
5.2 参数组合拳:max-model-len、gpu-memory-utilization、block-size
我实际调试一个DeepSeek-7B量化模型时,卡是RTX 4090(24GB),模型权重大约6GB。启动参数我这样试:
第一次:
--max-model-len 32768 --gpu-memory-utilization 0.9结果显存爆了,根本起不来。因为wights加上预留的KV Cache太贪心,CUDA直接报out of memory。
改成:
--max-model-len 16384 --gpu-memory-utilization 0.85先能起来,但并发4个请求时,有一个超过一定长度会报block capacity不足。说明KV Cache还是不够。
再调,开prefix caching:
--enable-prefix-caching这个功能会缓存相同前缀的KV Cache块,比如多轮对话里历史部分重合很多,能大幅降低重复计算。实际测试下来,长对话场景的显存压力降了差不多40%。代价是额外一点管理开销,但绝对划算。
--block-size默认是16 token的块大小。你可以试着设置成8或32。块越小,碎片化越少,但管理开销越大;块越大,长序列时分配效率更高,但短序列浪费放大。我平时固定用默认16,除非遇到特定碎片问题才去动它。
5.3 量化方案:从AWQ到FP8
如果你模型权重就占了卡上大半显存,再怎么做KV Cache也是杯水车薪,终极办法是给权重瘦身。当前最主流的做法是AWQ和GPTQ量化。AWQ在精度损失和性能之间平衡得不错,很多开源模型都有AWQ权重直接下。
启动AWQ模型非常方便,只要模型是AWQ格式,vLLM自动识别量化类型,你什么都不用改:
vllm serve /models/deepseek-awq-4bit要是你用的是FP8权重,新版vLLM支持--quantization fp8显式指定。FP8比AWQ还能省一点显存,但硬件需要适配好,我的卡和推理库版本配合不太好,FP8偶尔会慢一些,得看具体型号。经验之谈:先跑AWQ,稳定第一,再玩FP8提速提容量。
5.4 Chunked Prefill:把长输入的显存尖峰削掉
长文本一次性进入模型时,Prefill阶段会把一个超长提示词变成一个巨大的激活矩阵,瞬间把显存顶到爆炸。vLLM从0.5.x开始支持chunked prefill,意思是在CUDA层面把Prefill分块处理,避免出现显存尖峰。
启动时加:
--enable-chunked-prefill然后你可以配一个--max-num-batched-tokens来控制一次处理多少token,比如4096或8192。这个值设得太低会导致模型需要多次调度,吞吐下降;太高又回到尖峰问题。我的经验是:先开着chunked prefill,设成和max-model-len/8差不多,再根据压力测试微调。
如果你的业务输入大多是短文本,那chunked prefill带来的收益不明显,但长文档问答场景里它几乎是救命稻草,你不想一个5万token的法律文件直接把服务搞挂吧。
6. 踩坑实录:部署DeepSeek到加载Embedding模型的弯路
6.1 模型保存路径乱套,服务启动即退
我第二次部署DeepSeek时,直接把Hugging Face缓存路径当作模型路径丢给vLLM,结果它一顿报错,说找不到safetensors文件。这是因为HF缓存目录下面往往还有一层哈希目录,vLLM需要的是模型文件的上一级,也就是包含config.json那个目录。
正确的做法是:huggingface-cli download deepseek-ai/DeepSeek-R1-Distill-Qwen-7B --local-dir /models/deepseek-7b,把模型完整拉到本地,然后再传给vLLM,别让它去缓存里猜。
6.2 端口占用和镜像版本对应关系
你在Docker里跑服务时,如果8000端口被其他进程占了,vLLM会给你端口冲突的报错,但错误信息不太直观。需要先用ss -lntp查是谁占着端口。镜像版本和vLLM版本的对应关系,一定要看镜像tag页面或README,别只看镜像名字带个v0.27.1就当是vLLM版本号,我吃过亏:装了个新镜像,命令却按老写法传参,结果新参数根本不认识,退出码给个0,日志里一片空。
6.3 显存明明没占满却OOM
有朋友跑来问我,nvidia-smi显示显存才用了60%,但vLLM还是OOM。这是因为vLLM的KV Cache预留在你看不到的地方,nvidia-smi显示的是当前内存占用,而vLLM的block management把那40%预留给了未来的KV Cache。所以你在外面看显存没满,里面实际已经分配完了。
判断是不是这种情况,就看服务日志里的KV Cache相关指标,比如kv cache size和free block数量。用/v1/...接口问服务状态?其实vLLM自带的/metrics接口能输出prometheus格式指标,里面能看到KV Cache利用率,这才是判断显存压力的第一手数据。
6.4 加载Embedding模型老报错,其实是任务类型忘写
热搜词里那个操作不算复杂,但翻车概率很高:用Docker镜像跑qwen3-embedding-0.6b,怎么传都报错。最常见就是忘了加--task embed,vLLM默认认为你要跑生成模型,于是尝试找generation_config和lm_head等,自然失败。加了--task embed后还要确认镜像支持,0.6.x之后的版本一般没问题,0.5.x就别想了。
还有一个小坑:embedding的并发症是--max-model-len设太大,显存本来就紧,结果每个embedding请求还占了一大块KV Cache(对,embedding也会分配序列KV Cache,只是生成阶段短)。所以加载embedding模型时,显存利用率不要调满,适当留buffer,比如设0.7,否则多个embedding并发也会OOM。
6.5 长上下文对话无故变慢,检查前缀缓存
部署完DeepSeek后,我发现多轮长对话越跑越慢。后来看了监控,发现大部分时间不是生成慢,而是每一轮都把历史全部重新算了。开了--enable-prefix-caching之后,相同会话前缀能复用缓存的KV块,实际多轮速度提升非常明显,显存占用也降了。如果你跑的是智能客服、代码助手这种高频多轮场景,一定要测这个参数。
注意:prefix caching不是免费午餐。它会额外记录缓存块的管理信息,对多样化的用户输入可能收益不大,但如果你的对话风格有大量重复历史,收益远大于开销。
7. 我的压箱底调优流程与补充建议
最后分享一个我实际跑业务时反复打磨出来的调优套餐,你可以直接抄:
第一步,先用最小参数起来服务:
vllm serve /models/你的模型 \ --max-model-len 4096 \ --gpu-memory-utilization 0.7第二步,拿一个典型长文本样本打进服务,观察显存和延迟。如果正常,逐步把--gpu-memory-utilization往上加,比如0.75、0.8、0.85。
第三步,把--max-model-len提高到目标值,比如16384,再压并发到预期水位。一旦出现KV Cache不足的报错,就不要硬顶了,去调低单请求长度或加更多缓存复用策略。
第四步,对于生成类模型,优先开--enable-chunked-prefill和--enable-prefix-caching,这两个参数组合下来,长文本场景能扛住更多并发。
补充一个很多人忽略的点:vLLM的日志很重要,你别跑起来就不管了。我习惯在启动命令里加上--log-stats --log-requests,它会周期性输出当前服务的token吞吐、KV缓存利用率、排队请求数。有了这些数据,调参就能变成“看仪表盘”,而不是“瞎猜”。
再提一句Docker部署时的资源限制。如果你在Kubernetes里跑容器,别只设置limits.memory,还要给limits.nvidia.com/gpu: 1。另外,把host IPC设置成hostIPC: true,防止shared memory不够导致vLLM加载数据时卡住。这个坑我遇到过一次,服务起来了,但加载大模型总在某个进度条卡死,后来才发现是默认共享内存只有64MB。
对于显存真的很紧张的小显卡用户,我的建议是:优先调低max-model-len,这比换量化方案还简单。你想想,如果业务里单个用户最多只发2000字,你却给模型开32768的上下文,那纯粹是自找麻烦。
此外,启动模型前最好确认模型的chat_template是否正确。DeepSeek这类模型默认模板在Hugging Face上一般没问题,但从别的渠道下载的老文件可能模板是空的,会导致服务能起、请求却报错。你可以用tokenizer_config.json里的chat_template字段核对。
写到这里,我回想起自己第一次启动vLLM时,对着满屏英文日志手足无措的样子。其实只要你把环境、版本、参数这三件事控制住,剩下的都是水磨工夫。希望这篇经验能帮你少绕几个弯,早点把模型真正用起来,而不是一直在折腾部署。