1–5 分钟交付

独享 Mac mini M4

$21.5 / 天起 · 物理机独享
配置云端 Mac
Web VNC 免安装 SSH 密钥接入 五节点可选

FIELD NOTE · Mac 租赁

2026 Ollama 0.34.0 跑 DeepSeek-R1:任务结束后内存不释放怎么办?

这篇文章面向在 Apple Silicon Mac 上运行 DeepSeek-R1 批处理、长文本和 AI Agent 的开发者与技术负责人。我们将区分 keep_alive 驻留、KV Cache、文件映射和 Runner 堆增长,并给出可验证的卸载、重启、并发保护与云端 Mac 迁移条件。

任务完成后,ollama ps 里的模型已经消失,但 Mac 的内存压力和 Runner 进程占用仍然不降。

本周最快动作:先不要继续扩大内存上限,也不要反复缩短上下文;在作业边界使用 keep_alive: 0ollama stop 临时止血,并同时记录 Runner PID、物理内存压力、交换活动和下一次冷加载结果。 如果进程堆随请求持续增长,卸载只是回收手段,不是底层泄漏修复;持续运行的 Agent 服务应进一步采用进程隔离或独立算力节点。

这篇文章适合三类人:

  • 批处理开发者:需要把模型回收纳入每个批次的生命周期。
  • AI Agent 工程师:服务必须持续运行,不能因为一次卸载破坏会话与工具调用。
  • 技术负责人:需要在冷加载成本、任务稳定性和扩容方式之间做容量决策。

最后更新于 2026 年 9 月 15 日,数据核实自 Ollama 官方 v0.34.0 发布页、API 文档、macOS 日志说明、Apple Metal 文档及相关公开 Issue。

先确认 Ollama 0.34.0 DeepSeek-R1 内存不释放属于哪一类问题

Ollama 默认会让模型在请求结束后继续驻留一段时间。官方 API 文档将 keep_alive 的默认值写为 5 分钟,而设置为 0 才表示在请求结束后立即卸载模型。模型仍显示在运行列表中,并不能直接证明发生了泄漏。(Ollama API 文档)

在 Apple Silicon 上,CPU 与 GPU 共享统一内存。Metal 的 hasUnifiedMemory 用来表示 GPU 是否与 CPU 共享全部内存,而 recommendedMaxWorkingSetSize 只是建议的工作集范围,不是可以随意覆写的“安全上限”。因此,系统出现交换活动或内存压力,不一定意味着 Ollama 没有释放模型;也可能是模型权重、文件映射、KV Cache、压缩内存和 Runner 堆同时存在。(Apple Metal 统一内存说明)

任务结束后为什么 Mac 仍然占用大量内存?

通常要先排除三种情况:

  1. 模型仍在 keep-alive 驻留期内。 ollama ps 还能看到模型,说明服务认为它仍可复用。
  2. 模型列表已清空,但系统缓存尚未立即回落。 macOS 会保留文件缓存,并通过压缩内存和交换机制维持活跃进程。
  3. 模型已卸载,但旧 Runner 进程的堆或匿名内存没有随模型一起释放。 这才是需要重点验证的路径。

Apple 对活动监视器的说明也提醒,Memory Pressure 由空闲内存、交换速率、wired memory 和文件缓存等因素共同决定;单看“内存已用”或某一次 Activity Monitor 读数,无法确认回收是否成功。(Apple 活动监视器内存压力说明)

降低上下文不一定有效,先找出真正的增长变量

如果内存增长主要来自 KV Cache,缩短上下文通常会改变占用;但如果增长跟随请求数量或并发度,继续把上下文从 16K 降到 8K、再降到更低,可能只会牺牲输出能力,却没有解决 Runner 堆增长。

建议使用同一个 DeepSeek-R1 模型建立三个最小复现序列:

  • 固定请求内容,只改变 num_ctx
  • 固定上下文,只增加请求数量;
  • 固定请求数量,分别使用单并发和多并发。

每一轮都记录请求编号、上下文参数、并发数量、Runner PID、vmmap 物理占用、交换活动和 ollama ps 返回值。macOS 上可以使用:

pgrep -alf 'ollama|runner|llama'
vmmap -summary <Runner-PID>

公开社区案例显示,Ollama 0.32.15 在 macOS / Metal 上出现过“/api/ps 中模型占用稳定,但 llama-server 进程堆随请求量增长”的报告;该案例使用的是其他模型,并非 DeepSeek-R1,也不是 Ollama 0.34.0 的官方确认结论。报告中的一次测试记录了进程物理占用从 8.25 GB 增长到 13.74 GB,且降低上下文没有改变增长趋势。这个数字只能作为排查线索,不能直接外推到当前环境。(Ollama 社区 Issue:macOS / Metal Runner 堆增长案例)

