Misc. bug: Memory not released after `llama_model_free` if no prompt was run (Metal)

在 macOS Metal 环境下,如果只加载模型、从未运行过任何 prompt 就直接调用 llama_model_free ,会导致 Metal 的 wired memory(线框内存)不释放,系统内存压力居高不下。优先排查是否在释放模型前执行过任何 GPU 操作(包括无关的 dummy 任务)

快速结论:在 macOS Metal 环境下,如果只加载模型、从未运行过任何 prompt 就直接调用 llama_model_free,会导致 Metal 的 wired memory(线框内存)不释放,系统内存压力居高不下。优先排查是否在释放模型前执行过任何 GPU 操作(包括无关的 dummy 任务)。

适用环境:llama.cpp (libllama core library),版本 10073(commit 91d2fc387),macOS ARM64(Apple Silicon),Metal 后端,启用了 Residency Set。

最快修复方案:llama_model_free 之前运行一次 prompt(即使是极短的输入);或者,在调用 llama_model_free 前主动提交一个不依赖模型缓冲区的 dummy Metal 命令缓冲区(如填充一个临时缓冲区)。Issue 中已验证:在 llama_model_free 之前加入任何 GPU 操作(包括非 Residency Set 的工作),wired memory 即可正常下降。

注意事项:该方案本质上是绕过 Apple Metal 驱动的一个 bug(已向 Apple 提报,FB23959296)。若采用 dummy work 修复,需确保在 llama_model_free 之前执行,且 dummy 操作本身不会引入显著的性能开销。如果应用场景允许释放模型前仍可执行 prompt,直接运行 prompt 是最简单的方法。

问题场景

用户使用 llama.cpp 的 C API(如 llama_model_load_from_file 加载模型后,立即调用 llama_model_free 释放模型,期间未执行过任何推理 prompt。观察到系统物理内存中 wired memory 居高不下(例如 13GB 不释放),直到进程退出才回收。该问题仅在使用 Metal 且启用 Residency Set(默认开启)时触发。

报错原文

Misc. bug: Memory not released after `llama_model_free` if no prompt was run (Metal)

(没有典型报错信息,表现为内存泄漏。通过 top -l 1 | grep PhysMem 观察:PhysMem: 44G used (16G wired, ...),释放后 wired 未降,直到进程退出才降至 3000M wired。)

原因分析

这是 Apple Metal 驱动的一个 bug:当进程创建 Residency Set 并调用 requestResidency 后,如果从未在该进程内执行过任何 GPU 操作,那么即使调用了 endResidency 并释放缓冲区和 Residency Set,系统也不会真正 unwire 原本由 Residency Set 占用的内存。只有进程执行过至少一次 GPU 操作(不论是否与 Residency Set 相关),系统才会正确跟踪 wired memory 的回收。因此,仅加载模型而不执行 prompt 时,llama_model_free 无法释放 wired 内存。

Issue 作者通过最小的 reproduction 确认该 bug 可独立于 llama.cpp 复现,并向 Apple 提交了反馈(FB23959296)。

环境排查

  • 操作系统:macOS (Apple Silicon)。
  • llama.cpp 版本:10073 或相近版本(91d2fc387)。
  • Metal 后端:确认是否启用了 Residency Set(默认启用,可通过 GGML_METAL_NO_RESIDENCY=1 关闭,但会影响性能)。
  • 模型:任何通过 Metal 加载的 GGUF 模型(可复现)。
  • 编译工具:AppleClang 21.0.0。

如果关闭 Residency Set(环境变量 GGML_METAL_NO_RESIDENCY=1)后问题消失,说明是 Residency Set 相关的泄漏。

解决步骤

  1. 方案 A(推荐):确保在调用 llama_model_free 前至少运行一次 prompt(例如使用 llama_decode 处理一个短输入)。即使不输出结果,GPU 操作也会触发正确的 unwire。
  2. 方案 B(需修改代码):llama_model_free 之前,手动提交一个不与模型缓冲区关联的 dummy GPU 操作。llama.cpp 的开发版已计划在内部添加该修复(详见 PR)。可参考 Issue 中提供的 patch:在释放 Residency Set 后,通过 Metal Blit Command Encoder 填充一个临时缓冲区,确保系统记录该进程的 GPU 活动。
  3. 临时规避:设置环境变量 GGML_METAL_NO_RESIDENCY=1 可避免此问题,但会禁用 Residency Set 优化,可能导致推理性能下降。

验证方法

使用 top 或 Activity Monitor 观察进程的 wired memory:先加载模型但不推理,直接释放模型;若 wired memory 没有明显下降,则问题存在。应用上述方案后,在 llama_model_free 后等待几秒,wired memory 应回落到接近未加载模型时的水平(例如从 16GB 降至 3GB 左右)。也可用最小 C 程序复现:llama_model_load_from_filellama_model_free,对比是否执行过 GPU work 时的内存差异。

参考来源

ggml-org/llama.cpp #25937

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 16057

发表回复

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