上一篇把KV缓存移到CPU内存,分析了逐层预取的传输等待。还有一条节省容量的路线,把每个缓存元素保存得更紧凑。问题随之变成,节省了多少字节,恢复数值要做多少工作,最终生成结果会受多大影响?
一、KV缓存里哪些数据可以缩小
自回归生成会不断积累注意力层的键和值。KV缓存量化用较低位宽保存其中的数值,并保留恢复数值所需的比例等信息。注意力计算需要用到这些历史状态,因此读取时要按实现进行解码或反量化。
本篇用Transformers 4.57.1的quantized缓存接口说明配置方式。其文档列出的后端包括Quanto和HQQ,支持位宽与量化轴选择各有要求。下面的机制实验使用独立Python代码,便于把舍入和分组的影响逐项算出来。Transformers缓存量化文档

栗子把一组原始数值转换成短整数,小橘根据同组的缩放系数恢复近似数值。保存整数时还要带上刻度,恢复后的数值才有意义。
二、先算容量,再看比例信息
假设全注意力模型有32层、8个KV头、头维度128,单请求保留8192个Token。K和V各一份,每个元素以16位保存。
text
元素个数 = 2 × 32 × 8 × 128 × 8192
16位原始载荷 = 元素个数 × 2字节 = 1 GiB
4位紧凑载荷 = 元素个数 × 0.5字节 = 256 MiB
这两行计算只比较数值载荷。实际还要加上每组的缩放参数、零点或其他元数据,以及高精度残留区、对齐与临时工作区。
以每64个元素一组、每组单独保存一个4字节缩放系数为例,4位载荷需要32字节,再加4字节,合计36字节。对应的16位载荷为128字节,这个简化格式节约71.875%的空间。把256MiB直接当成整个进程的显存峰值,就遗漏了模型权重和这些额外开销。
三、离群值为什么会影响普通数值
先采用一个便于手算的对称整数编码。4位编码使用-7到7的整数级别,比例为该组最大绝对值除以7。对每个数执行除比例、舍入、裁剪,恢复时再乘回比例。
对于[0.1, 0.2, 0.3, 7.0],比例是1。前三个数都舍入成0,最后一个数能准确恢复。保存7.0的范围占用了整个刻度,小数值的差异被压平了。
如果改成[0.1, 0.2, 0.3]和[7.0]两组,各组使用自己的刻度,小数值可以得到更细的表示,同时比例信息也多了一份。实际后端会按张量轴和分组规则组织数据,不能把本例的四个数直接当作某个模型的真实KV分布。

