☰
Mac M系列芯片GPU加速机器学习实战:PyTorch MPS配置与调优
2026/10/14 8:53:00 网站建设 项目流程

最近一位朋友找我帮忙调试Mac上的机器学习环境,他刚入手一台M系列芯片的笔记本,第一个问题就是:“这机器能跑GPU训练吗?”这个问题放在三五年前,答案基本是“别想了,老老实实租云GPU”。但今天,M系列芯片的统一内存架构加上MPS(Metal Performance Shaders)后端的成熟,让Mac上做机器学习(Machine Learning)并用上GPU加速,从一个噱头变成了真正可以落地的操作。这篇文章就是我在多台Mac上实操的完整经验总结,覆盖技术路线选型、环境准备、PyTorch MPS加速、TensorFlow与JAX的Metal配置,以及我自己踩过的坑,适合想在本地用GPU跑模型训练或推理的开发者参考。

1. 为什么Mac突然能跑GPU加速了

1.1 过去Mac跑机器学习为什么费劲

早年在Mac上跑机器学习,基本只能靠CPU硬扛。CPU跑小模型还行,稍微上点规模,训练时间能让人等到怀疑人生。根本原因有几个。

一是CUDA生态被NVIDIA牢牢占住,Mac没有NVIDIA显卡,跑不了CUDA代码。社区里的预训练脚本大部分都是为CUDA写的,拿到Mac上直接报错,CUDA runtime根本没有。

二是即使Mac有集成显卡,早期也没一个统一的加速接口。OpenCL半死不活,Metal一开始又只面向图形渲染,机器学习框架根本不愿意花精力适配。那时候的Mac GPU加速,更多是理论可能,不是实际可用的路径。

三是内存带宽和显存容量都受限。老款Intel Mac的GPU和CPU各自占一块内存,数据来回拷贝开销非常大,实际加速效果远不如预期,反而让人更愿意用CPU省心。

所以我早年在Mac上做实验,都是远程连一台Linux服务器,把训练丢到云GPU上,本地笔记本只当终端用。当时基本可以下结论:Mac适合写代码,不适合跑训练。

1.2 统一内存架构改变了什么

M系列芯片把之前那条结论推翻了。最核心的变化是统一内存(Unified Memory),CPU和GPU共享同一块物理内存,不再有“显存”和“内存”的硬性界限。

这个设计的直接好处有两个。

第一个是GPU能访问的内存容量大幅增加。集群的内存容量就是GPU能用的容量。比如标配32GB内存的机器,GPU可以几乎完整使用其中大部分。这在传统独显上不可想象,独显显存是独立焊死的,8GB就是8GB,满了只能换卡或降batch。

第二个是数据搬运动开销变小。传统架构里CPU算完的数据要先进PCIe总线再拷进显存,来回倒腾很费时间。统一内存架构下,CPU和GPU共享同一份数据,中间少了很多拷贝过程,数据管道更容易喂饱计算单元。

我实测下来,这种架构对ML训练的直接感受就是:原来在独显上因为显存不够被迫做的各种“显存优化技巧”,在Mac上很多都省了。

1.3 什么人适合用Mac跑GPU加速

不是所有人都适合用Mac训练,先说明白边界。经过大量测试,以下三类场景我认为非常适合。

中小规模模型的训练和微调。比如几亿到几十亿参数级别的模型微调、批量调参、Debug训练代码,本地跑比云GPU轻量很多,不花钱、不排队,随时可以中断重来。

推理和端侧验证。训练完的模型要转成部署格式,在Mac上用GPU加速推理验证速度,跟在Mac和iOS应用里的运行表现更接近,排查问题更快。

教学和数据科学实验。课程作业、论文复现、数据分析里的轻量模型,完全可以用Mac替代云服务器,环境在自己手里,动手成本极低。

如果目标是训练超大模型,比如几百亿参数,那就别用Mac了,该上集群上集群,该租云GPU租云GPU。工具选对了才有效率。

2. Mac上GPU加速的主流技术路线怎么选

目前能用、用起来比较顺的路线有四条,我把每条的实际情况讲清楚,选型时心里有数。

2.1 PyTorch加MPS后端

这是目前Mac上使用最广、文档最全、社区反馈最积极的一条路线。PyTorch从1.12开始把MPS后端设为稳定功能,2.x版本持续完善,到现在已经覆盖了大部分常用算子。

