为什么要写这个
前段时间跟别人讨论起做过的一些agent项目,他问我:你把llm执行react循环过程中的tool call和结果都存在哪?我说放在了system prompt里面。他说这就不太专业了,会导致请求走不了模型的kv cache,增加成本。我很惊讶,于是赶紧来学习了一下
prefix caching机制
1. 为什么要走缓存
一般大模型提供商对输入token都会有两个价格,一个是正常价格,这部分是不命中缓存的,另一个是缓存命中价格。一般后者的价格远低于前者,以claude opus为例,缓存命中部分的token价格仅为1/10
2. LLM 视角下的“扁平化”输入
当你通过 OpenAI Compatible API 发送如下格式的请求时:
[ {"role": "system", "content": "你是一个有用的助手...(此处省略2000字规则)"}, {"role": "user", "content": "请帮我分析这份文档...(此处省略5000字文档)"}, {"role": "user", "content": "总结一下第一段。"} ]
在大模型的推理引擎内部,这一串消息会被转换成一个长字符串(Token 序列),类似于:
[System_Start]你是一个...[System_End][User_Start]请帮我...[User_End]总结一下...
3.Prefix Caching 的工作原理:最长公共前缀
Prefix Caching 的核心逻辑是 Radix Trie(基数树) 或类似的哈希匹配机制。它只关心:“从第一个 Token 开始,这新的请求和之前的请求有多少是重合的?”
因此,缓存是也就是这一连串 Token 的顺序决定的。
场景 A:重复内容在 System Prompt
- 请求 1: [System: A] + [User: B]
- 请求 2: [System: A] + [User: C]
- 结果: 推理引擎发现 [System: A] 这一段在缓存里有,直接复用 KV Cache。
- 命中率: System Prompt 部分被缓存。
场景 B:重复内容在 User Prompt,且 System Prompt 也相同
- 请求 1: [System: A] + [User: LongContext + Question 1]
- 请求 2: [System: A] + [User: LongContext + Question 2]
- 结果: 推理引擎发现 [System: A] + [User: LongContext] 这一长串都在缓存里。
- 命中率: System Prompt 加上 User Prompt 的前半部分都会被缓存。这是最高效的用法。
场景 C:System Prompt 变了(这是最大的坑)
- 请求 1: [System: A] + [User: LongContext]
- 请求 2: [System: B] + [User: LongContext]
- 结果: 因为序列是从头开始匹配的,一旦开头的 System Prompt 变了(A 变成了 B),后面的 User Prompt 即使完全一样,也无法利用 Prefix Caching。
- 原因: Transformer 的注意力机制是因果的(Causal),后面的 Token 的 KV 值依赖于前面所有 Token 的计算结果。前面的变了,后面的计算结果全都要变。
回到我前面的场景,如果我把变量部分,也就是每轮react产生的工具调用放到system prompt中,那么变量后面的部分全部都是无法走缓存的。所以总的来说:prompt中的变量部分比如每轮产生的工具结果/思考/对话内容,都尽可能放在user-prompt的最后面