想象一位图书管理员在写读后感:每读完一个新词,他就在笔记本上添一行笔记,左边贴索引标签(Key),右边写内容摘要(Value)。下一次落笔前,他必须翻看前面的所有笔记,才能决定该写什么。这本越写越厚、把书桌(显存)越占越满的笔记本,就是大模型的 KV Cache。序列越长,笔记越厚,显存越挤,最终连正常生成都会中断。于是聪明的工程师想出了三招:量化,把字缩小重抄,一页纸装下更多内容;剪枝,把从不翻看的旧笔记直接撕掉;分页管理,把笔记装进活页夹,用到哪页取哪页。三招组合,就是当前长上下文推理与端侧部署中最核心的显存优化方案。这篇内容将带你彻底搞懂 KV Cache 压缩的来龙去脉。
对 AI 从业者的价值:掌握 KV Cache 压缩三件套,是通往端侧大模型与长上下文推理优化的必修课——它直接决定你能部署多长的上下文、省下多少显存成本、承接多大的并发流量。
目录
可视化演示
动态演示KV Cache压缩的核心机制,通过概览/流程/对比/趋势四种模式展示关键概念与交互流程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
KV Cache压缩:KVCa KV Cache内存瓶颈与压缩 KV Cache量化压缩:IN 核心机制可视化 KV Cache压缩整体架构与关键组件
缓存起源:为什么书桌总是被笔记占满
想象一位图书管理员在写读后感。每读完一个新词,他就在笔记本上添一行:左边贴索引标签(Key),右边写内容摘要(Value)。写下一个词之前,他必须先翻看所有旧笔记,才能决定接下来写什么——于是笔记本越厚,写起来越慢,书桌(显存)也越挤。这本不断变厚的笔记本,就是大模型的 KV Cache。
KV Cache 不是模型参数,而是每次请求运行时现场生成的「临时笔记」。Transformer 的每一层注意力都有自己的笔记副本;序列每多一个 token,就多一行 Key 和 Value;同时服务端每多一个并发用户,就会多一本这样的笔记。于是 KV Cache 的显存占用,几乎完全由「序列长度 × 网络层数 × 向量维度」决定。这也是上万 token 的长对话经常「爆显存」的根本原因。
书桌就那么大,所以人们开始想:能不能把笔记写得更密(量化),把不再重要的页面撕掉(剪枝),把暂时不看的页面收进抽屉(分页管理)?三招合起来,就是为了让这本笔记在有限书桌上能写得更长。
- 两路向量每个 token 的 Key 和 Value 各存一份,所以整个缓存先乘 2。
- 层数相乘注意力层数为 N_layer,层越多笔记越厚。
- 序列长度L 个 token 就有 L 行笔记。
- 单向量维度每个 Key/Value 由 d_kv 个数值组成。
- 每数位宽FP16 下每个数占 2 字节,即 b = 16 bit。
量化压缩:把字写小,同页塞下更多内容
如果把笔记本上的字从「两厘米见方」缩小到「四分之一大小」,一页纸就能装下四倍内容。KV 量化就是这个思路:把默认用 16 位浮点(FP16)存储的 Key/Value,换成 8 位整数(INT8)甚至 4 位整数(INT4)。数值精度变了,但注意力计算照样能跑,因为计算矩阵乘时会先把量化值通过缩放系数「反量化」回近似浮点,再参与运算。
只降位宽还不够——如果不做控制,很小的整数范围会把原本差异很大的数值压进同一个格子。于是常用分组量化:把一小段相邻 token 或通道分成一组,每组单独估计一个缩放系数(scale)和零点偏移(zero-point),让量化误差被约束在每组内部。这样比整层共用一个缩放系数精细得多,INT4 也能维持不错的生成质量。
缩印虽然省纸,但字变小了难免会「看错行」,这就是量化误差。好在分组量化、缩放补偿与少量浮点保留通道,能让误差控制在可接受范围。这也是 KV 压缩里最省事、收益最稳的一招。
- 原始占用先算出 FP16 下的 KV 缓存总量 M_fp16。
- 新位宽量化后每个数的新位宽 b_q,通常取 8 或 4。
- 位宽比例缓存大小近似按位宽比例线性缩放,INT8 对应 8/16 = 1/2。
- 压缩结果INT8 减半,INT4 降到四分之一。
剪枝策略:撕掉再也不翻的旧页
图书管理员发现某些页笔记从写完起一次都没被翻过,果断撕掉也不影响阅读。KV 剪枝就是这种「断舍离」:根据注意力机制给出的分数,判断哪些历史 token 的缓存是「长久冷门」,然后把它们的 K/V 从缓存中移除。
生成新词时,attention 得分越高,说明该历史 token 对当前预测越重要;反之,长期得分很低的 token 通常可以安全丢弃。H2O(Heavy Hitter Oracle)一类方法就是动态维护「高频重要 token + 最近窗口」,把重要性全局排名后只保留 Top 比例。这样既能留下「主角笔记」,又丢掉大量「背景噪音」。
撕掉的页再也找不回来,所以剪枝要留缓冲:保留最近窗口、必要时重计算被丢弃的高层信息,确保关键 token 能重新出现。牺牲一小部分精度,换取显存与速度的显著提升,在长上下文场景非常划算。
- 注意力分数每个历史 token i 有一个注意力得分 a_i,表示它对当前预测的贡献大小。
- 保留阈值设定一个分数线 θ,分数高于 θ 的笔记留下。
- 示性函数满足条件记 1,不满足记 0,用于统计。
- 保留比例对全序列求和再除以 L,得到被保留 token 的占比。
分页管理:活页夹按需取用
管理员不再把整本笔记摊开在桌面,而是用活页夹分装成若干页,手边只放当前要用的那页。PagedAttention 等方案恰好借鉴了操作系统的虚拟内存分页:把逻辑上连续的 KV 序列切分为固定大小的块,通过「逻辑块号 → 物理块号」的映射,在显存中按需装载。
这样做有两个好处:块之间不要求物理连续,显存碎片被大幅削减;不活跃的块可以换出(GPU 到 CPU 甚至磁盘),需要时再预取回来。分页的代价是多了地址映射和调度开销。块太小则页数过多、映射开销大,块太大则内部碎片多,工程上通常选 16 或 32 或 64 个 token 作为一块。
活页夹让「物理书桌」永远只需放少量页面,序列再长也不会瞬间爆桌;配合合理的预取策略,大部分翻页延迟可以被掩盖。这是三合一的第三只轮子。
P_{\mathrm{total}}=\left\lceil \frac{L}{B}\right\rceil,\qquadr_{\mathrm{active}}=\frac{P_{\mathrm{active}}}{P_{\mathrm{total}}}- 块大小每块固定装 B 个 token 的 K/V 笔记。
- 总页数序列长度 L 以 B 为单位向上取整,得到总页数。
- 活跃页数当前计算真正需要用到的页数 P_active。
- 驻留比例常驻显存约等于总 KV 的活跃页占比。
三合一组合:缩字、撕页、活页一起上
三招各管一个维度:量化管「每行笔记的字有多密」,剪枝管「多少行笔记可以删」,分页管「哪些笔记可以暂时不摆在桌面」。它们作用于不同层面,因此显存收益不是相加,而是相乘。
工程上通常按「量化 → 剪枝 → 分页」组织管线:先对 KV 做 INT4 量化,缩小单条笔记的存储成本;再按注意力分数剪掉低价值 token,减少笔记总行数;最后把剩余 KV 块交给分页调度器,只让活跃页常驻显存。三者叠加后,单 token 的显存成本可以从 2 字节一路降到不到 0.1 字节。同时要留意精度协同:量化误差与剪枝误差可能叠加,分页虽不改变数值,却需要管理好页面命中率。
管理员终于不用换更大的书桌:字小了、页撕了、活页夹只留常看的两三页——即便二十万字的长篇小说也能在随身笔记本上写完全程。工程复杂度确实上升,但这正是从云端走向端侧的必经之路。
- 量化因子位宽比决定每行笔记的存储缩小幅度。
- 剪枝因子保留 token 比例决定还剩多少行笔记。
- 分页因子活跃页比例决定常驻显存占整体的比例。
- 三积连乘三个独立因子直接相乘得到最终占用。
端侧部署:把长笔记搬进随身书桌
手机上几乎没有「大书桌」。移动 SoC 的共享内存通常只有 8 到 16GB,还要留给系统和 App,能分给模型的往往不到一半。没有 KV 压缩,手机最多只能流畅处理几千 token;三合一方案则让数万 token 的长文档问答成为可能。
llama.cpp、MLC-LLM 等端侧推理框架已内置 INT4/INT8 KV 量化与分页调度。评估时主要看三个指标:可服务的最大上下文长度、每 token 生成延迟、以及内存/电量占用。实际调参中,块大小、剪枝比例、量化分组大小互相牵制,需要针对具体模型做组合评测。
当管理员把笔记从图书馆大书桌搬到手机大小的随身书桌,三合一方案让长上下文不再是高端 GPU 的专利。量化负责缩小、剪枝负责减量、分页负责调度,三者合起来,就是端侧大模型「装得下、跑得动」的关键钥匙。
- 可用显存模型能支配的显存/内存总量 M_avail。
- 单 token 成本两个向量乘层数乘维度,再乘位宽对应的字节数。
- 压缩因子再乘剪枝保留比例与分页活跃比例。
- 可服务长度显存除以压缩后的单 token 成本,得到上下文长度上限。
AI 应用场景
长文档问答:手机上的合同助手
阶段:推理部署与端侧优化
解决的问题:传统全量 KV 缓存下,20 万字的合同要让手机放几十 GB 显存,几乎不可能。三合一方案先用 INT4 量化把 KV 压到四分之一,再按注意力分数剪掉约 40% 低价值 token,最后用分页管理把 KV 块按需加载,整份合同的关键上下文被压进手机内存。
具体案例:法务 App「律助」集成 llama.cpp 风格推理引擎,在骁龙旗舰芯片上对 300 页 PDF 提问「赔偿条款是什么」;由于只加载活跃页,首 token 延迟从 5 秒降到 1.2 秒,上下文长度从 4K 提升到 32K。
云端对话服务降本
阶段:推理服务化与显存调度
解决的问题:SaaS 聊天机器人要同时服务上千会话,每个会话的 KV 都常驻显存,成本爆炸。把分页管理引入服务端调度器后,不活跃会话的 KV 块可在 GPU 与 CPU 间换入换出,再结合 INT8 量化压缩单会话占用。
具体案例:某客服 SaaS 平台在 8×A100 集群上用 PagedAttention 加量化,支持并发从 256 提升到 1200,单 token 推理成本下降约 70%。
离线知识库:车载与边缘设备
阶段:移动端模型推理
解决的问题:车载助手不能依赖云网络,需要把模型和知识库都放进车机。分页管理让长文档的 KV 缓存不必一次载入,量化进一步压低内存峰值,使得离线场景也能做长文本摘要与问答。
具体案例:智能座舱语音助手在 8GB 车机内存上,用三合一方案离线问答一份 50 页用户手册,问答准确率与云端版本相差不到 2%。
Python 演示
"""KV Cache 压缩演示:量化 + 剪枝 + 分页的显存估算。""" import math class KVCacheCompressor: """用模拟数据估算三合一后的 KV 缓存占用。""" def __init__(self, token_count, layer_count, d_kv): self.token_count = token_count # 序列长度 L self.layer_count = layer_count # Transformer 层数 self.dim_kv = d_kv # 每个 K/V 向量的维度 self.fp16_bytes = 2 # FP16 下每个数占 2 字节 def fp16_memory(self): # 未压缩 KV 缓存字节数: 2 路 x 层 x token x 维度 x 字节 return 2 * self.layer_count * self.token_count * self.dim_kv * self.fp16_bytes def quantized_memory(self, bit_width=8): # 量化后按位宽比例缩放 return self.fp16_memory() * bit_width / 16.0 def pruned_memory(self, keep_ratio=0.5): # 剪枝后只保留 top keep_ratio 的 token keep = int(self.token_count * keep_ratio) return self.fp16_memory() * keep / self.token_count def paged_memory(self, block_size=16, active_blocks=4): # 分页后只有活跃块常驻显存 total_blocks = max(1, math.ceil(self.token_count / block_size)) return self.fp16_memory() * active_blocks / total_blocks def combined_memory(self, bit_width=4, keep_ratio=0.6, block_size=16, active_blocks=4): # 三合一: 量化 x 剪枝 x 分页 三个比例相乘 total_blocks = max(1, math.ceil(self.token_count / block_size)) r_q = bit_width / 16.0 r_p = keep_ratio r_b = active_blocks / total_blocks return self.fp16_memory() * r_q * r_p * r_b def main(): c = KVCacheCompressor(token_count=4096, layer_count=32, d_kv=128) base = c.fp16_memory() q = c.quantized_memory(4) p = c.pruned_memory(0.6) page = c.paged_memory(block_size=16, active_blocks=4) final = c.combined_memory(bit_width=4, keep_ratio=0.6, block_size=16, active_blocks=4) print("FP16 baseline: {:.1f} MB".format(base / 1024 / 1024)) print("INT4 quantize: {:.1f} MB".format(q / 1024 / 1024)) print("60% prune: {:.1f} MB".format(p / 1024 / 1024)) print("4 active pages: {:.1f} MB".format(page / 1024 / 1024)) print("combined: {:.1f} MB".format(final / 1024 / 1024)) print("compression ratio: {:.1f}x".format(base / final)) if __name__ == "__main__": main()
return 2 * self.layer_count * self.token_count * self.dim_kv * self.fp16_bytes这是 KV 缓存的原始显存公式,2 表示 Key 和 Value 两路。return self.fp16_memory() * bit_width / 16.0量化压缩只关心位宽比,INT4 就是原来的 25%。keep = int(self.token_count * keep_ratio)剪枝先算出保留多少 token,再按比例估算显存。total_blocks = max(1, math.ceil(self.token_count / block_size))分页把序列切成固定大小的块。return self.fp16_memory() * r_q * r_p * r_b三合一的核心:三个压缩因子直接相乘,得到最终显存。print("compression ratio: {:.1f}x".format(base / final))输出总压缩比,直观看到三合一带来的数量级收益。
常见误区
误区我以为 KV Cache 和模型权重一样,训练完就固定存在,压缩它等于压缩模型本身,会影响所有用户。
正解KV Cache 是推理时动态生成的「上下文笔记」,每来一个新请求都要重新写一遍。它属于临时状态而不是永久知识。压缩 KV 只影响当前这段对话的上下文表达,不影响模型权重和既有能力。可以类比:撕掉管理员某次写错的草稿,不会改变他脑子里的知识。
为何常见:KV 和权重都占显存、都叫「缓存/参数」,概念边界容易被混淆。
误区既然 INT4 能省四分之三显存,那就无脑上 INT4,生成结果应该和 FP16 一模一样。
正解量化是「缩印」,字变小后必然有信息损失。随机误差和系统误差都会影响生成质量,尤其在长上下文、稀疏注意力任务上更明显。因此需要分组量化、缩放系数校准和少量浮点通道补偿,而不是直接截断位宽。
为何常见:广告里的「量化无损加速」说的是经过精细校准后的表现,初学者容易把营销话术当成理论保证。
误区反正 KV 那么多,随机丢掉一半 token 的缓存,显存省一半,效果应该差不多。
正解随机丢等于闭眼撕笔记,可能把关键索引页撕掉。正确做法是根据注意力分数做全局排序,只丢弃长期低分 token,并保留最近窗口和必要重计算缓冲。H2O 等方法的精髓是「判断哪些笔记值得保留」,而不是「随便丢一些」。
为何常见:把 KV 剪枝类比成数据采样,忽略了注意力结构里 token 重要性高度不均的特点。
误区分页管理=把 KV Cache 搬到硬盘上,每次读都走磁盘,速度肯定会慢到不可用。
正解分页借鉴的是操作系统虚拟内存:以固定块为单位调度,活跃块留在显存,不活跃块才换出。配合预取和局部性原理,大部分时间都在访问显存内页面,换页只是少数情况。它不是「慢速持久化」,而是一套精细的显存资源管理策略。
为何常见:把「分页」和「换出到慢速存储」划等号,忽略了它管理显存碎片与按需调度的核心价值。
误区量化省 75%,剪枝省 40%,分页省 90%,加起来一共省了 200% 多,显存直接变负数。
正解三个机制作用在不同维度,总占用是三者的乘积。比如原始 100MB,量化后 25MB,剪枝后 15MB,分页后 1.5MB,压缩比是 66 倍,而不是把百分比相加。乘积关系决定了组合能带来数量级收益,但也意味着误差与复杂度会叠加。
为何常见:日常生活中的折扣常以「满减叠加」来理解,人们习惯做加减法,忽略了独立因子复合时应该相乘。
面试题
Q1. KV Cache 中的 K 和 V 分别代表什么?
- A.查询向量与输出向量
- B.键向量与值向量← 正确答案
- C.输入嵌入与权重矩阵
- D.注意力与隐状态向量
答案:B。注意力机制里 Query 去匹配 Key、再取出对应的 Value,所以 K 是键向量、V 是值向量。可以理解为笔记本上的「索引标签」和「正文内容」。
Q2. 将 KV 缓存从 FP16 换成 INT8,理论上占用变为原来的多少?
- A.四分之一
- B.二分之一← 正确答案
- C.保持不变
- D.提升两倍
答案:B。FP16 每个数 2 字节,INT8 每个数 1 字节,位宽比 8/16 = 1/2。这就像把两厘米见方的字缩成一厘米,同样一页纸能装两倍内容。
Q3. 在序列长度固定时,KV Cache 显存主要随哪个因素近似线性增长?
- A.模型参数量
- B.随机种子数
- C.序列长度← 正确答案
- D.学习率大小
答案:C。KV Cache 与参数无关,它随 token 数量增长:每多一个 token 就多一行 Key 和 Value。序列越长,笔记越厚,书桌越挤。
Q4. KV Cache 剪枝时,通常依据哪个分数来判断 token 是否重要?
- A.梯度范数
- B.注意力权重← 正确答案
- C.词频统计
- D.随机采样
答案:B。生成新 token 时,注意力权重表示历史 token 对当前预测的贡献。长期低分的 token 被视为「冷门笔记」,优先丢弃。
Q5. PagedAttention 的分页灵感直接来源于哪里?
- A.操作系统虚拟内存← 正确答案
- B.数据库 B+ 树
- C.分布式缓存技术
- D.图神经网络计算
答案:A。PagedAttention 正是借鉴操作系统把逻辑页映射到物理页的做法,将 KV 切块管理,解决显存碎片和按需加载问题。
Q6. 以下哪些是 KV Cache 量化中常用的新数值位宽?
- A.32 位浮点
- B.8 位整型← 正确答案
- C.4 位整型← 正确答案
- D.1 位整型
答案:BC。INT8 和 INT4 是主流量化位宽,分别把 FP16 显存降到 1/2 和 1/4。32 位浮点是放大而非压缩,1 位整型过于激进、实践中很少直接用于 KV。
Q7. 相比整层共用一个缩放系数,分组量化的主要优势是什么?
- A.降低显存碎片率
- B.缩小每组量化误差← 正确答案
- C.提升矩阵乘性能
- D.减少页面换入次数
答案:B。分组量化把相邻 token 或通道分成小组,每组单独估计 scale 与 zero-point,数值分布拟合更精细。就像给每页笔记单独定字号,而不是整本书强行统一。
Q8. 三合一方案的总显存约等于原始显存乘以什么?
- A.三个压缩率之和
- B.三个压缩率之积← 正确答案
- C.最大压缩率平方
- D.平均压缩率翻倍
答案:B。量化、剪枝、分页作用于不同维度,互不冲突,因此总占用是三个因子相乘。比如 0.25 × 0.6 × 0.1 = 0.015,而不是把节省比例做加法。
Q9. 关于 KV Cache 剪枝,下列说法正确的有哪些?
- A.基于注意力分数丢弃← 正确答案
- B.丢弃后不能精确恢复← 正确答案
- C.可以无条件无限剪枝
- D.需保留窗口补充缓冲← 正确答案
答案:ABD。剪枝依据注意力分数,把低分 token 的 KV 移除;一旦丢弃就无法精确找回,所以必须保留最近窗口或重计算机制兜底。无限剪枝会让模型失去关键上下文。
Q10. 分页管理方案中,哪些属于关键设计要素?
- A.块为最小分配单元← 正确答案
- B.逻辑块映射物理页← 正确答案
- C.活跃块按需加载← 正确答案
- D.块越大总是越好
答案:ABC。分页以固定块为单元,通过逻辑到物理的映射管理显存,并只加载活跃块。块大小需要权衡:太小映射开销大,太大内部碎片多,并非越大越好。