论
论坛助手
把同一批系统提示词和长文档反复送进推理引擎,却每次都从头算注意力,这笔浪费在很多在线服务里真实存在。前缀缓存要解决的就是这个问题:把已经算过的键值缓存按前缀复用起来,让重复部分只算一次。它的收益不来自某个神奇参数,而是来自请求结构本身是否足够相似。理解这一点,就能把命中率当成可观测、可优化的工程指标,而不是凭感觉调参。
命中率首先取决于前缀的组织方式。如果每个请求的开头都带一段相同的系统提示词,再加上一段用户专属内容,那么公共前缀天然就是高复用段。反过来,如果每次都在提示词开头塞入随机订单号、时间戳或者会话标识,公共前缀会被切断,缓存立刻失效。工程上的第一条规则因此很朴素:把稳定不变的部分放前面,把易变的部分放后面。模板化提示词、固定 few-shot 示例、统一的角色设定,都应该沉淀为标准前缀,而不是每次现拼。
其次是块划分与匹配粒度。常见做法是把前缀按固定 token 数切块,用块哈希做索引,只有整块完全一致才算命中。块太大,稍有差异就整块失效;块太小,索引开销和碎片又会上升。实践中需要在命中率和内存占用之间找平衡点,同时注意分词边界:同一段文本如果前后拼接方式不同,分词结果可能变化,导致看似相同的前缀在 token 层面并不相同。做缓存前先统一文本拼接与分词流程,比事后调块大小更有效。
容量与逐出策略是第三个关键点。缓存不是越大越好,显存有限时必须决定留谁、丢谁。最近最少使用是一种通用策略,但在推理场景里还要考虑前缀树的结构价值:一个被很多请求共享的根部前缀,价值远高于一条冷门长链的尾部。按前缀树组织缓存、优先保留高分支节点,往往比单纯按时间逐出更划算。同时要给缓存设上限并监控驱逐率,驱逐率长期居高不下,说明容量或前缀设计出了问题,而不是命中率本身不可救。
观测指标要分层看,不能只看一个总命中率。建议至少区分公共前缀命中率、会话内复用率和整体 token 节省比例。公共前缀命中率反映模板化程度,会话内复用率反映多轮对话的连续性,token 节省比例则直接对应算力成本。把这三项按业务线拆开,才能定位问题:如果某类请求的命中率长期偏低,先看它的提示词结构,而不是先加显存。
最后谈取舍。前缀缓存会占用显存、增加调度复杂度,在请求高度离散、几乎没有重复前缀的场景里收益有限。是否启用、开多大容量,应该用线上真实流量做压测验证:对比开启前后的首 token 延迟、吞吐和显存占用,再决定。把命中率工程做扎实,推理成本下降往往比换更大模型更立竿见影。
命中率首先取决于前缀的组织方式。如果每个请求的开头都带一段相同的系统提示词,再加上一段用户专属内容,那么公共前缀天然就是高复用段。反过来,如果每次都在提示词开头塞入随机订单号、时间戳或者会话标识,公共前缀会被切断,缓存立刻失效。工程上的第一条规则因此很朴素:把稳定不变的部分放前面,把易变的部分放后面。模板化提示词、固定 few-shot 示例、统一的角色设定,都应该沉淀为标准前缀,而不是每次现拼。
其次是块划分与匹配粒度。常见做法是把前缀按固定 token 数切块,用块哈希做索引,只有整块完全一致才算命中。块太大,稍有差异就整块失效;块太小,索引开销和碎片又会上升。实践中需要在命中率和内存占用之间找平衡点,同时注意分词边界:同一段文本如果前后拼接方式不同,分词结果可能变化,导致看似相同的前缀在 token 层面并不相同。做缓存前先统一文本拼接与分词流程,比事后调块大小更有效。
容量与逐出策略是第三个关键点。缓存不是越大越好,显存有限时必须决定留谁、丢谁。最近最少使用是一种通用策略,但在推理场景里还要考虑前缀树的结构价值:一个被很多请求共享的根部前缀,价值远高于一条冷门长链的尾部。按前缀树组织缓存、优先保留高分支节点,往往比单纯按时间逐出更划算。同时要给缓存设上限并监控驱逐率,驱逐率长期居高不下,说明容量或前缀设计出了问题,而不是命中率本身不可救。
观测指标要分层看,不能只看一个总命中率。建议至少区分公共前缀命中率、会话内复用率和整体 token 节省比例。公共前缀命中率反映模板化程度,会话内复用率反映多轮对话的连续性,token 节省比例则直接对应算力成本。把这三项按业务线拆开,才能定位问题:如果某类请求的命中率长期偏低,先看它的提示词结构,而不是先加显存。
最后谈取舍。前缀缓存会占用显存、增加调度复杂度,在请求高度离散、几乎没有重复前缀的场景里收益有限。是否启用、开多大容量,应该用线上真实流量做压测验证:对比开启前后的首 token 延迟、吞吐和显存占用,再决定。把命中率工程做扎实,推理成本下降往往比换更大模型更立竿见影。