MPS是Metal Performance Shaders的缩写,是苹果自己的高性能计算框架。PyTorch通过MPS后端把张量运算映射到GPU上执行,使用方式几乎和CUDA一模一样,只是设备名从“cuda”换成了“mps”。

如果你只记一条路线,就选这个。跟CUDA代码的兼容性最接近,现有的大部分PyTorch代码只需要改device参数就能跑起来,迁移成本极低。

2.2 TensorFlow加Metal插件

TensorFlow官方推出了Metal插件,配合tensorflow-macos包使用。装上插件后,TensorFlow会自动把一部分算子放到GPU上执行。

但老实说,这条路径在更新节奏和算子覆盖上不如PyTorch MPS积极。我试过一些模型,经常会出现“回退到CPU”的警告,而且新版TensorFlow和老插件之间的兼容性也有点麻烦。TensorFlow重度用户可以用,但我个人更推荐PyTorch路线。

2.3 JAX加Metal支持

JAX在社区里越来越火,特别是研究向的项目。JAX也提供了Metal后端支持,通过jax-metal包可以启用GPU加速。

JAX的Metal支持目前还比较初级,算子覆盖和稳定性都不如MPS。适合做科研实验、喜欢函数式编程风格的朋友尝鲜,但如果是生产环境,我建议再等等,别拿自己的时间投资一个不成熟的基础设施。

2.4 Core ML的定位

Core ML是苹果自家生态里的机器学习推理框架,更多是部署端的事情。你可以在PyTorch里训练,然后把模型转换成Core ML格式,在Mac和iOS应用里跑离线推理。

Core ML有一套针对自家芯片的编译优化工具,能把模型优化得很高效。但它不直接参与训练,主要是推理部署。如果你的目标是做App内离线推理,这是必学路线。

2.5 选型对比表

我整理了一张表,方便对比:

路线擅长场景训练支持算子覆盖上手难度推荐度
PyTorch加MPS训练、微调、推理好高低极高
TensorFlow加Metal训练、部署中中中中
JAX加Metal科研实验弱低高低
Core ML端侧推理不支持高中高

新手阶段,首选PyTorch加MPS,没有悬念。

3. 动手前的环境准备

3.1 确认芯片和系统版本

不是所有Mac都能用MPS。MPS加速只支持搭载M系列芯片的Mac,Intel芯片的Mac只能靠CPU跑,或者用老旧的OpenCL方案,体验很差。

所以第一步永远是确认硬件。点左上角苹果标志,选“关于本机”,看芯片那一行:

  • M1、M2、M3、M4系列都能用MPS。
  • 芯片是Intel的话,这篇文章的GPU部分不适用。

系统版本同样关键。MPS支持需要macOS 12.3或更高版本。我建议直接升到最新稳定版,因为后续的PyTorch版本对老系统的兼容性会越来越差,别让自己卡在旧版本上。

3.2 安装Python和虚拟环境

我强烈建议用虚拟环境管理Python依赖,避免把系统Python环境搞乱。推荐用conda,也可以用python3自带venv。

我平时习惯用conda创建独立环境,这样切换项目时不会互相污染依赖。macOS上安装好miniconda后,创建环境:

conda create -n mps python=3.10 -y conda activate mps

Python版本建议3.10或3.11。太新的版本容易出现某个依赖还没适配的情况,太旧的版本又可能装不了新版框架。

3.3 安装PyTorch MPS版

PyTorch在macOS上的安装有两种选择:稳定版和测试版。

稳定版直接通过pip安装,macOS默认的PyTorch包就已经带MPS支持:

pip install torch torchvision

如果你想要最新的算子覆盖和性能改进,可以装夜间版(nightly):

pip install --pre torch torchvision --index-url https://download.pytorch.org/whl/nightly/cpu

nightly包往往对MPS新算子适配更快。我建议两个版本都试一下,优先用稳定版,遇到算子缺失再换nightly。我自己实测下来,nightly的稳定性其实也足够好,不必有心理负担。

3.4 第一行验证代码

安装完之后,先跑一个最简单的验证脚本,确认框架真的识别到了GPU:

import torch print("PyTorch版本:", torch.__version__) print("MPS是否可用:", torch.backends.mps.is_available()) print("MPS是否构建成功:", torch.backends.mps.is_built())

