KV Cache 优化实战:6GB 显存上的每一 MB 都算数

📅 2026/7/2 1:28:03
KV Cache 优化实战:6GB 显存上的每一 MB 都算数
副标题量化、Flash Attention、模型架构——实测 6 种配置告诉你 KV Cache 能省多少日期2026年7月1日一、引子你每天都在用 KV Cache但你未必知道在上一篇博文中我对比了 Ollama 和 llama.cpp 的性能。当时埋了一个伏笔——llama.cpp Ollama VRAM 占用 5,336 MiB ~5,000 MiB相差不大对吧但注意一件事5,336 MiB 里模型的参数占了 4,688 MiB4.7GB那剩下的 600 多 MiB 去哪了答案就是KV Cache——LLM 推理过程中用来缓存之前看过的内容的中间数据。没有它模型每生成一个 token 就要重新算一遍之前的注意力速度慢几十倍。而 KV Cache 最大的问题是它随上下文线性增长。6GB 显存 6,144 MiB 模型参数 4,688 MiBQ4_K_M 计算缓存 ~800 MiB 剩余给 KV Cache ~656 MiB当你把上下文从 4096 扩到 8192KV Cache 翻倍。再扩到 16384再翻倍。很快你的显存就不够用了。本文的目标用同一台 GTX 1660 Ti6GB跑同一个 Qwen3-8B实测 6 种 KV Cache 优化配置——量化、Flash Attention、模型架构——看看到底能省多少、速度会不会降。二、KV Cache 到底是什么一句话生成第 N 个 token 时不需要重新计算前 N-1 个 token 的 Key 和 Value 向量。这个过程用文字说不直观看个类比传统 transformer没有 KV Cache 生成 token 1算全部 → 输出我 ✅ 生成 token 2算全部 → 输出是 ❌ token 1 重算了一遍 生成 token 3算全部 → 输出谁 ❌ token 1、2 重算了一遍 有 KV Cache 生成 token 1算 token 1 → 存(K,V)₁ → 输出我 ✅ 生成 token 2算 token 2 → 存(K,V)₂ → 输出是 ✅ (K,V)₁ 直接复用 生成 token 3算 token 3 → 存(K,V)₃ → 输出谁 ✅ (K,V)₁₂ 直接复用KV Cache 的大小有一个精确的计算公式KV Cache 2 × layers × kv_heads × d_head × ctx × dtype_size ↑ 上下文越长越大以 Qwen3-8B 为例参数值layers36kv_heads8GQA32 个 Q 头分为 4 组d_head128dtypeFP162 bytes代入公式KV Cache 2 × 36 × 8 × 128 × ctx × 2 147,456 × ctx bytes上下文KV Cache占 6GB 显存的比例4,096576 MiB9.4%8,1921,152 MiB18.8%16,3842,304 MiB37.5%32,7684,608 MiB75%注意 32,768 这一行——KV Cache 本身就要吃掉 4.6GB比模型参数4.7GB还大。这就是为什么长上下文推理那么难。这里有个细节KV Cache 可以选择放在 GPU 还是 CPU。llama.cpp 默认全放 GPU最快但 6GB 显存下 ctx8192 时已经快要溢出了。加--no-kv-offload可以把 KV Cache 放到 CPU 内存里牺牲一点速度换更长上下文——Ollama 就是这么做的。三、实验一KV Cache 量化——近乎免费的显存KV Cache 默认存储为 FP16每个值 2 bytes。但如果我用更少的精度来存呢最常见的方案是INT8 量化q8_0——每个值只用 1 byte直接省一半。llama.cpp 提供--cache-type-k和--cache-type-v两个参数分别控制 K 和 V 的量化类型。我测试了 6 种组合数据Qwen3-8B · ctx4096 · GTX 1660 Ti ═══════════════════════════════════════════ 配置 K 缓存 V 缓存 总 KV Cache 节省 ────────────────────── ────────── ────────── ──────────── ──── FP16基线 288 MiB 288 MiB 576 MiB — K q8_0 153 MiB 288 MiB 441 MiB -23% V q4_0 288 MiB 81 MiB 369 MiB -36% V q8_0 FA❶ 288 MiB 153 MiB 441 MiB -23% K q8_0 V q8_0 FA❶ 153 MiB 153 MiB 306 MiB -47% ✅❶ FA Flash AttentionV 量化需要 FA 配合关键发现① K 单独量化是最安全的标配只加--cache-type-k q8_0KV Cache 从 576 MiB 降到 441 MiB-23%性能几乎不变pp512 tg128 FP16: 148.72 tok/s 24.08 tok/s K q8_0: 147.41 tok/s 24.58 tok/s ↓ -0.9% ↑ 2%这是最简单的优化——一行参数零成本省 135 MiB 显存。② V 量化有坑——需要 Flash Attention且 Turing 卡有兼容问题V 的量化比 K 更敏感。llama.cpp 要求--cache-type-v q8_0必须同时开启--flash-attn 1。问题是GTX 1660 Ti 用的是 Turing 架构没有 Tensor Core。Flash Attention 在 Turing 上没有加速反而会因为 kernel 路径不同导致prompt eval 速度下降 46%V q8_0 FA on: pp512 80.51 tok/s ← 比基线慢一倍 tg128 22.86 tok/s ← -5%③ 但 KV 同时量化表现反而最好当 K 和 V 都量化时结果出乎意料K q8_0 V q8_0 FA on: pp512 154.69 tok/s ↑ 4% tg128 26.00 tok/s ↑ 8% KV Cache 306 MiB ↓ -47%又快又省。为什么我推测是因为当 K 和 V 都是 q8_0 时注意力计算可以全在 INT8 路径上完成不需要来回转换格式。而单独量化 VK 是 FP16反而导致额外的格式转换开销。④ 质量验证量化后模型输出会变差吗省显存不代表可以牺牲质量。我拿了同一个 prompt计算 12345 × 67890和写 fibonacci 函数在 4 种配置下各跑了一次temp0对比输出math 测试 code 测试 (fibonacci) ─────────────────────── ─────────────────────── K q8_0 ✅ 思维链措辞略不同 ✅ 同上 不影响最终输出路径 KV q8_0 FA ✅ 125 tokens 完全一致 ✅ 思路一致仅索引基 与基线一字不差 础讨论有细微差异 V q4_0 ❌ 输出为空 ❌ 输出为空V 量化到 INT4q4_0超出了安全边界——模型产生了空输出说明 V 的精度损失大到注意力计算无法正常工作。V 的量化底线是q8_0。K 和 KV 量化的输出质量与基线基本一致。这是因为 KV Cache 存的是当前对话的中间状态而非模型学到的知识——少量精度损失不影响模型的理解能力。思维链中措辞的微小差异如five-digitvsfive属于正常的浮点误差累积不影响最终结论。给 GTX 1660 Ti 用户的建议不要单独用--cache-type-v q8_0直接上--cache-type-k q8_0 --cache-type-v q8_0 --flash-attn 1配置组合效果最好。四、实验二GQA 架构差异——选对模型等于赢在起跑线KV Cache 公式里有两个模型架构参数层数和KV head 数。如果你还没下载模型这两个指标几乎决定了你能跑多长的上下文。把 Qwen3-8B 和 DeepSeek-R1-Distill-Qwen-7B 放在一起比Qwen3-8B DeepSeek-R1-Distill-Qwen-7B ──────────── ────────────────────────── 层数 36 28 Q heads 32 28 KV heads 8 4 GQA 分组 ×4 ×7 每 token KV Cache 36 × 2 × 8 × 128 28 × 2 × 4 × 128 73,728 bytes 28,672 bytes在 ctx8192 下实测Qwen3-8B DeepSeek-7B 差距 KV Cache (ctx8192) 1,152 MiB 448 MiB ×2.6 倍 pp512 148.72 tok/s 162.19 tok/s 9% tg128 24.08 tok/s 27.55 tok/s 14%DeepSeek 的 KV Cache 只有 Qwen3 的 39%——层数少了 22%KV head 少了 50%两者相乘就是 28/36 × 4/8 0.39。这意味着什么同样 6GB 显存同样的 -ngl 全量 offload同样的量化配置 Qwen3-8B DeepSeek-7B ctx │ 剩余显存 ctx │ 剩余显存 ──────┼────────── ──────┼────────── 4096 │ ~600 MiB 4096 │ ~1,300 MiB 8192 │ OOM ❌ 8192 │ ~850 MiB 16384 │ OOM ❌DeepSeek 在 8192 ctx 下仍然能跑Qwen3 已经爆了。这就是 GQA 分组数带来的差距。五、实战配置对照表基于以上所有实验不同场景的最佳配置┌──────────────────┬──────────────────────────────┬──────────────────────────┐ │ 场景 │ 推荐配置llama.cpp 参数 │ 能跑多长上下文 │ ├──────────────────┼──────────────────────────────┼──────────────────────────┤ │ 日常聊天 │ 默认设置什么都不加 │ 8K用完 KV Cache 后 │ │ 128-512 tok │ │ 再长也不影响聊天质量 │ ├──────────────────┼──────────────────────────────┼──────────────────────────┤ │ 文档分析/RAG │ --cache-type-k q8_0 │ 16KQwen3 │ │ 4K-16K tok │ --cache-type-v q8_0 │ 32KDeepSeek │ │ │ --flash-attn 1 │ │ ├──────────────────┼──────────────────────────────┼──────────────────────────┤ │ 代码仓库分析 │ --cache-type-k q8_0 │ 8KQwen36GB 极限 │ │ 16K-32K tok │ --cache-type-v q8_0 │ 16KDeepSeek │ │ │ --flash-attn 1 │ │ │ │ --no-kv-offload │ │ ├──────────────────┼──────────────────────────────┼──────────────────────────┤ │ 多轮对话 │ --cache-type-k q8_0 │ 取决于每轮长度 │ │ 多轮累积 │ --cache-type-v q8_0 │ 量化后能多撑 2-3 轮 │ │ │ --flash-attn 1 │ │ ├──────────────────┼──────────────────────────────┼──────────────────────────┤ │ 极致省显存 │ 选 DeepSeek 架构模型 │ 同样显存多跑 2× 长度 │ │ 6GB 卡极限 │ 上述量化 FA │ │ └──────────────────┴──────────────────────────────┴──────────────────────────┘六、一张图看懂所有优化KV Cache 优化全景Qwen3-8B ctx4096 · GTX 1660 Ti ═════════════════════════════════════════════════════════════════ 基线 ─────────────────────────── 576 MiB, 148.72 / 24.08 tok/s │ ▼ 只量化 K ────────────── 441 MiB (-23%), 147.41 / 24.58 tok/s 只量化 V ────────────── 441 MiB (-23%), 80.51 / 22.86 tok/s ⚠️ KV 都量化 ────────────── 306 MiB (-47%), 154.69 / 26.00 tok/s ✅ │ ▼ 换 DeepSeek KV 量化 ──── ctx8192 时仅 448 MiB Qwen3 在 ctx8192 已 OOM七、总结KV Cache 优化给我的最大感受是它不像模型量化那样有降质风险。量化模型参数Q4_K_M vs Q8_0会改变推理质量需要权衡。但 KV Cache 量化几乎不影响输出质量——它存的不是模型学到的知识而是当前对话的中间状态。精度低一点推理结果几乎不变。所以这是一道送分题如果你想省显存--cache-type-k q8_0 --cache-type-v q8_0 --flash-attn 1— 省 47%如果你想完全不损失性能--cache-type-k q8_0— 省 23%性能不变如果你想一劳永逸选 GQA 分组大的模型KV head 少的——DeepSeek-7B 在 8192 ctx 下的 KV Cache 只有 Qwen3-8B 的 39%你的 6GB 显存很珍贵——帮它减负它回报你更长的上下文和更快的推理。附本文所有数据均在同一硬件上实测获得硬件参数GPUNVIDIA GeForce GTX 1660 Ti 6GB驱动570.133.07 / CUDA 12.8CPU12 核 / 15GB RAMllama.cppcommit d414db02, build 7152测试工具llama-cli, llama-bench测试命令# KV Cache 大小查看llama-cli-mqwen3-8b-q4_k_m.gguf-ngl36-c4096-phello-n1# 量化后查看llama-cli-mqwen3-8b-q4_k_m.gguf-ngl36-c4096\--cache-type-k q8_0 --cache-type-v q8_0 --flash-attn1-phello-n1# 性能基准llama-bench-mqwen3-8b-q4_k_m.gguf-ngl36-p512-n128-r2\--cache-type-k q8_0 --cache-type-v q8_0 --flash-attn1# 查看 KV Cache 信息在 llama-cli 输出中找# llama_kv_cache: size 306.00 MiB (4096 cells, 36 layers)系列全篇CSDN从零到一用 AI Agent 辅助在 6GB 显卡上本地部署大模型实战 — 部署全流程只有 B 级能力的大模型怎么干出 A 级的活 — 任务拆解方法论Agent 不是更聪明的模型而是长了手脚的模型 — Agent 能力框架从 Ollama 到 llama.cpp一次降一层的本地推理探索 — 推理引擎对比[KV Cache 优化实战6GB 显存上的每一 MB 都算数] — 本文