跳到主要内容

核心概念

本章深入讲解 vLLM 的核心技术原理,帮助你理解为什么 vLLM 能够实现如此高的推理效率。

PagedAttention:显存管理的革命

PagedAttention 是 vLLM 最核心的创新技术,它彻底改变了大模型推理中的显存管理方式。

传统方法的痛点

在理解 PagedAttention 之前,我们先看看传统方法的问题:

KV Cache 简介

Transformer 模型在生成文本时,需要缓存之前 token 的键(Key)和值(Value)向量,这些缓存合称为 KV Cache。对于每个生成的 token,模型需要:

  1. 计算当前 token 的 Query、Key、Value
  2. 将当前 Key 和 Value 加入缓存
  3. 使用所有缓存的 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 通过以下核心技术实现了高效的大模型推理:

  1. PagedAttention:动态显存管理,消除浪费,支持共享
  2. 连续批处理:最大化 GPU 利用率
  3. 推测解码:降低延迟
  4. 前缀缓存:复用历史计算结果
  5. Chunked Prefill:平滑长序列处理延迟