{"schema_version":1,"id":"article:kv-cache-why-cheaper","kind":"article","slug":"kv-cache-why-cheaper","title":"不是存答案，而是存「计算进度」：KV Cache 为什么能省钱？","author":"晨旭","url":"https://chenxu.xin/writing/kv-cache-why-cheaper","date":"2026-09-07","updated":"2026-09-07","description":"KV Cache 缓存的不是答案，而是模型处理上下文时产生的中间计算结果。从这一句出发，解释改动前文为什么会让缓存失效、换模型为什么要重新付费，以及它和上下文压缩之间的取舍。","position":null,"group":null,"series":"AI技术理解","tags":["context-engineering","memory","agent"],"publication_status":"public","maturity":"evergreen","body_sha256":"4b73cfc4068a7cec67512d477f68731070621fb78ce7496b98e3bf80a89ce30a","paragraph_rule":"src/lib/paragraphs.mjs@1","paragraphs":[{"id":"p1","kind":"heading","text":"开篇","raw":"## 开篇"},{"id":"p2","kind":"paragraph","text":"做 Agent 产品的时候，大概都听过这句话：","raw":"做 Agent 产品的时候，大概都听过这句话："},{"id":"p3","kind":"quote","text":"命中缓存，计费就会少很多。","raw":"> 命中缓存，计费就会少很多。"},{"id":"p4","kind":"paragraph","text":"用 AI 产品的时候，可能也会看到这样的小提示：","raw":"用 AI 产品的时候，可能也会看到这样的小提示："},{"id":"p5","kind":"quote","text":"如果切换模型，花费会更多哦～","raw":"> 如果切换模型，花费会更多哦～"},{"id":"p6","kind":"paragraph","text":"这两句话其实指向同一件事：KV Cache 可以改变一次请求要花多少钱。","raw":"这两句话其实指向同一件事：KV Cache 可以改变一次请求要花多少钱。"},{"id":"p7","kind":"paragraph","text":"第一次听到 KV cache 可能会下意识地觉得必须先把 Transformer、注意力机制、矩阵运算都搞明白，才有资格理解它。但如果只是想在做产品时把它用对，完全不需要这些术语。","raw":"第一次听到 KV cache 可能会下意识地觉得必须先把 Transformer、注意力机制、矩阵运算都搞明白，才有资格理解它。但如果只是想在做产品时把它用对，完全不需要这些术语。"},{"id":"p8","kind":"paragraph","text":"下面可以从我们听到最多的那个问题开始：","raw":"下面可以从我们听到最多的那个问题开始："},{"id":"p9","kind":"paragraph","text":"为什么同一段上下文，第一次交给大模型处理比较贵，再次使用却能便宜很多？难道是模型把答案提前存好了吗？🤔","raw":"为什么同一段上下文，第一次交给大模型处理比较贵，再次使用却能便宜很多？难道是模型把答案提前存好了吗？🤔"},{"id":"p10","kind":"paragraph","text":"不是这样的。","raw":"不是这样的。"},{"id":"p11","kind":"paragraph","text":"KV Cache 缓存的是模型处理这些内容时产生的中间计算结果。下一次遇到相同的开头，系统可以直接复用这些结果，不必从头再算。","raw":"KV Cache 缓存的是模型处理这些内容时产生的中间计算结果。下一次遇到相同的开头，系统可以直接复用这些结果，不必从头再算。"},{"id":"p12","kind":"paragraph","text":"接下来解决的每一件事：为什么改动前面的内容会让缓存失效、为什么换模型就要重新付费、它和上下文压缩是什么关系，都是从这一句推出来的。👇","raw":"接下来解决的每一件事：为什么改动前面的内容会让缓存失效、为什么换模型就要重新付费、它和上下文压缩是什么关系，都是从这一句推出来的。👇"},{"id":"p13","kind":"heading","text":"模型「读一遍」并不便宜","raw":"## 模型「读一遍」并不便宜"},{"id":"p14","kind":"paragraph","text":"首先我们平时和大模型聊天，它为什么会一直记得前面说过的话？这个我在之前的文章中有写到～（一定要先理解这个原理才能理解下面的内容🌟）深度解析 Memory 本质（在第二部分：记忆的本质）","raw":"首先我们平时和大模型聊天，它为什么会一直记得前面说过的话？这个我在之前的文章中有写到～（一定要先理解这个原理才能理解下面的内容🌟）[深度解析 Memory 本质](/writing/agent-memory-essence)（在第二部分：记忆的本质）"},{"id":"p15","kind":"paragraph","text":"人读一份一百页的资料需要花时间理解，大模型也一样。","raw":"人读一份一百页的资料需要花时间理解，大模型也一样。"},{"id":"p16","kind":"paragraph","text":"模型在收到一段文字后，它不能直接开始回答。它需要先处理所有输入，理解每句话之间的关系：","raw":"模型在收到一段文字后，它不能直接开始回答。它需要先处理所有输入，理解每句话之间的关系："},{"id":"p17","kind":"list","text":"- 这句话是谁说的？\n- 它和前面的要求有什么关系？\n- 哪些信息比较重要？\n- 当前问题应该参考哪些内容？","raw":"- 这句话是谁说的？\n- 它和前面的要求有什么关系？\n- 哪些信息比较重要？\n- 当前问题应该参考哪些内容？"},{"id":"p18","kind":"paragraph","text":"这个「阅读和理解输入」的过程需要大量计算。","raw":"这个「阅读和理解输入」的过程需要大量计算。"},{"id":"p19","kind":"paragraph","text":"一段几百字的对话可能不明显，但在编程、办公助手这种长程任务 Agent 中，输入经常包含：","raw":"一段几百字的对话可能不明显，但在编程、办公助手这种长程任务 Agent 中，输入经常包含："},{"id":"p20","kind":"list","text":"- 几十轮聊天记录\n- 大量代码\n- 文件内容\n- 搜索结果\n- 命令执行日志","raw":"- 几十轮聊天记录\n- 大量代码\n- 文件内容\n- 搜索结果\n- 命令执行日志"},{"id":"p21","kind":"paragraph","text":"一轮请求可能包含几万甚至几十万字。","raw":"一轮请求可能包含几万甚至几十万字。"},{"id":"p22","kind":"paragraph","text":"如果每次请求都从头计算一遍，这会浪费大量时间和算力。","raw":"如果每次请求都从头计算一遍，这会浪费大量时间和算力。"},{"id":"p23","kind":"paragraph","text":"于是，KV Cache 出现了。👇","raw":"于是，KV Cache 出现了。👇"},{"id":"p24","kind":"heading","text":"KV Cache：把已经算过的部分保存下来","raw":"## KV Cache：把已经算过的部分保存下来"},{"id":"p25","kind":"paragraph","text":"大模型在收到一段文字之后，不能马上给出回答。它需要先处理整段输入，通过大量计算建立各部分内容之间的关系，再在这些计算结果的基础上生成回复。","raw":"大模型在收到一段文字之后，不能马上给出回答。它需要先处理整段输入，通过大量计算建立各部分内容之间的关系，再在这些计算结果的基础上生成回复。"},{"id":"p26","kind":"paragraph","text":"而且，后面的计算会受到前面内容的影响。","raw":"而且，后面的计算会受到前面内容的影响。"},{"id":"p27","kind":"paragraph","text":"即，大模型处理一次请求，可以粗略分成两个阶段：","raw":"即，大模型处理一次请求，可以粗略分成两个阶段："},{"id":"p28","kind":"list","text":"1. 处理输入，建立对上下文的计算结果；\n2. 根据这些结果生成回答。","raw":"1. 处理输入，建立对上下文的计算结果；\n2. 根据这些结果生成回答。"},{"id":"p29","kind":"paragraph","text":"第一阶段也叫预填充。KV Cache 主要节省的就是这一阶段中重复发生的计算。","raw":"第一阶段也叫预填充。KV Cache 主要节省的就是这一阶段中重复发生的计算。"},{"id":"p30","kind":"paragraph","text":"可以把这个第一阶段想象成一张很长的电子表格。表格中有一百行公式，后面每一行都需要参考前面的结果。","raw":"可以把这个第一阶段想象成一张很长的电子表格。表格中有一百行公式，后面每一行都需要参考前面的结果。"},{"id":"p31","kind":"code","text":"第 1 行   → 计算结果 1\n第 2 行   → 计算结果 2\n……\n第 100 行 → 计算结果 100","raw":"```text\n第 1 行   → 计算结果 1\n第 2 行   → 计算结果 2\n……\n第 100 行 → 计算结果 100\n```"},{"id":"p32","kind":"paragraph","text":"第一次打开表格时，电脑需要把一百行全部计算一遍。","raw":"第一次打开表格时，电脑需要把一百行全部计算一遍。"},{"id":"p33","kind":"paragraph","text":"如果我们只在末尾新增第 101 行，前面一百行完全没有变化，电脑就不必从头重算，只需要读取以前保存的结果，再计算新增的一行。","raw":"如果我们只在末尾新增第 101 行，前面一百行完全没有变化，电脑就不必从头重算，只需要读取以前保存的结果，再计算新增的一行。"},{"id":"p34","kind":"paragraph","text":"👉 KV Cache 保存的，就是大模型在阅读和理解已有内容时产生、并会在后续生成中反复使用的一部分中间计算结果。","raw":"👉 KV Cache 保存的，就是大模型在阅读和理解已有内容时产生、并会在后续生成中反复使用的一部分中间计算结果。"},{"id":"p35","kind":"paragraph","text":"它不是对原文的摘要，也不是模型对过去的记忆，而是模型服务替它保存下来的计算结果。","raw":"它不是对原文的摘要，也不是模型对过去的记忆，而是模型服务替它保存下来的计算结果。"},{"id":"p36","kind":"heading","text":"顺便说一句：为什么叫 KV Cache 呢？","raw":"### 顺便说一句：为什么叫 KV Cache 呢？"},{"id":"p37","kind":"paragraph","text":"KV 中的 K 和 V，分别来自两个英文单词：","raw":"KV 中的 K 和 V，分别来自两个英文单词："},{"id":"p38","kind":"list","text":"- K：Key\n- V：Value","raw":"- K：Key\n- V：Value"},{"id":"p39","kind":"paragraph","text":"它们是模型处理上下文时产生、并会在后续生成中反复使用的两组内部数据。","raw":"它们是模型处理上下文时产生、并会在后续生成中反复使用的两组内部数据。"},{"id":"p40","kind":"paragraph","text":"不理解背后的数学公式没关系～只要记住：","raw":"不理解背后的数学公式没关系～只要记住："},{"id":"p41","kind":"paragraph","text":"KV Cache 保存的不是聊天记录、摘要或者最终答案，而是模型处理上下文时产生的部分中间计算结果。","raw":"KV Cache 保存的不是聊天记录、摘要或者最终答案，而是模型处理上下文时产生的部分中间计算结果。"},{"id":"p42","kind":"paragraph","text":"所以，可以把 KV Cache 简单理解为：","raw":"所以，可以把 KV Cache 简单理解为："},{"id":"p43","kind":"paragraph","text":"大模型的上下文计算缓存。","raw":"**大模型的上下文计算缓存。**"},{"id":"p44","kind":"paragraph","text":"把这个过程放到真实对话中看，就更容易理解了～👇","raw":"把这个过程放到真实对话中看，就更容易理解了～👇"},{"id":"p45","kind":"paragraph","text":"第一次请求是：","raw":"第一次请求是："},{"id":"p46","kind":"quote","text":"你是一名编程助手。项目使用 Go。数据库使用 MySQL。请帮我设计用户表。","raw":"> 你是一名编程助手。项目使用 Go。数据库使用 MySQL。请帮我设计用户表。"},{"id":"p47","kind":"paragraph","text":"第二次请求是：","raw":"第二次请求是："},{"id":"p48","kind":"quote","text":"你是一名编程助手。项目使用 Go。数据库使用 MySQL。请帮我设计用户表。接下来再帮我设计订单表。","raw":"> 你是一名编程助手。项目使用 Go。数据库使用 MySQL。请帮我设计用户表。接下来再帮我设计订单表。"},{"id":"p49","kind":"paragraph","text":"第二次请求的开头与第一次完全相同，只在末尾增加了一个新问题。","raw":"第二次请求的开头与第一次完全相同，只在末尾增加了一个新问题。"},{"id":"p50","kind":"paragraph","text":"这时，模型服务就可以直接复用前面那部分已经保存的计算结果，只处理新增加的内容。这就是所谓的「缓存命中」。","raw":"这时，模型服务就可以直接复用前面那部分已经保存的计算结果，只处理新增加的内容。这就是所谓的「缓存命中」。"},{"id":"p51","kind":"paragraph","text":"连续对话特别适合使用 KV Cache，正是因为每一轮通常都可以理解为：","raw":"连续对话特别适合使用 KV Cache，正是因为每一轮通常都可以理解为："},{"id":"p52","kind":"quote","text":"上一轮的全部内容 + 本轮新增内容","raw":"> 上一轮的全部内容 + 本轮新增内容"},{"id":"p53","kind":"paragraph","text":"👉 前面的聊天历史保持不变，新的消息不断追加在末尾，因此已经完成的计算可以被反复复用。","raw":"👉 前面的聊天历史保持不变，新的消息不断追加在末尾，因此已经完成的计算可以被反复复用。"},{"id":"p54","kind":"paragraph","text":"但如果我们把原来的「项目使用 Go」改成「项目使用 Python」，情况就不一样了。👇","raw":"但如果我们把原来的「项目使用 Go」改成「项目使用 Python」，情况就不一样了。👇"},{"id":"p55","kind":"paragraph","text":"这时，后面几句话可能就需要根据 Go 和 Python 这两个不同编程语言的特点，去重新理解。从发生变化的位置开始，后续计算结果通常都不能继续使用。（因为模型理解输入的方式是计算，而这个计算过程就是和上面比喻的表格一样，前后相互关联着。）","raw":"这时，后面几句话可能就需要根据 Go 和 Python 这两个不同编程语言的特点，去重新理解。从发生变化的位置开始，后续计算结果通常都不能继续使用。（因为模型理解输入的方式是计算，而这个计算过程就是和上面比喻的表格一样，前后相互关联着。）"},{"id":"p56","kind":"paragraph","text":"所以，KV Cache 并不要求整次请求从头到尾都与上一次完全相同，但它通常只能复用：","raw":"所以，KV Cache 并不要求整次请求从头到尾都与上一次完全相同，但它通常只能复用："},{"id":"p57","kind":"paragraph","text":"从开头开始，连续保持相同的那一部分内容。","raw":"**从开头开始，连续保持相同的那一部分内容。**"},{"id":"p58","kind":"paragraph","text":"不过，拥有相同的开头还不够。KV Cache 是某个具体模型计算出来的结果，因此能否复用，还与当前使用的是不是同一个模型有关。👇","raw":"不过，拥有相同的开头还不够。KV Cache 是某个具体模型计算出来的结果，因此能否复用，还与当前使用的是不是同一个模型有关。👇"},{"id":"p59","kind":"heading","text":"为什么换了模型，花费通常会增加？","raw":"## 为什么换了模型，花费通常会增加？"},{"id":"p60","kind":"paragraph","text":"前面提到，聊天过程中只要上下文的开头保持不变，模型服务就可能复用之前保存的计算结果，只处理后来新增的内容。","raw":"前面提到，聊天过程中只要上下文的开头保持不变，模型服务就可能复用之前保存的计算结果，只处理后来新增的内容。"},{"id":"p61","kind":"paragraph","text":"但这些缓存并不只由聊天内容决定，它还属于生成它的具体模型。","raw":"但这些缓存并不只由聊天内容决定，它还属于生成它的具体模型。"},{"id":"p62","kind":"paragraph","text":"不同模型使用不同的参数和计算方式。即使读取完全相同的聊天记录，产生的中间计算结果通常也不一样：","raw":"不同模型使用不同的参数和计算方式。即使读取完全相同的聊天记录，产生的中间计算结果通常也不一样："},{"id":"p63","kind":"code","text":"模型 A：聊天历史        → 模型 A 的缓存\n模型 B：同一段聊天历史  → 模型 B 的缓存","raw":"```text\n模型 A：聊天历史        → 模型 A 的缓存\n模型 B：同一段聊天历史  → 模型 B 的缓存\n```"},{"id":"p64","kind":"paragraph","text":"因此，模型 B 通常不能直接使用模型 A 生成的缓存。","raw":"因此，模型 B 通常不能直接使用模型 A 生成的缓存。"},{"id":"p65","kind":"paragraph","text":"如果在聊天途中切换模型，可能出现这样的过程：","raw":"如果在聊天途中切换模型，可能出现这样的过程："},{"id":"p66","kind":"list","text":"- 切换前：模型 A 已有缓存 → 主要处理本轮新增内容\n- 刚切换到模型 B：模型 B 没有对应缓存 → 需要重新处理整段聊天历史\n- 继续使用模型 B：模型 B 已经建立自己的缓存 → 后续请求又可以复用相同的上下文前缀","raw":"- 切换前：模型 A 已有缓存 → 主要处理本轮新增内容\n- 刚切换到模型 B：模型 B 没有对应缓存 → 需要重新处理整段聊天历史\n- 继续使用模型 B：模型 B 已经建立自己的缓存 → 后续请求又可以复用相同的上下文前缀"},{"id":"p67","kind":"paragraph","text":"这就是为什么聊天进行到一半时切换模型，花费往往会突然增加：聊天记录虽然没有消失，但新模型需要重新处理它。","raw":"这就是为什么聊天进行到一半时切换模型，花费往往会突然增加：聊天记录虽然没有消失，但新模型需要重新处理它。"},{"id":"p68","kind":"paragraph","text":"那如果后来又切回模型 A，能不能重新使用原来的缓存呢？🤔","raw":"那如果后来又切回模型 A，能不能重新使用原来的缓存呢？🤔"},{"id":"p69","kind":"paragraph","text":"有可能，但也不能保证。还要看旧缓存是否仍然存在、是否已经过期，以及请求开头是否仍然完全一致等等。","raw":"有可能，但也不能保证。还要看旧缓存是否仍然存在、是否已经过期，以及请求开头是否仍然完全一致等等。"},{"id":"p70","kind":"paragraph","text":"所以，缓存能否复用，需要两件事同时成立：请求的开头没有变化，以及处理它的还是同一个模型。可以这样记：","raw":"所以，缓存能否复用，需要两件事同时成立：请求的开头没有变化，以及处理它的还是同一个模型。可以这样记："},{"id":"p71","kind":"paragraph","text":"聊天历史属于这段对话，但缓存属于处理它的那个具体模型。","raw":"**聊天历史属于这段对话，但缓存属于处理它的那个具体模型。**"},{"id":"p72","kind":"paragraph","text":"不过，这里就有一个矛盾。KV Cache 能省钱，前提是前面的内容一直保持不变。但在真实的 Agent 产品里，有一件事专门在做相反的事情：上下文压缩。👇","raw":"不过，这里就有一个矛盾。KV Cache 能省钱，前提是前面的内容一直保持不变。但在真实的 Agent 产品里，有一件事专门在做相反的事情：上下文压缩。👇"},{"id":"p73","kind":"heading","text":"上下文压缩与 KV cache","raw":"## 上下文压缩与 KV cache"},{"id":"p74","kind":"paragraph","text":"KV Cache，是在缓存模型处理上下文时产生的中间计算结果，回到前面说过的两个阶段，它只参与第一阶段：处理输入，完全不参与生成答案。","raw":"KV Cache，是在缓存模型处理上下文时产生的中间计算结果，回到前面说过的两个阶段，它只参与第一阶段：处理输入，完全不参与生成答案。"},{"id":"p75","kind":"paragraph","text":"所以，即使前面的聊天记录命中了缓存，模型仍然需要根据本轮新增的问题生成回答。同一段历史后面接上不同的问题，当然会得到不同的答案；即使问题完全相同，由于生成过程可能存在随机性，两次回答也不一定一模一样。","raw":"所以，即使前面的聊天记录命中了缓存，模型仍然需要根据本轮新增的问题生成回答。同一段历史后面接上不同的问题，当然会得到不同的答案；即使问题完全相同，由于生成过程可能存在随机性，两次回答也不一定一模一样。"},{"id":"p76","kind":"paragraph","text":"如果输入完全相同，读取此前保存的中间计算结果，与重新计算这部分内容，理论上应该得到相同的效果。KV Cache 没有删除原始信息，也没有重新改写上下文，它只是让相同的内容不必重复计算。","raw":"如果输入完全相同，读取此前保存的中间计算结果，与重新计算这部分内容，理论上应该得到相同的效果。KV Cache 没有删除原始信息，也没有重新改写上下文，它只是让相同的内容不必重复计算。"},{"id":"p77","kind":"paragraph","text":"这也是 KV Cache 和上下文压缩最重要的区别：","raw":"这也是 KV Cache 和上下文压缩最重要的区别："},{"id":"p78","kind":"list","text":"- KV Cache 是「相同的内容不重复算」\n- 上下文压缩是「把原来的内容变短以后再算」","raw":"- KV Cache 是「相同的内容不重复算」\n- 上下文压缩是「把原来的内容变短以后再算」"},{"id":"p79","kind":"paragraph","text":"👉 KV Cache 通常是无损复用，而上下文压缩会删减、截断或总结历史信息，因此可能遗漏细节。","raw":"👉 KV Cache 通常是无损复用，而上下文压缩会删减、截断或总结历史信息，因此可能遗漏细节。"},{"id":"p80","kind":"paragraph","text":"下面重点讨论一下最常见的摘要式压缩：👇","raw":"下面重点讨论一下最常见的摘要式压缩：👇"},{"id":"p81","kind":"paragraph","text":"假设一段对话原本是：","raw":"假设一段对话原本是："},{"id":"p82","kind":"code","text":"A + B + C + D + E + F","raw":"```text\nA + B + C + D + E + F\n```"},{"id":"p83","kind":"paragraph","text":"系统保留最近的 E、F，把前面的内容整理成「摘要 1」：","raw":"系统保留最近的 E、F，把前面的内容整理成「摘要 1」："},{"id":"p84","kind":"code","text":"摘要 1 + E + F","raw":"```text\n摘要 1 + E + F\n```"},{"id":"p85","kind":"paragraph","text":"随后，对话又产生了 G、H、I：","raw":"随后，对话又产生了 G、H、I："},{"id":"p86","kind":"code","text":"摘要 1 + E + F + G + H + I","raw":"```text\n摘要 1 + E + F + G + H + I\n```"},{"id":"p87","kind":"paragraph","text":"在正常聊天阶段，前面的内容没有变化，只是在末尾不断增加新消息，因此已有缓存可以持续复用。","raw":"在正常聊天阶段，前面的内容没有变化，只是在末尾不断增加新消息，因此已有缓存可以持续复用。"},{"id":"p88","kind":"paragraph","text":"但当系统再次压缩时，就需要在两种方式之间选择。","raw":"但当系统再次压缩时，就需要在两种方式之间选择。"},{"id":"p89","kind":"paragraph","text":"（1）第一种方式是保留旧摘要，只压缩后来新增的历史：","raw":"（1）第一种方式是保留旧摘要，只压缩后来新增的历史："},{"id":"p90","kind":"code","text":"摘要 1 + 摘要 2 + H + I","raw":"```text\n摘要 1 + 摘要 2 + H + I\n```"},{"id":"p91","kind":"paragraph","text":"👉 这种方式能够保留更稳定的输入前缀，有利于继续复用旧缓存。但随着压缩次数增加，摘要会越来越多，内容之间也可能出现重复、过时甚至矛盾。","raw":"👉 这种方式能够保留更稳定的输入前缀，有利于继续复用旧缓存。但随着压缩次数增加，摘要会越来越多，内容之间也可能出现重复、过时甚至矛盾。"},{"id":"p92","kind":"paragraph","text":"（2）第二种方式是把旧摘要和新增历史重新整理成一份统一摘要：","raw":"（2）第二种方式是把旧摘要和新增历史重新整理成一份统一摘要："},{"id":"p93","kind":"code","text":"摘要 2 + H + I","raw":"```text\n摘要 2 + H + I\n```"},{"id":"p94","kind":"paragraph","text":"👉 这样可以删除过时信息、合并重复内容，并让上下文长期保持简洁一致。不过，「摘要 1」变成「摘要 2」以后，输入前缀发生了变化，压缩后的对话就需要重新计算并建立新的缓存。","raw":"👉 这样可以删除过时信息、合并重复内容，并让上下文长期保持简洁一致。不过，「摘要 1」变成「摘要 2」以后，输入前缀发生了变化，压缩后的对话就需要重新计算并建立新的缓存。"},{"id":"p95","kind":"paragraph","text":"但是，（这里有个小分支）这难道就意味着之前的缓存完全浪费了吗？","raw":"但是，（这里有个小分支）这难道就意味着之前的缓存完全浪费了吗？"},{"id":"p96","kind":"paragraph","text":"不一定。🙂‍↔️","raw":"不一定。🙂‍↔️"},{"id":"p97","kind":"paragraph","text":"如果压缩系统设计得当，那么生成新摘要的请求这里就可以用到 KV cache 了～系统只需要继续处理新增内容和压缩指令，再生成新的摘要。","raw":"如果压缩系统设计得当，那么生成新摘要的请求这里就可以用到 KV cache 了～系统只需要继续处理新增内容和压缩指令，再生成新的摘要。"},{"id":"p98","kind":"paragraph","text":"但是，（回到主线）新摘要替换旧内容以后，主对话模型处理请求就不能用之前的缓存了，因为「摘要 1」已经变成「摘要 2」，从变化的位置开始，旧缓存通常不能继续用于压缩后的对话。","raw":"但是，（回到主线）新摘要替换旧内容以后，主对话模型处理请求就不能用之前的缓存了，因为「摘要 1」已经变成「摘要 2」，从变化的位置开始，旧缓存通常不能继续用于压缩后的对话。"},{"id":"p99","kind":"paragraph","text":"整个过程可以概括为：","raw":"整个过程可以概括为："},{"id":"p100","kind":"list","text":"1. 正常对话：前文不变，末尾增加消息 → 持续命中缓存\n2. 生成摘要：利用未变化的前缀生成新摘要 → 旧缓存仍可能降低压缩成本\n3. 摘要替换：新摘要改变了输入前缀 → 重新建立缓存\n4. 继续对话：新摘要保持不变，末尾增加消息 → 再次持续命中缓存","raw":"1. **正常对话**：前文不变，末尾增加消息 → 持续命中缓存\n2. **生成摘要**：利用未变化的前缀生成新摘要 → 旧缓存仍可能降低压缩成本\n3. **摘要替换**：新摘要改变了输入前缀 → 重新建立缓存\n4. **继续对话**：新摘要保持不变，末尾增加消息 → 再次持续命中缓存"},{"id":"p101","kind":"paragraph","text":"所以，摘要管理和缓存复用之间存在一个取舍：","raw":"所以，摘要管理和缓存复用之间存在一个取舍："},{"id":"p102","kind":"list","text":"- 保留所有旧摘要，缓存更加稳定，但上下文会越来越杂乱；\n- 定期合并旧摘要，会暂时损失部分缓存，但上下文更加简洁一致。","raw":"- 保留所有旧摘要，缓存更加稳定，但上下文会越来越杂乱；\n- 定期合并旧摘要，会暂时损失部分缓存，但上下文更加简洁一致。"},{"id":"p103","kind":"paragraph","text":"👉 实际系统可能采用增量摘要、定期合并，或者两者结合。无论选择哪一种，核心目标都是在缓存成本、上下文长度和信息完整性之间取得平衡。","raw":"👉 实际系统可能采用增量摘要、定期合并，或者两者结合。无论选择哪一种，核心目标都是在缓存成本、上下文长度和信息完整性之间取得平衡。"},{"id":"p104","kind":"heading","text":"最后","raw":"## 最后"},{"id":"p105","kind":"paragraph","text":"KV Cache 是模型服务保存下来的中间计算结果。相同的上下文再次出现时，系统可以直接复用这些结果，只计算新增的内容。","raw":"KV Cache 是模型服务保存下来的中间计算结果。相同的上下文再次出现时，系统可以直接复用这些结果，只计算新增的内容。"},{"id":"p106","kind":"paragraph","text":"但它不是完全免费，因为读取缓存、新增内容和生成答案仍然需要资源。","raw":"但它不是完全免费，因为读取缓存、新增内容和生成答案仍然需要资源。"},{"id":"p107","kind":"paragraph","text":"它也不是在缓存答案，更不是模型突然拥有了长期记忆。","raw":"它也不是在缓存答案，更不是模型突然拥有了长期记忆。"},{"id":"p108","kind":"paragraph","text":"完整过程可以概括为：","raw":"完整过程可以概括为："},{"id":"p109","kind":"list","text":"- 第一次请求：处理全部内容 → 产生中间计算结果 → 保存可复用结果 → 生成回答\n- 后续请求：读取已有计算结果 → 处理新增内容 → 生成新回答","raw":"- **第一次请求**：处理全部内容 → 产生中间计算结果 → 保存可复用结果 → 生成回答\n- **后续请求**：读取已有计算结果 → 处理新增内容 → 生成新回答"},{"id":"p110","kind":"paragraph","text":"所以，「缓存命中为什么更便宜」的真正答案是：","raw":"所以，「缓存命中为什么更便宜」的真正答案是："},{"id":"p111","kind":"paragraph","text":"因为大模型不必重新完成已经做过的计算。存储和读取依然有成本，但通常远低于重新调用 GPU 计算整段上下文的成本。","raw":"因为大模型不必重新完成已经做过的计算。存储和读取依然有成本，但通常远低于重新调用 GPU 计算整段上下文的成本。"}],"authored_claims":[{"id":"kv-cache-stores-computation","text":"KV Cache 存的既不是聊天记录、摘要，也不是答案，而是模型读完输入后产生、并会在后续生成中反复使用的中间计算结果。它是上下文计算缓存，不是模型的记忆。","stance":"fact","confidence":"high","supersedes":null,"note":null,"formed_at":"2026-09-07"},{"id":"cache-reuse-needs-identical-prefix","text":"缓存只能复用从开头开始、连续保持相同的那一部分内容。从发生改动的位置起，后面的计算结果通常都不能继续使用。","stance":"fact","confidence":"high","supersedes":null,"note":null,"formed_at":"2026-09-07"},{"id":"cache-belongs-to-the-model","text":"聊天历史属于这段对话，但缓存属于处理它的那个具体模型。中途切换模型，新模型必须把整段历史重新处理一遍，这就是花费突然增加的原因。","stance":"fact","confidence":"high","supersedes":null,"note":null,"formed_at":"2026-09-07"},{"id":"compression-conflicts-with-cache","text":"上下文压缩和缓存复用天然冲突：保留旧摘要能维持稳定前缀、利于命中缓存，但上下文会越来越杂乱；合并成一份新摘要能让上下文简洁，却改写了前缀、让旧缓存作废。","stance":"judgment","confidence":"medium","supersedes":null,"note":null,"formed_at":"2026-09-07"}],"claim_ids":["kv-cache-stores-computation","cache-reuse-needs-identical-prefix","cache-belongs-to-the-model","compression-conflicts-with-cache"],"revision":"d9ae498a29486d2c30549c088427acbb960922544926fcf93353b2daac1b08f1"}