关键词:vLLM、PagedAttention、连续批处理、KV Cache、大模型推理、显存优化
一、为什么需要 vLLM
大模型推理和训练不同,训练追求的是吞吐,而推理服务追求的是"低延迟 + 高并发 + 低成本"三者兼顾。在实际生产环境中,使用 HuggingFace Transformers 原生的 generate() 方法部署推理服务,往往会遇到两个致命问题:
- 显存浪费严重:自回归生成过程中,每个请求都需要缓存历史 token 的 Key/Value(也就是 KV Cache),传统做法是按"最大可能长度"预先分配一整块连续显存。但实际生成长度往往远小于预留长度,导致大量显存被闲置,实际利用率常常只有 20%~40%。
- 并发能力差:因为显存碎片化和过度预留,同一张卡能同时处理的请求数(batch size)被严重限制,直接拖累整体吞吐量。
针对这两个痛点,UC Berkeley 团队在 2023 年提出了 vLLM,其核心创新是 PagedAttention 算法。如今 vLLM 已经成为开源大模型推理服务的事实标准,广泛支持 Llama、Qwen、DeepSeek 等主流模型系列,单卡吞吐量相比朴素实现通常能提升 10~24 倍。
二、核心原理:PagedAttention
2.1 传统 KV Cache 管理的问题
在自回归生成中,每生成一个新 token,模型都需要用到之前所有 token 的 Key 和 Value 矩阵,这部分数据被称为 KV Cache。它有两个特点:
- 体积巨大:以 13B 级别模型为例,单条较长序列的 KV Cache 就可能占用 GB 级别显存;
- 动态增长:长度取决于输入和已生成的 token 数,事先无法精确得知。
传统推理引擎的做法是:按照"prompt 长度 + 最大输出长度"提前分配一整块连续显存。这种方式会产生两类浪费:
- 内部碎片(internal fragmentation):预留空间没有被实际使用;
- 外部碎片(external fragmentation):空间太小太散,无法分配给其他请求。
这背后的根本原因是:深度学习框架处理张量要求数据在显存中是连续存放的,而 KV Cache 的连续性要求恰恰是这一切低效的源头。
2.2 PagedAttention 的解决思路
PagedAttention 的灵感来自操作系统的虚拟内存分页机制:与其死板地为每个进程预留一整块连续物理内存,不如把物理内存切成固定大小的"页",再用一张页表把进程看到的连续逻辑地址映射到分散的物理页上。
vLLM 把这套思路平移到了 KV Cache 管理上:
- 把显存预先划分成许多大小固定的 Block(块),每个 Block 默认可以存放 16 个 token 的 K/V 数据;
- 每个请求的 KV Cache 在逻辑上仍然是"连续"的一串 Block,但物理上可以离散地分布在显存的任意位置;
- 通过一张 Block Table(块表) 记录逻辑块到物理块的映射关系,在做 Attention 计算时按图索骥找到对应的物理块。
这样带来的直接收益是:因为每个 Block 只存固定数量的 token,对于任意一个请求,最多只会浪费"不到一个 Block"的空间(也就是最多浪费 block_size - 1 个 token 的容量),内部碎片被压缩到极小;同时因为分配是按需进行、随用随取,外部碎片也基本消失。
可以做一个类比:
| 操作系统虚拟内存 | vLLM PagedAttention |
|---|---|
| 进程 | 一个推理请求(sequence) |
| 字节 | Token |
| 页(Page) | Block |
| 页表 | Block Table |
2.3 显存共享:Copy-on-Write
因为 KV Cache 是按块管理的,vLLM 天然可以在块粒度上做显存共享。典型场景包括:
- Parallel Sampling / Beam Search :同一个 prompt 生成多条候选序列时,前缀部分的 KV Cache 完全相同,多个序列可以共享同一批物理 Block,只有在某个分支真正开始"分叉"(生成不同 token)时,才会触发 Copy-on-Write,为该分支单独复制一份 Block。
- 前缀缓存(Prefix Caching):多个请求如果共享相同的系统提示词(System Prompt)或长文档前缀,也可以复用已经计算好的 KV Cache Block,省去重复计算。
这种共享机制进一步提升了显存利用率,尤其在多轮对话、共享模板等场景下效果显著。
2.4 显存不够时怎么办:抢占与恢复
由于 vLLM 采用"按需动态分配"策略,理论上存在显存被打满、但仍有请求未完成的情况。此时调度器会对部分请求执行抢占(Preemption) ,vLLM 采用的是 all-or-nothing 策略:被抢占的请求会释放其占用的全部 Block。恢复时有两种方式:
- 重新计算(Recomputation):把被抢占请求当作一个新的 prompt,重新做一次 prefill;
- 交换(Swapping):把 KV Cache 换出到 CPU 内存,待显存空闲后再换回来继续生成。
三、连续批处理(Continuous Batching)
如果说 PagedAttention 解决的是"显存怎么省",那连续批处理解决的就是"GPU 怎么不空转"。
3.1 传统静态批处理的问题
早期推理系统多采用静态批处理 :把一批请求打包送入模型,必须等这批请求全部生成完毕才能处理下一批。问题在于:
- 不同请求的输出长度差异很大,短请求生成完了也要陪跑到最长的那条结束,GPU 利用率被拉低;
- 新来的请求必须排队等待整批处理完,排队延迟高。
3.2 vLLM 的迭代级调度
vLLM 把批处理的粒度从"整个请求"下沉到"每一次前向计算(每个 decoding step)":
- 每一步(step)结束后,调度器都会检查:哪些请求已经生成完毕(可以被移出批次),哪些新请求可以被纳入批次;
- 新请求可以在任意一步"插队"加入正在运行的批次,无需等待整批结束;
- 已完成的请求立刻释放资源(包括 KV Cache Block),显存和算力马上被下一位请求复用。
这种迭代级调度 + 动态批次的组合,就是 continuous batching。它和 PagedAttention 是互补关系:PagedAttention 让显存分配变得灵活可控,continuous batching 则让调度器可以放心地随时增删请求而不担心显存管理的复杂度。二者共同作用,使得 vLLM 在高并发场景下相比朴素实现能取得数量级的吞吐提升。
3.3 Prefill 与 Decode 两个阶段
一次请求的生命周期通常分为两个阶段:
- Prefill(预填充) :对输入 prompt 做一次前向计算,一次性生成所有输入 token 的 KV Cache,这个阶段是计算密集型的(可以并行处理很多 token);
- Decode(解码) :每次只生成一个新 token,并把新 token 的 K/V 追加进缓存,这个阶段是访存密集型的(每步只算一个 token,但要频繁读取显存中的历史 KV Cache)。
vLLM 的调度器需要在一个批次里同时安排处于 prefill 阶段和 decode 阶段的请求,这也是它工程实现里比较精细的部分。较新版本还引入了 Chunked Prefill(分块预填充),把长 prompt 的 prefill 计算切成若干小块,穿插在 decode 请求之间执行,避免一个长 prompt 的 prefill 独占整个 step,从而降低其他请求的排队延迟。
四、vLLM 的整体架构
一个典型的 vLLM 服务由三个核心模块协同工作:
- Scheduler(调度器):决定每一个 step 里,哪些请求参与本次前向计算,负责 KV Cache Block 的分配、抢占与回收;
- Block Manager(块管理器):维护物理 Block 的分配状态和每个请求的 Block Table,是 PagedAttention 在工程上的具体落地;
- Worker / Model Executor:真正执行模型前向计算的进程,负责加载模型权重、执行自定义的 PagedAttention CUDA/HIP Kernel,在多卡场景下还要处理张量并行的通信。
对外,vLLM 提供两种典型使用方式:
- 离线批量推理 :直接调用 Python API(
LLM类 +generate),适合批量跑数据集、评测等场景; - 在线服务 :启动一个兼容 OpenAI API 协议的 HTTP 服务(
vllm serve),可以直接作为聊天/补全接口对外提供服务,方便无缝替换 OpenAI 客户端。
五、快速上手示例
5.1 安装
pip install vllm
5.2 离线批量推理
from vllm import LLM, SamplingParams
prompts = [
"介绍一下大语言模型推理加速的常见方法",
"什么是KV Cache?",
]
sampling_params = SamplingParams(temperature=0.7, top_p=0.9, max_tokens=256)
llm = LLM(model="Qwen/Qwen2.5-7B-Instruct", tensor_parallel_size=1)
outputs = llm.generate(prompts, sampling_params)
for output in outputs:
print(output.outputs[0].text)
5.3 启动 OpenAI 兼容的在线服务
vllm serve Qwen/Qwen2.5-7B-Instruct \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.9 \
--max-model-len 8192 \
--port 8000
启动之后即可用标准 OpenAI SDK 或 curl 调用:
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen2.5-7B-Instruct",
"messages": [{"role": "user", "content": "你好"}]
}'
六、关键部署参数详解
| 参数 | 作用 | 调优建议 |
|---|---|---|
--gpu-memory-utilization |
允许 vLLM 使用的显存比例(预留给 KV Cache Block 池) | 单模型部署建议设到 0.85~0.95,多进程共卡需调低 |
--max-model-len |
单条序列支持的最大上下文长度 | 按业务实际需求设置,越大单条请求占用的 Block 越多 |
--block-size |
每个 KV Cache Block 存放的 token 数,默认 16 | 一般无需调整,特殊硬件/长上下文场景可尝试更大值 |
--tensor-parallel-size |
张量并行的 GPU 数量 | 单卡放不下模型权重时使用,通常与单机 GPU 数对齐 |
--max-num-seqs |
单个批次最多容纳的请求数 | 并发量大但显存有限时适当调低 |
--enable-prefix-caching |
是否开启前缀缓存 | 多轮对话、共享 System Prompt 场景强烈建议开启 |
--quantization |
权重量化方式(如 AWQ、GPTQ、FP8) | 消费级显卡部署大模型的关键手段,可显著降低显存占用 |
七、分布式与多卡场景
当单张 GPU 显存无法容纳模型权重时,vLLM 支持:
- 张量并行(Tensor Parallelism) :把每一层的权重矩阵切分到多张 GPU 上,各卡并行计算后做通信同步,通过
--tensor-parallel-size指定; - 流水线并行(Pipeline Parallelism):把模型按层切分到不同 GPU/节点,适合超大模型的多机部署;
- 结合 Ray 或 Kubernetes,可以进一步实现多实例的负载均衡与弹性扩缩容,满足生产级多租户场景的需求。
八、量化与显存优化
配合 AWQ、GPTQ、FP8 等量化技术,vLLM 可以把权重显存占用压缩到原来的 1/2 甚至 1/4,这意味着:
- 消费级显卡也能跑起实用的 7B/13B 模型;
- 同等显存下可以容纳更多 KV Cache Block,间接提升并发能力。
需要注意的是,量化通常会带来轻微的精度损失,生产环境上线前建议做充分的效果评估。
九、vLLM 的优势总结
- 显存利用率高:PagedAttention 将显存利用率从传统方案的 30% 左右提升到 85% 以上,内部/外部碎片基本消除;
- 吞吐量大幅提升:连续批处理配合 PagedAttention,同等硬件下并发能力可提升数倍到数十倍;
- 长上下文友好:分页式 KV Cache 管理天然适配长文档、长对话等超长上下文场景;
- 生态成熟:无缝兼容 HuggingFace 模型库和 OpenAI API 协议,社区活跃,主流大模型(Llama、Qwen、DeepSeek 等)基本第一时间获得支持;
- 部署门槛低:单条命令即可拉起服务,结合量化技术后中小团队也能低成本私有化部署。
十、小结
vLLM 之所以能成为当下开源 LLM 推理服务的事实标准,核心在于它用一套"操作系统式"的思路重构了 KV Cache 的内存管理方式:PagedAttention 解决了"显存怎么分配更省"的问题,连续批处理解决了"GPU 怎么调度更满"的问题,两者相辅相成,共同把大模型推理服务的吞吐天花板提高了一个量级。对于需要自建大模型推理服务的团队而言,无论是单卡部署中小模型,还是多卡张量并行部署超大模型,vLLM 目前都是工程成熟度和社区活跃度最高的选择之一。
参考资料
- vLLM 官方文档:https://docs.vllm.ai
- Efficient Memory Management for Large Language Model Serving with PagedAttention(SOSP 2023)
- vLLM GitHub 仓库