尧图精选

ik_llama.cpp 长上下文中的 KV 缓存复用与上下文平移(Context Shift)机制解析

🕒 发布时间:2026/9/20 0:03:18 📁 来源:尧图网络
ik_llama.cpp 长上下文中的 KV 缓存复用与上下文平移Context Shift机制解析【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp本文以仓库内讨论 Context reuse / context shift for long prompts 为主线结合 ik_llama.cpp 的实际源码系统梳理 KV 缓存复用cache_prompt、上下文平移context shift / K-shift与上下文重用context reuse / chunking三者的区别、底层实现、质量损失机理与适用限制。读完你将能准确判断长上下文溢出时这个 fork 会做什么、为什么这样做、何时该开启或关闭 context shift以及社区正在探索的替代方案。一、问题起点长上下文溢出时的重算噩梦讨论的发起者从 koboldcpp 迁移到 ik_llama.cpp使用 Qwen 3 的 62k 上下文窗口时遇到典型困境上下文溢出后系统提示词system prompt被保留但对话历史被丢弃每次新消息都要从头重新处理约 58k tokens在约 40 tokens/sec 的 prompt 处理速度下每发一条新消息需要等待数分钟而如果存在有效的缓存复用这个时间可以压缩到几秒。这正是长上下文场景下所有推理引擎都要面对的核心矛盾prompt 处理PP是昂贵的而 KV 缓存正是为了让它变便宜而存在的记忆化memoization手段。讨论中 cmoncure 准确点出了这一点KV Cache 本质是 Key-Value 缓存存储的是昂贵的 prompt 处理计算结果以便复用。需要澄清的是ik_llama.cpp并非没有对应实现。讨论早期参与者包括维护者 ikawrakow 本人与贡献者 saood06在后续回复中确认本仓库已经具备 context shift上下文平移能力只是缺少文档说明而按块复用缓存chunking式的 context reuse 则是另一回事仓库刻意没有照搬 mainline 的做法详见下文质量损失与未来方向两节。二、三个容易混淆的概念cache_prompt、context shift、context reuse要理解这场讨论必须先分清三个概念这也是讨论中反复出现的主题概念做什么ik_llama.cpp 中的现状KV 缓存复用cache_prompt请求之间共享相同前缀时直接复用已计算的 KV 缓存不重新处理公共前缀已支持需要请求携带cache_prompt: true早期版本默认关闭见下文上下文平移context shift / K-shift上下文接近窗口上限时丢弃一段旧 token 并对剩余 KV 重新做位置编码RoPE 修正使对话可以无限继续已支持属 llm 核心层能力由 server 在溢出时自动触发上下文重用context reuse / chunking缓存中某段被删除后若新提示词在删除段之后仍有与缓存完全匹配的 token则把它们也拼回来复用如aaaaccccbbbb缓存中复用完整的aaaabbbb而非仅aaaa未实现mainline 的--cache-reuse方案未被移植见后文原因讨论中 ikawrakow 用一个例子精确描述了第三种能力mainline llama.cpp 从 kobold.cpp 借鉴的 chunking缓存里有aaaaccccbbbb新上下文是aaaabbbb理想情况应复用完整的aaaabbbb而不仅仅是前缀aaaa。ik_llama.cpp 目前只做前缀级复用。三、KV 缓存复用cache_prompt的前世今生关于 cache_prompt 的来龙去脉仓库中另一份资料问题 #455KV cache is never reused in OpenAI compatible Chat Completion api与本次讨论直接相关用户通过 OpenAI 兼容的/v1/chat/completions接口调用时发现每次重新生成最后一条回复都会从 p0 全量重算日志中三次请求的 prompt eval 均为 536 tokens 全量处理排查结论不是实现缺失而是默认关闭。贡献者 saood06 指出llama.cpp 现在默认开启 cache_prompt但我们这里默认不开启只有请求中显式传cache_prompt: true才会复用缓存该问题随后推动了一次默认行为变更saood06 表示这个改动很 trivial而且以我们这里的缓存实现几乎没有任何理由关掉它。因此在使用 ik_llama.cpp 的 server 时若通过 OpenAI 兼容 API 集成 WebUI如 Open WebUI、SillyTavern务必在请求中携带cache_prompt: true或确认所用版本已默认开启。这是让多轮对话与重新生成操作避免全量重算的最直接手段也正是讨论发起者等待几秒而非几分钟诉求的基础能力。四、上下文平移Context Shift的源码级实现当上下文真正溢出时起作用的是 context shift。它分为 server 层的触发逻辑与 llm 核心层的 K-shift 计算两部分。4.1 server 层溢出检测与 token 取舍server 侧实现在 examples/server/server-context.cppcontext_shift()L3582-L3630在update_slots主循环中被调用L5069-5071 处注释apply context-shift if needed。当某个 slot 满足system_tokens.size() slot.n_past slot.n_ctx - 1即上下文即将耗尽时触发保留量n_keep默认取整个 prompt 长度slot.params.n_keep 0时并加上 BOS token 计数丢弃量n_discard若未显式指定则默认取剩余可用空间的一半n_discard n_left / 2L3602。这也印证了讨论中 saood06 的描述——context shifting 会平移整个上下文窗口我记得是一半并保留 system prompt平移前会先调用tokens_support_context_shift()L3444-L3457做前置检查多模态mtmdtoken 场景下保证切分点不落在媒体 token 中间必要时通过adjust_n_to_support_context_shift()L3459-L3481微调边界context_shift_find_n_tokens()L3485-L3501利用公共前缀匹配计算实际被保留与被丢弃的 token 数处理提示词与缓存之间分词不一致mistokenization的情况——例如新请求的提示词被重新分词后与缓存中的 token 序列存在细微差异此时按公共前缀对齐后再平移最终通过discard_n_kv_and_cache_tokens()同步丢弃 KV 缓存中的行与cache_tokens并更新slot.n_past - n_discard_cacheL3622-L3623。server 侧还提供相似度评估get_tokens_similarity()与get_cached_tokens_similarity()examples/server/server-common.cpp L1813-L1842用于比较丢弃部分 token 后的缓存与当前提示词的匹配程度从而决定复用策略。4.2 llm 核心层K-shift 与 RoPE 位置修正真正执行平移的是 llm 核心层 src/llama.cpp位置重映射llama_kv_cache_seq_add()L2710-L2765将序列在[p0, p1)区间内的所有 cell 的位置pos与偏移delta同步平移并置位cache.has_shift true、cache.cells_disordered true。对循环Mamba 类模型则只平移pos不搬数据K-shift 图构建下一次llama_kv_cache_update_internal()L7959-L7995检测到kv_self.has_shift时构建llama_build_graph_k_shift()计算图通过llama_set_k_shift()L5346注入 K-shift 输入对每个 KV cell 的 K 向量按新的位置差重新施加 RoPE 旋转完成位置编码修正状态复位计算完成后清除has_shift标志并将所有 cell 的delta归零L7989-L7993随后因 cache 已无序化还会触发 defragllama_kv_cache_defrag_internal重整缓存布局。一句话概括context shift 丢弃一段旧 KV 行 对剩余行重新做 RoPE 位置修正。这样对话可以继续下去代价是被删除 token 的影响并未真正消失见下一节。五、核心争议为什么平移会带来质量损失这是讨论中价值最高、也最富技术深度的一段。ikawrakow 亲自给出的例子KV cache: Yesterday I saw a movie. I absolutely enjoyed it. The main actor was ... New context: Yesterday I saw a movie. The main actor was假设这段新上下文出现在你人生中看过的最烂电影的语境里你期待的回答是a disaster但现存 KV 缓存尽管经过了 context shift仍会强烈偏向 brilliant、amazing 这类正面词汇。关键结论ikawrakowYou cannot undo the impact of the skipped tokens by just changing the position encoding via RoPE.——通过修改位置编码无法撤销被跳过 token 的已生效影响。saood06 进一步把这个机理讲透The tokens do not poison the cache, it is just that a token holds the information of all prior tokens from that sequence when it was calculated. If you get rid of tokens and then shift tokens that had come after the now deleted tokens in order to re-use them, the shifted tokens will still contain the information from the deleted tokens.即每个 token 的 KV 值在计算时已蕴含了该序列中它之前所有 token 的信息。删除I absolutely enjoyed it.这几个 token 后再把后面的The main actor was平移复用这些 KV 行里仍然残留被删 token 的语义影响。要么接受这种残留快但可能偏题要么重新计算被影响的 token慢但干净。讨论中 cmoncure 曾提出一个诱人的设想KV 缓存的影响如果是加法的能否算出被删 token 的贡献f(A)并从后续 KV 中减去Ba - f(A) Bsaood06 的回答隐晦地否定了这种线性可逆性——transformer 注意力与逐层前向传播的组合是非线性的、逐 token 累积的不存在廉价的减法逆运算。这也是为什么行业普遍只能接受平移换速度、重算换质量的二元选择。六、适用限制哪些模型不能 context shift仓库源码对 context shift 施加了明确的模型级与配置级限制模型级llama_model_supports_ctx_shift()src/llama-model.cpp L2664-L2669返回 false 的架构包括 openPangu、DeepSeek4注释说明二者把位置相关的私有状态放在通用 KV cache 之外、Gemma3 与 Cohere2逐层 rope 几何不一致hparams.swa_layers 未记录上下文级get_can_shift()src/llama.cpp L7950-L7957在模型级判断之上还排除MLA 模型lctx.model.is_mla_model()即 DeepSeek 系列 MLA 注意力、IMROPE 位置编码LLAMA_ROPE_TYPE_IMROPE以及已压缩的 KV 缓存--swa-compress因为压缩层的窗口行数无法对应 n_ctx 行视图server 侧当模型不支持平移且上下文溢出时server 会拒绝请求并返回context_length_exceeded错误server-context.cpp L3586-L3592对 DeepSeek4 还会打印明确警告 DeepSeek4 does not support context shifting; use --no-context-shift or increase context sizesrc/llama.cpp L7966。注意MLA 模型不支持 K-shift这意味着 DeepSeek-V3/R1 系列在长对话溢出时无法依赖 context shift应优先扩大上下文或借助缓存复用。这与讨论中 saood06 强调trie 方案可能只对 MLA 模型可行的观察互为印证MLA 的 KV cache 极轻但代价是放弃了传统 K-shift 路径。七、实用配置建议综合讨论与源码针对长上下文场景给出如下实操指引以当前仓库代码为准优先保证前缀复用通过 OpenAI 兼容 API 集成时请求中携带cache_prompt: true旧版本必须显式开启若构建版本已默认开启则可省略。这解决重新生成上一条回复这类前缀未变的场景上下文溢出策略默认情况下 server 会在溢出时执行 context shift丢弃约一半可用空间并保留 system prompt。若你的模型不被支持如 MLA 模型或你无法接受平移带来的质量漂移可通过--no-context-shift显式禁用对应params_base.ctx_shift见 server-context.cpp L1889-L1902 的自动降级逻辑并相应增大--ctx-size精细控制切分点n_keep/n_discard参数允许自定义保留多少、丢弃多少默认分别为保留整个 prompt与丢弃剩余一半L3595-L3602。对于 system prompt 较长的场景可显式指定 n_keep 以确保关键指令不被切掉知晓边界不要指望 context shift 提供无损续聊。对质量敏感的任务如代码生成、推理链更稳妥的做法是保留足够上下文并在溢出前主动截断/重写历史。八、未来方向trie 缓存与零质量损失的探索讨论末尾贡献者 saood06 披露了一个正在酝酿的替代方案用 trie前缀树保存会话中所有处理过的 token并可保存/恢复到文件。其动机与权衡如下目标是保留会话中探索过的每一条分支或多会话共享的大型初始 prompt用最少空间、无质量损失地实现复用该方法不做 chunking 也不做 shifting因此不会像平移那样引入质量退化但它无法解决移除 thought token思考 token而不重算的常见诉求——因为不重算就无法消除被删 token 的残留影响方案可能只对 MLA 模型可行KV cache 极轻且与从 trie 上做 chunk shift的完全融合被认为过于复杂该工作曾因 KV 缓存保存/加载的 bug仓库问题 #436而停滞问题修复后得以继续但仍被描述为large undertaking。这一方向与 mainline 的--cache-reusechunking形成鲜明对比ik_llama.cpp 刻意没有移植 chunking原因是作者认为块级复用尤其小粒度、频繁执行时存在明显的质量损失trie 方案追求不同 tradeoff——对部分场景更好无质量损失、可持久化对另一些场景更差不能复用中间被删的块。截至讨论时间点该方案尚未进入 PR 阶段。结语围绕Context reuse / context shift这场讨论可以提炼出 ik_llama.cpp 在长上下文问题上的完整姿态前缀级 KV 缓存复用cache_prompt是日常多轮对话的第一道加速手段context shiftK-shift RoPE 修正是溢出时的兜底机制但以被删 token 的语义残留为代价且对 MLA 等架构不可用块级 context reusechunking则被有意搁置转而探索 trie 持久化缓存这条更重的替代路线。理解这三者的边界与取舍是正确部署长上下文服务的前提——也呼应了讨论中最重要的一句话你无法用位置编码的魔法抹掉已经发生过的计算。延伸阅读核心实现见 src/llama.cppllama_kv_cache_seq_add、llama_kv_cache_update_internal、llama_build_graph_k_shift模型支持判定见 src/llama-model.cppserver 触发与切分逻辑见 examples/server/server-context.cpp 与 examples/server/server-common.cppKV 缓存复用默认值问题的完整来龙去脉见 问题 #455。【免费下载链接】ik_llama.cppllama.cpp fork with additional SOTA quants and improved performance项目地址: https://gitcode.com/GitHub_Trending/ik/ik_llama.cpp创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →