长上下文服务的显存瓶颈与量化诱惑
一家提供长文档问答的服务商把模型从 4K 上下文升级到 32K 后,发现单卡最多只能同时服务 2 个请求。排查显存分配时,权重只占一小部分,真正吃满显存的是推理过程中不断增长的键值缓存(KV Cache)。每个 token 的 Key 和 Value 都要按层保存,序列越长、并发越高,这部分显存增长越快。
KV Cache 量化看起来是直接的解法:把缓存从 FP16 压到 INT8,显存立刻减半;压到 INT4 能省更多。但上线后评测发现,短文本任务困惑度几乎不变,一旦输入超过 8K token,困惑度开始明显爬升,生成内容出现重复和离题。问题不在量化本身,而在量化误差随序列长度累积的方式。
KV Cache 为什么成为显存大头
Transformer 解码时,每个新 token 都要与之前所有 token 的 Key 和 Value 计算注意力。为了避免重复计算,推理框架把历史 token 的 Key 和 Value 缓存下来,这就是 KV Cache。
KV Cache 的显存占用可以这样估算:
总字节数 = 2(K 和 V)× 层数 × 头数 × 头维度 × 序列长度 × 批大小 × 字节数/元素
以 7B 模型为例,假设 32 层、32 个注意力头、头维度 128,FP16 下每个 token 的缓存约 0.5 MB。序列长度 32K、批大小 8 时,KV Cache 需要约 128 GB,远超权重本身。工程上,KV Cache 是长上下文和并发场景下显存的主要消耗者。
基线方案是直接放弃缓存、每次重新计算,但那样延迟会随序列长度线性恶化,生产环境不可接受。另一种基线是减少批大小或缩短上下文,但会牺牲吞吐或功能。量化是少数能同时降低显存和带宽压力的手段,代价是精度损失。
量化误差如何随序列长度累积
KV Cache 量化把连续的浮点数值映射到有限的离散整数。映射过程会引入舍入误差,每个 token 的 Key 和 Value 都经过量化-反量化,误差范围取决于量化步长。
关键问题在于,这些误差不是独立白噪声。注意力计算是加权求和,当前 token 的表示会与所有历史 token 的 Key 交互。当历史 KV 都被量化后,注意力分数和输出向量都带有误差。序列越长,参与计算的量化 token 越多,误差通过注意力权重不断叠加,最终反映在生成概率分布上,表现为困惑度上升。
困惑度(Perplexity,PPL)衡量模型对下一个 token 预测的不确定性。量化误差使预测分布偏离真实分布,PPL 上升意味着生成质量下降。短序列时,误差被模型自身的鲁棒性吸收;长序列时,误差累积超过阈值,退化变得明显。
可以推断,这种累积效应与注意力机制的全连接特性有关:每个新 token 都依赖全部历史,误差会沿着时间维度传播。资料中 KVQuant 的实验也显示,在长序列评估中,量化方法的退化比短序列更显著,说明误差累积是长上下文量化的核心挑战。
量化粒度:逐 token 与逐通道的取舍
量化粒度决定了缩放因子和零点如何计算,直接影响误差分布。
逐 token 量化对每个 token 的向量单独计算缩放因子,实现简单,但忽略了通道维度的分布差异。
逐通道量化沿通道维度共享缩放因子,能更好地匹配 Key 和 Value 的分布。KVQuant 发现,Key 激活在 RoPE 之前存在明显的通道级离群值,某些通道的数值范围远大于其他通道。如果按 token 量化,离群通道会拉大量化范围,导致非离群通道的量化步长过大,误差增加。
KVQuant 提出的 Per-Channel Key Quantization 就是针对这一现象:在 RoPE 之前按通道量化 Key。RoPE 会旋转 Key 向量,使离群值分散到不同通道,破坏通道级分布的一致性。在 RoPE 之前量化,可以保留清晰的通道结构,让缩放因子更准确。
Value 的分布相对均匀,逐 token 量化通常足够。这种不对称处理反映了 Key 和 Value 在注意力中的不同角色:Key 用于计算相似度,对数值精度更敏感;Value 用于加权求和,对噪声的容忍度稍高。
校准数据与离线量化:精度从哪来
量化参数(缩放因子、零点)需要从数据分布中估计。校准数据的选择直接影响量化质量。
如果校准数据与线上分布不一致,量化范围会偏离实际,导致误差增大。例如,用短文本校准,面对长文本时 Key/Value 的数值范围可能超出预期,产生截断误差。
KVQuant 采用离线校准,从训练数据或代表性样本中统计每层激活的分布,并计算敏感度加权。敏感度反映该层量化误差对最终困惑度的影响,敏感度高的层分配更多量化级别或更精细的非均匀间隔。
FlexGen 则采用不同的思路:它不追求极低比特,而是通过异构存储(GPU、CPU、磁盘)和批量调度来提升吞吐,同时把权重和注意力缓存压缩到 4 位。FlexGen 的 4 位量化在短上下文任务中精度损失可接受,但长上下文下的表现资料未详细说明。
校准数据的选择需要覆盖目标场景的长度和领域。工程上,通常从验证集或真实请求日志中采样,并监控量化前后的困惑度差异。如果差异超过阈值,需要重新校准或降低压缩比。
替代方案对比:SmoothQuant、FlexGen 与 KVQuant
SmoothQuant 主要针对权重和激活的 INT8 量化,通过数学等价变换把激活的离群值迁移到权重,使激活更易量化。它不专门针对 KV Cache,但可以与其他 KV 量化方法结合。
FlexGen 面向单 GPU 高吞吐场景,通过卸载和 4 位量化来扩大批大小,牺牲单请求延迟换取整体吞吐。
KVQuant 专注于 KV Cache 的低比特量化,结合了逐通道 Key 量化、Pre-RoPE 量化、非均匀数据格式和稠密-稀疏量化,在 3 位精度下实现小于 0.1 的困惑度退化。
下表从精度、显存节省、延迟影响和适用场景进行比较:
| 方法 | 量化对象 | 典型位宽 | 精度影响 | 显存节省 | 延迟影响 | 适用场景 |
|---|---|---|---|---|---|---|
| INT8 逐 token | KV Cache | 8 位 | 短上下文可忽略,长上下文累积 | 约 50% | 低 | 长上下文服务,精度敏感 |
| INT8 逐通道(KVQuant 风格) | Key 为主 | 8 位或更低 | 长上下文退化更小 | 约 50% 以上 | 低 | 长上下文,Key 有通道离群 |
| SmoothQuant | 权重和激活 | 8 位 | 短上下文可忽略 | 权重减半,KV 未压缩 | 低 | 权重和激活量化,需配合 KV 量化 |
| FlexGen | 权重和 KV Cache | 4 位 | 短上下文可接受 | 大幅减少,但依赖卸载 | 高(卸载) | 单 GPU 高吞吐,延迟不敏感 |
| KVQuant | KV Cache | 3 位 | 短上下文 <0.1 PPL 退化 | 约 75% 以上 | 低(定制 kernel) | 超长上下文(百万级) |
选择哪种方案取决于瓶颈。如果显存瓶颈完全来自 KV Cache,优先优化 KV 量化;如果同时需要压缩权重,SmoothQuant 或 FlexGen 更合适。
GQA 与 MHA:量化适用边界
多头注意力(MHA)中,每个头都有独立的 Key 和 Value,量化时按头分组,分布相对一致。
分组查询注意力(GQA)让多个查询头共享一组 Key 和 Value,减少了 KV Cache 总量,但每个 Key/Value 被更多查询头使用,量化误差的影响面更大。GQA 下,一个 Key 的误差会传播到多个查询头的注意力输出,可能放大质量退化。
KVQuant 的实验主要基于 LLaMA 等模型,这些模型在不同版本中使用了 MHA 或 GQA。资料未明确区分 GQA 下的量化表现,但可以推断:GQA 的共享机制使误差更集中,对量化精度更敏感。工程上,在 GQA 模型中应用量化时,应更保守地选择位宽,并重点监控长序列的困惑度。
此外,RoPE 的位置编码会改变 Key 的数值分布,KVQuant 的 Pre-RoPE 量化正是为了规避这一影响。如果模型使用其他位置编码(如 ALiBi),分布特征不同,量化策略需要重新评估。
可观测指标与失败模式
部署 KV Cache 量化后,需要监控以下信号来发现质量问题:
- 困惑度(PPL):在固定验证集上对比量化前后的 PPL,尤其关注不同长度区间的差异。
- 生成重复率:量化误差累积可能导致模型陷入重复循环,可统计 n-gram 重复比例。
- 注意力分布熵:量化可能使注意力分布更均匀或更尖锐,熵的变化可反映异常。
- 显存与吞吐:确认量化确实降低了显存,并测量吞吐变化。
常见失败模式包括:
- 校准数据分布偏移:线上输入长度或领域与校准数据不一致,导致量化范围失配。
- 离群值处理不当:未隔离离群通道时,量化范围被拉大,普通值精度下降。
- 长序列下的误差雪崩:序列超过某长度后,困惑度急剧上升,需要提前检测并降级。
当困惑度超过阈值时,可以动态切换到更高精度(如 FP16)或减少并发,但这需要缓存管理支持。
量化流程与决策点
下图展示了从模型部署到 KV Cache 量化的决策流程,以及关键监控点:
flowchart TD
A[加载模型与权重] --> B[选择量化方案]
B --> C{是否量化 KV Cache?}
C -- 否 --> D[FP16 缓存,限制上下文或批大小]
C -- 是 --> E[收集校准数据]
E --> F[分析 Key/Value 分布]
F --> G{存在通道离群?}
G -- 是 --> H[逐通道量化 Key,Pre-RoPE]
G -- 否 --> I[逐 token 量化]
H --> J[离线校准缩放因子]
I --> J
J --> K[部署并监控 PPL 与显存]
K --> L{PPL 上升?}
L -- 是 --> M[检查序列长度与校准分布]
M --> N[调整位宽或重新校准]
L -- 否 --> O[继续服务]
流程中关键决策点是是否量化 KV Cache。如果显存充足,保持 FP16 最简单;如果不足,需要根据分布特征选择量化粒度。校准后必须监控 PPL,一旦上升需回溯检查。
未解决的问题与工程建议
KV Cache 量化仍存在未解决的问题:如何在不同模型架构和位置编码下自适应选择量化参数?如何在线调整量化策略以应对分布漂移?这些问题目前没有统一答案。
工程上,建议从 INT8 逐通道量化开始,因为它在多数场景下精度损失小,实现复杂度可控。如果显存压力大,再尝试 4 位或更低,但必须用长序列验证集评估。同时,结合 GQA 的共享特性,对量化误差做更严格的上限估计。
最终,量化不是免费的午餐,而是在显存、精度和延迟之间做权衡。理解误差累积机制,才能选择合适的方案,避免在长上下文场景中牺牲生成质量。
资料来源
- KVQuant: Towards 10 Million Context Length LLM Inference with KV Cache Quantization
- FlexGen: High-Throughput Generative Inference of Large Language Models with a Single GPU
- SmoothQuant: Accurate and Efficient Post-Training Quantization for Large Language Models
- KVQuant: Towards 10 Million Context Length LLM Inference with KV Cache Quantization