前言
Qwen2.5-7B,seq=8192,batch=16,KV Cache 占 12GB 显存。910B 单卡 64GB 显存,跑完模型只剩 8GB 给激活值。开 KV Cache 量化后显存降到 4.8GB,能跑 batch=32,吞吐涨了 104%。
KV Cache 优化不是"省显存",真正的收益是省出的显存能跑更大 batch,更大 batch 吞吐更高。
KV Cache 的显存占用计算
每个 token 的 KV Cache 大小:
KV_per_token = 2 × layers × hidden_dim × dtype_sizeQwen2.5-7B 为例:
- layers = 28
- hidden_dim = 3584
- dtype_size = 2(FP16)
KV_per_token = 2 × 28 × 3584 × 2 = 401KBseq=8192 时,单个请求的 KV Cache:
KV_cache = 401KB × 8192 = 3.2GBbatch=16 时:
总 KV Cache = 3.2GB × 16 = 51GB51GB 光 KV Cache 就吃掉 80% 显存,只剩 13GB 给模型权重、激活值、运行时开销。
工程经验:很多人以为 KV Cache 优化就是省显存。其实真正的收益是:省出的显存允许跑更大的 batch。Qwen2.5-7B 在 910B 单卡,FP16 batch=8 吞吐 72 tokens/s;开 KV Cache 量化 + PagedAttention 后 batch 开到 32,吞吐 147 tokens/s。算力没变,显存够用了。
INT8 量化:显存省一半
INT8 量化是最简单的 KV Cache 压缩方案。
原理:
FP16 的 KV Cache 转成 INT8:
KV_int8 = round(KV_fp16 / scale)scale 是量化参数,可以按层、按头、按 token 三种粒度。
显存收益:
| 量化方案 | KV Cache 大小 | 节省 |
|---|---|---|
| FP16(基准) | 12GB | - |
| INT8(按层量化) | 6.2GB | -48% |
| INT8(按头量化) | 6.0GB | -50% |
精度损失:
| 量化方案 | PPL 变化 | 下游任务精度损失 |
|---|---|---|
| 按层量化 | +0.02 | < 0.3% |
| 按头量化 | +0.05 | < 0.5% |
实现方式:
ops-transformer 提供KVCacheQuantize算子:
fromops_transformerimportKVCacheQuantize# 初始化量化器quantizer=KVCacheQuantize(quant_mode="per_head",# 按头量化scale_type="dynamic"# 动态计算 scale)# 量化 KV Cachek_int8,v_int8,k_scale,v_scale=quantizer.quantize(k_fp16,v_fp16)# 反量化(用于 Attention 计算)k_fp16,v_fp16=quantizer.dequantize(k_int8,v_int8,k_scale,v_scale)工程经验:前 4 层 KV Cache 量化影响精度大。原因:前几层捕获基础特征,量化误差会放大到后面所有层。我们的做法:前 4 层用 FP16,后面用 INT8。混合精度下精度损失 < 0.2%,显存省 45%。
PagedAttention:消碎片
KV Cache 的显存碎片是个大问题。
问题场景:
batch=8,每个请求的 seq 长度不同:
- 请求 1:seq=1024,KV Cache = 400MB
- 请求 2:seq=4096,KV Cache = 1.6GB
- 请求 3:seq=512,KV Cache = 200MB
- …
分配连续内存给每个请求,会产生大量碎片。请求 2 结束后释放 1.6GB,但请求 4(seq=2048)要 800MB 连续空间,可能分配失败(碎片化导致没有足够大的连续块)。
PagedAttention 方案:
把 KV Cache 分成固定大小的 block(例如每个 block 存 16 个 token)。按需分配 block,不要求连续。
Block 0: token 0-15 的 KV Block 1: token 16-31 的 KV Block 2: token 32-47 的 KV ...请求的 KV Cache 用链表串起来:
请求 1: Block 0 → Block 1 → Block 2 请求 2: Block 3 → Block 4 → Block 5 → Block 6 请求 3: Block 7Block 可以不连续,消碎片。
显存利用率对比:
| 方案 | 显存利用率 | 碎片率 |
|---|---|---|
| 连续分配 | 62% | 38% |
| PagedAttention | 95% | < 5% |
实现方式:
MindIE 推理引擎内置 PagedAttention:
frommindieimportLLMEngine engine=LLMEngine(model="Qwen/Qwen2.5-7B",kv_cache_config={"enable_paged_attention":True,"block_size":16# 每个 block 存 16 个 token})工程经验:block_size 选 16 最优。block_size=8 时管理开销大,block_size=32 时碎片多。实测:block_size=16 时碎片率 < 5%,吞吐最高。
CSA/HCA 压缩:DeepSeek-V4 的 128 倍压缩
DeepSeek-V4 用 CSA(Compressed Sparse Attention,4 倍压缩)和 HCA(Hierarchical Compressed Attention,128 倍压缩)交替使用,实现长序列推理。
原理:
HCA 用"压缩器"把 KV Cache 压到原来的 1/128:
KV_compressed = Compressor(KV_original) # 128 倍压缩压缩后的 KV Cache 用于 Attention 计算:
Q × Compressed_K^T → Attention Score128K 序列的显存收益:
| 方案 | KV Cache 大小 | TPUT |
|---|---|---|
| Full Attention | 7.3GB | > 80ms |
| CSA(4 倍压缩) | 1.8GB | 25ms |
| HCA(128 倍压缩) | 57MB | < 10ms |
128K 序列下,HCA 把 KV Cache 从 7.3GB 压到 57MB,TPOT < 10ms。
精度影响:
| 压缩方案 | PPL 变化 | 长文档任务精度损失 |
|---|---|---|
| CSA(4 倍) | +0.08 | < 1% |
| HCA(128 倍) | +0.35 | < 2% |
实现方式:
ops-transformer 提供压缩算子:
fromops_transformerimportHCACompressor compressor=HCACompressor(compression_ratio=128,num_levels=3# 3 级层次压缩)# 压缩 KV Cachek_compressed,v_compressed=compressor.compress(k,v)# Attention 时解压k_decompressed,v_decompressed=compressor.decompress(k_compressed,v_compressed)工程经验:HCA 短序列(< 4K)比 Full Attention 慢 12%。原因:Compressor 本身有计算开销,短序列时开销比省的时间还大。我们的做法:前两层用 Window Attention(sliding_window=128),中间层按序列长度动态选 CSA 或 HCA。
三条路径对比
| 方案 | 显存节省 | 精度损失 | 适用场景 |
|---|---|---|---|
| INT8 量化 | 50% | < 0.5% | 通用场景 |
| PagedAttention | 提升利用率 33pp | 0% | 多请求、变长序列 |
| CSA/HCA 压缩 | 85%-99% | < 2% | 超长序列(> 32K) |
组合使用:
INT8 量化 + PagedAttention:显存省 50%,碎片率 < 5% INT8 + PagedAttention + HCA:128K 序列 KV Cache 57MB实测性能数据
Qwen2.5-7B,910B 单卡:
| 优化方案 | KV Cache | batch | 吞吐 (tokens/s) |
|---|---|---|---|
| FP16(基准) | 12GB | 8 | 72 |
| INT8 量化 | 6GB | 16 | 112 |
| INT8 + PagedAttention | 6GB | 32 | 147 |
| INT8 + PagedAttention + batch=48 | 9GB | 48 | 138(swap) |
batch=32 时吞吐最高。batch=48 时 KV Cache 开始 swap 到 Host 内存,吞吐反而掉。
DeepSeek-V4 Flash,950DT(16 卡,128K 序列):
| 方案 | KV Cache | TPOT |
|---|---|---|
| Full Attention | 7.3GB | > 80ms |
| CSA(4 倍压缩) | 1.8GB | 25ms |
| HCA(128 倍压缩) | 57MB | < 10ms |
踩坑实录
坑 1:前 4 层量化影响精度
前几层捕获基础特征,量化误差会放大。解决:前 4 层用 FP16,后面用 INT8。
坑 2:PagedAttention 的 block_size 选错
block_size=8 管理开销大,block_size=32 碎片多。最优值:16。
坑 3:HCA 短序列反而慢
seq < 4K 时,Compressor 开销比省的时间大。解决:短序列用 Full Attention 或 Window Attention。
坑 4:batch 开太大导致 swap
batch=48 时 KV Cache swap 到 Host 内存,HBM 带宽利用率掉到 40%。解决:监控显存,batch 不超过 32。
https://atomgit.com/cann/ops-transformer
https://atomgit.com/cann/ascend-transformer-boost
https://atomgit.com/cann/cann-recipes-infer