如果输出里“MPS是否可用”为True,说明PyTorch正确检测到了GPU加速能力。如果为False,先确认芯片类型和系统版本是否满足条件,再看是否需要升级PyTorch。

这一步非常关键。我见过不少人卡在这里,原因往往是系统版本不够新,或者安装了一版不带MPS支持的PyTorch。

4. 核心实操:把PyTorch模型切换到GPU加速

4.1 一个完整的训练示例

下面这个例子,我跑一个最简单的多层感知机分类任务,用于演示从零开始跑通MPS训练。完整代码如下:

import torch import torch.nn as nn import torch.optim as optim from torch.utils.data import DataLoader, TensorDataset # 1. 选择设备 device = torch.device("mps" if torch.backends.mps.is_available() else "cpu") print("使用设备:", device) # 2. 构造一个简单的二分类数据集 x = torch.randn(20000, 64) y = (x.sum(dim=1) > 0).long() dataset = TensorDataset(x, y) dataloader = DataLoader(dataset, batch_size=128, shuffle=True) # 3. 定义一个简单的两层全连接网络 model = nn.Sequential( nn.Linear(64, 128), nn.ReLU(), nn.Linear(128, 2), ).to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=1e-3) # 4. 训练循环 model.train() for epoch in range(20): total_loss = 0.0 for batch_x, batch_y in dataloader: batch_x = batch_x.to(device) batch_y = batch_y.to(device) outputs = model(batch_x) loss = criterion(outputs, batch_y) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() print(f"Epoch {epoch+1:02d}, Loss: {total_loss / len(dataloader):.4f}")

这段代码里最关键的是三行:

  • device = "mps":所有张量和模型都会被放到GPU上。
  • model.to(device):把网络参数移入GPU。
  • batch_x.to(device):把每个batch的数据移入GPU。

这个写法和CUDA代码完全一样。MPS后端最大的价值就在这,曾经用CUDA写的训练代码,换到Mac上几乎不用改逻辑。

4.2 数据加载与内存注意事项

新手最容易踩的坑,就是在训练循环里每轮把整个数据集反复放到设备上。统一内存架构下,CPU和GPU共享物理内存,大数据集一旦把内存吃满,任何一个分配失败都会导致进程崩溃,而且崩溃前没有任何警告。

我的建议是:

  • 适度使用DataLoader的num_workers参数,开2到4个进程预加载数据,减少主循环的等待时间。
  • 小数据集或随机数据,可以提前整体放到GPU上,例如dataset = TensorDataset(x.to(device), y.to(device)),训练时直接取。
  • 真实场景里不要轻易把整个数据集搬上GPU,因为模型本身也占统一内存,两者叠加容易OOM。

我见过一些朋友为了“加快加载”把全部训练集搬到GPU,结果内存直接飙红,系统卡死。这种操作只能用在小型数据集。

4.3 混合精度与性能开关

MPS对float16(半精度)通常有比较好的加速效果。但PyTorch官方的自动混合精度(AMP)模块里,很多接口仍然只给CUDA设备做了完整适配。我测试下来,MPS上直接调用torch.cuda.amp.GradScaler经常报错,或者行为不符合预期。

实际可行的替代方案有两种:

  • 手动把输入张量转换为float16,让大部分算子走半精度。可以省内存,但要求你对自己的模型足够了解,否则精度和收敛速度会不稳定。
  • 等官方把MPS的AMP支持完善后再用,目前我建议优先float32训练,不去折腾不确定性高的东西。

我的判断是:如果模型在float32下能正常跑,优先不折腾混合精度。只有当你明确需要更大batch或更省内存时,再尝试手动float16,并且随时做好精度下降的心理准备。

4.4 几个踩过的坑

我踩过的三个典型问题,先列出来。

算子缺失。MPS覆盖率再高,也还是有一些底层算子没实现。遇到NotImplementedError时,我的排查思路不是急着改代码,而是看看能不能把特定算子移到CPU上运算,或者换一个等价的数学表达。如果确实需要这个算子,就尝试升级到nightly版。

某些层在CPU上更快。小模型和极小batch下,GPU调度开销可能大于计算收益,训练时间反而变慢。解决办法很直接:增大batch size,让计算量把GPU喂饱。我实测从batch=32调到128后,训练时间能明显降下来。

