大数跨境
0
0

LLM中的KV缓存与预填充阶段:核心技术详解

LLM中的KV缓存与预填充阶段:核心技术详解 ai算法芯片与系统
2025-12-07
5
导读:LLM推理包含预填充和解码阶段,KV缓存是提升效率的核心技术。本文解析KV缓存原理,展示其如何减少注意力计算冗余,并通过代码示例讲解实现,助力加速文本生成。

 

摘要

大型语言模型(LLM)的推理过程包含两个关键阶段:预填充阶段(Prefill Phase)解码阶段(Decoding Phase)。KV缓存(KV Cache)是连接这两个阶段、提升推理效率的核心技术。本文将从基本概念出发,深入解析LLM推理的工作流程,阐明KV缓存如何解决注意力计算中的冗余问题,并通过代码示例展示其具体实现。我们将探讨这一技术如何在不影响模型输出的前提下,显著加速文本生成过程。

目录

  1. 1. 引言:理解LLM推理流程
  2. 2. 核心问题:解码阶段的重复计算
  3. 3. KV缓存:消除冗余的解决方案
    • • 3.1 核心思想
    • • 3.2 工作原理
  4. 4. 预填充阶段:解码的准备工作
    • • 4.1 阶段定义与特点
    • • 4.2 与解码阶段的对比
  5. 5. 代码实现:KV缓存的存储与使用
    • • 5.1 向缓存追加数据
    • • 5.2 从缓存读取数据
  6. 6. 内存与计算复杂度分析
  7. 7. 实践要点与常见优化
  8. 8. 总结与展望

1. 引言:理解LLM推理流程

大型语言模型(LLM)的文本生成过程是一个自回归(Autoregressive) 的序列预测任务。整个过程可以类比为“填空游戏”:模型基于已有的文本序列,预测下一个最可能出现的词(或token),然后将预测结果追加到序列末尾,如此循环往复。

一次完整的推理(Inference)通常始于用户的提示(Prompt),例如“what is a LLM?”。下图概括了从输入到模型完成首次预测的基本流程:

LLM推理的初始步骤图示。输入句子“what is a LLM?”被分词(Tokenized),每个token被转换为768维的嵌入向量(以GPT-2为例),然后送入Transformer块进行上下文编码。

这个流程可以分解为三个核心步骤:

  1. 1. 分词(Tokenization):将输入文本(即上下文+用户提示)转换为模型能理解的离散token序列。
  2. 2. 嵌入(Embedding):将每个token映射为一个高维稠密向量(例如,GPT-2中维度为768)。
  3. 3. 前向传播(Forward Pass):将嵌入序列输入Transformer块,通过自注意力(Self-Attention)等机制进行上下文编码(Contextualization),最终输出下一个token的预测分布。
Transformer块内部注意力计算示意图。展示了查询(Q)、键(K)、值(V)向量的投影以及随后的缩放点积注意力(Scaled Dot-Product Attention)计算过程。

2. 核心问题:解码阶段的重复计算

自回归生成的核心挑战在于效率。在标准的解码过程中,为了生成第 t 个token,模型需要将前 t-1 个token组成的完整序列重新输入网络进行计算。这就导致了严重的计算冗余。

问题的根源在于注意力机制(Attention Mechanism)。在每一轮解码中,注意力块都需要计算查询(Query)、键(Key)、值(Value)矩阵之间的缩放点积(Scaled Dot Product):

注意力计算中的矩阵乘法示意图。当序列长度为1000,每个向量的维度为768时,计算QK^T需要实例化(Materialize)一个1000x1000x768大小的张量,消耗大量计算和内存资源。

实例化(Materialise)是一个技术术语,指的是需要为一个特定形状的张量(例如1000x1000,每个维度768)分配内存并进行计算。

考虑生成一个长度为 L 的序列。在无优化的情况下,总计算量大致与序列长度的平方( )成正比,因为每次生成新token时,模型都需要重新处理所有历史token。对于长文本生成,这种计算开销变得难以承受。

3. KV缓存:消除冗余的解决方案

3.1 核心思想

KV缓存(KV Cache) 的关键在于洞察到自回归解码中的一个特性:在生成第 t 个token时,只有当前最新的token(即第 t-1 个token的预测结果)是“新”的输入,而所有历史token的表示在之前的步骤中已经计算过了。

更具体地说,在Transformer的自注意力层中,每个输入token通过线性变换独立地生成其对应的键(K)和值(V)向量。这些向量只依赖于该token自身的嵌入,而不依赖于序列中其他token的位置(位置信息由位置编码单独提供)。因此,一旦计算了某个token的K和V向量,只要该token的嵌入不变,这些向量就可以被缓存并复用于后续所有解码步骤。

3.2 工作原理

