1–5 分钟交付

独享 Mac mini M4

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

FIELD NOTE · AIDevelopment

PyTorch 2.14 MPS 内存不足:2026 年科研训练怎么修

这篇文章面向在 Apple Silicon Mac 上训练或推理的研究生、科研软件维护者和课题组技术负责人。文章不把升级版本当作万能修复,而是从真实张量占用、缓存与碎片、计算图引用、动态形状和 CPU 回退五条问题轴展开诊断,并给出干净环境复现与双轨验收方法。

2026 年 9 月 12 日更新。 PyTorch 2.14 已于 2026 年 9 月 2 日发布,官方说明包含 MPS 缓存分配器和复制路径调整,但这不等于所有 MPS 内存不足问题都会自动消失。PyTorch 2.14 发布说明提到,2.14 对大块分配、缓存预留和 CPU 到 MPS 的复制路径做了改进;因此本周的建议是:不要先取消内存限制硬撑,先分辨真实张量、缓存碎片、计算图、动态形状和 CPU 回退,再在干净环境中复验。

这篇文章适合三类人:在 Apple Silicon 上训练或推理并收到 MPS out of memory 的研究生;需要核对 Linux GPU 与 macOS 结果一致性的科研软件维护者;以及实验室没有 Mac、需要临时建立干净复现环境的课题组技术负责人。

先把“内存不足”分成四种故障

同样是程序退出,处理路径可能完全不同。PyTorch 抛出分配错误,通常意味着某次申请没有通过 MPS 分配器;如果程序变慢、界面失去响应或被系统直接终止,则还要考虑 macOS 全局内存压力。Apple 的 Activity Monitor 会综合空闲内存、压缩内存、交换空间和文件缓存显示 Memory Pressure,绿色、黄色和红色并不等价于“PyTorch 已分配多少显存”。Apple 内存压力说明

先建立一份基线,至少保存以下信息:

  • PyTorch、Python、macOS 的完整版本;
  • Apple Silicon 芯片型号与统一内存配置;
  • 模型名称、参数规模、数据类型、batch size、序列长度或输入分辨率;
  • 完整错误堆栈,而不是只截取最后一行;
  • 第 1、10、50、100 次迭代的内存记录;
  • 是否启用 torch.compile、多进程 DataLoader 或 CPU fallback。

可以先用下面的函数记录 3 个不同层次的数字:

import torch

def report_memory(tag):
    cur = torch.mps.current_allocated_memory() / 1024**2
    drv = torch.mps.driver_allocated_memory() / 1024**2
    rec = torch.mps.recommended_max_memory() / 1024**2
    print(f"{tag}: current={cur:.1f} MiB, driver={drv:.1f} MiB, recommended={rec:.1f} MiB")

current_allocated_memory()反映当前由张量等对象使用的分配;driver_allocated_memory()则包含 MPS 分配器缓存,以及 MPS 和 MPSGraph 等底层框架的分配。PyTorch 对这两个接口的定义并不相同,具体可分别参考 current_allocated_memory 文档driver_allocated_memory 文档

因此,不能拿 Activity Monitor 的进程内存、current_allocated_memory()driver_allocated_memory()互相替代。三者变化方向不同,恰恰是定位问题的线索。

模型边界与任务缩减

Apple Silicon 的统一内存并不意味着所有物理内存都可以安全交给 MPS。操作系统、后台应用、Python 进程、数据加载、Metal 框架和缓存都要共享资源;PyTorch 文档中的 recommended_max_memory()只是 Metal 返回的推荐工作集上限,不是整台 Mac 的可用内存总量。recommended_max_memory 文档

训练时,峰值通常来自多个部分叠加:

  1. 模型参数;
  2. 中间激活;
  3. 梯度;
  4. 优化器状态;
  5. 临时算子缓冲区;
  6. 输入复制和数据预处理。

只降低 batch size,未必能解决由输入分辨率、序列长度或优化器状态造成的峰值。更稳妥的做法是一次只改一个变量:

  1. 固定随机种子和数据样本;
  2. 保持模型、数据类型和优化器不变;
  3. 将 batch size 缩小一档;
  4. 运行至少一个完整的最小训练周期;
  5. 再恢复 batch size,只缩短序列长度或输入尺寸;
  6. 对比每次迭代的 currentdriver 和系统内存压力。

如果缩小 batch size 后第 1 次迭代就失败,主要增量可能在模型参数、优化器状态或单个输入样本;如果第 1 次成功、运行一段时间后持续增长,则应转向计算图、输出保存和动态形状,而不是继续缩小任务。

缓存、碎片与动态形状

torch.mps.empty_cache()经常被误解为“释放 MPS 的全部内存”。官方定义是释放缓存分配器中当前未占用的缓存,仍被张量或其他对象引用的内存不会被释放。empty_cache 文档

可以用下面的判断方式区分几种情况:

