4. KV Cache原理与优化

各位同学,今天我们来聊聊大模型推理里一个绕不开的话题——KV Cache。说实话,我第一次接触这个概念时,也觉得不就是缓存一下中间结果嘛,有什么好讲的?直到我在实际项目中,被显存爆炸的问题折磨得死去活来,才真正意识到这东西有多关键。

4.1 KV Cache是什么?

先问个问题:大模型做推理时,是怎么生成一个词的?

以GPT这样的自回归模型为例,生成第N个词时,需要把前面N-1个词全部重新过一遍Transformer。你想想看,这得多浪费?每次生成新词,前面算过的Key和Value都得重算一遍。

KV Cache就是干这个的——把之前计算好的Key矩阵和Value矩阵存下来。下次生成新词时,只需要算新词的Query,然后跟缓存的Key、Value做注意力计算就行。

核心定义:KV Cache是Transformer解码过程中,对历史token的Key和Value矩阵进行缓存的技术。它避免了重复计算,是自回归生成加速的关键手段。

我习惯用一个比喻来理解:你写文章时,前面写过的段落不需要重写一遍,只需要接着往下写就行。KV Cache就是帮你记住前面写了什么。

4.2 为什么需要KV Cache?

没有KV Cache会怎样?我们来算笔账。

假设模型有L层,每层注意力头数为H,每个头的维度为d。生成序列长度为S。没有KV Cache时,生成第S个token需要计算前面S-1个token的完整注意力。复杂度是O(S²)。

有了KV Cache呢?每次只需要计算新token的注意力,复杂度降到O(S)。

场景 计算复杂度 实际感受
无KV Cache O(S²) 生成100个词,要算10000次注意力
有KV Cache O(S) 生成100个词,只算100次

我在项目中遇到过,一个7B模型,生成512个token时,没有KV Cache的推理速度慢了将近10倍。说白了,没有KV Cache,长文本生成基本没法用。

4.3 显存占用分析

好了,KV Cache这么好,那它有没有代价?当然有——显存。

我们来算一下KV Cache的显存占用公式:

KV Cache显存 = 2 × L × H × d × S × 精度字节数

其中:

  • L:层数
  • H:注意力头数
  • d:每个头的维度
  • S:序列长度
  • 2:Key和Value各一份

举个例子,LLaMA-13B模型:

  • L = 40层
  • H = 40头
  • d = 128维
  • 精度 = FP16(2字节)

生成2048个token时:

KV Cache = 2 × 40 × 40 × 128 × 2048 × 2 = 1.6 GB

注意:这只是一个请求的KV Cache!如果并发处理多个请求,显存会线性增长。我曾经在部署服务时,就因为没算清楚这笔账,导致8张A100的显存被吃满,服务直接OOM。

更可怕的是,KV Cache的显存占用跟序列长度成正比。处理长文档时,比如10万token的上下文,KV Cache能吃掉几十GB显存。

4.4 PageAttention与vLLM的实现

显存问题怎么解决?vLLM给出了一个漂亮的方案——PageAttention。

这个名字是不是很眼熟?对,它借鉴了操作系统的虚拟内存分页思想。

传统做法是:为每个请求预分配一块连续的显存,存放KV Cache。但问题是,你没法提前知道请求最终会生成多长的序列。预分配少了,生成到一半显存不够;预分配多了,造成浪费。

PageAttention的思路是:把KV Cache切成固定大小的块(Page),每个块可以独立分配和释放。就像操作系统管理内存页一样。

传统KV Cache分配 Token 1-10 Token 11-20 Token 21-30 问题:必须连续分配,容易产生碎片 PageAttention分配 逻辑页表:Page 0 → 物理块 3 Page 1 → 物理块 7 Page 2 → 物理块 1 物理内存块池: 块0 块1 块2 块3 逻辑到物理映射 优势: 1. 非连续分配,消除碎片 2. 按需分配,避免预分配浪费 3. 支持内存共享(多个请求共享相同前缀) 4. 高效的内存复用和回收

vLLM的具体实现有几个关键点:

  • 块大小设计:每个Page通常包含16或32个token的KV值。太小了管理开销大,太大了浪费。
  • 物理块池:预先分配一大块显存,切成等大小的物理块。请求来了,按需分配物理块。
  • 逻辑页表:每个请求维护一个逻辑到物理的映射表。生成新token时,如果当前页满了,就申请新页。
  • Copy-on-Write:多个请求共享相同前缀时,物理块可以共享。只有需要修改时才复制。

我的经验:在实际部署中,PageAttention能把显存利用率从60%提升到95%以上。特别是处理长序列时,效果非常明显。我曾经用vLLM部署一个70B模型,8张A100可以同时处理32个并发请求,每个请求生成2048个token,这在传统方案下根本不可能。

不过要注意,PageAttention也不是银弹。它增加了内存管理的复杂度,CPU和GPU之间的页表同步也有开销。但对于长文本生成场景,这点开销完全值得。

嗯,KV Cache这块内容就讲到这里。从原理到显存分析,再到PageAttention的实现,希望能帮你建立起一个完整的认知。下次遇到显存瓶颈时,记得想想今天讲的这些优化手段。

核心要点回顾:

  • KV Cache避免重复计算,是自回归推理加速的关键
  • 显存占用随序列长度线性增长,长文本场景下是主要瓶颈
  • PageAttention通过分页管理,解决了显存碎片和浪费问题
  • vLLM的Copy-on-Write机制进一步提升了内存共享效率

专注资料整理