KV缓存机制巧妙地利用了这一点。其基本思路是:在每一解码步骤中,仅计算新输入token的K和V向量,并将它们追加到缓存中;在计算注意力时,则使用缓存中所有历史token的K和V向量与当前token的查询(Q)向量进行计算。

KV缓存工作原理示意图。展示了在解码阶段(T>1),模型仅输入新生成的token,并从缓存列表中读取之前所有时间步(Timestep)已计算好的键(K)和值(V)状态,用于当前步的注意力计算。
KV缓存工作原理示意图。展示了在解码阶段(T>1),模型仅输入新生成的token,并从缓存列表中读取之前所有时间步(Timestep)已计算好的键(K)和值(V)状态,用于当前步的注意力计算。

这个“缓存列表”通常是一个按层(Layer)和注意力头(Head)组织的张量队列。在实现上,它可以是动态增长的结构,也可以是预分配好固定大小的缓冲区。

通过引入KV缓存,解码阶段的总计算复杂度从   降低到了  ,因为每个token的K和V向量仅被计算一次。虽然内存占用从   增长到了  ,但在现代硬件上,用额外的内存换取显著的计算速度提升通常是划算的。

4. 预填充阶段:解码的准备工作

4.1 阶段定义与特点

在LLM推理中,预填充阶段(Prefill Phase) 特指处理初始用户提示的第一个推理步骤(即 T=1 的时刻)。

预填充阶段示意图。在T=1时刻,完整的输入提示(例如6个token)被并行地输入模型,模型输出对应每个位置的下一个token预测。

与后续的解码阶段(T>1)不同,预填充阶段具有以下特点:

特性
预填充阶段 (Prefill Phase)
解码阶段 (Decoding Phase)
输入
完整的初始提示(多个token)
仅上一个时间步生成的单个token
处理方式 并行计算

所有输入token同时通过模型
序列计算

循环执行,每次处理一个token
缓存操作 填充缓存

计算并存储所有输入token的K和V向量
更新缓存

仅计算新token的K和V向量,并追加到缓存
计算模式
类似训练时的前向传播
特殊的自回归推理模式
主要耗时
与提示长度成正比,通常一次性完成
与生成长度成正比,循环进行

Transformer本质上是一个序列到序列(Sequence-to-Sequence) 模型。在预填充阶段,当输入 N 个token时,模型会为这 N 个位置中的每一个都计算下一个token的预测。然而,在自回归生成任务中,我们通常只关心最后一个位置的预测结果(即基于整个提示的下一个token),而中间位置的输出会被丢弃。

4.2 与解码阶段的协同

预填充阶段是解码阶段的“准备工作”。它为后续所有解码步骤完成了两件关键事情:

  1. 1. 生成第一个输出token
  2. 2. 计算并填充KV缓存,为后续的高效解码奠定基础。

从 T=2 开始,推理进入纯解码阶段。此时,模型进入一个循环:输入是上一轮预测出的单个token,利用已填充的KV缓存计算注意力,生成下一个token,并将新token的K和V向量追加到缓存中。

5. 代码实现:KV缓存的存储与使用

理解了原理后,我们来看一个高度简化的KV缓存实现伪代码。在实际框架(如Hugging Face Transformers)中,实现会更复杂,但核心逻辑一致。

5.1 向缓存追加数据

在每个Transformer层的自注意力模块中,当计算出当前步的 key_states 和 value_states 后,需要将它们追加到该层对应的缓存中。


   
    
   # 假设 self.key_cache 和 self.value_cache 是预先初始化好的列表或张量,按层索引
# layer_idx: 当前层的索引

# key_states, value_states: 当前步计算出的键和值状态,形状通常为 (batch_size, num_heads, seq_len, head_dim)


# 将当前步的键值状态沿着序列长度维度(dim=-2)拼接到缓存中

self
.key_cache[layer_idx] = torch.cat([self.key_cache[layer_idx], key_states], dim=-2)
self
.value_cache[layer_idx] = torch.cat([self.value_cache[layer_idx], value_states], dim=-2)

关键点

  • • torch.cat 操作沿着序列维度拼接,使得缓存中保存了从第一步到当前步所有token的K和V向量。
  • • 缓存需要在每个解码步骤、每个Transformer层进行更新。

5.2 从缓存读取数据

在计算注意力时,我们需要使用完整的、缓存中的所有历史K/V向量,而不仅仅是当前步新计算出的。


   
    
   # 假设 q_len 是当前查询向量的序列长度(解码阶段通常为1)
# 首先,将当前步的查询(query_states)和键(key_states)重塑为标准的注意力头格式

key_states = key_states.view(bsz, q_len, self.num_key_value_heads, self.head_dim).transpose(1, 2)
query_states = query_states.view(bsz, q_len, self.num_heads, self.head_dim).transpose(1, 2)

