怎么做到的,动态提示词而不改kv cache

核心思路其实一句话:不是"动态提示词还能不改 KV cache",而是把会变的内容挪到前缀之外,让真正稳定的那一段永远命中缓存。 先讲原理。现在主流推理框架的 prefix caching(vLLM 的 automatic prefix caching、SGLang 的 RadixAttention)都是按 token 前缀做精确匹配的:它把 prompt 切成固定大小的 block(vLLM 默认 16 个 token 一块),每块的 KV 连同它的 token 哈希一起存起来。下一个请求进来,从头逐块比对哈希,命中几块就直接复用那几块的 KV,从第一个不匹配的 block 开始才重新算。 所以关键不是"改不改提示词",而是"变化点在第几个 block"。只要变化点足够靠后,前面的固定部分就一直是缓存命中。 具体做法有四条,从最重要的开始: 一、系统提示词冻结,只在必要时重建 把身份定义、工具定义、skills、行为规则这些每轮都不变的东西拼成一段稳定文本,启动时构造一次,之后每个 turn 原样复用。注意是原样——顺序、空格、标点都不能动,任何一处改动都会让整个前缀哈希对不上,缓存全废。 二、动态内容后置到用户消息里,不要塞进系统提示词 需要每轮变的东西,比如检索结果、记忆 prefetch、当前时间、用户画像,都不要往系统提示词里插。做法是在真正调用 API 前,把当前用户消息临时改写成这样: 用户原始消息 <memory-context>...本轮检索到的相关记忆... </memory-context> 也就是说,系统提示词和前面所有历史都是字节级不变的,只有最后一条消息在变。因为变化点在整段 prompt 的最末尾,前面所有 block 的 KV 全部命中。 三、原始消息永不突变 这是最容易踩的坑。上面那个注入只在构造 API 请求的那一刻做,存进 session 的原始消息一个字都不改。如果你图省事,把注入内容直接写回历史,下一轮历史就变了,前缀从那一轮开始全部失效,而且越往后越亏——因为每次都只差最后一块。 四、把周期性的变化因素也赶出前缀 比如日期时间,如果放在系统提示词开头,每过一天(或者每过一分钟)整段缓存就废一次。放到用户消息里就没这个问题。工具定义的顺序、few-shot 示例的排列也一样,都要固定下来。 举个直观的对比。假设系统提示词 4000 token、历史 8000 token: 坏做法是把记忆塞进系统提示词末尾,结果每次请求的前 4000 token 哈希都不一样,缓存命中率直接归零。 好做法是系统提示词 + 历史共 token 全部命中,只有最后那条用户消息的几百 token 需要重算。在支持 prompt caching 的 API 上,这部分的价格差通常在 10 倍左右。 还有一个量级上的细节值得知道:缓存是 block 粒度的,不是 token 粒度。所以如果你实在没法把变化完全赶出系统提示词,退而求其次把它挪到系统提示词的最后一段,至少前面那些整块还能复用,损失的是一个 block 的量级,不是全部。 最后说个反直觉的点:真正让缓存大面积失效的,通常不是"动态提示词",而是上下文压缩。你一压缩历史,前面几十轮的内容被重写,前缀立刻断裂,之前攒的缓存全没了。所以压缩要攒着做、批量做,别每轮都动一下历史。 参考: vLLM 自动前缀缓存的设计文档 https://docs.vllm.ai/en/v0.24.0/design/v1/prefix_caching.html SGLang RadixAttention 与 vLLM 前缀缓存的对比 https://www.spheron.network/blog/context-engineering-production-ai-agents-kv-cache-long-context/