即使您不是 Mac 用户,您可能也听说过 Apple 正在从英特尔 CPU 迁移到他们自己的定制 CPU,他们统称为“Apple 硅芯片”。Apple 上一次如此戏剧性地改变其计算机架构是在 15 年前,当时他们从 PowerPC 迁移到英特尔 CPU。因此,技术媒体上发表了许多关于这种过渡对 Mac 用户意味着什么的文章,但很少从 Python 数据科学家的角度出发。在这篇文章中,我将分析 Apple 硅芯片对当今 Python 用户(尤其是从事科学计算和数据科学的人员)意味着什么:什么有效,什么无效,以及未来的发展方向。
Mac 的变化是什么?
简而言之,Apple 正在将其所有笔记本电脑和台式机产品线从使用英特尔 CPU 迁移到使用 Apple 自己设计的 CPU。Apple 已经是一家 CPU 设计公司近十年了(从 2012 年发布 iPhone 5 开始),但直到 2020 年底,他们的 CPU 仅用于 iPhone 和 iPad 等移动设备。经过多次迭代,技术观察人士清楚地认识到,Apple 的 CPU(尤其是在 iPad Pro 中)在性能方面已经与低功耗英特尔笔记本电脑 CPU 相比。因此,当 Apple 在 2020 年的开发者大会上宣布将在未来两年内将其所有 Mac 产品线迁移到他们自己的 CPU 时,这并不是完全出乎意料。正如承诺的那样,Apple 于 2020 年 11 月 发布了首款硅芯片 Mac。它们包括 MacBook Air、13 英寸 MacBook Pro 和 Mac Mini,它们看起来与之前的型号相同,但使用的是 Apple M1 CPU,而不是英特尔 CPU。在 2021 年 4 月,他们将 M1 添加到 iMac 和 iPad Pro 中。
那么这些 Apple CPU 的优势在哪里?根据许多专业评论(以及我们 Anaconda 的测试),M1 的主要优势是出色的单线程性能和笔记本电脑的更长电池续航时间。需要注意的是,基准测试是一个棘手的话题,对于 M1 的确切速度,存在不同的观点。一方面,有什么比用量化方式衡量性能更客观呢?另一方面,基准测试也是主观的,因为评测者必须决定要测试哪些工作负载,以及要比较的基线硬件和软件配置。尽管如此,我们非常有信心,对于“典型用户”(不一定是指“数据科学用户”)工作负载,M1 通常比之前的英特尔 Mac 更快,同时功耗更低。
从 CPU 架构的角度来看,M1 与之前的英特尔 CPU 有四个主要区别
-
CPU 架构从 x86 变为 ARM
-
Apple 现在设计片上 GPU(而不是来自英特尔的片上 GPU 或来自 NVIDIA 或 AMD 的独立 GPU 芯片)
-
来自 iPhone 和 iPad 芯片的自定义 Apple 加速器现在可用于 Mac,例如 Apple 神经引擎 (ANE)
-
系统 RAM 直接焊接到 CPU 封装上,并由 CPU、GPU 和 ANE 内核共享。
我们将在后面的部分中更详细地探讨这些变化的影响。
指令集、ARM 和 x86 兼容性
作为软件用户和创建者,我们关于 Apple 硅芯片的第一个问题是:我是否需要更改任何内容才能使我的软件继续运行?答案是:需要,但比您想象的要少。
为了详细说明这一点,我们需要深入了解指令集架构 (ISA)。ISA 是一个规范,描述了芯片家族在低级别的工作方式,包括机器代码必须使用的指令集。应用程序和库是针对特定 ISA(和操作系统)编译的,除非重新编译,否则它们将不会直接在具有不同 ISA 的 CPU 上运行。这就是为什么 Anaconda 为我们支持的每个平台提供不同的安装程序和软件包文件的原因。
英特尔 CPU 支持 x86-64 ISA(有时也称为AMD64,因为 AMD 最初在 1999 年提出了对 x86 的 64 位扩展)。新的 Apple 硅芯片 CPU 使用 ARM 设计的 ISA,称为 AArch64,与 iPhone 和 iPad CPU 沿用的一样。因此,这些新的 Mac 通常被称为“ARM Mac”,与“英特尔 Mac”形成对比,尽管 ARM 只是定义了 Apple M1 使用的 ISA,但没有设计 CPU。总的来说,64 位 ARM 架构的命名约定令人困惑,因为不同的人会对同一事物使用略微不同的术语。您可能会看到一些人将这些 CPU 称为“ARM64”(Apple 在其开发工具中使用的是这个名称),或者略微不准确地称为“ARMv8”(这是一个描述 AArch64 和 AArch32 的规范)。即使在 conda 中,您也会看到平台名称 osx-arm64,您将在运行 M1 的 macOS 上使用它,以及 linux-aarch64,您将在运行 64 位 ARM CPU 的 Linux 上使用它。在本帖中,我们将使用“ARM64”,因为它比“Apple 硅芯片”更短,而且比“AArch64”更简洁。
细心的读者会注意到,ISA 兼容性只对编译代码很重要,编译代码必须被翻译成机器代码才能运行。Python 是一种解释型语言,因此用纯 Python 编写的软件在英特尔 Mac 和 ARM Mac 之间不需要更改。但是,Python 解释器本身是一个编译程序,并且许多 Python 数据科学库(如 NumPy、pandas、Tensorflow、PyTorch 等)也包含编译代码。这些软件包都需要在 macOS 上为 ARM64 CPU 重新编译,才能在新的基于 M1 的 Mac 上原生运行。
但是,Apple 也为此提供了解决方案。Apple 在 macOS 11 中包含了一个系统组件,名为Rosetta2,它沿用了他们 PowerPC 模拟器的“Rosetta”名称。Rosetta2 允许 x86-64 应用程序和库在 ARM64 Mac 上保持不变地运行。关于 Rosetta2 的一个有趣的事实是,它是一个 x86-64 到 ARM64 的翻译器,而不是模拟器。当您第一次运行 x86-64 程序(或加载编译库)时,Rosetta2 会分析机器代码,并创建等效的 ARM64 机器代码。这会在您第一次启动 x86-64 应用程序时产生一点前期延迟,但翻译后的机器代码也会被缓存到磁盘上,因此后续运行应该更快地启动。这与模拟器(如 QEMU)形成对比,模拟器在软件中逐条指令地模拟 CPU 的行为和状态。模拟器通常比运行翻译后的二进制文件慢得多,但从一个 ISA 翻译到另一个 ISA 并使代码仍然能够正确且高效地运行可能会很困难,尤其是在处理多线程和内存一致性时。
Apple 尚未披露他们如何才能从 x86-64 二进制文件生成性能良好的 ARM64 机器代码,但有一些理论认为,他们设计 M1 时增加了额外的硬件功能,使它能够在运行翻译后的代码时模仿 x86-64 芯片的一些行为。最终结果是,大多数人在 Rosetta2 下运行 x86-64 程序时,与原生 ARM64 相比,性能会下降 20-30%。在 ARM64 编译的软件选择赶上来之前,这是一个相当合理的权衡。
但是,这有一个问题,尤其是对于 Python 中数值计算包的用户而言。x86-64 ISA 不是一个固定的规范,而是一个英特尔不断发展完善的规范,随着时间的推移增加了针对不同工作负载的新的专用指令。特别是,在过去的十年中,英特尔和 AMD 推出了对“向量指令”的支持,这些指令可以同时对多个数据片段进行操作。具体来说,对 x86-64 指令集的这些补充是 AVX、AVX2 和 AVX-512(本身具有不同的变体)。正如您可能想到的那样,这些指令在处理数组数据时非常有用,并且许多库在为 x86-64 编译二进制文件时采用了这些指令。问题是,Rosetta2 不支持任何 AVX 系列指令,如果您的二进制文件尝试使用它们,它将产生非法指令错误。由于存在具有不同 AVX 功能的不同英特尔 CPU,许多程序已经可以动态地在 AVX、AVX2、AVX-512 和非 AVX 代码路径之间进行选择,在运行时检测 CPU 功能。这些程序在 Rosetta2 下可以正常工作,因为在 Rosetta2 下运行的进程报告的 CPU 功能不包括 AVX。但是,如果应用程序或库在运行时没有选择非 AVX 代码路径的功能,那么如果打包程序在编译库时假定 CPU 将支持 AVX,那么它将无法在 Rosetta2 下工作。例如,TensorFlow 轮子在 M1 上的 Rosetta2 下无法工作。此外,即使程序在 Rosetta2 下可以工作,没有像 AVX 这样的东西也会使 M1 在执行某些类型的数组处理时速度变慢。(再次,与在英特尔 CPU 上使用 AVX 的程序相比。由于各种原因,只有某些 Python 库使用 AVX,因此您可能会或可能不会注意到很大的差异,具体取决于您的用例。)
获取 M1 的 Python 软件包
目前有三种方法可以在 M1 上运行 Python
-
使用 pyenv 创建环境,并使用 pip 安装原生的 macOS ARM64 轮子或从源代码构建软件包。
-
使用 x86-64 Python 发行版(如 Anaconda 或 conda-forge),并使用 Rosetta2。
-
使用实验性的 conda-forge macOS ARM64 发行版。
第一个选项目前比较困难,因为很少有 Python 项目上传了适用于 macOS 的原生 ARM64 轮子。许多 Python 项目使用免费的持续集成服务(如 Azure Pipelines 或 Github Actions)来构建二进制轮子,而这些服务还没有 M1 Mac 可用。如果您尝试安装没有轮子的包,pip 将尝试从头开始构建包,这可能会成功,也可能不会成功,具体取决于您是否拥有合适的编译器和其他外部依赖项。
第二个选项提供了最广泛的软件包范围,虽然不是最快的选项。您可以 下载 Anaconda(或 conda-forge)的 macOS x86-64 安装程序并将其安装在 M1 Mac 上,一切都会正常工作。您将看到安装程序发出警告,提示“体系结构似乎不是 64 位”,但您可以安全地忽略该警告。在运行 Python 应用程序时,您可能会注意到启动 Python 或第一次导入包时出现暂停(有时很长!),因为 Rosetta2 将机器代码从 x86-64 翻译成 ARM64。但是,这种暂停将在后续运行中消失。主要限制是需要 AVX 的软件包无法运行。例如,由 pip 安装的 TensorFlow 2.4 轮子将失败(由于其构建方式),但是,来自 conda-forge 的 TensorFlow 2.4 包将能正常工作。您可以使用以下命令将其安装到您的 conda 环境中
conda install conda-forge::tensorflow
第三个选项是使用为 macOS ARM64 构建的实验性 conda-forge 发行版。conda-forge 团队使用一种称为“交叉编译”的技术,利用其现有的 macOS x86-64 持续集成基础设施构建 macOS ARM64 二进制文件。这对于没有 M1 Mac 硬件访问权限来说是一个很好的解决方法,但缺点是构建系统无法测试软件包,因为没有 M1 CPU 可运行。尽管如此,我们测试过的软件包似乎运行良好。如果您想尝试使用 Miniforge for osx-arm64
-
访问 Miniforge 下载页面 并下载 OSX ARM64 安装程序。
-
按照 安装说明 进行操作
许多流行的 OSX ARM64 软件包都可以在 conda-forge 中获得,包括 PyTorch、TensorFlow(目前仅适用于 Python 3.8)、Scikit-learn 和 pandas。
我们正在将 macOS ARM64 添加为 Anaconda 支持的平台。这些软件包将在 M1 Mac 上构建和测试。这些软件包目前还没有提供,敬请关注!
M1 的“效率”核心如何被 Python 使用?
虽然有时被称为 8 核 CPU,但 M1 最好被描述为 4+4 核 CPU。它有 4 个“高性能核心”(在 Apple 文档中有时被称为“P 核心”)和 4 个“高效率核心”(有时被称为“E 核心”)。P 核心提供 CPU 处理能力的大部分,并消耗大部分功率。E 核心与 P 核心设计不同,以降低功耗为代价换取最大性能。虽然桌面和笔记本芯片拥有两种不同的核心并不常见,但在移动 CPU 中很常见。后台进程和低优先级计算任务可以在 E 核心上执行,从而节省电力并延长电池续航时间。Apple 提供 用于设置线程和任务的“服务质量”的 API,这会影响操作系统将它们分配给 P 核心还是 E 核心。
但是,Python 不会使用任何这些特殊 API,那么在您的应用程序中使用多线程或多进程时会发生什么?Python 将 CPU 核心数报告为 8,因此,如果您启动 8 个工作进程,其中一半将在较慢的 E 核心上运行。如果您运行少于 8 个进程,操作系统似乎更倾向于在 P 核心上运行它们。为了量化这一点,我们创建了一个简单的微基准测试,其中我们创建了一个计算余弦的函数
import math
def waste_time(_):
n = 1000000
for i in range(n):
math.cos(0.25)
然后在一个 multiprocessing.Pool 中运行它的多个副本,并使用不同的进程数量计算吞吐量。结果如以下图表所示
该行为与操作系统调度程序在有四个或更少进程时将工作进程分配给性能核心的行为一致。随着进程池超过四个,每个核心的吞吐量增加减少,直到我们达到八个进程,这表明较慢的 E 核心被用于额外的进程。最后,没有额外的 CPU 资源可以利用超过八个进程,现在调度效果和内存竞争会导致吞吐量变化,但不会提高。此外,有趣的是,对于 M1 的峰值总吞吐量(在本测试中),P 核心提供 75% 的吞吐量,E 核心提供 25% 的吞吐量。E 核心提供了非凡的贡献,因此,如果可以,最好使用它们,并且您的工作分配系统可以处理工作者之间可能需要不同时间的工作。(也就是说,必须动态调度工作,或者必须支持工作窃取。)在英特尔 Mac 上运行类似的扩展测试表明,M1 上 E 核心的性能增益与英特尔 CPU 上超线程的增益大致相当。但是,这两种功能完全不同。
Linux 和 Windows 在 M1 上怎么样?
虽然 Apple 表示 他们不会阻止用户在 M1 硬件上运行其他操作系统,但目前还没有 Boot Camp 等效程序用于在 M1 Mac 上双启动 Linux 或 Windows,并且将 Linux 移植到 M1 硬件上以原生运行的努力仍然处于高度实验阶段。但是,(截至本文撰写时)有两种方法可以在 M1 上运行 Linux
在这两种情况下,您都需要运行为 ARM64 编译的 Linux 发行版,但这些发行版很容易获得,因为 Linux 长时间以来一直在 ARM 系统上使用。我们发现 M1 上的 Docker 非常简单易用,M1 Mac mini 是持续集成系统的绝佳选择,这些系统需要测试 Linux ARM64 软件,而不是使用 Raspberry Pi 和 NVIDIA Jetson 等低功耗单板计算机。例如,Numba 的广泛单元测试套件在我们的 M1 Mac mini 上的 Docker 中运行速度比运行 Ubuntu Linux 的 Raspberry Pi 4 快约 7 倍。(请注意,我们尚未找到在 M1 上运行 32 位 ARM Linux 发行版的方法。)
Parallels Desktop 现在也可以运行 Windows 的 ARM64 移植版,但适用于 ARM 的 Windows 软件并不像 Linux 软件那样普遍。Windows 本身仅通过 Windows 预览体验成员计划提供适用于 ARM 的版本,而数据科学家可能使用的几乎所有软件都可以在 Linux 上获得。
对于数据科学家来说,有哪些优缺点?
M1 为数据科学家带来的最大好处与普通用户基本相同
-
高单线程性能
-
出色的电池续航时间(适用于笔记本电脑)
M1 Mac 没有提供专门针对数据科学家的功能,但这可能会在将来出现(请参阅下一节)。
但是,M1 目前针对的是 Apple 产品线中功耗最低的设备,这使其存在一些缺点
-
只有四个高性能 CPU 核心:许多数据科学库(如 TensorFlow、PyTorch 和 Dask)从更多 CPU 核心获益,因此只有四个核心是一个缺点。
-
最大 16 GB 的 RAM:更多内存始终有助于处理更大的数据集,而 16 GB 对于某些用例可能不够。
-
对 Python 数据科学生态系统中的 GPU 和 ANE 支持有限:M1 上所有不同计算单元和内存系统之间的紧密集成可能非常有利。但是,在这一点上,唯一能够使用 M1 GPU 或 ANE 的非 alpha Python 库是 coremltools,它只加速模型推理。
-
ARM64 软件包可用性:PyData 生态系统需要时间才能赶上来并持续为 ARM64 Mac 提供 Python 轮子,而且可能会存在空白。在 Rosetta2 下运行可能是更好的选择,只要您可以避免需要 AVX 且根本无法运行的软件包。
与现有的英特尔 Mac 相比,CPU 核心数和 RAM 限制非常大,尽管 M1 在其拥有的资源方面非常高效。
未来有什么?
M1 当前的许多缺点仅仅是由于它是新 ARM64 基于 Mac 产品线的第一个 CPU。为了将更高性能的 Mac(尤其是 Mac Pro)转换为这种新的架构,Apple 将需要发布具有更多 CPU 核心和更多内存的系统。与此同时,Python 开发者生态系统将赶上来,更多原生 ARM64 轮子和 conda 软件包将变得可用。
但最令人兴奋的进展是,机器学习库可以开始利用 Apple Silicon 上的新 GPU 和 Apple Neural Engine 核心。Apple 提供了 Metal 和 ML Compute 等 API,这些 API 可以加速机器学习任务,但它们在 Python 生态系统中并未得到广泛使用。Apple 有一个 TensorFlow 的 alpha 移植版 使用 ML Compute,也许其他项目将在未来几年能够利用 Apple 硬件加速。
除了 Apple 之外,M1 展示了桌面级 ARM 处理器可以做什么,因此,希望我们能看到其他 ARM CPU 制造商在这方面的竞争。例如,来自其他制造商的 M1 级 ARM CPU 运行 Linux 并配有 NVIDIA GPU 也可能成为令人印象深刻的移动数据科学工作站。
结论
M1 Mac 是一个令人兴奋的机会,可以让我们看到笔记本电脑/桌面级 ARM64 CPU 可以取得的成就。对于一般用途,性能非常出色,但这些系统目前并不针对数据科学和科学计算用户。如果您出于其他原因需要 M1,并且打算进行一些轻量级的数据科学工作,它们就足够了。对于更密集的用途,您现在可能需要坚持使用英特尔 Mac,但请关注软件开发(因为兼容性会提高)以及未来的 ARM64 Mac 硬件,这些硬件可能会消除我们今天看到的一些限制。