EN

KV 缓存优化:技术深度解析

键值缓存如何加速自回归生成

返回教程列表
高级11 分钟
AI Skill Navigation 编辑团队

KV 缓存优化:技术深度解析

键值缓存如何加速自回归生成

KV Cache 优化深解(2026):吞吐瓶颈在缓存不在权重——每 token 字节公式与实算(8B 模型 8K 上下文≈1GB)、PagedAttention、GQA 选型、FP8 量化、前缀缓存与提示词稳定前缀设计,按优先级给出行动清单。

KV 缓存优化:技术深度解析

如果你在部署 LLM,限制吞吐量的通常是 KV 缓存,而不是模型权重。权重是固定成本;KV 缓存随着每个并发请求的每个 token 增长,在大规模场景下,当权重只占显存一半时,30 个并发用户就能让 GPU 内存耗尽。本指南涵盖缓存的本质、如何估算大小,以及真正能提升生产指标的那些优化。

什么是 KV 缓存

自回归生成每次产生一个 token,每个新 token 需要关注之前的所有 token。如果不缓存,你会在每一步重新计算整个前缀的注意力键(K)和值(V)——复杂度为 O(n²)。KV 缓存将每个 token 的 K 和 V 张量存储一次,使得生成每个 token 的复杂度降为 O(n)。这是最重要的推理优化,所有服务栈都在使用。

代价:内存。每个 token,缓存需要存储每一层和每个 KV 头的 K 和 V:

text
bytes_per_token = 2 (K 和 V) × layers × kv_heads × head_dim × bytes_per_element

实际例子——Llama-3.1-8B(32 层,8 个 KV 头,head_dim 128,FP16): 2 × 32 × 8 × 128 × 2 字节 = 每个 token 131 KB。一个 8K 上下文的请求:约 1 GB。二十个这样的请求:约 20 GB——比 8B 模型本身的权重(FP16 下约 16 GB)还大。这就是问题所在。

优化 1:PagedAttention(使用真正的推理引擎)

朴素方法为每个请求预分配一个最大长度的连续缓冲区;由于大多数请求远短于最大长度,实际利用率可能很差——vLLM 论文测量到朴素服务中 60–80% 的缓存内存被浪费。PagedAttention(vLLM 的核心思想)像虚拟内存一样管理缓存:按需分配固定大小的块,无需连续性,几乎无碎片。这是 vLLM 类引擎相比朴素 HuggingFace 服务能带来数倍吞吐提升的主要原因——也是 KV 优化的第一课:*不要手写服务;运行 vLLM、TensorRT-LLM 或 SGLang*。对比见我们的推理优化指南

优化 2:GQA——更少的 KV 头(模型层面)

看公式:kv_heads 会放大所有项。分组查询注意力让每个 K/V 头被一组查询头共享——Llama-3 的 32 个查询头共享 8 个 KV 头,相比经典多头注意力减少 4 倍缓存,质量损失很小。现代开源模型(Llama 3、Qwen 2.5、Mistral)已经内置 GQA;实际要点是*模型选择决定了你的缓存预算*——在容量规划前检查配置中的 num_key_value_heads。(MQA——单个 KV 头——是极端版本;DeepSeek 的 MLA 将 KV 压缩为潜在向量以获得更大节省。)

优化 3:KV 缓存量化

权重量化但缓存保持 FP16 是在浪费内存。vLLM 支持 FP8 KV 缓存--kv-cache-dtype fp8),相比 FP16 缓存内存减半——意味着并发请求数或每 GPU 上下文长度翻倍——对大多数工作负载精度影响很小(请自行基准测试;长多轮对话是敏感场景)。INT4 KV 方案存在于研究和 llama.cpp 中,质量损失更明显。

优化 4:前缀缓存——避免重复计算系统提示

生产流量中前缀经常重复:相同的系统提示和 few-shot 示例块出现在每个请求开头;多轮对话会重新发送不断增长的历史。自动前缀缓存(vLLM --enable-prefix-caching,SGLang 的 RadixAttention)按内容哈希存储 KV 块并在请求间复用——共享的 2K token 前导码只计算一次,后续请求直接跳到新 token。对于提示重、输出轻的工作负载,首 token 时间显著下降。托管 API 以“提示缓存”形式提供相同功能,并对缓存输入打折——同样的设计规则适用于所有场景:将稳定内容(系统提示、示例、文档)放在前面,将易变内容(用户问题、时间戳)放在后面,因为缓存是前缀匹配,一个字节的改变会使之后的所有内容失效。

优化 5:限制进入缓存的内容

  • 限制 max_model_len 为工作负载实际需要的长度——针对从未使用的 128K 理论上下文做容量规划会浪费整个预算。
  • 服务端裁剪对话历史(滑动窗口+摘要)而不是重新发送所有内容;不发送的 token 就是不需要分配的缓存。
  • 某些架构从结构上限制缓存(滑动窗口注意力层限制每个窗口的 KV)——这是另一个模型选择杠杆,而非服务标志。
  • 按顺序执行的操作

  • 使用 vLLM/SGLang/TensorRT-LLM 提供服务(PagedAttention 是基本要求)。
  • 选择 GQA 模型并检查 num_key_value_heads
  • 启用前缀缓存;按稳定内容优先重构提示。
  • 计算每 token 字节数,规划并发;限制 max_model_len
  • 如果仍然内存受限:使用 FP8 KV 缓存,然后重新基准测试质量。
  • 常见问题

    KV 缓存会改变输出吗? 不会——它存储的是原本需要重新计算的精确值。(量化会引入近似。)

    为什么第一个 token 慢,后面的快? 预填充阶段计算整个提示的 KV(计算受限);解码阶段逐 token 生成并读取缓存(内存带宽受限)。前缀缓存针对前者;其他优化针对后者。

    批处理会共享缓存吗? 调度是共享的(连续批处理交错请求),但每个请求的 KV 条目是独立的——只有前缀缓存会去重相同内容。


    *最后更新:2026 年 6 月。数字来源于模型配置;请根据自身工作负载进行基准测试。*

    相关工具

    vllmpython