KV Cache 到底缓存了什么:大模型显存、首字延迟与吞吐量解析

摘要:KV Cache 是自回归大模型推理中的核心状态。它避免模型在生成每个新 token 时重复计算历史 token 的 Key 和 Value,却也会随上下文长度、批量大小和层数持续占用显存。本文从注意力计算出发,解释缓存内容、显存估算、首字延迟、解码吞吐量,以及常见服务端优化为什么有效。

使用大模型 API 或本地推理框架时,经常能看到这些参数和指标:最大上下文长度、首 token 延迟、每秒输出 token 数、KV Cache 使用率和最大并发请求数。

这些概念并不是彼此独立的。它们都和 Transformer 在自回归生成阶段如何处理历史 token 有关。

从自回归生成说起

假设输入是:

KV Cache 可以减少

模型先处理全部输入 token,并预测下一个 token。得到新 token 后,模型再次基于“原输入 + 已生成 token”预测下一项,循环直到遇到停止条件。

如果每一步都重新计算所有历史 token 在每一层注意力中的中间结果,计算量会大量重复。KV Cache 的作用,就是保存历史 token 已经算过的 Key 和 Value,下一步只计算新 token 对应的部分。

注意力层里的 Q、K、V

简化后的单头注意力可以写成:

Q = XWq
K = XWk
V = XWv

Attention(Q, K, V) = softmax(QK^T / sqrt(d))V

这里:

  • X 是当前层的输入表示;
  • Q 用来表达当前 token 在寻找什么信息;
  • K 用来和查询计算相关性;
  • V 提供被加权汇总的内容。

在生成第 t 个 token 时,新 token 的 Query 需要和第 1 到 t 个 token 的 Key 计算注意力,再对对应 Value 加权。

历史 token 的 K 和 V 在同一轮请求中不会变化,因此可以缓存。Query 只服务于当前这次注意力计算,通常没有必要像 K、V 一样跨生成步骤保存。这就是“KV Cache”名称的来源。

Prefill 与 Decode 是两个阶段

推理通常可以拆成两个阶段。

Prefill:处理整段输入

Prefill 阶段一次处理提示词中的多个 token,计算每一层的表示,并建立初始 KV Cache。

输入越长,需要处理的 token 越多,首 token 往往出现得越晚。因此 TTFT 不只受网络影响,也受提示词长度、模型规模、批处理调度和硬件状态影响。

Decode:逐个生成 token

Decode 阶段通常一次为每个活跃序列生成一个 token。借助 KV Cache,模型不需要重新计算历史 token 的 K 和 V,但新 Query 仍需要读取历史缓存并执行注意力。

因此,KV Cache 消除了大量重复计算,却没有让生成成本变成与上下文长度完全无关。上下文越长,每步需要读取和参与注意力的历史状态仍然越多。

KV Cache 为什么占显存

一个粗略估算式是:

KV Cache 字节数
≈ 2 × 层数 × token 数 × KV 头数 × 每头维度 × 每元素字节数 × 批量大小

式子开头的 2 分别代表 Key 和 Value。

为了理解量级,假设一个示例模型具有 32 层、32 个 KV 头、每头维度 128,使用每元素 2 字节的 FP16 或 BF16,单个序列有 4096 个 token。

代入后:

2 × 32 × 4096 × 32 × 128 × 2
= 2,147,483,648 字节
≈ 2 GiB

这只是用来说明公式的假设模型,并不代表某个具体产品。实际模型可能使用多查询注意力或分组查询注意力,使 KV 头数少于 Query 头数;也可能对缓存采用更低精度,或者按页管理内存。

从公式可以看到,缓存会随这些变量近似线性增长:

  • 活跃序列数量;
  • 每个序列已经占用的 token 数;
  • 模型层数;
  • KV 头数量和每头维度;
  • 缓存数据类型的字节数。

为什么 GQA 和 MQA 能减少缓存

标准多头注意力中,Query、Key 和 Value 通常都有多个头。多查询注意力(MQA)让多个 Query 头共享一组 K/V;分组查询注意力(GQA)则让一组 Query 头共享 K/V。

如果 Query 头数是 32,而 KV 头数从 32 减少到 8,前面的估算式中与 KV 头数相关的部分理论上会缩小到四分之一。

这能降低缓存占用和内存带宽压力,但模型结构的变化也会影响训练与质量权衡,不能把它简单理解成没有代价的运行时开关。

TTFT 与每秒输出 token 数为何不同

几个常见指标描述的是不同阶段:

指标主要对应阶段反映什么
TTFTPrefill、排队和网络用户多久看到第一个 token
TPOTDecode相邻输出 token 的平均时间
tokens/sDecode稳态生成速度
端到端时延全流程请求从发出到完成共花多久

长提示词可能明显增加 TTFT,但生成开始后的速度仍然可接受;也可能首 token 很快,却因为上下文很长或系统并发较高,后续 token 输出较慢。

因此,只看一个“总耗时”很难定位问题。测试推理服务时,至少应区分排队与网络时间、Prefill 时间、首 token 到达时间、Decode 阶段的 token 间隔和输出 token 总数。

连续批处理为什么提高吞吐量

不同请求的提示词长度和输出长度各不相同。如果服务端必须等同一批所有请求结束后才能接收下一批,计算资源会出现空闲。