因此,读者应使用自己的 DeepSeek-R1 请求序列完成最小复现,再决定是否采用回收方案。不要把其他模型、其他版本的社区现象直接改写成 Ollama 0.34.0 的已确认缺陷。

第二步:分别验证 API 卸载、命令行停止和服务重启

keep_alive=0 只是在 API 层请求模型卸载。官方文档规定,当请求内容为空并且 keep_alive0 时,接口会返回 done_reason: "unload";这说明卸载动作被 API 接受,但不等于宿主机所有内存立刻回到基线。(Ollama API 文档中的卸载语义)

API 服务场景

可以先记录 PID 和内存状态,再发起卸载:

MODEL='deepseek-r1:你的标签'

pgrep -alf 'ollama|runner|llama'
vm_stat
curl http://127.0.0.1:11434/api/chat \
  -d "{\"model\":\"${MODEL}\",\"messages\":[],\"keep_alive\":0}"
curl http://127.0.0.1:11434/api/ps

重点检查四项:

  • 返回对象是否包含 done_reason: "unload"
  • /api/ps 是否不再列出目标模型;
  • 原 Runner PID 是否退出或发生变化;
  • 新请求是否出现新的加载过程,而不是继续复用旧进程。

如果模型从 /api/ps 消失,但原 PID 仍然存在,或者物理占用没有回落,就不能把“列表清空”当成“内存已经释放”。

命令行场景

可以使用:

ollama ps
ollama stop deepseek-r1:你的标签
ollama ps
pgrep -alf 'ollama|runner|llama'

如果是手动执行 ollama serve,服务日志就在启动它的终端中;如果是 Ollama.app,macOS 日志通常位于 ~/.ollama/logs/server.log,GUI 相关日志还可能出现在 app.log。(Ollama macOS 日志与故障排查说明)

如果服务重启后问题消失,建议保留重启前后的日志、PID 和 vmmap 摘要,而不是立即删除日志。若需要撤销临时调试设置,应关闭服务后移除 OLLAMA_DEBUG 或其他实验性环境变量,再重新启动 Ollama.app 或 ollama serve

用这张表判断是驻留、缓存还是 Runner 堆增长

观察结果 更可能的原因 下一步验证 临时处理
ollama ps 仍有模型,等待后消失 keep-alive 驻留 查看模型过期时间和 API 返回状态 在作业边界使用 keep_alive: 0
模型从列表消失,Runner PID 也退出 模型已完成卸载 冷加载一次,比较新旧 PID 与加载结果 批次结束回收即可
模型从列表消失,但旧 PID 仍在 Runner 或服务层未完全退出 vmmap -summary、服务日志、交换活动 完整重启服务并保存证据
上下文改变后占用明显变化 KV Cache 或工作集受上下文影响 固定请求量,比较不同 num_ctx 降低上下文,但不要把它当泄漏修复
请求数量增加时堆持续增长 可能是请求路径或 Runner 堆问题 固定上下文和并发,按请求编号采样 按作业边界隔离或卸载
多个调用方同时请求,卸载后出现重复加载 并发误卸载 检查队列、请求状态和 PID 变化 排空队列,使用独占回收锁

第三步:不要把每个请求都变成一次冷加载

keep_alive=0 的成本不是抽象的“性能下降”,而是下一次请求必须重新加载模型。重新加载时间、首个请求延迟和模型切换成本取决于模型文件、统一内存容量、磁盘、并发策略及实际服务方式,不能用一个通用秒数替代实测。

因此,回收边界应按工作负载选择:

  • 单次短任务: 请求结束后不必立即卸载,先按批次回收。
  • 短批处理: 一个批次完成后卸载,再启动下一个批次。
  • 连续对话: 不要逐请求卸载,否则会破坏上下文复用。
  • Agent 队列: 在队列排空、工具调用完成、失败重试状态清空后回收。
  • 多人共享 API: 不能由某一个调用方直接执行卸载。

keep_alive 设为 0 能不能修复 Ollama 内存泄漏?

不能。它能请求模型立即卸载,并且在 Runner 随模型退出时回收一部分堆内存;但它无法修复底层分配器、请求路径、Metal 工作集或 Runner 生命周期中的缺陷。官方文档确认的是卸载语义,不是“泄漏修复保证”。

并发 Agent 必须先排空队列,再执行回收

