核心概念
本章深入讲解 vLLM 的核心技术原理,帮助你理解为什么 vLLM 能够实现如此高的推理效率。
PagedAttention:显存管理的革命
PagedAttention 是 vLLM 最核心的创新技术,它彻底改变了大模型推理中的显存管理方式。
传统方法的痛点
在理解 PagedAttention 之前,我们先看看传统方法的问题:
KV Cache 简介
Transformer 模型在生成文本时,需要缓存之前 token 的键(Key)和值(Value)向量,这些缓存合称为 KV Cache。对于每个生成的 token,模型需要:
- 计算当前 token 的 Query、Key、Value
- 将当前 Key 和 Value 加入缓存
- 使用所有缓存的 Key 和 Value 计算注意力
显存浪费问题
传统方法为每个序列预分配最大长度的连续显存空间:
序列 A(实际长度 100,最大长度 2048)
├─ 已使用: [100 tokens]
├─ 预留空间: [1948 tokens]
└─ 显存浪费: 95%
序列 B(实际长度 50,最大长度 2048)
├─ 已使用: [50 tokens]
├─ 预留空间: [1998 tokens]
└─ 显存浪费: 97%
更严重的是,当序列长度变化时,这种预分配策略会导致:
- 内部碎片:已分配但未使用的显存
- 外部碎片:分散的小块空闲显存无法被利用
- 无法共享:不同序列无法共享 KV Cache
PagedAttention 的设计思想
PagedAttention 借鉴了操作系统虚拟内存的分页机制:
核心概念:块(Block)
┌─────────────────────────────────────┐
│ 物理显存 │
├─────────┬─────────┬─────────┬───────┤
│ Block 0 │ Block 1 │ Block 2 │ ... │
│ 16 tokens│ 16 tokens│ 16 tokens│ │
└─────────┴─────────┴─────────┴───────┘
↑
固定大小(如 16 tokens)
逻辑视图 vs 物理视图
序列 A 的逻辑视图(连续) 物理显存(非连续)
┌─────────────────────┐ ┌─────────────────────┐
│ Token 0-15 │─────────→│ Block 5 │
│ Token 16-31 │─────────→│ Block 2 │
│ Token 32-47 │─────────→│ Block 8 │
│ ... │ │ ... │
└─────────────────────┘ └─────────────────────┘
通过块表(Block Table)映射:
[5, 2, 8, ...] 表示逻辑块到物理块的映射
PagedAttention 的优势
1. 消除显存浪费
传统方法为每个序列预分配最大长度的显存,而 PagedAttention 按需分配,实际使用多少分配多少。
2. 支持动态扩展
序列增长时动态分配新的块,无需预先知道最大长度。
3. 内存共享
PagedAttention 支持多种内存共享场景,如束搜索(Beam Search)和并行采样时共享 prompt 的 KV Cache。
连续批处理(Continuous Batching)
传统批处理采用静态批次,需要等待批次内所有请求完成后才能处理下一批。vLLM 实现了连续批处理机制:
静态批处理的问题
时间线:
批次 1: [Req A(100 tokens), Req B(10 tokens), Req C(50 tokens)]
└──────────────────────────────────────────────────────┘
等待 B 和 C 完成才能处理批次 2,GPU 空闲时间多
批次 2: [Req D, Req E, Req F]
└──────────────────────────────────────────────────────┘
问题:短请求完成后,必须等待长请求完成才能处理新请求,造成 GPU 空闲。
连续批处理的工作原理
时间线:
T1: [Req A(100), Req B(10), Req C(50)] 批次开始
T2: [Req A(90), Req B完成, Req C(40)] B 完成,立即加入 Req D
T3: [Req A(80), Req D(20), Req C(30)] 动态调整批次
T4: [Req A(70), Req D(10), Req C完成, Req E(30)] C 完成,加入 E
...
优势:
- 请求完成后立即退出,释放资源
- 新请求可以随时加入
- GPU 利用率最大化
在 vLLM 中启用连续批处理
from vllm import LLM, SamplingParams
llm = LLM(
model="facebook/opt-125m",
max_num_seqs=256, # 最大并发序列数
max_num_batched_tokens=4096 # 最大批处理 token 数
)
推测解码(Speculative Decoding)
推测解码是一种通过小型草稿模型预测未来 token,再由主模型验证的加速技术。
工作原理
传统解码:
主模型: [计算] → token 1 → [计算] → token 2 → [计算] → token 3
└────────┘ └────────┘ └────────┘
每次只生成 1 个 token
推测解码:
草稿模型: [计算] → token 1 → token 2 → token 3 → token 4 → token 5
(快速生成候选序列)
主模型: [并行验证所有候选 token]
↓
接受 token 1, 2, 3,拒绝 token 4, 5
↓
从 token 4 继续
使用推测解码
vllm serve meta-llama/Llama-2-7b-chat-hf \
--speculative-model meta-llama/Llama-2-1b-chat-hf \
--num-speculative-tokens 5
前缀缓存(Prefix Caching)
前缀缓存用于缓存历史对话的 KV Cache,避免重复计算。
应用场景
对话历史:
User: 你好
AI: 你好!有什么可以帮助你的吗?
User: 请介绍一下 Python
AI: Python 是一种...
User: 它有什么优点? ← 新请求
传统方法:重新计算整个对话历史的 KV Cache
前缀缓存:只计算新 query 的 KV Cache,复用之前的缓存
启用前缀缓存
from vllm import LLM
llm = LLM(
model="meta-llama/Llama-2-7b-chat-hf",
enable_prefix_caching=True
)
Chunked Prefill
Chunked Prefill 将长序列的预填充阶段分块处理,减少延迟峰值。
传统 Prefill 的问题
长 prompt(4096 tokens):
[一次性计算所有 token 的 KV Cache]
└────────────────────────────────────┘
延迟高,用户体验差
Chunked Prefill 方案
长 prompt(4096 tokens):
[Block 1: 1024 tokens] → 部分结果
[Block 2: 1024 tokens] → 部分结果
[Block 3: 1024 tokens] → 部分结果
[Block 4: 1024 tokens] → 完整 KV Cache
可以交错处理解码请求,降低延迟
启用 Chunked Prefill
from vllm import LLM
llm = LLM(
model="meta-llama/Llama-2-7b-chat-hf",
enable_chunked_prefill=True,
max_num_batched_tokens=2048
)
小结
vLLM 通过以下核心技术实现了高效的大模型推理:
- PagedAttention:动态显存管理,消除浪费,支持共享
- 连续批处理:最大化 GPU 利用率
- 推测解码:降低延迟
- 前缀缓存:复用历史计算结果
- Chunked Prefill:平滑长序列处理延迟