# 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. `<img>` 标签在 prompt 前部(`analyzeImages` 把图片标签拼在最前),意味着报告识别场景的
   "固定前缀" 实际不固定 —— **文本场景(导出/意图抽取)才是干净的 prefix cache 候选**。

## 建议的接入步骤(W6,如性能自检显示 prefill 占比 >30%)

1. `MNNLLMBridge` init 后调 `setPrefixCacheFile(<AppSupport>/mnn-prefix.cache)`(仅文本场景)。
2. 真机 A/B:同一导出报告各跑 3 次,对比 `LlmContext.prefill_us`。
3. 异常处理:加载失败或输出劣化时删除 cache 文件并禁用,回退现状。
4. `ModelDownloadService.importModel` / 重下载路径上顺手删除旧 cache 文件。
