一个长上下文聊天服务很容易遇到这样的情况:模型权重已经固定放进 GPU,算力也没有完全跑满,但并发请求一增加,服务却先因为显存不足而拒绝新会话。继续观察会发现,真正持续增长的对象不是模型参数,而是每个请求自己的 KV Cache。Prompt 越长、生成越久、同时活跃的会话越多,缓存就越大。
KV Cache 量化针对的正是这部分“随请求增长的状态”。它把原本以 FP16 或 BF16 保存的 Key 和 Value 压缩成更低位宽的表示,希望用更少显存保留同样长度的历史上下文。表面上,这很像把模型权重从 16 Bit 压到 8 Bit、4 Bit,实际却更棘手:权重通常在部署前就已确定,而 KV Cache 每生成一个 Token 都会新增,量化、存储、读取和反量化全部发生在在线请求的关键路径上。
KV Cache 为什么会从辅助结构变成主要显存成本
自回归模型在生成第一个 Token 时,会为已有上下文计算 Key 和 Value。生成第二个 Token 时,新 Query 需要与之前所有 Key 做注意力计算,同时利用对应的 Value 聚合历史信息。为了避免每一轮都重新计算旧 Token 的 Key 和 Value,推理系统把它们缓存下来,这就是 KV Cache。
缓存规模与多个维度近似线性相关:Transformer 层数、KV Head 数量、每个 Head 的维度、序列长度、批次中的活跃请求数量,以及每个元素的存储位宽。对于单个请求,序列长度从几千增长到几万时,KV Cache 会随之增长;对于在线服务,同一张卡上同时运行的请求数量又会把这部分成本继续放大。
这也是为什么 GQA 能直接降低缓存规模。GQA 减少 KV Head 数量,相当于从模型结构层减少每个 Token 需要保存多少组 K/V。但即使已经采用 GQA,只要上下文足够长或并发足够高,剩余缓存仍可能成为瓶颈。KV Cache 量化是在另一个维度继续压缩:不再减少 KV 的“份数”,而是降低每个元素的位宽。
假设某个服务已经使用 GQA,每个 Token 仍会在每一层产生固定数量的 K/V 元素。若这些元素原本以 16 Bit 保存,改成 4 Bit 后,纯数据体积理论上可以下降到原来的四分之一;改成 2 Bit 则更小。但生产系统不能直接把“位宽缩小四倍”换算成“总显存一定缩小四倍”,因为量化参数、残留高精度区域、对齐、分页元数据和其他运行状态也会占空间。
最简单的统一低比特量化为什么容易失真
量化需要把连续的浮点数映射到有限的离散整数级别。位宽越低,可用的离散值越少。如果一组数的分布非常集中,少量刻度也能较好表示;一旦同一组里存在远离主体分布的离群值,整个量化范围会被拉大,普通数值只能挤在很少几个刻度之间,误差随之增加。
KV Cache 的难点在于,Key 和 Value 并不一定呈现相同的数据分布。若把所有元素统一按一个大范围量化,某些维度上的异常值可能显著影响同组其他元素。对于 Attention 而言,误差也不是均匀传播的:Key 参与 Query 与 Key 的相似度计算,Value 则参与后续加权聚合。不同位置、不同 Channel 上的误差,对最终注意力分布和输出表示的影响可能不同。
因此,工程问题不再是“能不能把 FP16 强制转换成 INT2”,而是“应该按什么粒度共享量化参数,哪些异常值需要隔离,哪些区域应该保留较高精度”。位宽只是最终格式,真正决定质量的往往是分组方式、尺度计算和运行时数据流。
KIVI 为什么对 Key 和 Value 使用不同量化粒度
KIVI 的关键观察之一,是 Key 和 Value 的离群值结构不同。论文据此采用非对称策略:Key 更适合按 Channel 维度处理,而 Value 更适合按 Token 维度处理。这里的“按 Channel”不是把所有 Token 的所有元素混在一起,而是让相同特征维度共享更合适的量化范围;“按 Token”则让每个 Token 的 Value 根据自身分布进行量化。
以 Key 为例,假设多个 Token 在某个固定 Channel 上都出现较大值。如果按照 Token 把一整行不同 Channel 放在一起量化,这个异常 Channel 会拉大整行范围,其他正常 Channel 的表示精度下降。按 Channel 分组后,异常值主要影响它所在的维度,不会把其他 Channel 的刻度同时拉宽。
Value 的分布若更适合按 Token 组织,则可以让每个新 Token 独立确定量化尺度。两种策略组合后,系统不必强迫 K 与 V 使用相同的分组逻辑。这个设计说明了 KV Cache 量化与普通“统一压成 2 Bit”之间的差异:低位宽只是结果,数据分布决定怎样压才不至于把注意力需要的信息一起抹掉。
用户提供的 KIVI 资料报告了 2-bit KV Cache 的实验结果,包括峰值内存、Batch Size 和吞吐方面的改善。此类数字应严格理解为论文在特定模型、硬件和工作负载下的结果。它们能够说明低比特缓存具有实际可行性,却不能直接推导出任意线上服务都能获得同样倍率的显存或吞吐收益。
一次 Token 生成中,量化状态如何流动
继续看长上下文聊天服务。某个请求已经拥有较长的历史 KV Cache,新一轮生成得到当前 Token 的 K 与 V。若系统采用 KV Cache 量化,新状态不会简单以低比特格式凭空出现,它需要经过量化参数计算、编码、写入,之后又在 Attention 读取时以适合 kernel 的方式参与计算。
flowchart TD
A[生成新 Token] --> B[计算当前 K 和 V]
B --> C{选择量化粒度}
C -->|Key| D[按 Channel 计算尺度]
C -->|Value| E[按 Token 计算尺度]
D --> F[编码低比特 KV]
E --> F
F --> G[写入量化 KV Cache]
G --> H[下一轮 Attention 读取]
H --> I[反量化或融合计算]
I --> J[完成注意力输出]
J --> K{继续生成}
K -->|是| A
K -->|否| L[释放请求缓存]
这条数据流里最重要的工程边界是:缓存省下来的 HBM 容量和带宽,必须大于新增的量化管理成本。若每产生一个 Token 都需要执行昂贵的独立 kernel,再把数据反量化到大块临时缓冲区,节省的存储可能被额外读写和 kernel 启动抵消。
更高效的实现会尽量把低比特读取、反量化和 Attention 计算融合,减少“先完整还原成 FP16,再执行普通 Attention”的中间步骤。是否能做到这一点取决于推理引擎和 GPU kernel。也正因如此,同一种量化算法在论文原型、通用框架和高度优化的服务引擎中,端到端表现可能明显不同。
还要考虑新旧缓存的共存。一些实现会保留一个较小的高精度残留区,让最近生成的 Token 暂时以较高精度存在,积累到一定长度后再批量量化。这可以减少每个 Token 单独量化的频繁开销,也可能改善最近上下文的精度,但会增加状态管理复杂度。
量化省的是容量,不保证每个请求都会更快
KV Cache 量化最确定的价值是降低缓存存储成本。只要低位宽表示和附加元数据的总量显著小于原始 FP16/BF16,单个请求可以占用更少显存。这可能让同一张 GPU 容纳更长上下文,或者在相同上下文长度下接受更多并发请求。
延迟收益则更依赖具体瓶颈。逐 Token decode 往往需要不断读取历史 KV Cache。如果系统受 HBM 带宽限制,低比特缓存减少读取数据量后,可能改善每 Token 延迟;如果反量化开销很高,或 Attention kernel 无法直接消费量化格式,新增计算又可能抵消这部分收益。
Hugging Face 提供的量化 Cache 资料也强调了这种边界:量化缓存更适合明显受缓存容量约束的长上下文场景,短上下文中量化和反量化的固定开销可能不划算。生产环境中不应把“显存下降”直接写成“请求一定更快”,而应分别测量容量、吞吐和延迟。
| 场景 | 主要瓶颈 | KV Cache 量化的潜在收益 | 主要风险 |
|---|---|---|---|
| 长上下文单请求 | KV Cache 容量和读取带宽 | 延长可容纳上下文,降低缓存读流量 | 低比特误差随长序列积累,反量化成本上升 |
| 高并发在线服务 | 每个请求都占独立缓存 | 同卡容纳更多活跃请求,提高并发上限 | 更大 Batch 可能带来调度和算力新瓶颈 |
| 短 Prompt、短回答 | Cache 占比低 | 收益通常有限 | 量化 kernel 和尺度计算可能增加延迟 |
| GQA/MQA 模型 | KV Head 已较少 | 在较小基线之上继续压缩位宽 | 绝对节省量可能低于 MHA 模型 |
| 超长上下文探索 | 显存容量极端受限 | 为更大缓存规模创造空间 | Prefill、Attention 计算和数据传输仍可能成为主瓶颈 |
KIVI、KVQuant 与混合精度路线的差异
KIVI 把重点放在 Key 与 Value 的非对称数据分布,通过不同量化粒度探索 2-bit KV Cache。KVQuant 则进一步关注离群值、不同层的数据分布和低比特 kernel,希望在更极端的上下文规模下继续压缩缓存。用户提供的 KVQuant 资料报告了百万到千万 Token 量级的实验探索,但“缓存能够容纳”与“端到端推理已经高效”是两个不同结论。
即使 KV Cache 不再首先耗尽显存,超长上下文仍要面对 Prefill 时间、Attention 计算量、跨设备数据传输和任务本身是否真的能利用这么长上下文等问题。KV Cache 量化解决的是容量和部分带宽,不会自动把标准 Attention 的计算复杂度消除。
LeanKV 一类后续工作把思路继续向动态资源分配推进:不是所有 Token、所有 Head、所有层都必须使用同一精度。重要状态可以保留较高位宽,不敏感区域使用更低精度,价值更低的历史状态甚至可以结合剪枝策略删除。这里的目标已经从“找到一个全局最佳 Bit 数”转向“把有限缓存预算分配给最值得保留的信息”。
RotateKV 所代表的旋转与离群值处理路线,则尝试通过改变表示分布,让极低位宽量化更稳定。对工程团队来说,这些方向说明一个趋势:真正可用的低比特 KV Cache 通常需要联合考虑数据分布、量化粒度、异常值、硬件 kernel 和模型质量,而不是单独修改一个 dtype 配置。
它与权重量化、SmoothQuant 和 FlexGen 解决的不是同一问题
项目选题资料同时列出了 SmoothQuant 与 FlexGen。它们适合用来建立边界,因为“降低显存”背后可能是完全不同的系统动作。
SmoothQuant 关注权重与激活量化,通过重新平衡激活和权重的量化难度来服务更高效的整数计算。权重属于相对静态的模型状态,部署后可以被大量请求共享;KV Cache 则是每个请求独立增长的动态状态。即使模型权重已经压缩得很小,长上下文请求仍可能因为 KV Cache 耗尽剩余显存。
FlexGen 代表的则是另一条系统路线:通过在 GPU、CPU 和存储之间调度模型状态与计算,换取受限硬件上的更大可运行规模。它解决的是“单个设备放不下时如何分层放置和搬运”,而 KV Cache 量化解决的是“同一份缓存本身能不能更小”。两者也可以组合,但会引入新的数据移动和调度权衡。
| 技术 | 主要压缩或管理对象 | 解决层级 | 典型收益 | 典型代价 |
|---|---|---|---|---|
| 权重量化 / SmoothQuant 类方案 | 模型权重与激活 | 模型计算与静态参数 | 降低模型驻留显存,可能利用低精度计算 | 校准、量化误差、硬件算子支持 |
| KV Cache 量化 | 请求级 K/V 历史状态 | 在线推理缓存 | 更长上下文或更高并发,可能减少缓存带宽 | 动态量化、反量化和低比特 kernel 复杂度 |
| FlexGen 类卸载 | 权重、激活、缓存等状态的放置 | 异构内存调度 | 在有限 GPU 显存下运行更大工作负载 | PCIe/存储搬运和调度延迟 |
| PagedAttention | KV Cache 的物理块管理 | 缓存分配 | 降低碎片并提高动态请求下的内存利用率 | 块表、调度与实现复杂度 |
这几类技术不应互相替代地理解。一个模型可以先做权重量化降低静态参数成本,再使用 GQA 减少 KV Head,采用 KV Cache 量化降低每个缓存元素的位宽,最后由 PagedAttention 管理这些缓存块。每一层解决的瓶颈不同,组合后的收益也必须重新测量。
与 GQA、PagedAttention 和 Prefix Caching 叠加时发生了什么
GQA、KV Cache 量化、PagedAttention 和 Prefix Caching 都围绕缓存优化,但它们分别作用在“数量、位宽、管理、复用”四个维度。
GQA 在模型结构上减少 KV Head 数量,因此每生成一个 Token,本来就需要保存更少的 K/V。KV Cache 量化再把这些剩余元素用更低位宽保存。PagedAttention 不改变元素内容,而是把不同请求的缓存组织成固定大小的物理块,减少预分配和碎片。Prefix Caching 则识别多个请求共享的前缀,让相同历史状态不必重复保存或计算。
叠加这些技术后,接口边界会变得更重要。例如 PagedAttention 的块大小如何与量化分组对齐,直接影响 kernel 读取效率;Prefix Cache 中共享块采用什么量化参数,关系到多个请求能否安全复用;GQA 改变 KV Head 数量后,原有 kernel 的并行布局也可能变化。
量化缓存还会影响缓存换入换出和复制成本。若引擎需要在 GPU 与 CPU 之间交换 KV 块,低比特表示可以减少传输数据量;但若 CPU 侧和 GPU 侧采用不同格式,转换成本又会出现。真正的生产优化需要把缓存的完整生命周期放在一起看,而不是只计算单个 Tensor 的压缩比。
生产环境应该观察哪些指标
上线 KV Cache 量化前,首先要确认当前系统是否真的受 KV Cache 限制。可以把线上请求按 Prompt 长度、生成长度和并发度分桶,观察显存中模型权重与 KV Cache 的占比,以及请求达到什么长度后开始排队或触发 OOM。若大多数请求很短,缓存只占很小比例,投入低比特 kernel 的收益可能有限。
启用量化后,容量指标至少包括 KV Cache 实际占用、可用块数量、最大可驻留 Token 数和最大并发请求数。延迟指标需要拆开 TTFT 与每 Token 延迟,因为量化主要影响 decode 阶段的缓存读写,但不同实现也可能影响 prefill 后的缓存写入。吞吐则要结合 Batch Size 和等待队列观察,避免出现“单卡塞进更多请求,但每个请求都更慢”的情况。
质量评估不能只跑短问答。低比特误差可能在长上下文、检索依赖、代码续写或需要精确复制历史信息的任务上更容易暴露。更可靠的验证应覆盖不同上下文长度,比较基线与量化版本的任务指标、输出一致性和异常样本,并按层、Head 或请求类型分析退化是否集中出现。
运行时还要记录量化和反量化 kernel 的时间占比。如果 GPU profiler 显示新增 kernel 占据显著时间,而 HBM 流量并未相应下降,说明实现路径没有把压缩优势转化为执行优势。此时继续降低 Bit 数可能只会增加误差,并不会改善端到端性能。
什么时候应该停止继续压低 Bit 数
更低位宽并不天然更优。2 Bit 比 4 Bit 占用更小,但可表示范围更有限,对量化粒度、离群值处理和 kernel 的要求更高。如果服务在 4 Bit 下已经能够满足目标上下文和并发,再继续压到 2 Bit 只带来很小容量收益,却显著增加质量风险和实现复杂度,就没有必要为了“更低 Bit”继续优化。
另一个停止条件是瓶颈已经迁移。KV Cache 缩小后,系统可能转而受模型权重带宽、MLP 计算、跨卡通信、Prefill 或请求调度限制。此时继续压缓存不会提升吞吐。和 FlashAttention 一样,系统优化会不断把最长路径推向其他组件,必须重新 profile,而不是沿着原来的瓶颈无限优化。
对于长上下文服务,更现实的决策通常是联合选择:模型结构上采用多少 KV Head,缓存保存几 Bit,最近多少 Token 保留高精度,是否启用 Prefix Caching,哪些历史状态可以剪枝,以及缓存块如何由调度器管理。最终目标不是把某个指标压到最低,而是在目标质量约束下,让 GPU 的容量、带宽和计算资源共同达到可接受的利用率。
KV Cache 量化最值得记住的不是“2 Bit 也能工作”这个结论,而是它暴露了大模型推理中的一个基本事实:缓存不是被动附属物,而是决定长上下文和高并发上限的核心运行时状态。KIVI 展示了 Key 与 Value 需要区别对待,KVQuant 把低比特缓存推向更极端的上下文规模,LeanKV、RotateKV 等方向则继续探索混合精度、离群值和动态资源分配。
当 GQA 已经减少 KV Head、PagedAttention 已经改善块管理、Prefix Caching 已经消除重复前缀后,KV Cache 量化仍然能从“每个元素占多少位”这一层继续降低成本。但它是否值得上线,最终仍要回答同一个工程问题:省下的显存和带宽,是否足以覆盖量化、反量化、kernel 集成和模型质量风险带来的全部新增成本。
资料来源
- FlexGen: High-Throughput Generative Inference of Large Language Models with a Single GPU
- SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models
- KIVI: A Tuning-Free Asymmetric 2bit Quantization for KV Cache
- Unlocking Longer Generation with Key-Value Cache Quantization
- KVQuant: Towards 10 Million Context Length LLM Inference with KV Cache Quantization
- Unifying KV Cache Compression for Large Language Models with LeanKV
- RotateKV: Accurate and Robust 2-Bit KV Cache Quantization for LLMs