Potential Memory Leak in Gradio – Observed Across Multiple Projects

该问题发生在 Gradio 应用长时间运行且多次触发事件后,内存持续增长且无法自动回收,即使会话结束也无法下降。优先排查事件回调中是否对大型对象(如 NumPy 数组)进行了不必要的复制或全局引用,并尝试将逻辑封装到类中。

快速结论:该问题发生在 Gradio 应用长时间运行且多次触发事件后,内存持续增长且无法自动回收,即使会话结束也无法下降。优先排查事件回调中是否对大型对象(如 NumPy 数组)进行了不必要的复制或全局引用,并尝试将逻辑封装到类中。

适用环境:Gradio(未指定具体版本)、Python 3.11、Docker/Kubernetes 容器环境,依赖 NumPy、FastAPI、Pydantic;未确认具体操作系统与显卡环境。

最快修复方案:暂无确认的一步修复方案。根据 Issue 讨论,将触发事件的逻辑封装到类中,并通过类方法调用缓存函数,可明显降低内存占用(测试中从 300MB 降至 31MB),但该方案仅在本地环境验证过。

注意事项:该方案尚未在容器环境中验证;且不能完全确定是 Gradio 本身的内存泄漏,也可能是用户代码中 `list()` 或 `@cache` 导致的引用生命周期问题。方案标注为“可优先尝试”。

问题场景

在基于 Gradio 构建的 Web 应用中,用户点击按钮触发事件(如生成、推理、状态更新)后,内存占用从约 100MB 飙升至 7GB 以上。即使等待超过 Gradio 设定的会话清理时间(delete_cache),内存也不会回落。该问题在 Docker 和 Kubernetes 环境中均能复现,最小复现 Demo 使用 gr.Stategr.Buttongr.Timer,回调中通过 @cache 缓存大型 NumPy 数组。

报错原文

Potential Memory Leak in Gradio – Observed Across Multiple Projects
Memory usage rises from around 100 MB to over 7 GB.
tracemalloc reports only 1.6 GB of allocated memory, while docker stats and htop reported the whole 7GB.
After one hour, when Gradio should have cleaned up all session states, memory usage was still at 5.4 GB.

原因分析

可能原因包括:

1. 回调返回值被意外引用:在示例代码中 yield list(big_variable_state())list() 会创建原 NumPy 数组的副本引用,可能导致垃圾回收器无法及时释放,尤其在 Gradio 事件循环中反复触发时累积内存。

2. 缓存对象生命周期管理不当:@cache 装饰的函数会永久保留返回值,如果这些大对象被 Gradio 的内部状态或全局变量持有,即使会话结束也不会释放。

3. Gradio 会话清理机制失效:虽然设置 delete_cache=(20, 5),但实际内存并没有按预期回收,可能原因包括底层 FastAPI/Starlette 的事件循环持有引用,或进程级缓存未被清理。

4. 与 diffusers 类似的已知问题:Issue 讨论中提到 diffusers 在 Gradio 中也存在类似内存泄漏,最终通过将逻辑封装到类中解决,可能与 Gradio 事件回调的函数作用域处理有关。

环境排查

  • 确认 Gradio 版本(Issue 未明确,建议记录当前版本号以便对比)。
  • Python 版本:已确认 Python 3.11 可复现。
  • NumPy 版本:确认是否使用大数组(np.float64,3000 万元素约 240MB)。
  • 容器环境:Docker 与 Kubernetes 均可复现,建议检查宿主机 vs 容器的内存统计差异。
  • 确认是否使用 tracemalloc 之外的测量工具(如 docker stats、htop),因为 tracemalloc 可能低估实际内存占用。
  • 检查是否安装了 diffusers 或其他大型模型库,可能引发与 Gradio 的已知兼容性问题。

解决步骤

  1. 优先尝试:将回调逻辑封装为类方法。将事件处理函数(如 update_state)、缓存函数(get_big_array)和内存监控函数放入一个类中,并实例化全局对象。参考 Issue 中 MemoryAllocation 类的写法,注意避免在局部函数中直接使用 yield list()

    class MemoryAllocation:
        def __init__(self):
            tracemalloc.start()
        def update_state(self):
            # 避免 list() 拷贝,直接返回数组引用
            yield self.get_big_array()
        @cache
        def get_big_array(self):
            return np.ones((30_000_000,), dtype=np.float64)
    memoryallocation = MemoryAllocation()
    # 在 gr.Blocks 中调用 memoryallocation 的方法
  2. 去除事件回调中的 list() 包装。如果你原本是为了在 Textbox 中显示而转换列表,请移除该转换,改为直接返回原生数组或改用其他展示方式(如图表组件),避免创建额外引用。

  3. 禁用或调整 @cache。确认大对象是否真的需要跨请求缓存;若不需要,移除 @cache 装饰器,或改用 @lru_cache(maxsize=1) 并确保在会话结束后手动清除。

  4. 手动清理全局缓存。在定时器回调中主动调用 缓存函数.cache_clear()(对 functools.cache 使用 cache_clear 方法),强制释放不再使用的缓存对象。

  5. 升级或更换 Gradio 版本。如果以上步骤无效,建议在 GitHub Issue 中跟踪版本更新,尝试升级到最新版 Gradio,或在社区中寻找针对同类内存问题的补丁。

验证方法

在修改后重新运行应用,使用 docker stats 或宿主机的 htop 监控内存:

  1. 启动基线:记录无用户访问时的内存(约 90-150MB)。
  2. 交互测试:多次点击按钮或触发事件,观察内存增量是否远低于之前的 7GB 级别。
  3. 等待超过 delete_cache 设定的时间(如 20 秒后),确认内存是否开始下降,最终是否回落到基线水平。
  4. 若使用类封装方案,确认内存稳定在 31MB 左右(参考 Issue 中作者的测试结果)。

参考来源

gradio-app/gradio #11602

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 18871

发表回复

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