持续运行的 DeepSeek-R1 Agent 服务最容易出现误卸载:任务 A 认为当前批次结束,调用 keep_alive=0;任务 B 仍在等待工具结果,随后不得不重新冷加载模型,甚至收到连接中断。

我们建议在 API 层增加以下保护:

  • 使用独占作业锁,只有持锁任务可以发起回收;
  • 回收前把新请求标记为排队,不再直接送入 Runner;
  • 等待生成、工具调用和重试全部完成;
  • 检查活动请求数为零,再发送卸载请求;
  • 卸载后确认旧 PID 已退出或服务状态已变化;
  • 用一个最小健康请求验证模型能够重新加载;
  • 失败时只重试一次,随后转入隔离节点或人工处理。

定时批处理则应把“任务成功率”和“回收后的首次加载是否成功”一起记录。不要把定期强杀进程写成所有生产环境的标准修复;强杀可能丢失请求、破坏工具调用状态,还会掩盖真正的版本问题。

第四步:持续失控时,停止修改内核参数

如果已经完成了以下对照,仍然出现请求后堆持续增长,就不建议继续试验未经官方确认的 sysctl 数值或扩大系统内存上限:

  • 固定模型和请求序列;
  • 固定上下文与并发;
  • 记录每个阶段的 Runner PID;
  • 对照 /api/psvmmap
  • 比较卸载前后的物理占用和交换活动;
  • 完成一次重新加载;
  • 保存 server.log 与失败请求证据。

Apple 的 Metal 文档提供的是资源工作集和当前分配量的定义,不是为某个 Ollama 版本给出可直接套用的内核覆写值。(Apple Metal 工作集指标说明)

持续运行 DeepSeek-R1 时多久重启一次 Runner?

没有可靠的通用时间间隔。重启频率应由请求数量、任务完成率、冷加载影响、内存增长曲线和节点可替换性决定,而不是由“每隔几小时重启一次”的经验值决定。

如果每次完成一小段任务就必须回收,说明当前服务边界与负载不匹配;如果回收会频繁打断 Agent 会话,也说明应该改用进程隔离,而不是继续让多个调用方共享一个长期 Runner。

最终决策:继续本机、拆分作业,还是迁移独立 Mac 算力

可以用下面的清单完成一次可复现验收:

  • [ ] 已确认模型是否仍处于官方 keep-alive 驻留期;
  • [ ] 已同时记录 ollama ps、Runner PID、物理占用和交换活动;
  • [ ] 已区分模型权重、KV Cache、文件映射与进程堆;
  • [ ] 已分别测试请求数量、上下文长度和并发度;
  • [ ] 已验证 keep_alive=0 返回的卸载状态;
  • [ ] 已确认旧 Runner 是否退出,而不是只看模型列表消失;
  • [ ] 已完成一次冷加载,并记录任务是否中断;
  • [ ] 已为共享 Agent 增加队列排空与独占回收锁;
  • [ ] 已保留服务日志,避免把重启后的“恢复”误判为根因修复;
  • [ ] 已设定退出条件:任务失败、冷加载影响或恢复频率超过业务可接受范围。

如果增长只出现在单个批次,且作业边界卸载后能稳定回收,可以继续本机运行;如果连续服务不稳定,可以先拆分批次、隔离 Runner 和调用方;如果必须频繁回收才能完成任务,就应把节点可替换性和故障恢复纳入评估。

对于 Mac 选型,可以先参考我们的 M4 Mac mini 与 M5 MacBook Air 节点选择分析,再结合 Mac mini 租赁交付验收要点核对远程连接、服务启动和故障恢复边界。

如果当前方案是单台本地 Mac 长期承载共享 Agent,它的真实缺点通常是:Runner 与其他开发任务争用统一内存、故障后需要人工重启、冷加载会影响所有调用方,而且无法快速替换节点。此时,先用自己的真实请求批次完成一次“运行—卸载—重新加载”对照;若仍需频繁回收才能保证成功率,可以进一步评估 JexMac 的 Mac 节点方案,把独立进程、可替换算力和故障恢复成本一起纳入测试,而不是继续冒险覆写系统内存参数。

物理机独享 · 1–5 分钟交付

内存迟迟不释放?把 AI 任务迁移到 JexMac 独享 Mac

使用 JexMac 真实 Mac mini M4 裸金属云主机,将批处理、长文本和 AI Agent 任务从本地设备分离,减少内存压力。

标准配置
芯片Apple M4 · 38 TOPS
CPU10 核(4P + 6E)
内存16 GB 统一内存
网络1 Gbps 独享带宽
SLA99.9% 可用性
交付1–5 分钟自动开通