MLX runner: prefix-cache restore truncated to a multiple of 8192, costing a fixed 17-27 s re-prefill after every cold prompt

这是 Ollama MLX runner 在 macOS 上的前缀缓存恢复缺陷,导致长上下文提示词每次冷启动后都会浪费 0–8191 个 token 的重预填充时间(实测 17–27 秒),且无法通过现有的 num_ctx 、 num_batch 、 OLLAMA_KV_CACHE_TYPE 等参数

快速结论:这是 Ollama MLX runner 在 macOS 上的前缀缓存恢复缺陷,导致长上下文提示词每次冷启动后都会浪费 0–8191 个 token 的重预填充时间(实测 17–27 秒),且无法通过现有的 num_ctxnum_batchOLLAMA_KV_CACHE_TYPE 等参数调节。

适用环境:macOS 26.5,Ollama 0.33.3(桌面版),MLX engine 0.32.2-27-g37c26e5,Apple M5 Max / 128 GiB,Qwen3.8-27B nvfp4 模型,OLLAMA_NUM_PARALLEL=1OLLAMA_FLASH_ATTENTION=1 环境变量。Issue 中提到完整复现需要 ≥8k token 的提示词,并建议使用 Ollama 的 OpenAI 兼容接口 /v1/messages 测试。

最快修复方案:暂无确认的一步修复方案。这是 MLX runner 内部的快照步长设计问题,截至 Issue 关闭时(2026-09-08)尚无官方补丁。可优先尝试升级 Ollama 到包含修复的最新版本,并关注官方更新日志中关于 MLX prefix-cache 的改动。

注意事项:这不是客户端(如 Claude Code)的问题——即使两个 session 只差一个 UUID 或一个字节,由于快照被截断到 8192 的倍数,仍然会触发 0–8191 token 的无谓重预填充。当前只能通过减少冷启动频率来规避(例如保持守护进程常驻、避免频繁重启 Ollama)。

问题场景

在 macOS 上使用 Ollama 的 MLX runner 运行本地大模型(如 Qwen3.8-27B nvfp4),配合 Agent 类工作负载(例如 Claude Code)时触发。当一次会话中发送一个 ≥8k token 的提示词,下次发送几乎相同但尾部略有差异的提示词(如 session UUID 变化、最后一字符改动),就会出现这个缓存恢复截断问题。实测一次真实 Claude Code 会话(37,460 token 的提示词)首次 turn 耗时 27–29 秒,其中约 20 秒是在重复预填充缓存中已包含的 12,885 个 token。

报错原文

MLX runner: prefix-cache restore truncated to a multiple of 8192, costing a fixed 17-27 s re-prefill after every cold prompt
cache hit total=23169 matched=21742 cached=16383 left=6786
cache hit total=38086 matched=36620 cached=32767 left=5319
cache hit total=35755 matched=35735 cached=32767 left=2988
```

(Note:以上是日志中可见的缓存命中/恢复统计行;Issue 中另记录了在 125B 模型预填充时可能出现的 Metal OOM panic,但其与主问题独立。)

原因分析

这不是提示词构造问题,而是 MLX runner 内部的快照机制设计缺陷:它的前缀缓存恢复始终向下截断到 8192 token 的整数倍边界(实测恢复量总是 n × 8192 − 1)。即使真实公共前缀更长,系统也只会恢复到最近的 8192 边界,导致边界到真实分歧点之间的 0–8191 个 token 必须从头重新预填充。

这可能是因为 MLX runner 只在固定步长(8192)保存 KV 缓存快照,而不在 fork 点单独保存一份。Issue 中还指出 options.num_batch 参数被静默忽略(无论设置为 256/2048/8192,prefill 日志中 chunk 始终固定在 2048),可能原因是该 runner 尚未实现对此参数的透传。

第三个可能存在的问题是:能成功加载的模型(如 qwen3.8-flash-next:125b-mlx,权重约 97.8 GiB)在 16k token 提示词预填充时仍可能 OOM(Metal Command buffer execution failed: Insufficient Memory),这与 num_batch 无法调节导致预填充工作集不可控有关。

以上均为基于 Issue 测试数据和作者分析的“可能原因”,尚未有官方架构师确认的根因解释。

环境排查

  • Python / OpenAI SDK:测试脚本只需 curl,不依赖 Python;如需编程复现,可用 OpenAI SDK 调用 /v1/messages
  • macOS:确认版本 ≥26.0(Issue 实测为 26.5),M 系列芯片(M5 Max 实测,其他 M 芯片理论同理)。
  • Ollama:确认版本为 0.33.3(桌面版),MLX engine 为 0.32.2-27-g37c26e5;如使用其他版本,需验证是否未包含修复。
  • 模型:需选择 ≥8k token 上下文的长上下文模型,建议使用 qwen3.8 系列(nvfp4 量化或类似格式)。
  • 环境变量:检查 OLLAMA_NUM_PARALLELOLLAMA_FLASH_ATTENTION 是否与 Issue 相同;如果设置了 OLLAMA_KV_CACHE_TYPE,请先取消并测试。
  • 显卡类型(macOS 环境):可选——确认 Metal 设备是 Apple Silicon(M 系列)而非 Intel + AMD GPU,因为此问题只影响 MLX runner。

解决步骤

  1. 验证当前是否触发问题:用任意 ≥8k token 的提示词(Issue 建议使用一段真实源代码或混合文本),先冷启动发送一次;再发送几乎相同内容但改最后一个字符的请求。观察 API 返回的 usage.input_tokens。如果第二次 input_tokens 显著大于 5(例如几百到 8000+),即触发本问题。
  2. 确认是否为 stride 截断:检查第二次的 input_tokens 是否约等于 len(第一个提示词) - floor((len-1)/8192)*8192。例如 len=65,304 时,预期 re-prefill ≈ 7,960(Issue 实测为 7,961)。
  3. 尝试升级 Ollama:检查是否有比 0.33.3 更新的版本;如果有,升级后重跑上述测试看是否修复。如果没有新版本,继续下一步。
  4. 减少冷启动频率(workaround):保持 ollama serve 或桌面应用常驻,不要频繁重启;如需跑 Agent 多会话,可在同一进程内连续发送请求,避免每次从零预热。
  5. 如果遇到 OOM:在 125B 级模型 + 长提示词时出现 Metal OOM panic,可尝试降低 num_ctx 以缩小预填充工作集(但注意:Issue 显示 num_ctx 对 cache 截断无影响,此处仅为规避 OOM)。
  6. 关注官方修复:定期查看 #18267 及 Ollama release notes,待官方实现“按精确匹配位置恢复”或“步长可配置”后升级验证。

验证方法

最直接的验证是重复“提示词→提示词+改最后一个字符”的测试:修复后,第二次请求的 usage.input_tokens 应接近 5(即完全命中缓存),而不是截断后的 7000–8000+。也可对比修复前后同一 65k token 提示词在“下一 session(仅 UUID 不同)”场景的墙钟时间——如果修复,30.6 秒应降回 0.5 秒内。对于 num_batch 问题,可在 prefill 日志中观察 chunk 大小是否为请求值,或直接检查 API 是否返回错误而非静默忽略。

参考来源

ollama/ollama #18267 — MLX runner: prefix-cache restore truncated to a multiple of 8192

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 22469

发表回复

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