2、大模型推理基础:Transformer解码过程、KV Cache的产生与作用、自回归生成的内存瓶颈

在深入vLLM的内存管理与PagedAttention机制之前,我们必须先建立对Transformer解码过程与自回归生成本质的清晰认知。大模型推理之所以面临严峻的内存压力,根源在于其逐token生成的计算模式与KV Cache的持续累积。本章将拆解解码过程的每一步,揭示KV Cache为何产生、如何工作,并量化分析其带来的内存瓶颈,为后续学习PagedAttention的优化策略奠定坚实的理论基础。

Transformer解码与KV Cache内存增长链路 输入 Token 序列 [t1, t2, ..., tn] Embedding + 位置编码 Transformer Decoder 层 ① Masked Self-Attention ② 前馈网络 (FFN) ③ LayerNorm + 残差连接 (重复 N 层) KV Cache 存储 Key 矩阵: [L, H, D] Value 矩阵: [L, H, D] L=序列长度 H=注意力头数, D=头维度 写入 下一轮读取 自回归生成循环(逐 Token 解码) Step i: 输入 t_i 前向计算 (含注意力) Softmax 采样 得到 t_{i+1} KV Cache 追加 新 token 的 K/V 循环直到生成 [EOS] 或达到最大长度 内存瓶颈量化 KV Cache 大小 ≈ 2 × 层数 × 注意力头数 × 头维度 × 序列长度 × 字节数 示例: 7B模型, 序列2048, FP16 → 约 1.5 GB 仅用于 KV Cache

2.1 Transformer解码过程:从输入到输出的逐Token生成

现代大语言模型(如GPT系列、LLaMA、Qwen等)均基于Transformer的解码器(Decoder)架构,采用自回归(Autoregressive)方式生成文本。所谓自回归,是指模型在生成第 \(i+1\) 个token时,会将前 \(i\) 个token(包括输入提示词和已生成的部分)全部作为上下文输入,通过前向计算预测下一个token的概率分布,再通过采样策略(如贪心搜索、Top-k、Top-p等)选出最终的token。这个过程循环往复,直到生成结束符(EOS)或达到预设的最大长度。

以一次典型的推理请求为例,假设用户输入提示词“中国的首都是”,模型需要生成“北京”。实际解码过程如下:

  1. 输入预处理:将提示词分词为token序列,如 ["中国", "的", "首都", "是"],并转换为对应的嵌入向量(Embedding),同时加入位置编码(Positional Encoding)以保留词序信息。
  2. 逐层前向计算:嵌入向量依次通过N层Decoder层。每一层包含两个核心子层:掩码多头自注意力(Masked Multi-Head Self-Attention)和前馈网络(FFN)。掩码注意力确保每个位置只能关注到它之前(含自身)的位置,防止未来信息泄漏。
  3. 输出层与采样:最后一层输出的隐状态经过线性变换(LM Head)映射到词表大小的logits向量,经Softmax归一化为概率分布。采样策略从中选出下一个token,例如“北”。
  4. 拼接与迭代:将新生成的“北”拼接到原序列末尾,作为下一步的输入,重复上述过程,直至生成“京”和结束符。

这种逐token生成模式决定了推理无法像训练那样并行处理整个序列,而是必须串行迭代。每一次迭代都需要重新计算整个序列的注意力,但其中大部分计算是重复的——这正是KV Cache存在的根本原因。

2.2 KV Cache的产生:消除重复计算的关键机制

在自回归解码的每一步中,如果不对历史信息做缓存,模型需要重新计算所有历史token的Key和Value矩阵。以第 \(t\) 步为例,输入序列长度为 \(t\),若不使用KV Cache,则需对长度为 \(t\) 的序列做完整的注意力计算;到第 \(t+1\) 步,又需对长度为 \(t+1\) 的序列重新计算。这导致计算量呈 \(O(t^2)\) 增长,且大量计算是重复的——前 \(t\) 个token的Key和Value在每一步都被重新计算一遍。

KV Cache的核心思想:在每一步前向计算中,将每一层注意力模块计算出的Key矩阵和Value矩阵保存下来。当生成下一个token时,只需计算新token对应的Key和Value,并与缓存中已有的历史K/V拼接,即可完成注意力计算。这样,每一步的计算量从 \(O(t^2)\) 降为 \(O(t)\),极大提升了推理速度。

KV Cache的存储结构为:对于每一层 \(l\),缓存包含两个张量——Key Cache和Value Cache,形状均为 [batch_size, num_heads, seq_len, head_dim]。其中 seq_len 是当前已生成的token总数(含提示词),head_dim 是每个注意力头的维度。以LLaMA-7B为例,其配置为:32层、32个注意力头、头维度128,采用FP16(2字节)存储。当序列长度为2048时,单条请求的KV Cache占用内存计算如下:

KV Cache大小 = 2 (K和V) × 32层 × 32头 × 128维 × 2048序列长度 × 2字节
             = 2 × 32 × 32 × 128 × 2048 × 2
             = 1,073,741,824 字节 ≈ 1.0 GB