观察结果 更可能的原因 最小验证动作 修复边界
current 随 batch size 同步下降 真实张量或激活过大 只缩小 batch size 接受较小任务,或改模型
current 回落,driver 长期偏高 缓存池或底层分配保留 在阶段边界调用 empty_cache() 只能回收未占用缓存
两个计数器较平稳,进程内存持续上升 动态形状、底层缓存或系统路径 固定所有输入形状并重启进程 不能仅靠清缓存证明已修复
运行变慢、系统压力变黄或变红 全局内存压力或交换 关闭无关程序并观察 Activity Monitor 需要降低峰值,而非取消限制
明确提示某算子不支持 MPS 算子能力或回退路径 分别在 CPU、MPS 运行最小脚本 不能把 CPU 与 MPS 结果直接视为同一路径

动态形状尤其值得单独测试。把序列长度、图像尺寸、点云数量或 batch 组成固定值,连续运行同一最小循环;再让这些形状变化,比较进程常驻内存和两个 MPS 计数器。如果只有变化形状时增长,应把问题标记为“形状相关”,不要简单写成“内存泄漏已确认”。

公开仓库中确实存在特定环境下的用户报告:某个 Apple Silicon、PyTorch 2.10.0 和变化形状推理组合中,进程 RSS 持续增长,而 MPS 计数器保持相对平稳;这只是一个故障模式线索,不足以证明 PyTorch 2.14 在所有模型上都会发生同样问题。相关 GitHub 用户报告

⚠️ 经验提醒:不要把 PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0当作常规修复。官方文档明确说明,0.0会关闭高水位限制,在系统级 OOM 时可能导致系统失稳;它适合受控诊断,不适合拿来延长正式科研训练。

计算图与结果引用

降低任务规模后仍增长,优先检查 Python 代码是否无意中保存了跨迭代引用。常见问题包括:

losses.append(loss)              # 可能保留计算图
predictions.append(pred)        # 可能保留设备张量
states.append(hidden_state)     # 可能保留跨轮状态
logs["loss"].append(loss)        # 日志对象同样可能持有引用

训练时,如果只需要记录数值,应明确转换为普通标量:

logs["loss"].append(float(loss.detach().cpu()))

如果需要保存预测结果,应先确认是否真的需要保留在 MPS 上:

predictions.append(pred.detach().cpu())

推理任务则检查 torch.no_grad()torch.inference_mode()的边界,不能只在函数外层包裹一部分代码,却在循环中继续创建需要梯度的张量。训练任务还要检查梯度清零、detach()以及循环状态的生命周期。

最小验证方法是删除所有非必要的结果保存、可视化和日志对象,只保留:

for step, batch in enumerate(loader):
    if step >= 20:
        break
    output = model(batch.to("mps"))
    loss = criterion(output, target.to("mps"))
    loss.backward()
    optimizer.step()
    optimizer.zero_grad(set_to_none=True)

如果最小循环稳定,而完整项目持续增长,问题更可能在引用关系或日志链路;如果最小循环也增长,再检查动态形状、算子路径和版本组合。

算子支持与 CPU 回退

MPS 是 macOS 上用于 GPU 加速的后端,模型和张量需要显式放到 mps 设备上;官方说明同时提醒,MPS 后端仍需关注具体算子的支持情况。MPS 后端说明

如果启用了:

export PYTORCH_ENABLE_MPS_FALLBACK=1

不支持的算子可能回退到 CPU。这样做有时能让脚本继续运行,但会改变数据复制路径、峰值内存和执行时间;回退不是“免费兼容层”。PyTorch 2.14 的环境变量含义与限制,应以 MPS 环境变量文档为准。

建议分两次运行最小脚本:

  1. 禁用 fallback,在 MPS 上直接观察第一个失败算子;
  2. 启用 fallback,观察 CPU 与 MPS 之间是否出现频繁迁移;
  3. 对比输入、输出摘要和错误阶段;
  4. 检查是否有代码把输入留在 CPU,却把模型放到了 MPS;
  5. 对涉及 non_blocking复制的代码先改为同步、保守路径,排除复制时序因素。

科研复现不能只看“程序跑完”。如果 CPU fallback 让结果数值略有差异、随机性控制失效或性能下降,验收记录中必须单独标注,而不是把它包装成纯 MPS 结果。

5 步干净环境验收清单

