Prompt缓存可以从两个角度来处理:
基于相似度的外部缓存
一种是对提示词和结果做相似性对比,对结果缓存,这一部分可以在外部来做,例如 langchain的 llm caching。具体实现方法包括:
向量化存储:
- 将prompt转换为向量表示
- 使用向量数据库(如FAISS、Milvus等)存储
- 通过向量相似度检索相近的历史prompt
模糊匹配:
- 使用编辑距离等算法计算文本相似度
- 设置相似度阈值进行匹配
- 返回最相似的历史响应
缓存策略:
- LRU(最近最少使用)淘汰
- 时间过期机制
- 容量限制管理
基于KV Cache的内部优化
另一种是利用 KV Cache 中的交叉注意力机制,复用相同的提示词前缀,这是ChatGPT使用的方法。其工作原理是:
KV Cache机制:
- 存储每个token的Key和Value计算结果
- 避免重复计算相同前缀
- 提高推理性能
增量计算:
- 只对新增的token进行注意力计算
- 复用已缓存的中间状态
- 显著减少计算量
内存管理:
- 自动清理过期缓存
- 动态调整缓存大小
- 优化内存使用
在实际应用中,我们可以综合运用这两种缓存方法来优化性能:
- 对于完全相同或高度相似的prompt,优先使用外部缓存机制
- 对于部分重叠的prompt,则可以利用KV Cache机制
- 具体使用哪种策略,需要根据实际场景和资源限制来权衡选择
值得注意的是,KV Cache中的Key和Value都包含了位置编码信息。这意味着要充分发挥prompt缓存的作用,需要确保提示词保持相同的前缀结构。如果提示词的位置发生变化,即使内容相同,对应的KV值也会不同。
具体来说,当两次不同的推理过程中,如果prompt具有相同的提示词前缀,那么这部分的KV计算结果是完全一致的,因此可以直接复用之前推理过程中的KV cache,从而提高推理效率。
最近有一个有趣的论文:通过使用DSL(领域特定语言)来描述prompt结构,可以更精确地控制位置编码。这种方法不仅能够缓存相同的前缀,还支持缓存相同的后缀,同时允许中间部分灵活变动,进一步提升了缓存的效率。但我个人感觉比较难用,等于给本来很灵活的prompt套上了一层结构化的描述语言,这种结构化的语言如果是一些GPT应用的开发有固定模式可能还好,但是通用场景下很难让用户能够用得起来这么专业的描述语言。
像RAG系统中的文档检索结果和固定的记忆上下文,都非常适合作为”提示词前缀”,这样可以更好地利用KV Cache机制。
在设计prompt结构时,我们应该按照内容的变化频率来排序,将越”稳定”的部分放在越前面的位置:
- 系统提示词(System Prompt):基本保持不变
- 个人记忆(Memory):对特定用户来说相对稳定
- RAG检索内容(Context):根据查询动态变化
- 对话历史(History):随交互持续更新
- 用户输入(User Input):每次都不同
这种由稳定到动态的排序结构可以最大化KV Cache的复用效果:
1 | System: 你是一个专业的助手。请基于以下上下文回答问题。 |