diff --git a/docs/research/mnn-kv-cache-prefix.md b/docs/research/mnn-kv-cache-prefix.md new file mode 100644 index 0000000..a31b66e --- /dev/null +++ b/docs/research/mnn-kv-cache-prefix.md @@ -0,0 +1,41 @@ +# MNN 前缀 KV Cache 调研(2026-06-10) + +## 结论 + +当前打包的 MNN.xcframework 已暴露 prefix cache 能力,技术上可以把每个场景**固定的 +system prompt + few-shot 模板**的 prefill 结果缓存到磁盘,二次调用跳过这部分 prefill。 +**建议 W6 polish 阶段、用性能自检卡量化 prefill 占比之后再决定是否接入**;当前瓶颈在 +decode 而非二次 prefill,优先级低于 C1/C2/Live Activity。 + +## 依据(`Frameworks/MNN.xcframework/ios-arm64/MNN.framework/Headers/llm/llm.hpp`) + +| API | 行号 | 含义 | +|---|---|---| +| `bool setPrefixCacheFile(const std::string& filename, int flag = 0)` | :161 | 指定前缀缓存文件;配套私有成员 `mPrefixCacheMode` / `mPrefixLength` / `mIsPrefixFileExist` / `completePrefixWrite()`(:250-255)印证:命中时 prefill 只算增量部分 | +| `bool reuse_kv()` | :171 | 读 config 开关 `reuse_kv`,多轮对话内复用 KV(同一会话增量 prefill) | +| `void syncPromptCache(const ChatMessages&)` | :176 | decode 结束后同步缓存文本——注释明确说明 cache 在 generate() 后自更新,此接口供做过后处理(如 deleteThinkPart)的调用方提供更准确版本 | +| `void setKVCacheInfo(size_t add, size_t remove, ...)` / `eraseHistory(begin, end)` | :158-160 | 更底层的 KV 区间管理,可做部分历史擦除 | + +## 对本项目的适用性 + +- 我们所有调用都是「固定模板前缀 + 可变数据后缀」的单轮 `response()`,与 prefix cache + 的模型吻合。 +- 模板体量(估):报告识别 ~900 tok、导出报告 ~700 tok、意图抽取 ~300 tok。 + 按性能自检卡实测的 prefill 速率推算,每次调用预计省 **1~3s**。 +- 多场景共用一个 cache 文件是否支持多前缀未知;最坏情况只对单一场景(建议选「报告识别」, + 模板最长、调用最频繁)生效。 + +## 风险 + +1. `flag` 参数语义在头文件无注释,需读 MNN 源码或实验确认。 +2. OMNI(多模态)分支下行为未验证——我们的 MNN 模型是 Omni 构建。 +3. cache 文件与模型权重版本绑定:模型更新/重下载后必须失效,否则可能输出乱码。 +4. `` 标签在 prompt 前部(`analyzeImages` 把图片标签拼在最前),意味着报告识别场景的 + "固定前缀" 实际不固定 —— **文本场景(导出/意图抽取)才是干净的 prefix cache 候选**。 + +## 建议的接入步骤(W6,如性能自检显示 prefill 占比 >30%) + +1. `MNNLLMBridge` init 后调 `setPrefixCacheFile(/mnn-prefix.cache)`(仅文本场景)。 +2. 真机 A/B:同一导出报告各跑 3 次,对比 `LlmContext.prefill_us`。 +3. 异常处理:加载失败或输出劣化时删除 cache 文件并禁用,回退现状。 +4. `ModelDownloadService.importModel` / 重下载路径上顺手删除旧 cache 文件。