LMCache 中的 KV Cache 压缩:CacheGen 编解码原理、配置方式与源码解析
【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache
KV Cache 在长上下文、多轮对话等场景下会迅速占据大量显存与远端存储,直接制约缓存命中率与加载速度。LMCache 在docs/source/kv_cache_optimizations/compression/index.rst中正式引入了基于 KV Cache 分布特性的压缩算法 CacheGen,将 KV Cache 编码为更紧凑的比特流,并以可忽略的解码开销换取存储/内存占用与加载速度的大幅改善。读完本文,你将掌握 CacheGen 在 LMCache 中的启用方式(离线与在线两种配置)、底层"量化—CDF 估计—算术编码"完整链路,以及如何在仓库中验证其压缩效果。
为什么需要 KV Cache 压缩
KV Cache 压缩能够显著减小缓存体积,这对以下两方面都有直接收益:
- 存储 / 内存占用:压缩后的 KV Cache 可以更少地占用本地 CPU 内存、本地磁盘或远端缓存服务(Redis、Mooncake 等)的空间,让同样的容量容纳更多 token 的缓存;
- 加载速度:更小的字节流意味着在远端缓存读取(cache hit 场景)时更少的网络传输时间与反序列化开销,从而缩短首 Token 延迟。
在 LMCache 当前的压缩功能矩阵中,官方支持的压缩算法为 CacheGen(对应docs/source/kv_cache_optimizations/compression/index.rst中的列表):
- CacheGen:
CacheGen: KV Cache Compression and Streaming for Fast Large Language Model Serving(SIGMETRICS 论文)
docs/source/kv_cache_optimizations/compression/cachegen.rst对 CacheGen 做了如下定位:CacheGen 利用 KV Cache 的分布特性(distributional properties),将其编码为更紧凑的比特流表示(bitstream),且解码开销可以忽略不计(negligible decoding overhead)。
需要注意,上述文档描述的是 LMCache 的 in-process 模式(已标记为 deprecated)行为。LMCache 更推荐使用 LMCache MP 模式 以获得更完善的功能支持与性能;MP 模式下的 CacheGen 支持尚未实现(见 MP 模式 CacheGen 文档,其内容为 "not implemented yet (coming soon)"),原始 in-process 实现被保留在 Legacy 部分,即本文所对应的 compression/index.rst。
启用 CacheGen:离线与在线两种配置
CacheGen 的配置与 LMCache 的 naive KV cache sharing 几乎完全一致,只需做少量改动即可启用。也就是说,它作用于"远端缓存读写"这一条链路上,对 KV Cache 进行压缩后写入远端、读取时解压还原。
离线推理场景(环境变量方式)
在离线推理(offline inference)中,通过设置环境变量LMCACHE_REMOTE_SERDE来启用:
# Enable cachgen compression in LMCache os.environ["LMCACHE_REMOTE_SERDE"] = "cachegen"在线推理场景(配置文件方式)
在在线推理(online inference,例如与 vLLM 集成的部署)中,需要在 LMCache 的配置 YAML 中设置remote_serde字段:
# Enable cachgen compression in LMCache remote_serde: "cachegen"配置项在源码中的落点
remote_serde是LMCacheEngineConfig的一个标准配置项,定义于 lmcache/v1/config.py:
- 类型:
Optional[str] - 默认值:
"naive"(即默认使用朴素序列化,不启用压缩) - 环境变量转换器:
str
从 lmcache/v1/storage_backend/naive_serde/init.py 的CreateSerde工厂函数可以看到,serde_type支持naive、kivi、cachegen三种取值,其中:
elif serde_type == "cachegen": s, d = ( CacheGenSerializer(config, metadata), CacheGenDeserializer(config, metadata), )即"cachegen"会创建CacheGenSerializer与CacheGenDeserializer这对编解码器;传入其他非法值会抛出ValueError。
另外两点值得注意:
- 在 lmcache/v1/cache_engine.py 与 lmcache/v1/cache_engine.py 中,创建远端序列化器的分支要求
method必须为"cachegen",进一步印证了 CacheGen 是当前 in-process 模式下唯一受支持的远端压缩算法(其余压缩能力走 MP 模式的 legacy 代码路径)。 - 远端后端在创建时会断言
config.remote_serde is not None(见 lmcache/v1/storage_backend/remote_backend.py),也就是说使用远端缓存(如 Redis、Mooncake)时remote_serde是必经之路,而默认值"naive"保证未显式配置时也能正常工作。
前置条件:先完成跨实例 KV Cache 共享
由于 CacheGen 是"远端 KV Cache 共享"链路中的序列化器,正式启用前需要先按照 share_kv_cache 文档 配置好跨实例 KV Cache 共享。LMCache 支持两类共享方式:
- centralized_sharing:通过集中式缓存服务器共享 KV Cache;
- p2p_sharing:通过点对点缓存传输共享 KV Cache。
CacheGen 的配置与这两种共享方式的配置"非常相似",只需在既有共享配置之上追加remote_serde: "cachegen"(或设置环境变量)即可,无需调整其他参数。
CacheGen 底层原理:源码级编解码链路
CacheGen 的核心思想是:先对 KV Cache 做按层分组的低位宽量化,再基于量化值的统计分布估计累积分布函数(CDF),最后用 CDF 驱动的算术编码把量化值压成紧凑比特流。编码、解码两条路径分别实现在:
- lmcache/storage_backend/serde/cachegen_encoder.py:
CacheGenSerializer.to_bytes() - lmcache/storage_backend/serde/cachegen_decoder.py:
CacheGenDeserializer.from_bytes()
1. 按层分组的量化配置(QuantizationSpec)
量化规格由QuantizationSpec描述(cachegen_basics.py),包含三个字段:
| 字段 | 含义 |
|---|---|
start_layer | 该量化区间的起始层号(含) |
end_layer | 该量化区间的结束层号(不含) |
bins | 量化 bin 数量,bin 数越大量化越精细 |
CacheGenConfig聚合了nlayers(总层数)、kspecs(K 的量化区间列表)、vspecs(V 的量化区间列表),并通过from_model_name()按模型族自动推导默认配置:
- 7B 系(
mistralai/Mistral-7B-Instruct-v0.2、lmsys/longchat-7b-16k、Qwen/Qwen-7B):32 层,K 前 10 层用 32 bins、其余 16 bins;V 前 2 层用 32 bins、其余 16 bins; - 8B 系(
meta-llama/Llama-3.1-8B-Instruct):同为 32 层,量化分段与 7B 一致; - 9B 系(
THUDM/glm-4-9b-chat):40 层,K 前 10 层 32 bins、其余 16 bins;V 前 2 层 32 bins、其余 16 bins(源码注释标明该配置 "needs tuning for better quality"); - 其他模型:通过
AutoConfig.from_pretrained(model_name)读取num_hidden_layers动态生成:层数 < 10 时 K/V 全部用 32 bins;否则按"前 10 层 / 前 2 层用 32 bins、其余用 16 bins"的规则生成;无法识别时抛出ValueError。
从配置语义可以看出,CacheGen 的量化策略偏好是:浅层(靠近输入)的 K/V 分布更"重要"或更复杂,给予更多 bin(32),深层则用更少的 bin(16),从而在保真度与压缩率之间取得平衡。
2. 量化(Quantization)
编码器入口to_bytes()接收形状为[num_layers, 2, num_tokens, num_heads, head_size]的 blob KV 张量,先通过_split_kv()拆成 K 与 V(reshape 后按第 1 维 unbind),得到[num_layers, num_tokens, num_channels]形式。
核心量化函数torch_quant_vectorized()(cachegen_encoder.py)逐层按各自 bin 数做对称量化:
MAX = (bins // 2 - 1)[:, None, None] # [nlayers, 1, 1] max1 = torch.amax(torch.abs(input_groups), dim=-1, keepdim=True) factor = MAX / max1 xq = torch.round(input_groups * factor + MAX).to(torch.int8)即对每个通道维度取绝对值的最大值作为缩放因子max1,将张量缩放到[-MAX, MAX]区间并 round 成int8。max1作为反量化所需的缩放系数随编码结果一并保存。
3. CDF 估计与归一化
量化完成后,编码器基于量化值的直方图统计每个 (层, 通道) 的符号分布,并用torch.cumsum计算累积分布函数(CDF)。为了让浮点 CDF 能安全喂给整数算术编码器,_convert_to_int_and_normalize()(cachegen_encoder.py)做了关键处理:
- 将 CDF 从
[0, 1)放大到2^16(16 位精度)量级; - 若需要归一化,先乘
2^16 - (Lp - 1)再叠加一个arange(Lp)斜坡,保证 CDF严格单调递增——算术编码器要求 CDF 单调,否则相同符号会出现重复区间导致解码失败。
4. 算术编码与 GPU Kernel
真正把量化符号压成比特流的是 GPU 端的算术编码 kernel。编码器按CACHEGEN_GPU_MAX_TOKENS_PER_CHUNK = 256(cachegen_basics.py)将 token 分批处理,调用:
device_ops.calculate_cdf(...):GPU 上直接计算 CDF(对应 csrc/cuda/cal_cdf.cu);device_ops.encode_fast_new(...):GPU 算术编码(对应 csrc/cuda/ac_enc.cu)。
每批编码结果以CacheGenGPUBytestream(bytestream+bytestream_lengths+ntokens)组织,最终封装为CacheGenGPUEncoderOutput,并通过 pickle 序列化成bytes写入远端。
5. 解码与反量化
解码路径(CacheGenDeserializer.from_bytes())是编码的逆过程:
- 反序列化得到
CacheGenGPUEncoderOutput; - 调用
device_ops.decode_fast_prefsum(...)(对应 csrc/cuda/ac_dec.cu)按 CDF 与长度前缀和还原每个 (层, 通道) 的量化符号; decode_function_gpu()将解码结果 reshape 回[2, nlayers, ntokens, nchannels]并拆出 K/V;do_dequantize()(cachegen_decoder.py)用保存的max_tensors_key/max_tensors_value与各层 bin 数做逆缩放:t = (t - C) / C * maxtensors;- 最后重新 stack 成
[nlayers, 2, ntokens, num_heads, head_size]并转回原始 dtype。
解码器内部还复用了get_output_buffer()的预分配缓冲来降低反复分配的抖动,并对key_bins/value_bins做设备同步(源码注释 #83 记录了多 worker 设备隐式绑定问题的临时修复)。
6. 与 vLLM 集成时的注意点
在 vLLM 集成路径中,lmcache/integration/vllm/utils.py 存在如下约束:
if use_mla and (config.remote_serde != "naive" and config.remote_serde is not None):即当模型使用 MLA(Multi-head Latent Attention)且remote_serde被设置为非naive时会有额外的检查逻辑。这意味着在启用 CacheGen 前,需要确认目标模型架构(尤其 MLA 类模型)与 CacheGen 的兼容性约束。
如何验证压缩效果:仓库自带的端到端 Benchmark
仓库在 tests/benchmarks/test_cachegen.py 提供了 CacheGen 编码/解码的端到端基准测试,可以直观验证压缩率与耗时。运行方式:
pytest tests/benchmarks/test_cachegen.py --benchmark-only该测试的关键设计:
- 使用
_generate_kv()构造 32 层、8 头、head_size 128 的 bfloat16 合成 KV blob; - 编解码器通过
LMCacheEngineConfig.from_defaults(chunk_size=...)与LMCacheMetadata(model_name="mistralai/Mistral-7B-Instruct-v0.2", ...)构建,即走 7B 模型的默认量化配置; - 编码测试对
chunk_size分别取[64, 128, 256, 768]四档,测量to_bytes()耗时,并打印raw / compressed的压缩率; - 解码测试测量
from_bytes()耗时; - 测试要求当前 torch 运行时可用(
torch_dev.is_available()),否则整模块 skip——因为 CacheGen 依赖手写的 GPU kernel(CUDA/XPU 等)。
此外,tests/test_serde.py 等单测覆盖了序列化器的一致性校验,可用于确认 CacheGen 编码-解码往返(round-trip)的正确性。
使用建议与限制小结
- 适用路径:CacheGen 作用于远端 KV Cache 读写的序列化链路,启用前请先完成 KV Cache 跨实例共享 的配置;
- 模式限制:本文档对应的 in-process 模式已被标记为 deprecated;MP 模式 的 CacheGen 尚未实现,生产环境建议关注 MP 模式路线并留意后续版本;
- 硬件依赖:CacheGen 的 CDF 计算与算术编解码均在 GPU kernel 中完成(csrc/cuda 下的
ac_enc.cu、ac_dec.cu、cal_cdf.cu),启用前需确认推理设备具备对应运行时; - 模型适配:默认量化配置针对常见 7B/8B/9B 模型族做了手工调优,其他模型会基于
num_hidden_layers自动推导;如需更优的精度/压缩权衡,可关注后续对量化规格可配置化的演进; - 架构约束:MLA 类模型与
remote_serde非默认值存在联动检查,使用前应核对 vllm 集成代码 中的约束条件。
整体来看,CacheGen 是 LMCache 在"KV Cache 压缩"方向上的核心实现,其"分布感知的按层量化 + 算术编码"思路在压缩率与解码开销之间做了精心取舍;通过remote_serde: "cachegen"一行配置即可接入,配合仓库自带的 benchmark 可以快速量化其在目标硬件上的实际收益。
【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考