四、运行一个能比较误差的量化器
python
def quantize(values, bits):
limit = 2 ** (bits - 1) - 1
peak = max(abs(value) for value in values)
scale = peak / limit if peak else 1.0
codes = [
max(-limit, min(limit, round(value / scale)))
for value in values
]
restored = [code * scale for code in codes]
error = max(abs(a - b) for a, b in zip(values, restored))
return codes, restored, error
values = [0.1, 0.2, 0.3, 7.0]
for bits in (4, 8):
codes, restored, error = quantize(values, bits)
print(bits, codes, [round(x, 4) for x in restored], round(error, 4))
parts = [values[:3], values[3:]]
errors = [quantize(part, 4)[2] for part in parts]
print("grouped 4-bit max error:", round(max(errors), 4))
运行结果如下。
text
4 [0, 0, 0, 7] [0.0, 0.0, 0.0, 7.0] 0.3
8 [2, 4, 5, 127] [0.1102, 0.2205, 0.2756, 7.0] 0.0244
grouped 4-bit max error: 0.0143
Python列表中的整数仍然是Python对象,程序演示的是数值映射,紧凑位存储需要另外实现。这里采用Python的round规则,具体后端的编码、舍入和元数据格式要按其实现核对。
误差较小只说明这组数值恢复得更接近原值。注意力分数、层间传播和生成选择会继续影响输出,应把最终任务质量纳入验收。
五、在生成入口选择缓存策略
模型和输入已经准备好时,可以用下面的参数指定Quanto后端与4位缓存。安装环境还需要该版本要求的后端依赖。
python
outputs = model.generate(
**inputs,
max_new_tokens=64,
do_sample=False,
cache_implementation="quantized",
cache_config={"backend": "quanto", "nbits": 4},
)
为保持对照条件一致,先用相同模型、输入和解码参数跑常规缓存,再单独运行量化缓存。分别记录输入Token数、输出Token数、GPU峰值、首Token时间和生成阶段延迟。两个运行都完成后,再比较任务正确率、关键字段和停止原因。
一些缓存实现把近期状态保留在高精度区域,积累到一定长度后再量化。这能减少频繁转换,但也增加驻留空间。调整残留长度时,需要同时看长上下文的峰值和短请求的转换成本。
六、按业务目标验收
| 目标 | 需要观察的结果 | 实验设计 |
|---|---|---|
| 容纳更长输入 | 相同输出上限下能完成的输入长度 | 逐级增加长度并记录峰值 |
| 提高并发 | 相同延迟目标下可同时服务的请求数 | 固定输入输出分布 |
| 控制质量损失 | 任务成功率与关键字段差异 | 使用同一组固定题目和评判规则 |
| 控制转换开销 | 逐Token延迟和总生成时间 | 分别比较短、中、长上下文 |
短上下文的缓存本来就小,额外转换可能更显眼。长上下文能省下更多载荷,但也会让量化状态更频繁地参与计算。先选真实业务里需要解决的容量问题,再判断量化策略是否满足延迟和质量要求。
七、🧠 思维导图
#mermaid-svg-d6s3BSue8kbIVAWj{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-d6s3BSue8kbIVAWj .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-d6s3BSue8kbIVAWj .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-d6s3BSue8kbIVAWj .error-icon{fill:#552222;}#mermaid-svg-d6s3BSue8kbIVAWj .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-d6s3BSue8kbIVAWj .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-d6s3BSue8kbIVAWj .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-d6s3BSue8kbIVAWj .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-d6s3BSue8kbIVAWj .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-d6s3BSue8kbIVAWj .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-d6s3BSue8kbIVAWj .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-d6s3BSue8kbIVAWj .marker{fill:#333333;stroke:#333333;}#mermaid-svg-d6s3BSue8kbIVAWj .marker.cross{stroke:#333333;}#mermaid-svg-d6s3BSue8kbIVAWj svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-d6s3BSue8kbIVAWj p{margin:0;}#mermaid-svg-d6s3BSue8kbIVAWj .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-d6s3BSue8kbIVAWj .cluster-label text{fill:#333;}#mermaid-svg-d6s3BSue8kbIVAWj .cluster-label span{color:#333;}#mermaid-svg-d6s3BSue8kbIVAWj .cluster-label span p{background-color:transparent;}#mermaid-svg-d6s3BSue8kbIVAWj .label text,#mermaid-svg-d6s3BSue8kbIVAWj span{fill:#333;color:#333;}#mermaid-svg-d6s3BSue8kbIVAWj .node rect,#mermaid-svg-d6s3BSue8kbIVAWj .node circle,#mermaid-svg-d6s3BSue8kbIVAWj .node ellipse,#mermaid-svg-d6s3BSue8kbIVAWj .node polygon,#mermaid-svg-d6s3BSue8kbIVAWj .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d6s3BSue8kbIVAWj .rough-node .label text,#mermaid-svg-d6s3BSue8kbIVAWj .node .label text,#mermaid-svg-d6s3BSue8kbIVAWj .image-shape .label,#mermaid-svg-d6s3BSue8kbIVAWj .icon-shape .label{text-anchor:middle;}#mermaid-svg-d6s3BSue8kbIVAWj .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-d6s3BSue8kbIVAWj .rough-node .label,#mermaid-svg-d6s3BSue8kbIVAWj .node .label,#mermaid-svg-d6s3BSue8kbIVAWj .image-shape .label,#mermaid-svg-d6s3BSue8kbIVAWj .icon-shape .label{text-align:center;}#mermaid-svg-d6s3BSue8kbIVAWj .node.clickable{cursor:pointer;}#mermaid-svg-d6s3BSue8kbIVAWj .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-d6s3BSue8kbIVAWj .arrowheadPath{fill:#333333;}#mermaid-svg-d6s3BSue8kbIVAWj .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-d6s3BSue8kbIVAWj .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-d6s3BSue8kbIVAWj .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d6s3BSue8kbIVAWj .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-d6s3BSue8kbIVAWj .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d6s3BSue8kbIVAWj .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-d6s3BSue8kbIVAWj .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-d6s3BSue8kbIVAWj .cluster text{fill:#333;}#mermaid-svg-d6s3BSue8kbIVAWj .cluster span{color:#333;}#mermaid-svg-d6s3BSue8kbIVAWj div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-d6s3BSue8kbIVAWj .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-d6s3BSue8kbIVAWj rect.text{fill:none;stroke-width:0;}#mermaid-svg-d6s3BSue8kbIVAWj .icon-shape,#mermaid-svg-d6s3BSue8kbIVAWj .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d6s3BSue8kbIVAWj .icon-shape p,#mermaid-svg-d6s3BSue8kbIVAWj .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-d6s3BSue8kbIVAWj .icon-shape .label rect,#mermaid-svg-d6s3BSue8kbIVAWj .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d6s3BSue8kbIVAWj .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-d6s3BSue8kbIVAWj .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-d6s3BSue8kbIVAWj :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} KV缓存量化
容量账本
位宽与紧凑载荷
比例与残留区
数值转换
舍入和反量化
分组与离群值
验收
峰值和逐Token延迟
任务质量
八、总结
总结要点
低位宽能缩小KV数值载荷,完整容量账本还要计入比例信息、高精度区域和工作区。先把节约的字节算清,再去测GPU峰值。
位宽和分组共同决定数值刻度。独立实验能解释离群值怎样压缩普通数值的表示空间,业务评测负责验证这些变化最终能否被接受。
下一篇讨论推理请求的准入控制,把上下文长度、输出预算和可用KV容量放在同一份资源账本里。
👉 如果你觉得这篇文章对你有所帮助,欢迎点赞、收藏、分享!😊