ggaaooppeenngg

为什么计算机科学是无限的但生命是有限的

在llm的prompt缓存方法

Prompt缓存可以从两个角度来处理:

基于相似度的外部缓存

一种是对提示词和结果做相似性对比,对结果缓存,这一部分可以在外部来做,例如 langchain的 llm caching。具体实现方法包括:

  1. 向量化存储

    • 将prompt转换为向量表示
    • 使用向量数据库(如FAISS、Milvus等)存储
    • 通过向量相似度检索相近的历史prompt
  2. 模糊匹配

    • 使用编辑距离等算法计算文本相似度
    • 设置相似度阈值进行匹配
    • 返回最相似的历史响应
  3. 缓存策略

    • LRU(最近最少使用)淘汰
    • 时间过期机制
    • 容量限制管理

基于KV Cache的内部优化

另一种是利用 KV Cache 中的交叉注意力机制,复用相同的提示词前缀,这是ChatGPT使用的方法。其工作原理是:

  1. KV Cache机制

    • 存储每个token的Key和Value计算结果
    • 避免重复计算相同前缀
    • 提高推理性能
  2. 增量计算

    • 只对新增的token进行注意力计算
    • 复用已缓存的中间状态
    • 显著减少计算量
  3. 内存管理

    • 自动清理过期缓存
    • 动态调整缓存大小
    • 优化内存使用

在实际应用中,我们可以综合运用这两种缓存方法来优化性能:

  • 对于完全相同或高度相似的prompt,优先使用外部缓存机制
  • 对于部分重叠的prompt,则可以利用KV Cache机制
  • 具体使用哪种策略,需要根据实际场景和资源限制来权衡选择

值得注意的是,KV Cache中的Key和Value都包含了位置编码信息。这意味着要充分发挥prompt缓存的作用,需要确保提示词保持相同的前缀结构。如果提示词的位置发生变化,即使内容相同,对应的KV值也会不同。

具体来说,当两次不同的推理过程中,如果prompt具有相同的提示词前缀,那么这部分的KV计算结果是完全一致的,因此可以直接复用之前推理过程中的KV cache,从而提高推理效率。

最近有一个有趣的论文:通过使用DSL(领域特定语言)来描述prompt结构,可以更精确地控制位置编码。这种方法不仅能够缓存相同的前缀,还支持缓存相同的后缀,同时允许中间部分灵活变动,进一步提升了缓存的效率。但我个人感觉比较难用,等于给本来很灵活的prompt套上了一层结构化的描述语言,这种结构化的语言如果是一些GPT应用的开发有固定模式可能还好,但是通用场景下很难让用户能够用得起来这么专业的描述语言。

像RAG系统中的文档检索结果和固定的记忆上下文,都非常适合作为”提示词前缀”,这样可以更好地利用KV Cache机制。

在设计prompt结构时,我们应该按照内容的变化频率来排序,将越”稳定”的部分放在越前面的位置:

  1. 系统提示词(System Prompt):基本保持不变
  2. 个人记忆(Memory):对特定用户来说相对稳定
  3. RAG检索内容(Context):根据查询动态变化
  4. 对话历史(History):随交互持续更新
  5. 用户输入(User Input):每次都不同

这种由稳定到动态的排序结构可以最大化KV Cache的复用效果:

1
2
3
4
5
System: 你是一个专业的助手。请基于以下上下文回答问题。
Memory: {personal_memory_context}
Context: {retrieved_documents}
History: {chat_history}
User: {user_question}