内存碎片化。长时间连续训练后,统一内存可能出现碎片化,导致分配失败。这种时候最简单的办法是重启Python进程,而不是一直挂着反复尝试。

5. TensorFlow与JAX的GPU加速配置

5.1 TensorFlow Metal插件安装

TensorFlow用户要装两样东西:核心包和Metal插件。

pip install tensorflow-macos pip install tensorflow-metal

装完之后跑一个小型模型,TensorFlow会自动检测到GPU。启动日志里看到类似“Metal device set to: M系列”这样一行,就说明GPU加速生效了。

不过在TensorFlow里,算子回退到CPU的情况非常频繁。一个常见现象是:模型结构复杂或者用到某些少见的层,日志里会出现大量“fall back to CPU”警告。这些警告不影响正确性,但影响性能。如果警告太多,提速效果会大打折扣,甚至不如CPU。

我的建议是:TensorFlow用户在做正式训练前,先跑一遍自己的模型,观察日志里回退警告的数量,再决定是否值得用MPS模式。

5.2 JAX Metal配置

安装JAX的Metal后端相对简单:

pip install jax-metal jax

然后跑一段代码验证:

import jax import jax.numpy as jnp print("默认后端:", jax.default_backend()) x = jnp.ones((1000, 1000)) y = jnp.dot(x, x).block_until_ready() print("计算完成")

如果default_backend显示gpu,说明加速成功。但说实话,我把一些常见模型搬到JAX上跑,算子覆盖和报错信息还是偏粗糙,新手用起来会很痛苦。我的态度是:JAX Metal适合留作观察和研究工具,不要在生产里依赖它。

5.3 与PyTorch的差异

三者比较下来,在Mac上的体验差距很明显。PyTorch MPS给我的感觉是“主流路径”,框架本身投入了大量维护精力,社区提问也容易搜到答案。TensorFlow Metal则是“还能用但别指望完美”,适合老项目迁移。JAX Metal基本是“实验属性明显”,适合有耐心的研究者。

如果你刚开始,别三心二意,直接用PyTorch MPS,把精力放在模型和数据处理上,比同时试三个框架更高效。

6. 常见问题与排查技巧实录

6.1 MPS设备不可用

现象很直接:torch.backends.mps.is_available()返回False。我按顺序排查:

  • 芯片确认。Intel芯片不支持MPS,直接放弃。
  • 系统版本确认。macOS 12.3以上才支持,低于就要升级。
  • PyTorch版本确认。低于1.12的版本不支持MPS。
  • 环境变量确认。有没有设置过PYTORCH_ENABLE_MPS_FALLBACK=0之类的影响标志。

如果以上都没问题还是False,大概率是安装包本身没带MPS相关依赖。最稳妥的办法是重新创建一个干净环境再装一次。

6.2 遇到不支持的算子

现象是某个正常的forward过程抛出异常,提示某个函数在MPS上未实现。我的处理思路有三个层次。

第一,使用官方回退机制。设置环境变量PYTORCH_ENABLE_MPS_FALLBACK=1,允许部分CPU实现被用于MPS不支持的算子:

export PYTORCH_ENABLE_MPS_FALLBACK=1

这个方案可以临时跑通,但性能会有损失,只建议作为过渡方案。

第二,手动把受影响的模块转到CPU。例如某个自定义层在GPU上不支持,可以单独把它的参数固定到CPU,把输入也搬到CPU算完再搬回来。这种做法稍微麻烦,但能保持主体训练在GPU上。

第三,升级到nightly版。我遇到过的很多“不支持”问题,在nightly里其实已经解决了。

6.3 性能反而比CPU更慢

这种现象在小模型上很常见,原因无非三个维度:

  • 数据带宽没喂饱,GPU大部分时间在等待数据。
  • 模型太小,GPU调度开销超过了计算收益。
  • 频繁的数据拷贝。训练循环里使用了太多同步操作,导致GPU不断等待CPU。

解决办法是:把batch size调大、开启数据预取、减少不必要的.item()和.numpy()调用,让计算尽量待在GPU上。

我有一次做图像分类,batch从16调到64后,训练速度直接提升了接近三倍。这个优化优先于任何硬件层面的调整。

6.4 内存不足

统一内存虽然大,但不是无限大。跑大模型训练,内存不够照样OOM。

