Mac 上装 PyTorch 这件事说简单也简单说坑也多。尤其是换了 M 系列芯片之后网上大量教程还停留在 Intel 时代照着敲命令要么装出来跑在 CPU 上慢得离谱要么 import 就报架构不匹配要么 conda 环境里 Python 版本和 torch 版本互相打架。我自己前前后后在几台 M 芯片的机器上折腾过好几轮从最早 M1 刚出时生态一片荒芜到现在 MPS 后端基本可用踩过的坑基本能凑成一本小册子。这篇就把 Mac M 系列芯片安装配置 PyTorch 的完整路径拆开讲清楚从环境隔离、Python 版本选择、安装方式对比到 MPS 加速的验证、常见报错的排查再到实际训练时那些官方文档不会明说的性能细节。不管你是刚拿到 Mac 想跑第一个深度学习 demo 的新手还是从 Intel Mac 迁移过来、发现老环境跑不动的研究者都能照着这篇从头走一遍少走弯路。1. 先搞清楚 M 系列芯片装 PyTorch 到底特殊在哪1.1 统一内存架构带来的机会与限制M 系列芯片最核心的变化是统一内存架构Unified Memory Architecture。传统 PC 上CPU 有自己的一块内存GPU 有独立的显存数据在两者之间搬运要走 PCIe 总线这个搬运开销在训练小模型时甚至能占到相当比例。M 系列把 CPU、GPU、神经引擎放在同一块芯片上共享同一池内存理论上 GPU 可以直接访问 CPU 写好的数据省掉了显式拷贝。这个特性对 PyTorch 用户意味着两件事。好消息是你不需要像在独立显卡机器上那样时刻盯着显存够不够模型和数据共享系统内存16G 内存的机器跑一些中等规模的模型是可行的。坏消息是共享内存不等于无限内存系统本身、浏览器、IDE 都在抢同一池资源一旦内存吃紧系统会开始疯狂 swap训练速度断崖式下跌严重时直接卡死。我在一台 16G 的机器上跑 batch size 稍大的图像分类监控里看到内存压力变黄loss 曲线立刻变得一顿一顿的这就是 swap 在拖后腿。另一个限制是 MPS 后端的能力边界。MPS 是 Metal Performance Shaders 的缩写是苹果提供的 GPU 计算框架PyTorch 通过它来调用 M 系列芯片的 GPU。它不像 CUDA 那样成熟部分算子没有 GPU 实现遇到这种情况 PyTorch 会回退到 CPU或者直接报错。所以你在 M 系列上跑模型不能假设“有 GPU 就一定能加速”得实际验证。1.2 为什么不能照搬 Intel Mac 的老教程Intel Mac 时代的 PyTorch 安装逻辑很简单装个 Pythonpip install torch跑在 CPU 上完事。那时候 Mac 基本没有可用的 GPU 加速路径AMD 显卡的支持也很有限大家对 Mac 跑深度学习的预期就是“能跑就行别指望快”。M 系列芯片出来后情况变了但网上大量教程没跟上。常见的过时做法包括用系统自带的 Python 直接 pip 安装、不建虚拟环境、装完不验证 device、以及最坑的一种——装了个老版本 torch那个版本压根不支持 MPS跑起来还是 CPU用户还以为自己已经用上 GPU 了。还有一种情况是用了某些第三方源的非官方 wheel装完 import 报错排查半天发现是架构不匹配x86_64 的包装在 arm64 的 Python 上。所以这篇的路线是先隔离环境再选对 Python 版本用官方渠道装最新稳定版 torch最后强制验证 MPS 是否真的生效。这四步任何一步偷懒后面都可能出问题。1.3 安装方式怎么选pip、conda 还是官方安装器三种主流方式各有适用场景我先把结论摆出来再解释原因。方式适合人群优点缺点pip venv大多数用户尤其是只做 PyTorch 的轻量、官方 wheel 更新快、MPS 支持及时需要自己管 Python 版本conda / mamba需要多框架共存、依赖复杂项目环境隔离彻底、二进制依赖处理好体积大、有时 torch 版本滞后官方安装器.pkg完全新手图形化、省心灵活性差、不便于多版本管理我个人的偏好是pip venv原因是 PyTorch 官方对 pip wheel 的更新最及时MPS 相关的修复往往先在 pip 渠道体现。conda 渠道的 torch 有时会慢半拍而且 conda 环境体积动辄几个 G对存储紧张的机器不太友好。当然如果你的项目还要装一堆科学计算库、且版本约束复杂conda 的依赖求解确实省心那就用 conda但要注意指定pytorch官方 channel。提示无论用哪种方式都不要往系统自带的 Python 里装包。macOS 自带的 Python 是给系统工具用的污染了之后排查起来非常痛苦。2. 环境准备Python 版本、包管理器与虚拟环境2.1 Python 版本怎么选才不踩坑PyTorch 对 Python 版本有明确的支持范围装之前先确认。以近两年的稳定版为例官方通常支持 Python 3.9 到 3.12 这个区间太老的版本3.8 及以下新 torch 不支持太新的版本比如刚发布的 3.13可能还没有对应的 wheelpip 会尝试从源码编译在 Mac 上编译 torch 是场灾难动辄半小时起步还大概率失败。我的建议是选一个处于支持区间中段、且生态兼容性最好的版本目前来看 3.10 或 3.11 是比较稳的选择。这两个版本既被主流科学计算库广泛支持又不会因为太新而缺 wheel。如果你机器上已经有 3.12 且用着没问题也不必降级装之前去 PyTorch 官网的 get-started 页面确认一下当前稳定版是否提供对应 wheel 即可。怎么查看当前 Python 版本python3 --version如果版本不合适别去动系统 Python用包管理器装一个独立的。Mac 上装 Python 最省心的方式是 Homebrew# 安装 Homebrew如果还没装 /bin/bash -c $(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh) # 安装指定版本的 Python brew install python3.11装完之后python3.11这个命令就可用了。注意 Homebrew 装的 Python 路径通常在/opt/homebrew/bin/Apple Silicon 的默认前缀而不是 Intel 时代的/usr/local/bin/这个差异在配置 PATH 时要注意。2.2 虚拟环境为什么必须做怎么做虚拟环境的作用是把项目依赖隔离开避免 A 项目要 torch 2.0、B 项目要 torch 2.3 时互相打架。Python 自带的venv模块就够用不需要额外装 virtualenv。创建和激活的完整流程# 在项目目录下创建虚拟环境命名为 .venv约定俗成的名字 python3.11 -m venv .venv # 激活zsh 是 macOS 默认 shell source .venv/bin/activate # 确认激活成功命令行前缀会出现 (.venv) which python # 应该输出类似 /Users/xxx/your_project/.venv/bin/python激活后python和pip都指向虚拟环境内的版本这时候装什么都只影响这个环境。退出用deactivate。这里有个细节很多人忽略创建 venv 时用的 Python 版本决定了这个环境的 Python 版本之后在环境里 pip 装包都是基于这个版本。所以第一步选对 Python 版本很关键选错了只能删掉.venv重建。注意如果你用 zsh 且装了 oh-my-zsh 之类的框架有时激活 venv 后提示符没变化容易误以为没激活。用which python确认最可靠别只看提示符。2.3 升级 pip 与基础工具链装 torch 之前先把 pip 升到较新版本老版本 pip 在解析 wheel 的架构标签时可能出问题尤其是在 arm64 机器上装包时老 pip 有时会错误地拉取 x86_64 的包。python -m pip install --upgrade pip setuptools wheelsetuptools和wheel也一起升某些包在安装时需要它们来构建。这一步很快但能省掉后面不少莫名其妙的报错。另外确认一下你的机器架构确保装的是 arm64 版本uname -m # Apple Silicon 应该输出 arm64如果这里输出的是x86_64说明你的终端跑在 Rosetta 转译模式下这种情况下装的 Python 和包都是 x86_64 架构性能会打折而且 MPS 可能不可用。解决办法是确保终端本身是原生 arm64 模式运行检查终端的“使用 Rosetta 打开”选项是否被勾选在 Finder 里右键终端 App - 显示简介取消勾选后重启终端。3. 安装 PyTorch官方渠道与版本选择3.1 用官方命令安装稳定版最稳妥的方式是去 PyTorch 官网的 get-started 页面选择 Mac、pip、Python 对应的选项它会生成一条安装命令。以近期的稳定版为例命令大致是pip install torch torchvision torchaudio在 Mac 上PyTorch 的 pip wheel 已经内置了 MPS 支持不需要额外装什么 CUDA 工具包那是 N 卡的事。装完之后 torch 会自动检测到 M 系列芯片的 GPU 能力。如果你需要固定某个版本比如项目要求 torch 2.2可以指定版本号pip install torch2.2.0 torchvision0.17.0 torchaudio2.2.0版本对应关系很重要torch、torchvision、torchaudio 三者的版本是配套发布的混装容易出现 ABI 不兼容。最省事的做法是不指定版本让 pip 装最新稳定版三个包一起装pip 会自动选配套的组合。安装过程会下载几百 MB 的 wheel取决于网速几分钟到十几分钟不等。装完后验证python -c import torch; print(torch.__version__)能打印出版本号就说明装成功了。但这只是第一步装成功不等于 GPU 可用下一节专门验证 MPS。3.2 关于 nightly 版本什么时候该用什么时候别碰PyTorch 有 nightly 构建渠道包含最新的算子实现和 bug 修复MPS 后端的一些新特性往往先在 nightly 里出现。但 nightly 的稳定性没有保证可能今天能跑明天就崩。我的建议是除非你遇到了稳定版里明确存在的 MPS bug且确认 nightly 已修复否则不要用 nightly。日常开发和跑实验稳定版足够。如果你确实需要试 nightlypip install --pre torch torchvision torchaudio --index-url https://download.pytorch.org/whl/nightly/cpu注意 Mac 的 nightly 渠道标签是cpu这不是说只能用 CPU而是苹果的 MPS 支持打包在这个渠道里别被名字误导。装完记得记录版本号出问题时方便回滚。3.3 验证 MPS 是否真正可用这是整个安装流程里最关键的一步也是最多人翻车的地方。装完 torch 后跑这段代码import torch print(PyTorch 版本:, torch.__version__) print(MPS 后端是否可用:, torch.backends.mps.is_available()) print(MPS 是否已构建:, torch.backends.mps.is_built()) if torch.backends.mps.is_available(): device torch.device(mps) print(使用设备:, device) else: device torch.device(cpu) print(MPS 不可用回退到 CPU)三个输出要一起看is_built()为 True说明这个 torch 版本编译时包含了 MPS 支持is_available()为 True说明当前机器和系统版本满足 MPS 运行条件两者都为 True才能放心用mps设备。如果is_built()是 True 但is_available()是 False常见原因是 macOS 版本太老MPS 需要 macOS 12.3 及以上或者跑在 Rosetta 模式下。如果is_built()就是 False那说明装的 torch 版本压根不带 MPS得升级 torch。再做一个实际计算的验证确保不只是“报告可用”而是真能算import torch device torch.device(mps) x torch.randn(1000, 1000, devicedevice) y torch.randn(1000, 1000, devicedevice) z x y print(矩阵乘法结果形状:, z.shape) print(计算设备:, z.device)这段能跑通且输出devicemps:0才算真正用上了 GPU。我见过有人只看了is_available()就以为万事大吉结果实际训练时某个算子不支持报错才发现问题所以这一步的实算验证别省。4. MPS 加速实战从能跑到跑得好4.1 把模型和数据搬到 MPS 上的正确姿势PyTorch 的设备管理逻辑很统一MPS 和 CUDA 的用法几乎一样都是通过.to(device)搬运。标准写法import torch import torch.nn as nn device torch.device(mps if torch.backends.mps.is_available() else cpu) # 模型搬到设备 model nn.Sequential( nn.Linear(784, 256), nn.ReLU(), nn.Linear(256, 10) ).to(device) # 数据搬到设备 inputs torch.randn(64, 784).to(device) labels torch.randint(0, 10, (64,)).to(device) # 前向、损失、反向 outputs model(inputs) loss nn.CrossEntropyLoss()(outputs, labels) loss.backward() print(loss:, loss.item())这里有个容易忽略的点优化器状态和模型参数要在同一设备上。如果你先创建优化器再把模型搬到 MPS优化器里缓存的参数引用可能还指向 CPU导致更新时报设备不匹配。正确顺序是先.to(device)再创建优化器或者创建后确认参数设备一致。另一个细节是 loss 的.item()调用。在 MPS 上把标量 tensor 转成 Python 数值会触发一次同步频繁调用比如每个 batch 都打印 loss会拖慢训练。建议累积若干 step 再打印或者用 tensorboard 之类的工具异步记录。4.2 那些在 MPS 上会报错的算子与规避方法MPS 后端不是 CUDA 的完整替代部分算子没有 GPU 实现。常见的踩坑场景某些稀疏操作稀疏矩阵相关算子在 MPS 上支持不全遇到报错可以先把相关张量转回 CPU 计算再转回来部分归一化层的特殊模式比如某些带复杂统计的归一化可能回退或报错自定义算子的 backward如果你用了自定义 autograd Functionbackward 里的某些操作可能没有 MPS 实现。遇到NotImplementedError或类似“operator not implemented for MPS”的报错处理思路是先定位是哪个算子然后要么换等价实现要么把那一小段逻辑放到 CPU 上算。PyTorch 提供了环境变量可以强制某些操作回退export PYTORCH_ENABLE_MPS_FALLBACK1设置后没有 MPS 实现的算子会自动回退到 CPU而不是直接报错。这个变量对调试很有用但不建议长期开着因为回退意味着数据在 CPU 和 GPU 之间搬运性能损失明显而且你很难察觉哪些操作偷偷跑了 CPU。正确做法是用它定位问题然后针对性改写代码。提示设置PYTORCH_ENABLE_MPS_FALLBACK1后建议在代码里加日志或者用 profiler 看看哪些操作耗时异常确认回退没有发生在热点路径上。4.3 性能调优batch size、数据类型与内存监控M 系列芯片的统一内存让“显存不够”这个问题变得模糊但性能调优的逻辑和独显机器类似核心是让 GPU 保持忙碌、减少数据搬运。batch size 的选择要结合内存。统一内存意味着 batch 开太大系统整体内存吃紧swap 一开训练速度反而下降。我的经验是从一个保守值开始比如图像分类 32 或 64用活动监视器盯着内存压力逐步往上加直到内存压力变黄之前停下。别迷信“越大越好”在共享内存架构上留出余量给系统和其他进程更明智。数据类型方面MPS 支持 float16混合精度训练能明显提速并省内存。PyTorch 的torch.autocast可以用with torch.autocast(device_typemps, dtypetorch.float16): outputs model(inputs) loss criterion(outputs, labels)不过要注意autocast 在 MPS 上的成熟度不如 CUDA某些模型可能出现数值不稳定loss 变 NaN。如果遇到先关掉 autocast 确认是精度问题再考虑只对部分层用半精度或者用torch.float32稳一手。内存监控方面Mac 自带的活动监视器就够用重点看“内存压力”图表绿色表示充裕黄色表示开始吃紧红色就是危险了。命令行可以用vm_stat看 swap 情况。训练时如果发现内存压力持续黄色以上优先降 batch size 或减小模型别硬扛。4.4 一个完整的训练循环示例把上面的要点串起来给一个可直接跑的最小训练循环方便你对照自己的代码import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset # 设备选择 device torch.device(mps if torch.backends.mps.is_available() else cpu) print(使用设备:, device) # 造点假数据 X torch.randn(2000, 20) y torch.randint(0, 2, (2000,)) dataset TensorDataset(X, y) loader DataLoader(dataset, batch_size64, shuffleTrue) # 模型 model nn.Sequential( nn.Linear(20, 64), nn.ReLU(), nn.Linear(64, 2) ).to(device) criterion nn.CrossEntropyLoss() optimizer torch.optim.Adam(model.parameters(), lr1e-3) # 训练 model.train() for epoch in range(5): total_loss 0.0 for batch_x, batch_y in loader: batch_x batch_x.to(device) batch_y batch_y.to(device) optimizer.zero_grad() out model(batch_x) loss criterion(out, batch_y) loss.backward() optimizer.step() total_loss loss.item() print(fepoch {epoch1}, avg loss: {total_loss/len(loader):.4f})这段代码里几个值得注意的点数据在循环内逐 batch 搬到设备而不是一次性全搬省内存optimizer.zero_grad()在 backward 之前loss 累积用.item()但只在每个 epoch 打印一次减少同步开销。照着这个骨架改自己的模型基本不会出设备相关的错。5. 常见报错与排查速查5.1 安装阶段的典型问题安装阶段最常见的是架构不匹配和版本冲突。整理成速查表报错现象可能原因解决方向ERROR: Could not find a version that satisfies the requirement torchPython 版本太新或太老无对应 wheel换 3.10/3.11或查官网支持矩阵装完 import 报mach-o file, but is an incompatible architecture装了 x86_64 的包跑在 arm64 Python 上确认uname -m为 arm64重装pip卡在 building wheel 很久没有对应 wheel在从源码编译换 Python 版本或换安装渠道装完is_built()为 Falsetorch 版本太老不含 MPS升级到较新稳定版架构不匹配这个坑特别隐蔽因为报错信息不一定直白。判断方法是看 Python 的架构python -c import platform; print(platform.machine())输出arm64才对。如果输出x86_64说明这个 Python 是 Rosetta 下跑的装的包也会是 x86_64MPS 大概率不可用。解决办法是重装原生 arm64 的 Python并确保终端不在 Rosetta 模式。5.2 运行阶段的 MPS 报错运行阶段的报错更杂核心思路是先确认是不是算子不支持再决定回退还是改写。遇到NotImplementedError: The operator xxx is not currently implemented for the MPS device处理流程是先用PYTORCH_ENABLE_MPS_FALLBACK1跑一遍看能不能跑通能跑通说明就是算子缺失然后定位这个算子在代码哪里评估能否用等价算子替换或者把那一小段搬到 CPU。如果这个算子在热点路径上每个 batch 都执行回退的性能损失可能让 GPU 加速失去意义那就得认真考虑改写。还有一种报错是内存相关的比如MPS backend out of memory。虽然统一内存理论上更宽松但 MPS 后端有自己的内存分配上限模型太大还是会 OOM。解决办法是降 batch size、用梯度累积模拟大 batch、或者用半精度。梯度累积的写法accum_steps 4 optimizer.zero_grad() for i, (batch_x, batch_y) in enumerate(loader): batch_x, batch_y batch_x.to(device), batch_y.to(device) out model(batch_x) loss criterion(out, batch_y) / accum_steps loss.backward() if (i 1) % accum_steps 0: optimizer.step() optimizer.zero_grad()这样等效 batch size 是原来的 4 倍但峰值内存只按单份 batch 算。5.3 环境与依赖冲突的排查依赖冲突往往表现为 import 时报某个底层库找不到或版本不对。排查顺序先确认当前在正确的虚拟环境里which python再pip list看 torch 相关包的版本是否配套最后看有没有多个 Python 环境互相干扰。一个高频问题是 torchvision 和 torch 版本不匹配报错类似RuntimeError: operator torchvision::nms does not exist。这是因为 torchvision 编译时链接的 torch ABI 和当前装的 torch 不一致。解决办法是卸载重装让 pip 重新解析配套版本pip uninstall torch torchvision torchaudio -y pip install torch torchvision torchaudio不指定版本让 pip 自己选配套组合通常能解决。如果项目必须固定版本那就去查 PyTorch 官方的版本对应表手动对齐三个包的版本号。注意卸载重装前先pip freeze requirements.txt备份当前环境万一重装后其他包出问题方便回滚对照。6. 一些官方文档不会明说的经验6.1 关于“MPS 到底比 CPU 快多少”的真实预期很多人装完 MPS 第一件事就是跑个 benchmark然后发现某些任务上 GPU 只比 CPU 快一点点甚至更慢于是怀疑自己装错了。这其实很正常。MPS 的加速效果高度依赖任务类型大矩阵乘法、卷积这类并行度高的操作GPU 优势明显而小模型、小 batch、或者大量标量操作的任务GPU 的调度开销可能盖过计算收益反而不如 CPU。我的实测体会是模型越大、batch 越大、计算越密集MPS 的相对优势越明显。跑一个几层的小 MLPCPU 和 MPS 差距可能就百分之几十换成 ResNet 级别的卷积网络、batch 开到 64 以上差距能拉到数倍。所以别拿玩具模型判断加速效果要拿你实际要跑的任务来测。另外第一次在 MPS 上跑某个操作时会有编译和初始化开销第一次迭代特别慢是正常的别把冷启动时间算进平均速度里。测速时先 warmup 几轮再计时。6.2 系统版本与 Xcode 命令行工具的隐性依赖MPS 依赖 macOS 的 Metal 框架系统版本太老会直接导致is_available()返回 False。MPS 需要 macOS 12.3 及以上建议保持系统在较新的稳定版。升级系统前记得评估一下项目依赖大版本升级偶尔会带来工具链变化。另外某些 Python 包的安装尤其是需要编译的依赖 Xcode 命令行工具。如果遇到编译报错提示缺头文件或编译器装一下xcode-select --install这个命令会弹窗引导安装装完编译环境就齐了。注意它装的是命令行工具不是完整的 Xcode体积小很多日常够用。6.3 多项目多版本的隔离策略如果你同时维护几个项目对 torch 版本要求不同光靠 venv 管理会有点累。我的做法是每个项目一个独立 venv命名带上项目名比如.venv-projA、.venv-projB激活时明确指定。配合 direnv 这类工具可以做到进入目录自动激活对应环境减少手动切换的出错概率。对于需要频繁切换 Python 版本的情况可以用 pyenv 管理多个 Python 版本再在每个版本下建 venv。pyenv 的安装和配置稍微复杂一点但对多版本共存场景确实方便。不过要注意 pyenv 装的 Python 和 Homebrew 装的可能路径冲突选一种方式统一管理别混用。最后分享一个我踩过的坑曾经在一个项目里同时用了 conda 和 venv结果 shell 的 PATH 里 conda 的 base 环境排在前面导致python指向了 conda 的 Python 而不是 venv 的装包装到了错误的环境里排查了半天。教训是一个项目只用一种环境管理方式并且每次操作前用which python和which pip确认指向这个习惯能省掉大量玄学问题。6.4 数据加载的瓶颈别忽视GPU 加速之后瓶颈往往会转移到数据加载上。如果 DataLoader 的num_workers设得太小GPU 会频繁等数据利用率上不去。Mac 上num_workers的设置和 Linux 略有不同因为 macOS 的进程启动机制spawn比 Linux 的 fork 开销大worker 开太多反而拖慢。我的经验是num_workers从 2 或 4 开始试配合persistent_workersTrue避免每个 epoch 重建 worker。同时用pin_memory在 CUDA 上有用但在 MPS 上意义不大可以不开。判断数据加载是不是瓶颈看训练时 GPU 利用率活动监视器里看 GPU 历史是否长期偏低如果是优先优化数据管道而不是继续调模型。loader DataLoader( dataset, batch_size64, shuffleTrue, num_workers4, persistent_workersTrue )这套配置在多数 Mac 项目上表现稳定worker 数量再往上加收益递减甚至因为进程开销变慢建议实测对比后再定。