市中心的写字楼楼下有一家川菜馆「辣到飞起」,饭点永远排着长队。这家店的后厨就像一块 GPU:灶台数量有限,显存就是后厨的冰箱和备菜台;每一桌客人是一次推理请求,后厨每上一道菜就生成一个新 token。客人少的时候还好,客人一多,老掌柜就手忙脚乱:他要么等四桌客人全部吃完才让新客人进来,结果有人拖堂,灶台闲一阵忙一阵;要么给每桌客人预留一整块白板记口味备注,结果常常只写了半块,剩下的空间白花花地浪费掉。 vLLM 就是新来的“调度总监”。它把大模型推理当成流水席来安排:显存里的缓存被切成一块块小磁吸白板,按需拼贴;有人结账立刻翻台,新客人马上坐下。于是同样的 GPU,能接待更多请求、出餐更快、排队更短。这就是 vLLM 的核心本事——PagedAttention 分页注意力、连续批处理、以及一套 OpenAI 兼容的 API 服务。
学会 vLLM,你就能把开源大模型快速部署成高吞吐、低显存的推理服务。这是大模型落地中最值钱的工程能力之一,也是推理平台工程师、MLOps 工程师的必备技能。
目录
可视化演示
展示PagedAttention分页KV缓存机制、连续批处理动态调度、以及与TGI/TensorRT-LLM框架性能对比三大核心维度。
本节配有交互动效(进入视口后自动加载,约 3KB)。
vLLM PagedAttention:KV缓存分页管理,逻辑非连续→物理连续,显存利用率96%+ 连续批处理:请求动态加入/退出batch,无需等待同步,吞吐提升2.8x Throughput ∝ BatchSize / AvgLatency 框架对比:vLLM在吞吐、延迟、显存效率上全面领先TGI和原生推理
认识 vLLM:给后厨请来调度总监
「辣到飞起」的老板最近很头疼:客人来了,后厨却总是闲着。最典型的问题是:一桌客人点了 100 道菜,另一桌只点 5 道菜,老掌柜规定必须等整批客人全吃完才翻台,结果 5 道菜那桌早早吃完,也只能干坐着占位置。对应到大模型推理里,一个请求要生成 100 个 token,另一个请求只要 5 个 token,如果采用“整批同进同出”的静态批处理,短请求完成后依然占着 GPU 资源,浪费非常明显。
vLLM 就是这个新来的调度总监。它不改菜单、不改菜谱,也就是说它不修改模型的权重和注意力计算公式,只改变后厨的排班方式、食材缓存方式和上菜节奏。通过这种方式,它把 GPU 这张“灶台”的利用率提上去,让显存这个“冰箱”装下更多东西。
从技术上说,大模型生成文本时是逐 token 进行的。每生成一个新 token,模型都要把之前所有 token 的 Key 和 Value 向量缓存起来,这就是 KV cache。你可以把它理解成“上菜记录”:后厨每次做下一道菜,都要参考上一道菜的口味记录,不用重新问一遍客人。问题在于,这个记录会越写越长,如果给每桌客人都预留一整块连续区域,很容易出现“一半空着、另一半不够用”的碎片化浪费。vLLM 的调度总监正是从这里下手:让每一块显存都尽量被真正用到。
以后厨打比方,vLLM 这位调度总监有三件法宝:把备注写成小份白板的分页注意力、随时翻台的连续批处理、以及把点单流程标准化的 API 服务。下面我们一件一件看。
PagedAttention 分页注意力:把订单备注贴进小格子
老掌柜以前给客人记备注,习惯在墙上挂一长条大白板,人数一多,每桌都得预留一整条。问题来了:有的客人只写“不要香菜”四个字,一整条白板几乎全空;有的客人洋洋洒洒写了一百条,一条白板又不够长,还得东拼西凑。后厨备菜台明明还有空位,却因为白板条不连续,怎么都用不上。
PagedAttention 的思路,是把大白板换成许多同样大小的磁吸小白板。每一块小白板能写固定数量的 token,比如 16 个 token 一小块。某个请求需要多少容量,就按需拿多少块,不需要提前占一整条连续区域。即使这些小白板在墙上东一块西一块也没关系,因为后厨手里有一个“块表”,也就是一本编号账本,记录“这个请求的第 3 块白板,实际贴在墙上第 7 个位置”。
这种设计让显存利用率大幅提升。用公式表示就是:
除了消除显存碎片,块表还带来一个额外好处:多个请求如果共享相同的前缀,比如都问“请用 Python 写一个排序函数”,那么它们前面的 KV 缓存可以共用同一批物理块,而不是每份请求都重复复制一份。这就像几位客人点了一模一样的招牌菜,后厨只用炒一大盘,再分别端到各桌去。PagedAttention 是 vLLM 最核心的引擎,也是它敢说“低显存占用”的底气。
- used tokens实际写入 KV 缓存的 token 数,相当于磁吸小白板上真正写了字的格子数量。
- allocated tokens系统分配给这个请求的块总容量,也就是预留的小白板总格数。
- 比值接近 1说明白板空间几乎全部被用上,没有浪费。PagedAttention 的目标就是让这个比值尽量接近 1。
连续批处理:不等整桌吃完就翻台
老掌柜以前翻台的规则是:一张桌子的客人全部吃完,才允许新客人坐下。于是经常出现这样的场景:一桌客人已经吃完了,但因为同批其他桌还在慢慢吃,这桌空位只能白白晾着。新客人站在门口排队,后厨却不敢开工。
vLLM 的连续批处理把规则改了:不用等整批客人全部吃完,而是每上一道菜就检查一次——如果这桌客人吃完了,立刻收拾桌子,让门口等待的客人坐下;如果后厨还有空灶,也立刻把新菜单下锅。对应到推理过程,就是“迭代级调度”:模型每完成一次 decode 解码,都会重新检查正在处理的请求列表,把已完成的请求移出去,把等待队列里的新请求加进来。
这种做法的直接收益是 GPU 空闲时间大幅减少,整体吞吐量提升。用公式表达:
如果显存实在不够,vLLM 还有一招“抢占式调度”:把某些还没做完的请求暂时请出 GPU,先服务更紧急的请求,等资源空出来再让它们回来继续做。这就像后厨忙不过来时,老板会说:“这桌的外卖先放进保温柜,等灶台空了再热一下继续出。”连续批处理让整个推理服务保持着流水线的节奏,而不是一锅端地等。
- N 个请求在一段时间内处理的所有推理请求,相当于接待的客人总桌数。
- 分子所有请求生成 token 的总数。生成得越多,说明后厨出菜越多。
- 分母总耗时。连续批处理让分母不变时分子更大,所以吞吐更高。
部署成 API:把后厨接到外卖平台
一家餐馆就算菜做得再好,如果客人只能站在门口喊单,生意也做不大。于是老板决定接入外卖平台:所有客人都通过一个标准化的点单接口下订单,后厨不需要管客人用的是小程序还是 App,只要按照统一格式接单、出餐、回传状态就行。
vLLM 给大模型提供的正是这样一个“外卖平台接口”。它内置了 OpenAI 兼容的 HTTP 服务,启动之后,你不需要自己写复杂的 Web 后端,直接用 OpenAI 的 Python SDK 就能调用。启动服务只需要一行命令:
启动后,服务会提供 /v1/completions 和 /v1/chat/completions 等接口。客户端只要把 base_url 指向 vLLM 服务地址,就能像调用 OpenAI 一样调用本地大模型,适合私有化部署。
在实际部署时,有几个参数特别像后厨的“经营配置”:--max-model-len 决定最大上下文长度,相当于餐厅最多能接待多大的宴席;--gpu-memory-utilization 控制给 KV 缓存预留多少显存,相当于留给备菜台多大的冰箱;--tensor-parallel-size 指定用几张卡并行切分模型,相当于把一道大菜分给几个灶台同时炒。调好这些参数,vLLM 才能在后厨不爆单的前提下,接待更多客人。
vllm serve YOUR_MODEL --host 0.0.0.0 --port 8000 --gpu-memory-utilization 0.9框架对比选型:三家面馆怎么选
现在市面上不止 vLLM 一家“后厨管理公司”。最常被拿来比较的是三兄弟:vLLM、TGI 和 TensorRT-LLM。你可以把 vLLM 想成社区里最活跃的网红面馆,更新快、年轻人多;TGI 是 Hugging Face 开的老字号连锁,稳重、和 Hugging Face 生态配合最好;TensorRT-LLM 则是 NVIDIA 开的定制化私厨,凡是 NVIDIA 的 GPU 它都能发挥到极致,但要提前做大量准备工作。
从上手难度看,vLLM 最友好。它是纯 Python/PyTorch 风格,一条命令就能跑起来,社区文档丰富,模型支持范围广,还支持 GPTQ、AWQ 等量化格式。TGI 的优势在于和 Hugging Face 的模型库深度绑定,如果你想快速托管 Hugging Face 上的各类模型,它会很顺手。TensorRT-LLM 的性能上限通常最高,但它需要把模型编译成 TensorRT 引擎,还要做层融合、精度校准等手工优化,适合对延迟和吞吐要求极高、且有专门工程团队的场景。
选型时别只看“谁最快”。如果你需要快速落地、社区提问有人答、团队以 Python 为主,vLLM 往往是首选;如果你重度使用 Hugging Face 全家桶,TGI 值得考虑;如果业务体量巨大且清一色 NVIDIA 显卡,TensorRT-LLM 才能把硬件潜力榨干。三面馆没有绝对的高下,只有合不合适的客人和后厨。
量化加速:把大锅菜换成半成品料理包
「辣到飞起」最近又想到一个省成本的办法:把一些工序复杂的菜提前做成半成品料理包,冻在冰箱里。客人点单时,后厨不用现场切菜、腌肉,只需要把料理包加热、装盘就行。代价是口味会比现炒稍微差一点。这个逻辑,对应到大模型部署里就是量化。
大模型默认权重通常用 FP16 表示,也就是每个数字占 16 位。如果把它变成 INT8 甚至 INT4,每个数字占的位宽变小,模型文件体积和显存占用都会明显降低。vLLM 支持加载 GPTQ、AWQ 这类量化模型,让“半成品料理包”直接上桌。量化的核心公式是:
从 FP16 到 INT4,权重显存理论上可以降到原来的四分之一左右。由于大模型推理常常是“显存带宽瓶颈”,权重变小后,每次从显存搬数据的压力也变小,吞吐自然提升。但量化不是免费的:位宽越低,精度损失风险越高,尤其对越大的模型、越复杂的任务越明显。所以 AWQ 这类方法会通过少量校准数据来“抱住”重要权重,尽量让口味损失最小化。部署时,你应该像老板试菜一样:先在小流量上对比量化前后的回答质量,再决定要不要全面使用料理包。
- 缩放因子 s根据原始数值范围和目标位宽算出来的“刻度”,相当于料理包标注的“每份多少克”。
- round(x/s)把原始数值除以刻度后取整,数值被压到更少的状态,比如从 16 位变成 4 位。
- 乘以 s 还原推理时再乘回刻度,得到近似原值。这一步会带来微小误差,但换来更低的显存占用。
AI 应用场景
企业内部私有化大模型 API 服务
阶段:推理服务部署
解决的问题:过去每个业务团队各自部署一套大模型,GPU 显存碎片化严重,请求少时显卡闲着,请求多时又排队。用 vLLM 统一部署后,PagedAttention 把显存利用率提上来,连续批处理让显卡持续满载,还能直接提供 OpenAI 兼容接口。
具体案例:某公司用 Llama-3-8B 搭建内部知识库问答服务。之前用静态批处理,单张 A100 上 QPS 只有 3 左右;改用 vLLM 部署后,同样一张卡,打开 /v1/chat/completions 接口,QPS 提升到 12 左右,多个业务线共用一套推理服务。
多租户 SaaS 客服助手
阶段:推理服务与并发优化
解决的问题:SaaS 产品里成百上千个用户同时对话,请求长度差异很大。如果让短请求等长请求结束,用户体验会非常差。vLLM 的连续批处理让每个请求在每一轮迭代都能进出批次,配合分页注意力,能扛住高并发。
具体案例:一个客服机器人产品接入 vLLM 后,300 个用户同时提问时,平均首 token 延迟从 1.8 秒降到 0.6 秒;长文档总结请求和短问答请求混在一起时,短请求不再被长请求“绑架”。
量化模型低成本上线
阶段:模型压缩与部署
解决的问题:开源大模型动辄几十 GB,FP16 权重需要多张卡才能放下,成本很高。用 AWQ 或 GPTQ 把模型量化为 INT4,再用 vLLM 加载,可以在一张卡上跑起来,同时保持可接受的效果。
具体案例:某创业公司原本用两张 A100 部署 70B 模型做内部 Copilot,成本压力大;换成 AWQ INT4 量化版本后,单张 A100 就能启动,回答质量在内部评测中只下降约 2%,月度 GPU 成本直接减半。
Python 演示
class PagedMemory: """模拟 vLLM 的块表: 逻辑块 -> 物理块""" def __init__(self, num_blocks, block_size): self.num_blocks = num_blocks self.block_size = block_size self.free_blocks = list(range(num_blocks)) self.block_tables = {} def alloc(self, seq_id, logical_blocks): if len(self.free_blocks) < logical_blocks: raise MemoryError("no free block") physical = [self.free_blocks.pop() for _ in range(logical_blocks)] self.block_tables[seq_id] = physical return physical def translate(self, seq_id, logical_idx): if seq_id not in self.block_tables: return None physical_idx = self.block_tables[seq_id][logical_idx] return physical_idx * self.block_size def release(self, seq_id): self.free_blocks.extend(self.block_tables.pop(seq_id)) def summary(self): for seq_id, table in sorted(self.block_tables.items()): print("seq %s table=%s" % (seq_id, table)) def demo(): mem = PagedMemory(num_blocks=8, block_size=16) mem.alloc("req_A", logical_blocks=3) mem.alloc("req_B", logical_blocks=2) mem.summary() print("A.logical[2] -> physical offset:", mem.translate("req_A", 2)) mem.release("req_B") print("free blocks after release:", mem.free_blocks) if __name__ == "__main__": demo()
self.free_blocks = list(range(num_blocks))初始时显存块全部空闲,相当于后厨的磁吸白板都空着。physical = [self.free_blocks.pop() for _ in range(logical_blocks)]每次分配都从空闲池取出物理块,像从抽屉里取小白板。self.block_tables[seq_id] = physical记录“逻辑位置对应哪块物理板”,这就是 vLLM 的 block table。physical_idx = self.block_tables[seq_id][logical_idx]把逻辑块编号翻译成物理块编号,并乘上块大小得到真实偏移。self.free_blocks.extend(self.block_tables.pop(seq_id))请求结束时把物理块放回空闲池,方便下一个请求复用。
常见误区
误区我一直以为 vLLM 会对模型权重做魔法优化,所以模型回答会变聪明,效果一定比原模型好。
正解vLLM 不修改模型权重,也不改变注意力计算公式。它优化的是显存分配、批调度和请求管理。同样的模型权重,通过 vLLM 和通过原生 PyTorch 推理,输出的概率分布基本一致,质量不会因为框架而变好。
为何常见:看到“高吞吐”“低显存”这些词,容易以为框架本身增强了模型,其实它只是让模型跑得更省钱、更高效。
误区PagedAttention 应该就是量化技术吧?不然它为什么能省显存?
正解两者完全不同。PagedAttention 管理的是 KV 缓存的物理存储方式,把缓存切成小块、按需分配,数值精度不变;量化是把权重从 FP16 压到 INT8/INT4,数值精度会损失。一个像优化冰箱布局,一个像把新鲜食材换成浓缩料理包。
为何常见:两者最终都表现为“显存占用降低”,但底层原理差异很大,容易混淆。
误区动态 batch 和连续批处理不是一样吗?都是等请求攒够一批再统一跑。
正解传统动态 batch 依然是以“整个批次”为单位,凑满一批才执行,批次中途很少增删请求。连续批处理更激进:每完成一次解码迭代,就可以把完成的请求移出、把等待的请求移入,调度粒度从“批次级别”细化到“迭代级别”。
为何常见:两个概念都包含“动态”的感觉,但真正的分水岭是调度单位是整批还是单次迭代。
误区既然量化省显存,那直接用 INT4 吧,位宽越低越好。
正解位宽越低,显存越省,但精度损失也可能越大。实际项目中要先做校准数据集上的效果评测,再决定用 INT8 还是 INT4。AWQ、GPTQ 这类方法可以降低损失,但不能把损失降到零。
为何常见:人们容易用“数字越小越省空间”来推导,却忽略了精度的非线性衰减。
面试题
Q1. vLLM 最核心的显存优化技术是哪一项?
- A.模型权重剪枝
- B.分页注意力机制← 正确答案
- C.低比特量化的权重
- D.老师学生蒸馏法
答案:B。vLLM 的招牌是 PagedAttention,它把 KV 缓存分成固定大小的块,用块表映射逻辑块与物理块。剪枝、量化和蒸馏也能压缩模型,但不是 vLLM 的核心创新。
Q2. PagedAttention 把 KV 缓存划分成的基本单位是什么?
- A.一段连续的显存
- B.固定大小的物理块← 正确答案
- C.固定长度的队列
- D.一个完整的注意力头
答案:B。PagedAttention 模仿操作系统分页,把 KV 缓存切成固定大小的物理块,需要多少就分配多少。它不要求连续显存,也不用等一个注意力头完整放下。
Q3. vLLM 中块表(block table)的核心作用是什么?
- A.保存模型全部权重参数
- B.记录逻辑块到物理块的映射← 正确答案
- C.压缩输入提示词的向量
- D.把注意力头数量减半
答案:B。块表类似餐馆后厨的编号账本,记录“请求的第几个逻辑块实际放在哪个物理块”。它让非连续显存能像连续显存一样被高效使用。
Q4. 与静态批处理相比,连续批处理最大的差异是什么?
- A.一个批次中的请求固定不变
- B.每轮迭代都可加入或移除请求← 正确答案
- C.必须等所有请求结束后换批
- D.同一时刻只处理一个请求
答案:B。连续批处理把调度粒度细化到“迭代级”。每次 decode 后,完成的请求立刻退出,等待的请求马上进入,而不是等整个批次结束才换批。
Q5. 使用 vLLM 启动 OpenAI 兼容服务时常用的命令是?
- A.python train.py --model
- B.vllm serve 模型名称← 正确答案
- C.llama.cpp --run 模型
- D.accelerate launch 模型
答案:B。vLLM 提供 vllm serve 命令,一条命令就能拉起 OpenAI 兼容的推理服务。python train.py 用于训练,accelerate launch 用于分布式训练,都不是 vLLM 的启动入口。
Q6. 关于 vLLM 与 TensorRT-LLM 的对比,下列说法正确的是?
- A.vLLM 一定比 TensorRT-LLM 慢
- B.TensorRT-LLM 通常需要更多手工优化← 正确答案
- C.vLLM 不支持任何量化格式
- D.TensorRT-LLM 只能运行在 CPU
答案:B。TensorRT-LLM 性能上限高,但需要编译 TensorRT 引擎、做层融合和校准,工程量更大。vLLM 支持 GPTQ、AWQ 等量化格式,也并非一定比 TensorRT-LLM 慢,具体要看场景。
Q7. 权重量化主要用更低的数值精度来换取什么?
- A.更大的词表
- B.更低的显存占用← 正确答案
- C.更长的训练数据
- D.更多的注意力头
答案:B。量化把 FP16 权重变成 INT8/INT4,单个权重占用的位变少,所以显存占用降低。代价是数值精度损失,而不是换来更大的词表或注意力头。
Q8. 下列哪些是 vLLM 降低显存占用的做法?
- A.PagedAttention 分页管理← 正确答案
- B.连续批处理释放完成请求← 正确答案
- C.降低训练数据的采样率
- D.将权重从 FP16 换成 INT4← 正确答案
答案:ABD。PagedAttention 减少 KV 缓存碎片,连续批处理让完成请求及时让出资源,INT4 量化压缩权重体积。降低训练数据采样率会伤害模型效果,不属于 vLLM 的部署优化手段。
Q9. 关于连续批处理的调度特点,正确的有?
- A.调度粒度从整个批次细化到单次迭代← 正确答案
- B.某个请求生成结束会立即让出槽位← 正确答案
- C.所有请求必须同时开始同时结束
- D.批大小在整个推理过程中固定不变
答案:AB。连续批处理的核心是迭代级调度,请求可以在不同时间完成并退出,新请求也可以在不同时间进入,因此批大小动态变化。C 和 D 描述的是静态批处理。
Q10. 部署 vLLM 时,影响显存与吞吐的常见参数有?
- A.--max-model-len 最大上下文长度← 正确答案
- B.--gpu-memory-utilization 显存比例← 正确答案
- C.--tensor-parallel-size 并行卡数← 正确答案
- D.--warmup-steps 预热训练步数
答案:ABC。--max-model-len 决定 KV 缓存上限,--gpu-memory-utilization 控制显存分配比例,--tensor-parallel-size 决定多卡并行方式,三者都直接影响显存和吞吐。--warmup-steps 是训练场景参数,与 vLLM 部署无关。