实验室没有 Mac 时,最经济的做法不是立刻更换整个 Linux GPU 工作流,而是先建立一个短期、隔离、可销毁的 Apple Silicon 复现环境。你可以按需使用 JexMac 的远程 Mac 访问入口,再按照 服务帮助说明确认连接方式和权限边界。

  • [ ] 保存 requirements.txt、锁定文件或完整 pip freeze,记录 PyTorch 2.14、Python、macOS 和芯片信息。
  • [ ] 准备一个只包含模型、单个输入样本和固定随机种子的最小脚本。
  • [ ] 在新环境中只安装必要依赖,不复制旧的 Python 缓存、编译产物和实验日志目录。
  • [ ] 每隔固定迭代记录 current_allocated_memory()driver_allocated_memory()和 Activity Monitor 的内存压力。
  • [ ] 分别测试固定形状与变化形状,不要在同一轮同时修改 batch size、输入尺寸和数据类型。
  • [ ] 分别运行 MPS、CPU,以及必要时启用 fallback 的版本,保存报错阶段和输出摘要。
  • [ ] 至少重启一次 Python 进程后复跑,区分进程生命周期缓存与单轮引用问题。
  • [ ] 由第二位成员按照同一锁定文件和最小脚本复跑,确认结果不是个人本机状态。
  • [ ] 把脚本、输入样本、环境清单、内存日志、输出摘要和 Linux 对照结果打包归档。

验收标准应写成条件,而不是一句“能跑”:固定形状任务能完成预定迭代;清理非必要引用后进程内存不再无界增长;fallback 是否启用被明确记录;Linux 与 Apple Silicon 的结果差异经过解释;若只在某个版本或系统组合出现,则锁定该组合并保留双轨验证。

版本选择与双轨安排

PyTorch 2.14 的 MPS 改动值得测试,但“升级后可能改善分配行为”和“升级后所有内存问题都解决”是两件事。官方发布信息确认了缓存分配器、复制路径和部分 Metal 内核的调整,却没有承诺消除所有模型、动态形状或系统压力相关故障。

我们的建议是:

  • 如果错误可以在最小脚本中稳定复现,先保留 PyTorch 2.14 的环境与日志,再测试一个已知可工作的锁定版本;
  • 如果只在完整项目中出现,先修正引用、形状和 fallback,再判断是否与版本相关;
  • 如果只在 Apple Silicon 出现,Linux GPU 继续作为结果和吞吐量对照,不要直接宣布两条路径科研等价;
  • 如果错误无法稳定复现,保留 Apple Silicon 与 Linux GPU 双轨,直到完成至少一次独立复跑。

对于长期大规模训练、依赖特定 CUDA 算子或需要严格复现既有 Linux GPU 数值的项目,Apple Silicon 更适合承担 macOS 兼容性验证、轻量推理和开发测试,而不是未经验收就替代现有 GPU 集群。

如果本地环境已经被旧依赖、缓存和后台进程污染,继续反复试错的成本通常高于短期建立一台干净的远程 Apple Silicon Mac。完成最小脚本、内存记录和 Linux 对照后,再决定是否继续租用、回到 Linux GPU,或长期保留双轨;这比直接购买一台无法确认能否解决问题的 Mac 更可控。需要临时复现时,可以先查看 JexMac 的套餐与租赁周期,但仍应以本文验收包确认环境是否真正适合课题组。

常见问题

PyTorch MPS out of memory 出现后,排查应该从哪里开始?

先保存完整错误信息和环境清单,再同时记录 current_allocated_memory、driver_allocated_memory 与 macOS 的内存压力。随后只缩小一个变量,例如 batch size 或输入尺寸,并用固定随机种子的最小脚本重复运行,避免在没有证据时直接修改高水位限制。

为什么 torch.mps.empty_cache 没有释放全部内存?

empty_cache 只能释放 MPS 缓存分配器中当前未被占用的缓存,仍被张量、梯度、计算图或底层 MPSGraph 引用的内存不会因此消失。若系统内存持续上升而 PyTorch 两个计数器变化不大,还要检查动态形状、进程级缓存和 macOS 内存压力。

Apple Silicon 训练时怎样查看 MPS 的实际占用?

在代码中分别调用 torch.mps.current_allocated_memory()、torch.mps.driver_allocated_memory() 和 torch.mps.recommended_max_memory(),并转换为 MiB 记录。前者偏向张量占用,后者包含缓存池及部分底层框架分配,不能用其中一个数字替代系统级内存压力判断。

降低 batch size 后,MPS 内存为什么还会持续增长?

batch size 只影响一部分激活和临时张量。如果训练循环把 loss、预测结果或带梯度张量追加到列表,或者每轮输入形状都变化,进程仍可能增长。应先运行固定形状、最小循环,再逐项删除结果保存和动态输入逻辑。

实验室没有 Mac,如何复现 PyTorch MPS 错误?

把项目锁定文件、最小脚本、输入样本、随机种子、完整环境信息和 Linux 对照结果整理成验收包,再使用一台干净的远程 Apple Silicon Mac 复现。远程环境只安装必要依赖,测试结束后保留日志,避免把本机缓存或后台软件误判成 MPS 缺陷。

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

用 JexMac 远程 Mac,快速复现并验证 MPS 训练环境

JexMac 提供独享裸金属 Mac mini M4 与 16 GB 统一内存,适合在真实 Apple Silicon 环境中复现内存不足、缓存碎片和动态形状问题。

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