KVCache-Factory:把十几种 KV Cache 压缩方法塞进同一个评测接口
Unified KV Cache Compression Methods for Auto-Regressive Models
秒懂
- 它是什么?
- 这个项目从 PyramidKV 起步,如今把 StreamingLLM、H2O、SnapKV、Quest、KIVI 等压缩、检索、合并与量化路线统一到 LongBench、RULER 和 needle-in-a-haystack 三个 runner 之下。它的价值在于横向对比的可复现性,代价是方法之间的成熟度并不整齐。
- 适合谁用?
- 适合已经在跑长上下文推理、需要把多种 KV cache 压缩策略放在同一套 LongBench 或 RULER 流程里做横向对照的团队,也适合要复现 PyramidKV、SnapKV 这类论文结果的研究者。如果你的目标只是单点加速,或者模型不在 Llama、Mistral 注意力路径覆盖范围内,这个仓库的适配成本会高于收益。
- 能商用吗?
- 可以。MIT 是宽松许可证:你可以使用、修改并销售基于它的软件,只需保留版权和许可证声明。
- 还在维护吗?
- 在维护。仓库最近一次提交在 34 天前。
- 用什么语言写的?
- 主要是 Python(依据 GitHub 的语言统计)。
以上回答依据项目的 GitHub 数据(最近同步于 2026年9月15日)和我们的分析,不构成法律意见。
开源项目深度解析
它解决的是对比成本,不是压缩率本身
KV cache 压缩这条线上,方法论文很多,代码却很散。StreamingLLM 有自己的实现,H2O 有自己的实现,SnapKV 又有自己的实现,每份代码对预算的定义、对 attention sink 的处理、对评测数据集的切分都可能不同。想判断哪个方法在自己的模型和任务上更合适,往往要先把几份代码分别跑通,再想办法让结果可比。KVCache-Factory 要处理的就是这一层:它把 FullKV、StreamingLLM、H2O、SnapKV、Quest、NACL、Scissorhands、MiniCache、PyramidKV、CAM、L2Norm、AdaKV、HeadKV、ThinK、HeadInfer、MInference,以及 KIVI、KVQuant、GEAR 三条量化路径放在一个评测接口下,用同一组命令行参数控制预算、注意力后端、数据类型和量化位宽。
目标读者是两类人。一类是研究侧,要复现 PyramidKV 论文里 128 和 2048 两个预算下的结果,或者拿自己的方法塞进同一套 LongBench 流程做对照。另一类是工程侧,已经在生产里跑长上下文推理,想先在小规模评测上确认某种驱逐策略掉不掉点,再决定要不要改推理栈。README 对方法类型的划分(压缩/驱逐、检索、编码期驱逐、跨层压缩、预算分配、合并、自适应、量化、无损卸载)说明作者并不打算只做一种路线,而是把不同思路并列出来。
预算、粒度与分数聚合:三个真正决定结果的旋钮
从命令行参数能看出这个框架的抽象方式。--max_capacity_prompts 是每层的目标 KV cache 预算,PyramidKV 的做法是把总预算按层重新分配,所以同一个数字在不同方法下的含义并不完全等价。--kv_cache_granularity 控制缓存布局,默认 query_head 是旧布局,kv_head 是面向 GQA 的高效布局,README 说明该布局目前支持 snapkv、pyramidkv、h2o、streamingllm、cam、l2norm,以及 adakv 和 headkv,但后两者的 GPU 验证状态标注为 pending。
当切到 kv_head 时,每个 KV head 要面对多个 query head 的分数,--gqa_score_agg 决定怎么合并:mean 是默认值,另有 max 和 sum。这个选择不是装饰性的,mean 会平滑掉个别 query head 的尖峰,max 会把最激进的保留需求传导到 KV head 上,直接改变哪些 token 被留下。仓库里有一份 docs/gqa_cache_layout.md 专门讲这套布局,run_ruler.py 和 run_needle_in_haystack.py 接受与 LongBench 相同的旗标,意味着三种评测共享同一套缓存语义。
方法之间的差异也体现在参数上。--merge 提供 pivot 和 weighted 两种合并策略,对应 CAM 这类合并路线。--quant_method 接 kivi、kvquant 或 gear,配合 --nbits 指定位宽,--quant_backend 默认 hqq,--quant_residual_length 控制全精度残差窗口且默认等于 max_new_tokens,--q_group_size、--axis_key、--axis_value 用来调整量化布局,KIVI 默认 key 轴为 1、value 轴为 0。GEAR 额外接受 --rank 和 --outlier_ratio。这些键名说明量化路径不是简单包一层,而是把分组、轴和离群值处理都暴露了出来。
从安装到一次 LongBench 跑批
依赖被钉在 transformers==4.44.2,加上 torch 和 flash-attn>=2.4.0.post1。README 的安装步骤是克隆仓库、pip install -r requirements.txt,然后 export PYTHONPATH="$PWD:${PYTHONPATH}",最后这步不能省,否则 pyramidkv 包不会被找到。flash-attn 是可选的,但只有在把 --attn_implementation 设成 sdpa 或 eager 时才可省;要跑 FlashAttention v2 实验,得先装 torch 再执行 pip install flash-attn --no-build-isolation。MInference 被刻意排除在基础依赖之外,需要单独 pip install -r requirements-minference.txt。
最小可运行示例来自 README:设置 CUDA_VISIBLE_DEVICES=0,然后调用 run_longbench.py,传入 --method pyramidkv、--model_path 指向本地的 Llama-3-8B-Instruct、--max_capacity_prompts 128、--attn_implementation flash_attention_2、--save_dir ./results_long_bench 和 --use_cache True。或者走封装脚本 scripts/scripts_longBench/eval.sh,参数顺序是 CUDA_VISIBLE_DEVICES、method、max_capacity_prompts、attn_implementation、source_path、model_path、merge_method、quant_method、nbits,例如 bash scripts/scripts_longBench/eval.sh 0 pyramidkv 128 flash_attention_2 ./ /path/to/model none none 8。
--datasets 接受逗号分隔的数据集名,比如 narrativeqa,qasper,不传则跑完整的 16 个数据集。--dtype 可选 float16(默认)、bfloat16 或 auto。方法名列表里 FullKV 是保留完整缓存的基线,用它可以先确认环境本身没问题,再换压缩方法。
方法的成熟度并不整齐
README 自己给出了一句关键提醒:Llama 和 Mistral 的注意力路径对主要压缩方法有支持,但一些较新的方法目前的 runner 和模型覆盖更窄,启动大规模任务前要检查 runner 的参数可选值。这句话的分量比它看起来重。一个方法出现在支持列表里,不等于它在三个 runner 里都能跑通,也不等于它在所有模型上都验证过。
具体的绑定约束有几处。--method think 要求 --attn_implementation eager,与 FlashAttention 路径互斥。--method headinfer 是无损路线,做法是把 KV cache 按 head 卸载到 CPU 并异步预取,保留完整缓存、不做近似,因此它忽略 --max_capacity_prompts,并且必须配 flash_attention_2。这意味着 headinfer 和压缩方法的对比维度不同:前者换的是显存与带宽,后者换的是精度。把它和 SnapKV 放在一张表里比吞吐,结论会误导人。
--kv_cache_granularity kv_head 的 GPU 验证在 adakv 和 headkv 上标注为 pending,这是另一个需要自己确认的点。如果你的模型是 GQA 结构、又想用自适应预算类方法,这条路径的可靠性没有在文档里得到保证。
HeadInfer 与 SnapKV 代表的两条不同取舍
要理解这个仓库的定位,可以把 HeadInfer 和 SnapKV 放在一起看。SnapKV 属于检索/压缩路线,用观察窗口做注意力池化,选出保留哪些 token,缓存被真正裁剪,显存占用下降,代价是信息丢失。HeadInfer 走的是无损卸载,按 head 把缓存搬到 CPU,用异步预取遮掩传输延迟,缓存内容一个不少,代价是 CPU 内存占用和预取调度开销。
两者的命令行行为也不一样:SnapKV 受 --max_capacity_prompts 约束,HeadInfer 直接忽略这个参数。所以做选型时,问题不是哪个更快,而是你的瓶颈在哪。显存吃紧、能接受轻微掉点,看压缩和驱逐这一类;显存够但想塞进更多并发请求,且不能容忍任何精度损失,看卸载这一类。KIVI、KVQuant、GEAR 又是第三种取舍,把缓存压到低位宽,--quant_residual_length 保留一段全精度窗口来兜住最近的 token,GEAR 再用 --rank 和 --outlier_ratio 处理离群通道。三条路线的失败模式完全不同:驱逐会丢关键 token,量化会引入数值误差,卸载会撞上 PCIe 带宽。
维护成本与许可证
仓库采用 MIT 许可证,没有检索到正式 release,也没有 homepage,这意味着版本管理靠 main 分支的提交历史,而不是带标签的发布。对使用者来说,这直接影响复现策略:如果论文里的数字需要长期可复现,应该记录下具体 commit,而不是只写仓库地址。
依赖侧的维护压力主要来自两处。transformers 被钉死在 4.44.2,升级需要重新验证各方法的注意力实现是否仍然兼容。flash-attn 的版本约束是 >=2.4.0.post1,而且安装必须加 --no-build-isolation 在 torch 之后执行,这在 CI 里是个容易出错的步骤。MInference 被拆成独立的 requirements-minference.txt,说明它有自己的依赖树,装与不装是两条路径。
项目在 2024-11-28 从 PyramidKV 更名为 KVCache-Factory,更名本身说明范围在扩张。范围扩张通常意味着新方法的接入速度快于既有方法的验证速度,这与 README 里关于 runner 覆盖较窄的提示是一致的。MIT 许可证本身对商用和修改都比较宽松,但仓库把 transformers、flash-attn、MInference 等第三方依赖作为单独分发,这些依赖各自的许可证需要单独确认,本文不构成法律意见。
什么时候不该用它
如果只需要一种压缩方法,比如确定就用 SnapKV,直接引它的原始实现可能更省事。KVCache-Factory 的抽象层是为多方法对比设计的,单方法场景下你要额外承担 transformers 版本钉死、PYTHONPATH 导出、参数校验这些固定成本,收益有限。
如果模型不在 Llama 和 Mistral 的注意力路径覆盖范围内,README 没有给出其他架构的支持承诺,硬改 runner 的工作量不可预估。如果评测目标不是 LongBench、RULER 或 needle-in-a-haystack 这三类,仓库提供的 runner 不能直接复用,需要自己接评测循环。
还有一个容易被忽略的点:--use_cache True 出现在 quickstart 里,但 README 没有展开说明它缓存的是什么、命中条件是什么。在正式跑批前,这一点需要从代码里确认,否则重复实验可能拿到非预期的结果。
编辑结论
适合已经在跑长上下文推理、需要把多种 KV cache 压缩策略放在同一套 LongBench 或 RULER 流程里做横向对照的团队,也适合要复现 PyramidKV、SnapKV 这类论文结果的研究者。如果你的目标只是单点加速,或者模型不在 Llama、Mistral 注意力路径覆盖范围内,这个仓库的适配成本会高于收益。动手前先确认三件事:目标方法是否在你打算使用的 runner 里被列为可选值,--attn_implementation 与方法的绑定关系(think 必须 eager,headinfer 必须 flash_attention_2),以及 --kv_cache_granularity kv_head 在 GQA 模型上是否已经过 GPU 验证,README 明确写着 adakv 与 headkv 的该路径验证尚未完成。
社区笔记