# 如果KV缓存存在(即非预填充阶段或预填充阶段后)

if
 kv_cache is not None:
    # 关键步骤:更新缓存,并获取用于当前注意力计算的完整键值状态

    # `kv_cache.update` 内部执行了上述的拼接操作,并返回拼接后的完整缓存

    key_states, value_states = kv_cache.update(key_states, value_states, self.layer_idx)

关键点

  • • 在解码阶段,key_states 和 value_states 经过 kv_cache.update 后,其序列长度维度会从1变为当前总步数。
  • • 随后进行的 QK^T 计算,是当前步的单个查询向量与缓存中所有历史键向量的点积,从而实现了高效的上下文注意力计算。

6. 内存与计算复杂度分析

引入KV缓存带来了显著的效率提升,但也改变了资源消耗的模式。

指标
无KV缓存
有KV缓存
计算复杂度
内存占用(K/V)
主要瓶颈
重复的矩阵计算
内存带宽与容量

解释

  • • 计算复杂度L 是生成的序列总长度,d 是模型维度。无缓存时,每次生成都需重新计算历史token的表示,导致平方复杂度。有缓存时,每个token的K/V只计算一次。
  • • 内存占用:缓存大小与序列长度 L、层数  、注意力头数  、每个头的维度   成正比。对于大模型和长上下文,这可能占用数GB甚至数十GB的GPU内存。
  • • 瓶颈转移:使用缓存后,计算不再是主要瓶颈。从缓存中读取大量K/V数据到计算核心的过程,其内存带宽成为关键限制因素。此外,过长的序列可能因缓存过大而耗尽GPU显存。

7. 实践要点与常见优化

在生产环境中部署带KV缓存的LLM推理时,需要考虑以下要点和优化策略:

  1. 1. 缓存初始化与清空
    • • 在开始一个新的生成任务前,必须清空(或重新初始化)KV缓存,防止不同会话间的信息污染。
    • • 在预填充阶段结束后,缓存应已包含提示词的所有K/V向量。
  2. 2. 内存管理策略
    • • 预分配(Pre-allocation):根据模型支持的最大上下文长度,预分配固定大小的缓存张量。这避免了动态拼接带来的内存碎片和分配开销,是高性能推理库(如vLLM, TensorRT-LLM)的常见做法。
    • • 分页注意力(Paged Attention):类似操作系统内存分页管理,将KV缓存划分为固定大小的“块”,按需分配和释放。这极大提高了显存利用率,支持更长的上下文和更高的并发。
    • • 量化(Quantization):将KV缓存中的浮点数(如FP16)转换为低精度格式(如INT8, INT4)。这能显著减少内存占用,但可能引入轻微精度损失,需谨慎校准。
  3. 3. 计算优化
    • • 融合内核(Fused Kernel):将注意力计算中涉及缓存读取、 、与 相乘等多个步骤融合到一个CUDA内核中执行,减少中间数据的读写,提升计算效率。
    • • 连续缓存(Continuous Cache):确保缓存张量在内存中是连续的,以利于硬件高效访问。
  4. 4. 长上下文处理
    • • 滑动窗口(Sliding Window):某些模型(如Mistral)的注意力本身具有滑动窗口限制,只关注最近的N个token。这自然限制了KV缓存的大小。
    • • 流式逐出(Streaming Eviction):当缓存达到上限时,采用特定策略(如淘汰最旧的token)逐出部分缓存,以容纳新token。

8. 总结与展望

KV缓存与预填充阶段是现代LLM高效推理不可或缺的组成部分。预填充阶段负责一次性、并行地处理初始提示,并为后续解码填充初始缓存;KV缓存则在解码阶段通过存储和重用历史注意力状态,将计算复杂度从平方级降至线性级,实现了量级上的速度提升。

这项技术的本质是用空间换时间。它成功地将推理瓶颈从计算转移到内存管理,从而催生了一系列围绕显存优化、内存带宽利用和高效内核设计的技术创新。

展望未来,随着模型规模持续增长和应用场景对长上下文、低延迟、高并发的要求不断提高,KV缓存技术将继续演进。例如,选择性缓存(只缓存重要的token)、更高效的压缩算法、以及与硬件协同设计的缓存架构,都将是重要的研究方向。深入理解KV缓存,是构建高性能LLM推理服务的基础。

 


【声明】内容源于网络
0
0
ai算法芯片与系统
长期关注ai领域,算法,芯片,软件(系统,框架,编译器,算子库)等联合设计
内容 196
粉丝 0
ai算法芯片与系统 长期关注ai领域,算法,芯片,软件(系统,框架,编译器,算子库)等联合设计
总阅读104
粉丝0
内容196