MLX runner: KV-cache memory accumulates per-request on dense models (independent of context size/tools), not released short of restart

该报错出现在 macOS 上使用 Ollama 的 MLX runner 跑 dense 模型时,靠 ollama ps 观察 resident SIZE 会随请求次数(分支数)上升,而单次超长 prompt 或工具 schema 体积几乎不影响;优先确认增长是否落在已知的 8G snapshot

快速结论:该报错出现在 macOS 上使用 Ollama 的 MLX runner 跑 dense 模型时,靠 ollama ps 观察 resident SIZE 会随请求次数(分支数)上升,而单次超长 prompt 或工具 schema 体积几乎不影响;优先确认增长是否落在已知的 8G snapshot 缓存上限内,再区分是正常淘汰还是真实未释放。

适用环境:Mac Mini,Apple Silicon,48 GB 统一内存;macOS(Darwin);Ollama v0.32.12(Homebrew 安装,报告时 v0.32.14 为最新);模型 qwen3.8:27b-mlx(dense,非 hybrid/SSM);相关环境变量 OLLAMA_KEEP_ALIVE=10m、OLLAMA_CONTEXT_LENGTH=65536、OLLAMA_KV_CACHE_TYPE=q8_0、OLLAMA_FLASH_ATTENTION=1、OLLAMA_MAX_LOADED_MODELS=2,且上述变量已核实在运行进程上生效。

最快修复方案:暂无确认的一步修复方案。Issue 中确认有效的只有临时规避:用完即通过 keep_alive: 0 强制卸载,或重启 Ollama(原文的 workaround 只能靠重启回收内存)。

注意事项:维护者指出 runner 存在约 8G 的 snapshot 缓存,用于保留历史请求分支以便对话回溯;短量请求下的增长不一定等于无界泄漏。但报告者在更真实的多轮工具调用负载下观察到 SIZE 超过 26 GB 继续上升,且日志显示 99.95% 的 prefix 命中率下内存仍在增长;此外日志中 freeAll() 触发后 SIZE 并未下降。该现象仍未定位,不能把“正常缓存”当作最终解释。

问题场景

用户在 macOS(Apple Silicon)上通过 Ollama 的 MLX runner 加载 dense 模型 qwen3.8:27b-mlx,向 http://127.0.0.1:11434/v1/chat/completions 连续发送相互独立、内容极简的单轮请求(每次用 /api/generate 带 keep_alive: 0 先强制干净卸载,并用 ollama ps 确认表为空),观察到 ollama ps 的 SIZE 随调用次数持续升高,从约 18 GB 爬到 23 GB;在真实的多轮 tool-calling 会话中(真实 system prompt + 约 89KB 的 32 工具 schema + 真实工具返回内容),9 次调用从 18 GB 升到 23 GB,随后 15 次近乎重复的请求继续升到 28 GB,未见回落。

报错原文

MLX runner: KV-cache memory accumulates per-request on dense models (independent of context size/tools), not released short of restart

原因分析

可能原因分两层:

一是缓存设计本身。维护者说明 runner 有一个约 8G 的缓存,用于保存过去请求的 snapshot,以便对话回溯到某个分支点;短序列请求下 SIZE 的上升与该预算相符,属正常淘汰而非无界泄漏。报告者的扩展测试(60 次独立简单请求)也验证了这一点:23 次后停在 26 GB(18 GB 底数 + 8 GB),且日志中没有出现 "failed to restore cache, freeing all caches" 警告,说明平台期来自正常淘汰,而非 freeAll() 兜底路径。

二是仍有未解释的增长。在带真实工具 schema 和工具结果的负载下,SIZE 越过 26 GB 继续上升,且这些请求在日志中显示 matched=18573/18583、cached=18573/18583(约 99.95% 前缀命中),内存仍以约 0.3 GB/次的速率增长——说明日志中的 matched/cached 未必对应内存真的被复用。另外 freeAll() 触发后会清空 trie,但不降低 ollama ps 的 resident SIZE,只有重启才回落;该箱 3 天内记录到 21 次 freeAll()。原报告还引用 #16698 讨论中的分类:一是 Qwen3.6 混合架构(Gated-DeltaNet)特有的 recurrent/SSM 缓存泄漏,二是 close() 中通用 trie 清理缺口(当时位于 cache.go:465),在未判断请求是否为完全 cache miss(begin() 中 prefix == 0)的情况下执行清理,导致死 trie 节点长期不释放。本 Issue 使用的是 dense 模型,与第二类(架构无关)描述相符,但 Issue 关闭时并未给出确认的根因或修复。

环境排查

  • 确认 Ollama 版本:报告为 v0.32.12;.13/.14 的 changelog 未提及 KV-cache/内存相关变更。
  • 确认平台为 macOS / Darwin 与 Apple Silicon,模型为 MLX 格式(-mlx 后缀)。
  • 确认模型类型:dense 还是 hybrid/SSM;后者可能叠加 Gated-DeltaNet 特有的缓存问题。
  • 确认运行进程实际生效的环境变量,而不只是 launchd plist:OLLAMA_KEEP_ALIVE、OLLAMA_CONTEXT_LENGTH、OLLAMA_KV_CACHE_TYPE、OLLAMA_FLASH_ATTENTION、OLLAMA_MAX_LOADED_MODELS。
  • 确认每次测试前的状态:ollama ps 是否为空表(证明已干净卸载)。
  • 检查 ollama.log:是否出现 failed to restore cache, freeing all caches,以及每次请求的 matched/cached 统计。
  • 区分负载形态:无工具短 prompt、单次超长 prompt、带真实工具 schema 的多轮会话,三者的增长曲线不同,需分开记录。

解决步骤

  1. 先建立基线。卸载模型后记录 ollama ps 的 SIZE 作为纯权重底数(本 Issue 约 18 GB)。
  2. 用相互独立的简单请求连续调用,记录每次的 SIZE,判断是否在 18 GB + 8 GB 附近形成平台期。若平台期出现且日志无 failed to restore cache 警告,说明这是正常 snapshot 缓存预算,不是泄漏。
  3. 若平台期不出现,改用更贴近真实的负载复测:真实 system prompt + 实际工具 schema + 真实工具返回内容,构成多轮 tool-calling 会话,再追加若干近乎重复的独立调用,观察是否越过 18 GB + 8 GB 继续上升。
  4. 同时收集该窗口内 ollama.log 的 cache 统计(matched/cached)与 freeAll() 日志,对比高命中率下 SIZE 是否仍在增长。
  5. 作为规避(Issue 已验证但非修复):会话结束或内存逼近上限前,用 keep_alive: 0 强制卸载模型;无法卸载下降时重启 Ollama 服务。
  6. 若使用 OLLAMA_KEEP_ALIVE=-1 长时间驻留,需更频繁检查 SIZE,因为驻留越久累积越明显(该箱曾记录到 45.18 GB 与 55.70 GB 的实例)。
  7. 若确认存在越过缓存预算的增长,带上平台期前后的完整 SIZE 序列、负载形态说明和日志片段回报上游;Issue 已关闭且无已确认修复,这类数据是后续定位的依据。

验证方法

在固定负载下连续请求并记录 ollama ps 的 SIZE:若稳定平台在“权重底数 + 约 8 GB”且日志无 failed to restore cache 警告,可认为增长属正常缓存预算;若在真实 tool-calling 负载下越过该平台持续上升、或在高 matched/cached 命中率下仍上涨,则问题未解决。另可检查 freeAll() 触发后 SIZE 是否回落:Issue 中观察为不回落、需重启,这也是判断问题是否仍在的直接指标。

参考来源

ollama/ollama #17875

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 34209

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注