这意味着,仅一条2048长度的请求,KV Cache就需占用约1GB显存。若并发处理多请求,内存压力将成倍增长。

关键理解:KV Cache不是一种“优化技巧”,而是Transformer解码架构的必然产物。它用显存空间换取计算时间,是推理引擎必须管理的第一类显存资源。vLLM的PagedAttention正是针对KV Cache的碎片化和动态增长问题设计的。

2.3 自回归生成的内存瓶颈:显存消耗的量化分析

在推理过程中,显存消耗主要分为三部分:模型权重、激活值(Activations)和KV Cache。模型权重是静态的,加载后常驻显存;激活值是前向计算过程中的中间变量,随批次大小和序列长度变化;KV Cache则随生成进度动态增长。下表对比了三者的特点:

内存类型 大小特征 生命周期 管理难度
模型权重 固定,与参数量成正比(7B模型FP16约14GB) 常驻显存 低,一次性加载
激活值 随批次大小、序列长度变化,但可预估 单次前向计算后释放 中,需优化算子融合
KV Cache 随序列长度线性增长,且每请求独立 从生成开始到结束持续存在 高,动态分配、碎片化严重

在长序列生成场景下,KV Cache往往成为显存占用的绝对主力。例如,在7B模型上处理并发32个请求、每个请求生成2048个token时,仅KV Cache就需要约32GB显存,远超模型权重本身。更棘手的是,KV Cache的内存分配是动态且不可预测的——不同请求的生成长度不同,传统显存分配方式(如预分配最大长度)会造成大量浪费,而按需分配则导致频繁的显存申请和释放,产生碎片。

2.4 案例拆解:一次长对话中的显存危机

假设某在线服务部署了一个13B参数的对话模型(FP16权重约26GB),单张A100-80GB显卡。服务配置最大输入长度1024,最大生成长度1024,即单请求最大序列长度2048。13B模型典型配置为40层、40头、头维度128,KV Cache单请求最大占用:

2 × 40 × 40 × 128 × 2048 × 2字节 = 1,677,721,600字节 ≈ 1.56 GB

初始状态:模型权重26GB + 激活值预留4GB + 系统预留2GB = 32GB,剩余约48GB可用于KV Cache。理论上可并发约30个满长度请求。然而实际运行中出现了以下问题:

  • 现象:并发请求数超过15个时,出现显存不足(OOM)错误,服务崩溃。
  • 分析:并非所有请求都达到最大长度,但传统推理框架(如FasterTransformer的简单实现)会为每个请求预分配最大长度的KV Cache空间。15个请求 × 1.56GB = 23.4GB,加上权重26GB和激活值,已接近80GB上限。而实际平均生成长度仅约500 token,浪费了约60%的显存。
  • 进一步排查:即使将预分配长度改为动态增长,频繁的显存申请(cudaMalloc)和释放导致显存碎片化,可用显存被切割成大量不连续的小块,无法满足大块连续分配需求。
  • 解决思路:这正是vLLM的PagedAttention要解决的核心问题——将KV Cache划分为固定大小的块(Page),按需分配,块与块之间通过索引表连接,无需物理连续。同时,块大小可配置(如16或32个token),极大减少内存浪费和碎片。

风险提示:在生产环境中,KV Cache的管理不当不仅会导致OOM,还会引发服务延迟抖动。因为显存分配和释放是同步操作,频繁调用cudaMalloc会阻塞计算流,造成推理延迟飙升。vLLM通过预先分配大块显存池(Memory Pool)并在池内进行块级管理,避免了这一风险。

2.5 常见误区与总结复盘

在理解KV Cache和内存瓶颈时,开发者常陷入以下误区:

  • 误区一:认为KV Cache只与模型大小有关。实际上,KV Cache与序列长度、层数、注意力头数、头维度直接相关,与模型参数量无直接比例关系。一个7B模型如果层数多、头数多,其KV Cache可能比13B模型更大。
  • 误区二:忽略批次大小的影响。KV Cache是每请求独立的,并发请求数线性增加总KV Cache占用。在服务端推理中,批次大小往往比单请求长度更影响显存。
  • 误区三:认为KV Cache可以无限增长。当序列超过预设最大长度时,需要截断或压缩历史信息(如滑动窗口、H2O等剪枝方法),否则显存必然溢出。
  • 误区四:混淆KV Cache与激活值。激活值在每次前向计算后即可释放,而KV Cache必须保留到该请求生成结束。两者的生命周期管理策略完全不同。

总结复盘:Transformer的自回归解码模式决定了KV Cache的必然存在,它是推理性能与显存消耗之间的核心权衡点。理解KV Cache的产生机制、存储结构和增长规律,是掌握vLLM内存优化的前提。下一层级的思考是:既然KV Cache如此重要且占用巨大,我们能否在操作系统层面借鉴虚拟内存的分页机制来管理它?这正是PagedAttention的核心灵感来源。