这段时间社区里聊大模型推理,十有八九绕不开 vLLM。我自己第一次接触它的时候也蒙:一堆概念堆在眼前,又是什么 PagedAttention,又是什么 Continuous Batching,装环境还能装到一半卡住,网上资料要么是源码级解析,要么就是一句"直接用就好"。这篇就是给像我当初一样、面对 vLLM 不知道从哪下手的同学写的快速入门引导,我会把环境准备、最小推理示例、关键参数、常见坑这些一环一环讲清楚,保证你照着走能跑通第一个推理请求,并且知道每一步在做什么、为什么这么做。
这套内容适合下面几类人:刚接触大模型、想把开源模型部署成可用 API 的开发者;被推理速度慢、显存不够折腾到头秃的算法工程师;以及想评估 vLLM 到底值不值得引入项目的技术负责人。我会默认你已经有一台带 N 卡的 Linux 机器,Python 基础也具备,代码上尽量保持简单,不涉及分布式训练那种复杂玩法。
1. 先搞明白:vLLM 到底解决什么问题
1.1 大模型推理慢的根源:算力只是其中一半
很多新手第一次跑大模型,发现生成一句话要等好几秒,第一反应是"显卡不够好"。其实算力只是一个维度,另一个被忽视的瓶颈是显存管理。像 GPT、LLaMA 这类模型在生成文字时,每个输出的 token 都会不断把历史计算出的中间状态存下来,这些状态有个统一的说法叫 KV Cache。它的容量会随着序列长度线性增长,而且不同请求的序列长度完全不一样。
如果用传统推理框架,显存里要预先给 KV Cache 划一大块连续区域。问题是,请求长短不一,这个预分配空间要么浪费、要么不够用。模型参数占了大部分显存,剩下给 KV Cache 的空间本来就很挤,再这样一浪费,能同时处理的并发请求数量就很少。实际表现就是:单个请求还能忍,并发一上来,轻则 OOM,重则排队排到怀疑人生。
1.2 vLLM 的三大核心设计:PagedAttention、Continuous Batching、张量并行
vLLM 之所以能火,不是靠疯狂压榨 GPU 算力,而是把操作系统的内存管理思想搬到了显存里。最核心的设计叫 PagedAttention,它把 KV Cache 切成固定大小的"页",按需分配,不再要求一块连续的存储空间。显存里的小碎片都能被利用起来,实际空闲显存利用率能提高不少,这在长上下文场景下尤其明显。
第二个设计是 Continuous Batching。常规批处理是把一批请求凑齐了再统一跑,一个请求提前结束,它占用的整排算力也得等这批全部跑完才能释放。Continuous Batching 的做法是每个解码步都动态决定哪些请求继续跑、哪些已完成退出、哪些新请求插入,空闲的算力立刻被新请求补上。这样吞吐提升很直接,尤其适合线上那种请求到达时间不规律的场景。
第三个设计是张量并行。一个 70B 模型单卡塞不下,可以把模型切分到多张卡上并行推理。vLLM 对张量并行的封装做得比较到位,命令里写个参数就能启用。但入门阶段建议先用单卡跑通,多卡并行等对流程熟悉了再玩。
1.3 什么场景适合上 vLLM,什么场景没必要
我要先泼个冷水:如果你的需求只是本地偶尔用 CPU 跑一下 demo,vLLM 并不适合你,它主要面向 GPU 场景,CPU 支持虽然有,但性能优势发挥不出来。如果你只是想把模型跑起来看看效果,一次只发一个请求,也不想调参数,那用最简单的推理脚本就行,接入 vLLM 反而多一层学习成本。
反过来,如果下面这些场景占了任意一条,vLLM 就是很值得上的方案:并发请求超过 5 个;输入输出上下文动辄几千 token;需要把模型包成稳定 HTTP 服务给业务侧调用;或者显存只够放下模型,几乎没有余地给 KV Cache。我自己的经验是,一个原本单个请求生成 256 个 token 就要 5、6 秒的 7B 模型,换到 vLLM 并把并发拉起来之后,整体吞吐肉眼可见地涨了一截,而且显存占用反而更可控。
2. 安装与运行环境准备:这一步卡住了不少人
2.1 硬件基线:显存到底怎么算
动手安装前,先确认显存够不够。模型权重占用的显存大约等于参数量乘以精度字节数。以 7B 参数、FP16 精度为例:7 乘以 2 个字节,约 14 GB,这个数字只是一个粗略下限。实际运行时会额外加载 KV Cache、激活值、CUDA 上下文等,推荐至少留出 4 到 6 GB 余量。也就是说,一块 24GB 显存跑 7B 模型比较舒服;13B 模型建议 40GB 以上;70B 模型即使量化后也要 40GB 以上才玩得动,非量化单卡基本不可能。
如果你显存不够,不要立刻放弃,可以看看后续量化的用法。入门阶段我建议找一块 24GB 的卡,用一个 7B 左右的对话模型练手,这是最稳的起点。显存更小的机器可以先从 1B 到 3B 的小模型开始。
2.2 创建干净的环境再安装
安装 vLLM 建议用虚拟环境隔离。我见过太多人直接在系统 Python 里装,结果和旧版本包冲突,最后连显卡驱动都被搞得乱七八糟。第一步,创建一个干净的 conda 环境,指定一个兼容的 Python 版本。不同的系统、CUDA 版本对 vLLM 的要求有差异,但一个基本共识是:用较新的 Python 版本问题少一些,比如 3.10 或 3.11。
创建好环境后直接执行:
conda create -n vllm-env python=3.11 -y conda activate vllm-env pip install vllm如果这一步顺利,你会看到一堆依赖被装好。CUDA 方面,vLLM 的预编译包通常已经包含所需的 CUDA runtime,不需要你自己额外装 CUDA Toolkit,但显卡驱动版本要够新。nvidia-smi里看到的驱动版本对应支持的 CUDA 版本必须满足要求,这是最容易忽略的一条。
2.3 安装失败的常见原因和处理思路
我碰到过两类安装失败,概率最高。第一类是 pip 在构建某个依赖时拉取源码编译,编译到一半 GCC 报错。这通常是网络源不稳定或者 Python 版本不匹配导致的。先用国内镜像源试试:
pip install vllm -i https://pypi.tuna.tsinghua.edu.cn/simple第二类是装完以后导入时报缺少共享库的错误,比如libcuda.so找不到。这个多半是 CUDA 驱动路径没让 Python 进程找到,把相关库路径加进环境变量通常能解决。具体路径每台机器不同,可以用find /usr -name "libcuda.so*" 2>/dev/null查找。
验证安装是否成功,就运行下面这行:
python -c "import vllm; print(vllm.__version__)"能打印出版本号,说明环境基本没问题。如果这行直接报错,建议把完整错误信息拿到检索工具里搜一下,按报错关键字逐个排查,而不是盲目重装。
3. 第一次上手:单卡跑通一个对话模型
3.1 准备模型权重:本地目录还是模型仓库 ID
vLLM 支持直接写一个模型在仓库里的 ID,也支持本地目录路径。入门阶段我建议先把模型下载到本地,再通过本地路径加载。原因是调试阶段频繁修改代码和参数,每次都从远端拉取权重既慢又容易被网络波动搞挂。下载模型需要使用模型仓库工具,命令大致是把目标模型仓库拉下来,因为模型权重动不动几个 GB,Git 操作容易失败。如果网络条件一般,建议不通过 Git 拉取,而是找到模型文件的直链后用下载工具多线程续传。
权重准备好后,本地路径下会有一堆文件,包括模型权重、配置 JSON、词表文件等。加载时把路径传给model参数就行。我这里用一个虚构的路径./my-chat-7b作为示例,你换成自己实际的模型目录。
3.2 最小的 Python 推理脚本
创建一个 Python 文件,比如quickstart.py,写入以下内容:
from vllm import LLM, SamplingParams llm = LLM( model="./my-chat-7b", dtype="bfloat16", gpu_memory_utilization=0.85, max_model_len=4096, ) sampling_params = SamplingParams( temperature=0.7, top_p=0.9, max_tokens=512, ) prompts = [ "用一句话解释什么是大语言模型。", "写一个 Python 快速排序函数。", ] outputs = llm.generate(prompts, sampling_params) for seq, output in zip(prompts, outputs): text = output.outputs[0].text print(f"用户: {seq}") print(f"模型: {text}") print("---")这段脚本做了三件事。第一,先初始化LLM对象,这一步会加载模型权重并建立推理引擎,通常需要几秒到几十秒,模型越大越慢,不要误以为卡住。第二,定义采样参数,temperature控制随机性,max_tokens控制最多输出多少 token。第三,一次性传入多个 prompt,生成结果后逐个打印。
gpu_memory_utilization我用的是 0.85,意思是给显存管理器留了 15% 的余量。如果你后续想提高并发,可以往下调;如果显存富余,可以往上调到 0.9 左右。max_model_len设成 4096,表示模型能接受的上下文总长度上限,包含你的输入和输出。如果设置过小,长输入会被截断;过大则 KV Cache 预留空间变大,可支持的并发数下降。
运行脚本:
python quickstart.py第一次运行会看到大量日志,里面包括模型加载进度、显存分配情况等。如果看到输出了两条结果,恭喜你,第一个 vLLM 推理任务跑通了。
3.3 命令行启动一个 HTTP 服务
Python 脚本适合测试和批处理,但很多人的真实需求是给业务侧提供一个可调用的 API。vLLM 内置了一个服务端命令,启动方式非常简单:
vllm serve ./my-chat-7b \ --served-model-name my-chat \ --dtype bfloat16 \ --max-model-len 4096 \ --gpu-memory-utilization 0.85启动后日志会显示服务监听在哪个端口,默认是8000。然后你可以用curl发一个请求验证:
curl http://localhost:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "my-chat", "messages": [{"role": "user", "content": "你好,请介绍一下你自己"}], "max_tokens": 256, "temperature": 0.7 }'返回的 JSON 里会包含大模型生成的回复内容,以及 token 使用量。这里要留意一个细节:curl请求里填的model名必须和--served-model-name保持一致,否则服务端会报模型不存在的错误。我第一回跑服务的时候就被这个字段坑过,一度以为是端口没起来。
3.4 并发请求第一次展示威力
最直观感受 Continuous Batching 的方法,是用多线程或开几个终端同时发请求。甚至更简单,你可以在 Python 脚本里把 prompts 列表扩到 20 条,再跑一次。你会发现,20 条请求的总耗时绝不是单条的 20 倍,而是远小于这个值。这就是批处理在起作用:vLLM 不断把处于解码阶段的请求拼在一起,同时推进,GPU 的算力被填得更满。
有个观点我想强调一下:vLLM 的单请求延迟不一定比其他框架快多少,甚至在某些场景下可能差不多。它最亮眼的指标是高并发下的吞吐量。所以评估 vLLM 时,不要只测单条请求要几秒,要测并发场景下每秒能处理多少请求、每请求的平均延迟是否有明显劣化。
4. 参数与用法进阶:入门和高手的差距就在这张表里
4.1 SamplingParams 里的核心参数
SamplingParams是控制模型生成行为的关键对象。很多人只调temperature,其实里面门道不少。我用一张表把常用参数列出来,后面再逐个补充经验。
| 参数 | 作用 | 推荐值 | 使用经验 |
|---|---|---|---|
| temperature | 控制随机性,越高越发散 | 0.6-0.9 | 代码生成建议 0.2 以下,创意写作可以高一些 |
| top_p | 概率累计采样的阈值 | 0.8-0.95 | 和 temperature 配合使用,不要同时调到极大 |
| max_tokens | 单次生成的最大 token 数 | 根据业务设定 | 设太大容易等很久,设太小回复截断且没有提示 |
| stop | 遇到标记停止生成 | 按模型定制 | 对话类模型常用 `< |
| n | 每个 prompt 返回几条候选 | 默认 1 | 多候选做重度筛选时才有必要,显著增加计算量 |
一个常见误区是把max_tokens当成"生成长度限制"就完了。事实上它同时影响服务端的内存预留和你的等待时间。设置过大会让某些请求在失败时才报错,且 OOM 概率上升。我在业务里定位到过好几次"请求一直不返回"的情况,最后都发现是max_tokens设置得离谱,模型输出超长,服务端还在慢慢生成,客户端早就超时断开。
4.2 服务端参数:并发能力该怎么调
除了采样参数,vLLM 服务启动时还有一批参数直接影响并发和稳定性。比较关键的是max-num-seqs,它限制了同时处理的序列数量上限。默认值不低,但如果你显存紧张,它可能是 OOM 的原因。建议根据显存和真实需求显式设置,而不是完全依赖默认值。
另一个是gpu-memory-utilization。这个值控制有多少显存被分配给模型权重和 KV Cache。设得太低,可用 KV Cache 少,并发吞吐上不去;设得太高,遇到输入特别长的突发请求容易直接显存溢出。我个人的经验是,对 24GB 显卡跑 7B 模型,0.85 是个不错的起点,如果并发请求高,再慢慢往下微调。
如果你想让系统自动管理请求排队,而不拒绝连接,关注一下是否有--limit-max-requests之类的参数。不同版本名字可能不一样,我建议启动服务时先用vllm serve --help查看当前版本的完整参数说明,养成这个习惯能少踩很多版本差异的坑。
4.3 显存不够时的量化方案
很多人的显卡跑 7B 模型很紧张,跑 13B 直接没戏,这时候量化是个实用出路。vLLM 支持加载已经量化好的低比特权重,比如 4bit 或 8bit 格式。量化的核心逻辑是把原本 2 字节的 FP16 权重压缩到更少的比特数,显著降低显存占用,代价是生成质量可能会有轻微下降。
量化权重的加载方法和普通模型一致,只需要把模型目录里的权重文件替换成量化版本,然后用--dtype配合实际格式。要注意的是,量化格式有版权和质量差异,不同格式对硬件的适配也不一样。入门阶段不要自己尝试训练或转换量化权重,直接下载社区里别人已经处理好的量化模型文件,先把流程跑通。等熟悉之后,再研究量化参数和精度的权衡。
另外,即便不换量化权重,也可以通过适当降低gpu-memory-utilization之外的一些特殊技巧省显存,比如使用--enforce-eager关闭算子图优化。这个选项默认不开启,它会省去构建优化图的额外显存,但代价是推理速度略慢。适合调试阶段快速验证,不适合生产环境长期开启。
4.4 前缀缓存带来的长文档处理提升
vLLM 还有一个容易被低估的功能叫前缀缓存。它会把请求中相同的前缀内容对应的 KV Cache 缓存下来,下次再遇到同样前缀时直接复用,不用重新计算。这个功能在 RAG 场景下特别明显,因为多个请求的开头往往都是那一大段系统提示词或文档内容。
启用方式很简单,启动服务时加上--enable-prefix-caching参数即可。效果有多大?我实测过一个固定 2000 token 前缀的文档问答场景,开启这个功能后,相同前缀部分的计算被跳过,吞吐提升非常可观。如果你还开着批处理,那真的是几层 buff 叠在一起的体验。
但要提醒一下,前缀缓存也有开销。它会占用额外的 CPU 内存和显存来做索引,对短请求、前缀变化大的流量反而可能增加管理成本。是否开启要看业务特征,不是无脑加的参数。
5. 推进生产之前,一定要学会处理的这些问题
5.1 显存不足的根本排查链路
即便做足了估算,生产环境依然可能爆显存。我碰到过一种现象,启动时明明显存够用,运行一会儿后就报 OOM。排查思路要从四个方向依次看。
第一,看模型权重是否加载完整。如果日志里有 "No enough memory" 这类字样,多半是权重本身就不止那点显存。当场手段是换更小的模型或启用量化。第二,看 KV Cache 是否限额。gpu_memory_utilization设得高不代表没有上限,可以临时把max-num-seqs调小,观察 OOM 是否消失。第三,看并发尖刺。有的请求输入长达上万 token,一个请求吃掉的 KV Cache 可能抵得上普通请求几十个,只看平均并发会漏掉这种极端请求。第四,看其他进程是否占用了显存。用nvidia-smi查看显存实际占用,经常发现模型已经加载完,另一块显卡上还残留着别的进程。
解决显存不足,优先级我个人排成这样:先限制服务并发和输入长度,再上量化或降低精度,最无奈才换小模型。限制服务参数改动成本最小,对业务影响也可控。
5.2 模型加载慢:不要误判成机器死机
有些新手加载一个大模型,看到日志停在某一步不动,就以为卡死了,直接重启。实际上 70B 模型冷启动加载权重耗时几分钟都很正常。加载慢通常有三个原因:磁盘读得慢、模型权重格式需要转换、显存碎片导致分配缓存重建。
如果你发现模型从本地加载比预期慢很多,可以考虑把权重文件放到 SSD 上,这个优化见效极快。另外,第一次加载一个新格式权重时,vLLM 可能会做权重格式转换并写缓存文件,后续加载就会快很多,这种"第一次慢、之后快"的现象不是 bug。所以我的建议是:模型文件尽量固定在稳定目录,不要频繁移动,首次启动前预留足够的耐心。
5.3 请求超时和回复乱码的排查思路
请求超时是接入服务后最频繁的线上问题。先定位是不是客户端超时设置太短,max_tokens设置太大,模型生成 512 个 token 可能需要十几秒,如果客户端只等了 5 秒,必然超时。再定位是不是服务端排队,当请求量超过并发能力,新来的请求会在队列里等待前序完成,这段时间也算在延迟里。这时候需要做的是观察时间分布,如果大部分延迟都集中在队列等待,就该提高并发上限或扩容。
回复乱码的情况,我在刚接触语言模型时也见过。排查顺序是:先看输入的 tokenization 是否正确,有些模型需要特定的聊天模板,如果你直接裸投文本,输出可能前言不搭后语。再看采样参数,temperature过高、top_p过小时,内容容易破碎。最后看模型本身,中文效果差的模型可能输出叠字、重复或很长的结束符序列,这不是框架的锅,换个合适对话模型往往能解决。
5.4 版本锁定和复现性:生产环境的隐形安全网
vLLM 迭代速度快,版本之间的启动参数、默认端口、API 响应字段都可能有改动。我踩过一次严重的坑:一个脚本基于旧版本开发,某天更新后服务启动时多了一个必选参数,线上所有自动化任务一夜之间全部失败。从那以后,我养成了固定版本的习惯,思路很简单:在任何生产环境里,都明确记录 vLLM 的版本号、Python 版本、CUDA 版本和模型文件哈希,这四个信息一起构成一个可复现的环境快照。
具体操作上,可以用项目依赖文件把 Python 包版本全部冻结,把服务启动命令写成一个脚本而不是裸敲命令,每次有变化都走代码评审流程。vLLM 的 GitHub Releases 页面会标明每个版本支持的 Python 和 CUDA 范围,升级前仔细比对。不要轻易用"最新版"这种模糊策略,除非你只在测试环境玩。
6. 一些额外的心得和小结
零零散散写了这么多,最后分享一点我自己的使用体会。
vLLM 的学习曲线并不陡,真正难的是在没搞清楚"它为什么快"之前就盲目堆参数。其实这框架的核心思想很朴素:把显存当成一个有碎片、有分页的存储系统来管理,把算力当成一个需要时刻喂饱的流水线。理解了这两点,那些参数名就不再是黑盒。入门时不要急着上多卡、上量化、上各种花哨特性,先用一个 7B 模型走通 Python 脚本和 HTTP 服务,然后再一个个加特性,每次只改一个变量,观察它对显存和吞吞吐量的影响。
还有一个小技巧是善用vllm serve --help。网络上各种博文写的参数可能对应旧版本,但当前环境里那个命令输出的参数说明永远是最准确的。遇到"网上说的没错但本地就是不认"的情况,先去翻--help,再对照自己的版本号,大部分悬案都能破。
vLLM 这个框架我还有大量玩法没展开,比如用结构化输出约束 JSON 格式、配合函数调用、做更长上下文的扩展。但那些都建立在今天这基础之上。你先跑通一个最小流程,后面的一切都好说。