7.2 端侧与小模型的缓存取舍 本节摘要:云端的共享边界再怎么划,设备那边还有一重硬约束——手机和边缘盒子没有弹性显存,多出来的 KV 占的是实打实的本地内存,功耗更是直接算进电费与发热。本节把云端那套"复用经济学"搬到端侧重算一遍:先讲清端侧与云端的约束差异,再用一笔账算出 3B 模型在 4K 上下文下的 KV 显存,接着看 llama.cpp 如何把 KV 存下来,最后给出端侧缓存的三条取舍路,并用一张表点明云端方案在端侧哪些失效、哪些反而更重要。 云端那套"显存不够就扩、复用越多越省"的逻辑,到了手机和边缘设备要推倒重来。端侧没有弹性资源池,多一点 KV 就少一点留给模型权重和并发的空间,多算一次 Prefill 就多耗一度电、多升一度温。把同样一本账在端侧重算,结论会和云端大不一样。
本节摘要:云端的共享边界再怎么划,设备那边还有一重硬约束——手机和边缘盒子没有弹性显存,多出来的 KV 占的是实打实的本地内存,功耗更是直接算进电费与发热。本节把云端那套"复用经济学"搬到端侧重算一遍:先讲清端侧与云端的约束差异,再用一笔账算出 3B 模型在 4K 上下文下的 KV 显存,接着看 llama.cpp 如何把 KV 存下来,最后给出端侧缓存的三条取舍路,并用一张表点明云端方案在端侧哪些失效、哪些反而更重要。
云端那套"显存不够就扩、复用越多越省"的逻辑,到了手机和边缘设备要推倒重来。端侧没有弹性资源池,多一点 KV 就少一点留给模型权重和并发的空间,多算一次 Prefill 就多耗一度电、多升一度温。把同样一本账在端侧重算,结论会和云端大不一样。
端侧部署(手机本地、边缘盒子、本地运行的 llama.cpp 之类)和云端推理服务有三类本质差异,它们共同决定了缓存在这里该怎么取舍。
第一是显存以 GB 计且不可弹性。云端一张卡是 80GB,不够可以挂多张、可以调度到别的节点;端侧一台手机能分给模型的通常是 4 到 8GB,且这是和操作系统、其他 App 共享的硬预算。KV Cache 和模型权重在这块地盘里此消彼长——KV 占多了,权重就得用更低精度或者更小模型,否则直接装不下。
第二是没有弹性扩缩容。云端可以在流量高峰临时拉起更多实例分摊并发,端侧一台设备同时服务几个请求是固定的。这意味着端侧的缓存命中率提升,省下的不是"别家机器的钱",而是"本机还能不能多接一个请求"的硬容量。
第三是功耗即成本。云端功耗摊在机房账单里,离终端用户很远;端侧每一次额外 Prefill 都会让电池掉得更快、机身更烫,烫到一定程度系统还会降频,反而更慢。所以端侧"省一次计算"的收益,比云端多一层用户体验的折合——这也是为什么端侧比云端更在意"该不该重算"这件事。
复用经济学的第一步永远是算体量。下面这段可运行代码算出端侧小模型在指定上下文长度下的 KV 显存占用,公式和结论都写在注释里。
# 估算端侧模型在指定上下文长度下的 KV 显存占用 # 公式:KV字节 = 2(键和值) × 层数 × (KV头数 × 头维度) × 精度字节 × 上下文长度 # 以 Llama 架构 3B 模型为例:28 层、GQA 下 KV 头数 8、头维度 128、bf16 精度占 2 字节 def kv_cache_bytes(layers, kv_heads, head_dim, ctx_len, dtype_bytes=2): per_token = 2 * layers * (kv_heads * head_dim) * dtype_bytes return per_token * ctx_len size_gb = kv_cache_bytes(layers=28, kv_heads=8, head_dim=128, ctx_len=4096) / (1024 ** 3) # 单 token KV ≈ 112 KB,4096 token 上下文合计约 0.44 GB print(f"3B 模型 4K 上下文 KV 显存约为 {size_gb:.2f} GB") # 输出:3B 模型 4K 上下文 KV 显存约为 0.44 GB
把这笔账补全:3B 模型权重本身以 bf16 存储约 6GB,加上 4K 上下文的 KV 约 0.44GB,单就一个满载上下文的请求就要吃下 6.4GB 上下。一台 8GB 的手机还剩不到 1.6GB 给系统和并发缓冲——这意味着第二个并发请求若各自带 4K 上下文,KV 又要再翻一倍,直接撞墙。所以端侧 KV 不是"存不存"的问题,而是"在这么紧的地盘里,存一份 KV 值不值它占掉的带宽"。
一个反直觉的推论:端侧上下文越长,KV 显存越呈线性膨胀,反而是长上下文场景里"截断"比"缓存"更先被端侧采用——因为截断直接砍掉 KV 的体量,而缓存只是复用已有 KV,体量本身没变小。云端可以靠缓存把重复计算的算力省掉,端侧却要先问"这份 KV 我到底装不装得下"。
端侧推理引擎里,llama.cpp 的提示词缓存(prompt cache)和状态保存(state / slot)机制最值得讲清,因为它直接对应前面几章的"复用"概念,只是发生在本地文件和设备内存里。
llama.cpp 在计算完一段提示词的 KV 后,可以把这份 KV 以二进制形式落盘或留在 slot 里,下次遇到相同前缀直接从文件读回、跳过 Prefill。它的适用场景和云端前缀缓存一致——系统提示词固定、多轮对话前缀稳定、或者同一段知识库反复作为上下文注入。区别在于:云端的 KV 池由平台管理、跨请求自动命中;端侧的 KV 文件是开发者自己用工具显式保存和加载的,命中没有"自动"可言,要么你代码里主动复用 slot,要么就白算。
更进一步,llama.cpp 的 state 保存能把"已生成到第 N 个 token 的完整解码状态"存下来,适合"恢复一个被打断的长对话"这类场景。这相当于把第 2 章说的"自回归每一步都依赖前面 KV"在端侧做了持久化——代价是这份状态文件本身也占本地存储,且模型版本一变就失效,必须配套版本管理。
端侧资源紧,缓存不能无脑上,通常有三条路各自权衡。
第一条是会话级 KV 保存 versus 重算。多轮对话里,前几轮的历史 KV 是稳定前缀,留着能省后续每轮的 Prefill;但留着就占显存。取舍看会话长度和热度:短会话、 infrequent 复用,不如每轮重算腾出显存给权重;长会话、同一会话内频繁追问,保存 KV 更划算。一个经验阈值——当单会话历史 KV 超过可用显存的百分之十且会话还会继续多轮,保存才值得。
第二条是嵌入缓存。端侧做检索增强时,文档切块后的向量可以缓存在本地,避免每次查询都重新编码。这类缓存体量小(几百维向量)、复用率高,是端侧性价比最高的一档,几乎无争议该做。它对应应用层语义缓存里"向量结果复用"的那一半,在端侧尤其稳。
第三条是小模型语义缓存的可行性。云端语义缓存靠向量库和相似度检索判断"这问题答过没",端侧同样能做,只是向量库要从服务端的大检索系统换成端侧轻量存储。难点不在检索,而在"端侧有没有足够多的历史问答可命中"——个人设备上的提问远没有云端那么多样、那么高频,语义缓存的命中率天然偏低。所以端侧语义缓存更适合"个人长期助手"这类有稳定习惯用语的场景,而非偶发提问的工具。
把前面几章的云端手段逐一搬到端侧,有些直接失灵,有些反而比云端更关键。下面对照表点明差异。
| 云端手段 | 端侧状态 | 原因与取舍 |
|---|---|---|
| 平台级 Prompt 缓存(自动前缀复用) | 基本失效 | 端侧无平台托管,命中靠自己写 slot 复用逻辑,没有自动跨请求 |
| 框架层前缀缓存(vLLM/RadixAttention) | 部分可行 | llama.cpp 可手动存 KV 文件,但需开发者显式管理,非自动 |
| 应用层语义缓存 | 可行但命中率低 | 端侧提问多样性低、量小,复用收益不如云端 |
| 嵌入缓存 | 强烈推荐 | 体量小、复用高,是端侧性价比最高的一档 |
| 截断上下文 | 反而更重要 | 端侧显存硬顶,砍长度直接释放 KV 体量,比缓存更先被采用 |
| 蒸馏到小模型 | 核心手段 | 端侧本就用小模型,蒸馏直接降低单价与显存,比缓存更治本 |
表里最该记住的两行:云端靠"自动"和"弹性"撑起来的缓存,到了端侧大多退化成"要自己写、且装不下";而截断和蒸馏这两样在云端常被当作补充手段的,在端侧反而成了主菜——因为端侧的首要矛盾不是"重复计算贵",而是"根本装不下、跑不快"。
背景到变式走一遍,看端侧取舍怎么落地。
背景:一个跑在手机本地的离线写作助手,模型 3B、上下文 4K,用户习惯在一篇长文上连续追问十几轮,每次提问都带着整篇文稿作为上下文。设备可用显存 6GB,系统和其他 App 还要占 2GB,留给模型加 KV 的只有约 4GB。
操作:先算账——3B 权重约 6GB 已超可用,必须把权重降到 4bit(约 1.5GB)才装得下;4K 上下文 KV 约 0.44GB。剩余空间约 2GB 留给并发和解码。会话采用"保存前几轮 KV、但只保留最近 8 轮"的策略:更早的历史用截断丢弃,既保证多轮复用的 Prefill 节省,又不让 KV 无限膨胀挤掉权重空间。同时把文稿切块向量做嵌入缓存,避免每次查询重编码整篇。
结果:权重降到 4bit 后整机装得下;保存近 8 轮 KV 让每轮追问省掉约七成的重复 Prefill;嵌入缓存把检索开销降到忽略不计。实测连续追问 15 轮,平均单轮响应比"每轮全量重算"快约两倍,且机身温度更可控。
解读:这个 case 的取舍顺序是"先确保装得下(蒸馏/降精度),再谈复用(KV 保存),复用也设上限(只留 8 轮)"。它印证了上面的判断——端侧第一矛盾是容量,缓存必须让位于"能不能跑",所以截断和降精度排在缓存前面。若强行每轮全量保存 KV 不做截断,第 10 轮就会把显存吃穿,反而崩溃。
变式:若用户改成长文一次性分析、不连续追问,会话级 KV 保存的意义就下降,应改为"单次截断到 4K 内 + 嵌入缓存";若设备升级到 12GB,则可放开 KV 保存轮数、甚至上更大模型。端侧策略永远跟着"可用显存"这个数字走,而非跟着云端的经验走。
下一节把视野从"在哪缓存"抬到"除了缓存还能怎么办"——当缓存的天花板够不到、或者端侧根本装不下时,蒸馏、截断、语义压缩这三张牌,要和第 6 章的成本框架同台比武。