日常操作可以这样缓解:

  • 降低batch size。
  • 使用更小的输入尺寸。
  • 开启gradient checkpointing,用时间换内存。
  • 推理场景考虑量化,或者转Core ML。

可以留意“活动监视器”里的内存压力指示。我在跑一些较大的Transformer微调时,32GB内存都经常逼近极限,所以大模型并不是Mac的舒适区。

6.5 常见问题速查表

我把排查要点整理成表格:

问题可能原因解决方向
MPS不可用Intel芯片、老系统、老版本PyTorch换M系列芯片、升级系统与框架
算子不支持框架覆盖不足或版本太旧升级nightly、开启fallback、算子移回CPU
训练比CPU慢小模型或小batch,数据管道瓶颈增大batch、优化数据预取
内存溢出数据集或模型过大降精度、降batch、梯度检查点
内存碎片化长期训练后内存分配失败重启进程、定期释放缓存

7. 性能调优与我的实测心得

7.1 统一内存的红利

我自己的实验里,在32GB统一内存的M系列芯片上,跑一个中小规模的Transformer微调,batch size可以设到16到32。这在同价位独显上比较难做到,因为独显总得受显存容量约束。

这个红利让我少做了很多显存裁剪和梯度累积的繁琐工作。但新的约束也很明显:所有内存共享,模型占得多,系统内存就少。同时开着浏览器和多个开发工具,整机响应会被拖慢。

我现在的习惯是:训练时关掉不必要的应用,给训练腾出足够的内存空间。这比任何代码调优都简单粗暴有效。

7.2 批量大小和精度

在MPS上,我总结了两个小技巧。

第一个是batch size不要太小。在模型能塞进内存的前提下,把batch size尽量调大,最好让一段训练时间里GPU始终保持高占用。用“活动监视器”看GPU占用率,如果长期低于50%,大概率是数据管道或调度开销在拖后腿。

第二个是尽量维持float32训练。除非确实遇到内存瓶颈,否则不要为了追求半精度训练而去随意切换。MPS在半精度支持上确实有加速,但部分算子半精度误差会引起训练不稳定,反而更头疼。

7.3 从数据管道开始优化

很多人把性能问题归结为GPU不够好,其实瓶颈经常在数据读取和预处理上。

我的优化顺序是:

  • 先把数据加载和预处理工作移出GPU,放到CPU上用多进程处理。
  • 把所有图像、文本序列尽可能提前做成Tensor格式,不在训练循环里临时做字符串或数组转换。
  • 用DataLoader的num_workers预取数据,并配合合适的batch大小。

有一次我没开num_workers,GPU占用率一直上不去;开了4个预取进程后,训练时间瞬间降到原来的一半。这个优化几乎零成本,强烈建议先做。

7.4 给不同基础读者的建议

如果你刚入门,我建议走这条路线:

  • 学PyTorch,把模型训练的代码都写成device无关的通用写法,这样以后换到CUDA机器也不改逻辑。
  • 小模型在Mac上训练,大模型优先考虑云GPU,别在本地硬扛。
  • 多关注MPS的更新日志,官方每次大版本更新都会补充一批算子支持。

如果你已经熟悉CUDA开发,换到Mac上只是把device改成mps。最开始会不习惯的一点是,很多为CUDA写的特殊优化工具在MPS上不可用,需要回到更基础的PyTorch API。

7.5 这个内容后续还能扩展

我还在尝试把训练好的模型转成Core ML,直接集成到Mac和iOS应用里做端侧推理。从PyTorch到Core ML的转换有不少细节,比如算子兼容性、量化位宽、固定输入维度,这些内容信息量不小,下一步有空会单独写一篇实操笔记。

目前阶段,Mac上的GPU加速虽然不能完全替代专业卡集群,但作为个人开发和中轻量级训练的主力环境,它已经足够可靠了。

我个人实际操作下来的体会是:Mac上的GPU加速不是一个“能不能用”的问题,而是“怎么用”的问题。最关键的是选对路线、写对代码、喂饱计算单元。最后再分享一个小技巧:在训练脚本开头打印设备信息和模型占用内存,用活动监视器对照观察,能帮你排查很多诡异的性能问题。如果你也在用Mac跑机器学习,建议从PyTorch MPS入手,先跑通一个简单模型,再逐步加深,这条路目前是最省心的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询