连续批处理会动态移除已完成的序列,并把新请求加入当前调度。这样可以提高设备利用率和整体吞吐量。

但吞吐量与单请求延迟存在权衡:

  • 批量更大,通常更容易充分利用加速器;
  • 同时服务更多序列,会增加 KV Cache 总量;
  • 调度不合理时,单个请求可能等待更久;
  • 长短请求混合时,需要避免长请求持续影响短请求。

所以“每秒处理的总 token 更多”并不自动等于“每个用户都更快”。

Paged Attention 解决什么问题

每个请求最终会生成多少 token,在开始时往往并不确定。若为每个序列提前预留一整块最大长度的连续显存,会产生内部浪费;不同长度请求反复进入和退出,也会带来碎片问题。

Paged Attention 的核心思路是把 KV Cache 切成固定大小的块,并通过逻辑映射组织这些块,类似虚拟内存分页:

  • 不要求一个序列的所有缓存物理连续;
  • 按实际增长逐步分配块;
  • 请求结束后可以回收块;
  • 某些共享前缀场景下可以复用缓存块。

它主要改善缓存的内存管理与利用率,不会改变注意力需要读取历史 K/V 这一基本事实。

前缀缓存什么时候有效

许多请求可能共享相同的系统提示词、工具定义或固定模板。如果服务端能识别完全相同的前缀,就有机会复用该前缀已经计算出的 KV Cache,从而减少重复 Prefill。

要让前缀缓存更容易命中,可以考虑:

  • 把稳定的系统指令放在前面;
  • 避免在固定前缀中加入每次变化的时间戳或随机 ID;
  • 保持工具定义和序列化格式稳定;
  • 把用户每次变化的内容放在共享前缀之后。

但缓存命中还取决于服务端实现、模型版本、分词结果和缓存策略。客户端不能只凭 Prompt 看起来相似,就假定一定复用了缓存。

上下文越长不一定越好

更大的上下文窗口提供了容纳更多信息的能力,但会带来现实成本:

  • Prefill 需要处理更多 token;
  • KV Cache 占用增加;
  • Decode 需要访问更长的历史状态;
  • 无关信息可能干扰模型定位关键内容;
  • API 费用通常与输入和输出 token 数有关。

实际应用中,比“一股脑塞入全部历史”更有效的做法通常包括:删除重复日志、对较早对话做结构化摘要、用检索选择相关文档片段,以及为必须保留的约束建立短而稳定的系统前缀。

这不是单纯压缩字数,而是在有限上下文中提高有效信息密度。

KV Cache 量化的收益与代价

如果把 KV Cache 从 FP16/BF16 降为 FP8 或更低精度,理论上可以减少缓存显存和读取带宽,让同一设备容纳更多并发或更长上下文。

但低精度可能引入量化误差,实际影响与模型、层、任务和实现有关。评估时不能只看显存下降,还应测试:

  • 长上下文任务质量;
  • 事实召回和指令遵循;
  • 不同输入长度下的退化程度;
  • 量化与反量化本身的运行开销;
  • 服务端是否真正支持相应精度和内核。

一个更完整的推理测试记录

测试大模型服务时,可以记录下面这些字段:

model
prompt_tokens
completion_tokens
queue_time_ms
time_to_first_token_ms
total_time_ms
tokens_per_second
finish_reason
http_status
timestamp

如果服务端不返回阶段时间,可以在客户端至少记录请求开始、第一个流式数据块到达和请求结束三个时间点。

比较不同结果时,要保持模型、提示词、输出上限、采样参数和测试网络尽可能一致。少量请求只代表当时的观察,不足以证明长期服务能力。

常见误区

误区一:有 KV Cache 后,长上下文生成就不再变慢

KV Cache 避免重复计算历史 K/V,但每步注意力仍要处理历史缓存。它降低了成本,不是消除了上下文长度的影响。

误区二:显存只由模型权重决定

线上推理还要考虑 KV Cache、临时激活、运行时工作区、内存碎片和并发请求。能装下权重不代表能稳定服务目标上下文和并发量。

误区三:吞吐量高等于单请求延迟低

大批量和连续调度可以提高系统总吞吐,但请求可能经历更长排队或更高 token 间延迟。服务目标必须明确偏向吞吐、延迟,还是二者平衡。

误区四:上下文窗口应该始终用满

窗口是容量上限,不是内容目标。无关上下文会消耗 Prefill、缓存、带宽和费用,还可能降低回答聚焦程度。

结论

KV Cache 缓存的是每层注意力中历史 token 的 Key 和 Value。它用显存换计算,避免自回归生成过程中反复计算相同历史状态。

理解这一点后,很多现象可以串起来:

  • 长 Prompt 会增加 Prefill 与 TTFT;
  • 长上下文和高并发会推高 KV Cache 占用;
  • GQA/MQA、分页管理和缓存量化能降低不同维度的开销;
  • 连续批处理提高系统吞吐,却需要权衡单请求延迟;
  • 精简上下文与稳定前缀既是 Prompt 工程,也是推理系统优化。

评估一个推理方案时,不应只问“模型能支持多少上下文”,还应问:在目标硬件、并发、延迟和质量要求下,真正需要保留多少有效上下文。

更多推荐