我如何解决因在 MacBook Pro 上为 运行 Spark/Python 代码导入 NumPy C 扩展失败而导致的 Python 模块导入问题?

How can I resolve Python module import problems stemming from the failed import of NumPy C-extensions for running Spark/Python code on a MacBook Pro?

当我在 Mac 终端 (Bash) 中尝试 运行 (simplified/illustrative) Spark/Python 脚本时,如果导入用于 numpypandaspyspark.ml。当使用下面列出的 'Section 1' 导入时(当它们包含 from pyspark.sql import SparkSession 时),此处显示的示例 Python 代码 运行 很好,但当任何 'Section 2'使用进口。完整的错误信息如下所示;它的一部分是:'..._multiarray_umath.cpython-310-darwin.so' (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64')。显然,将 NumPy 'c-extensions' 导入某些计算节点时出现问题。有没有办法解决错误,使各种 pyspark.ml 和其他导入功能正常运行? [剧透警报:原来有!请参阅下面的解决方案!]

问题可能源于一个或多个潜在原因,我认为:(1) 环境变量设置不当(例如 PATH),(2) SparkSession 设置不正确代码,(3) 省略但必要的 Python 模块导入,(4) 相关下载的不正确集成(在本例中为 Spark 3.2.1 (spark-3.2.1-bin-hadoop2.7),Scala (2.12.15), Java (1.8.0_321), sbt (1.6.2), Python 3.10.1, and NumPy 1.22.2) 在本地开发环境(a 2021 MacBook Pro (Apple M1 Max) 运行ning macOS Monterey 版本 12.2.1),或者 (5) 可能 hardware/software 不兼容。

请注意,现有的代码组合(形式更复杂)加上软件和硬件 运行 可以使用终端导入和处理数据以及显示 Spark 数据帧等,只要因为导入仅限于 pyspark.sql 的基本版本。其他导入似乎会导致问题,而且可能不会。

示例代码(一个简单但有效的程序,仅用于说明问题):

# Example code to illustrate an issue when using locally-installed versions
# of Spark 3.2.1 (spark-3.2.1-bin-hadoop2.7), Scala (2.12.15),
# Java (1.8.0_321), sbt (1.6.2), Python 3.10.1, and NumPy 1.22.2 on a
# MacBook Pro (Apple M1 Max) running macOS Monterey version 12.2.1

# The Python code is run using 'spark-submit test.py' in Terminal

# Section 1.
# Imports that cause no errors (only the first is required):
from pyspark.sql import SparkSession
from pyspark.sql.types import *
from pyspark.sql.functions import *

# Section 2.
# Example imports that individually cause similar errors when used:
# import numpy as np
# import pandas as pd
# from pyspark.ml.feature import StringIndexer
# from pyspark.ml.feature import VectorAssembler
# from pyspark.ml.classification import RandomForestClassifier
# from pyspark.ml import *

spark = (SparkSession
    .builder
    .appName("test.py")
    .enableHiveSupport()
    .getOrCreate())

# The associated dataset is located here (but is not required to replicate the issue):
# https://github.com/databricks/LearningSparkV2/blob/master/databricks-datasets/learning-spark-v2/flights/departuredelays.csv

# Create database and managed tables
spark.sql("DROP DATABASE IF EXISTS learn_spark_db CASCADE")
spark.sql("CREATE DATABASE learn_spark_db")
spark.sql("USE learn_spark_db")
spark.sql("CREATE TABLE us_delay_flights_tbl(date STRING, delay INT, distance INT, origin STRING, destination STRING)")

# Display (print) the database
print(spark.catalog.listDatabases())

print('Completed with no errors!')

这是仅使用第 1 节导入时产生的无错误输出(一些细节已被替换为“...”):

MacBook-Pro ~/.../Spark2/spark-3.2.1-bin-hadoop2.7/LearningSparkGitHub/chapter4/py/src$ spark-submit test.py
[Database(name='default', description='Default Hive database', locationUri='file:/Users/.../Spark2/spark-3.2.1-bin-hadoop2.7/LearningSparkGitHub/chapter4/py/src/spark-warehouse'), Database(name='learn_spark_db', description='', locationUri='file:/Users/.../Spark2/spark-3.2.1-bin-hadoop2.7/LearningSparkGitHub/chapter4/py/src/spark-warehouse/learn_spark_db.db')]
Completed with no errors!

这是 使用 from pyspark.ml import * 或其他(第 2 节)单独导入时通常会导致的错误

MacBook-Pro ~/.../Spark2/spark-3.2.1-bin-hadoop2.7/LearningSparkGitHub/chapter4/py/src$ spark-submit test.py
Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/numpy/core/__init__.py", line 23, in <module>
    from . import multiarray
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/numpy/core/multiarray.py", line 10, in <module>
    from . import overrides
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/numpy/core/overrides.py", line 6, in <module>
    from numpy.core._multiarray_umath import (
ImportError: dlopen(/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/numpy/core/_multiarray_umath.cpython-310-darwin.so, 0x0002): tried: '/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/numpy/core/_multiarray_umath.cpython-310-darwin.so' (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64')), '/usr/lib/_multiarray_umath.cpython-310-darwin.so' (no such file)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/.../Spark2/spark-3.2.1-bin-hadoop2.7/LearningSparkGitHub/chapter4/py/src/test.py", line 28, in <module>
    from pyspark.ml import *
  File "/Users/.../Spark2/spark-3.2.1-bin-hadoop2.7/python/lib/pyspark.zip/pyspark/ml/__init__.py", line 22, in <module>
  File "/Users/.../Spark2/spark-3.2.1-bin-hadoop2.7/python/lib/pyspark.zip/pyspark/ml/base.py", line 25, in <module>
  File "/Users/.../Spark2/spark-3.2.1-bin-hadoop2.7/python/lib/pyspark.zip/pyspark/ml/param/__init__.py", line 21, in <module>
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/numpy/__init__.py", line 144, in <module>
    from . import core
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/numpy/core/__init__.py", line 49, in <module>
    raise ImportError(msg)
ImportError: 

IMPORTANT: PLEASE READ THIS FOR ADVICE ON HOW TO SOLVE THIS ISSUE!

Importing the numpy C-extensions failed. This error can happen for
many reasons, often due to issues with your setup or how NumPy was
installed.

We have compiled some common reasons and troubleshooting tips at:

    https://numpy.org/devdocs/user/troubleshooting-importerror.html

Please note and check the following:

  * The Python version is: Python3.10 from "/Library/Frameworks/Python.framework/Versions/3.10/bin/python3"
  * The NumPy version is: "1.22.2"

and make sure that they are the versions you expect.
Please carefully study the documentation linked above for further help.

Original error was: dlopen(/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/numpy/core/_multiarray_umath.cpython-310-darwin.so, 0x0002): tried: '/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/numpy/core/_multiarray_umath.cpython-310-darwin.so' (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64')), '/usr/lib/_multiarray_umath.cpython-310-darwin.so' (no such file)

回应错误消息中提到的评论:是的,上面提到的 Python 和 NumPy 版本似乎是正确的。 (但事实证明,对 Python 3.10 的引用具有误导性,因为它可能是对 Python 3.10.1 而不是 Python 3.10.2 的引用,如下面的编辑 1 中所述.)

以下是~/.bash_profile目前使用的设置供您参考:

export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_321.jdk/Contents/Home/
export SPARK_HOME=/Users/.../Spark2/spark-3.2.1-bin-hadoop2.7
export SBT_HOME=/Users/.../Spark2/sbt
export SCALA_HOME=/Users/.../Spark2/scala-2.12.15
export PATH=$JAVA_HOME/bin:$SBT_HOME/bin:$SBT_HOME/lib:$SCALA_HOME/bin:$SCALA_HOME/lib:$PATH
export PATH=$JAVA_HOME/bin:$SPARK_HOME:$SPARK_HOME/bin:$SPARK_HOME/sbin:$PATH
export PYSPARK_PYTHON=python3
export PYTHONPATH=$SPARK_HOME/python/:$PYTHONPATH
# export PYSPARK_DRIVER_PYTHON="jupyter"
# export PYSPARK_DRIVER_PYTHON_OPTS="notebook"
PATH="/Library/Frameworks/Python.framework/Versions/3.10/bin:${PATH}"
export PATH

# Misc: cursor customization, MySQL
export PS1="\h \w$ "
export PATH=${PATH}:/usr/local/mysql/bin/

# Not used, but available:
# export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-16.0.1.jdk/Contents/Home
# export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_144.jdk/Contents/Home
# export PATH=$PATH:$SPARK_HOME/bin

# For use of SDKMAN!
export SDKMAN_DIR="$HOME/.sdkman"
[[ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]] && source "$HOME/.sdkman/bin/sdkman-init.sh"

以下网站有助于加载和集成 Spark、Scala、Java、sbt 和 Python(上述版本):https://kevinvecmanis.io/python/pyspark/install/2019/05/31/Installing-Apache-Spark.html。请注意 jupyternotebook 驱动程序设置已在 Bash 配置文件中被注释掉,因为它们可能是不必要的(并且因为在某一时刻,它们似乎会干扰使用spark-submit 终端中的命令)。

对参考 numpy.org 网站的评论没有太大帮助: https://numpy.org/devdocs/user/troubleshooting-importerror.html

回应 numpy.org 网站上的一些评论: Python3 shell 运行 在 Mac 终端中正常,并且pyspark 和其他导入(numpy 等)在那里正常工作。这是 output 以交互方式从 Python 打印 PYTHONPATHPATH 变量时产生的结果(一些细节被替换为“...” ):

>>> import os
>>> print("PYTHONPATH:", os.environ.get('PYTHONPATH'))
PYTHONPATH: /Users/.../Spark2/spark-3.2.1-bin-hadoop2.7/python/:
>>> print("PATH:", os.environ.get('PATH'))
PATH: /Users/.../.sdkman/candidates/sbt/current/bin:/Library/Frameworks/Python.framework/Versions/3.10/bin:/Library/Java/JavaVirtualMachines/jdk1.8.0_321.jdk/Contents/Home//bin:/Users/.../Spark2/spark-3.2.1-bin-hadoop2.7:/Users/.../Spark2/spark-3.2.1-bin-hadoop2.7/bin:/Users/.../Spark2/spark-3.2.1-bin-hadoop2.7/sbin:/Library/Java/JavaVirtualMachines/jdk1.8.0_321.jdk/Contents/Home//bin:/Users/.../Spark2/sbt/bin:/Users/.../Spark2/sbt/lib:/Users/.../Spark2/scala-2.12.15/bin:/Users/.../Spark2/scala-2.12.15/lib:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/MacGPG2/bin:/Library/Apple/usr/bin:/usr/local/mysql/bin/

(我不确定此输出的哪一部分指向问题。)

之前尝试的补救措施包括这些(均未成功):

迄今为止,没有任何操作解决问题。


编辑 1

我正在添加最近发现的信息。

首先,上面提到的 PATH 设置 (export PYSPARK_PYTHON=python3) 似乎指向位于 /Library/Frameworks/Python.framework/Versions/3.10/bin/python3 的 Python 3.10.1 而不是 Python 3.10.2在我的开发环境中。我随后卸载了 Python 3.10.1 并在我的 Mac (macOS Monterey 12.2. 1),但尚未更改 PYSPARK_PYTHON 路径以指向开发环境(欢迎就如何操作提出建议)。如前所述,代码仍然会抛出错误。

其次,多了解一下计算机的体系结构可能会有所帮助,因为错误消息指出了潜在的硬件-软件不兼容问题:

/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/numpy/core/_multiarray_umath.cpython-310-darwin.so' (mach-o file, but is an incompatible architecture (have 'arm64', need 'x86_64')

计算机是“MacBookPro18,2”,配备 Apple M1 Max 芯片(10 核:8 个性能,2 个效率;32 核 GPU)。像这样的一些网站 (https://en.wikipedia.org/wiki/Apple_silicon#Apple_M1_Pro_and_M1_Max, https://github.com/conda-forge/miniforge/blob/main/README.md) 建议 'Apple silicon' 像 M1 Max 需要为 'arm64' 架构设计的软件。在 Mac 上使用终端,我检查了 Python 3.10.2 和麻烦的 _multiarray_umath.cpython-310-darwin.so 文件的兼容性。 Python 3.10.2 是一个 'universal binary' 有 2 种架构(x86_64 和 arm64),文件是 arm64:

MacBook-Pro ~$ python3 --version
Python 3.10.2
MacBook-Pro ~$ whereis python3
/usr/bin/python3
MacBook-Pro ~$ which python3
/Library/Frameworks/Python.framework/Versions/3.10/bin/python3
MacBook-Pro ~$ file /Library/Frameworks/Python.framework/Versions/3.10/bin/python3
/Library/Frameworks/Python.framework/Versions/3.10/bin/python3: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64:Mach-O 64-bit executable arm64]
/Library/Frameworks/Python.framework/Versions/3.10/bin/python3 (for architecture x86_64):   Mach-O 64-bit executable x86_64
/Library/Frameworks/Python.framework/Versions/3.10/bin/python3 (for architecture arm64):    Mach-O 64-bit executable arm64
MacBook-Pro ~$ file /Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/numpy/core/_multiarray_umath.cpython-310-darwin.so
/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/site-packages/numpy/core/_multiarray_umath.cpython-310-darwin.so: Mach-O 64-bit bundle arm64

所以我仍然对错误消息感到困惑,它说 'x86_64' 需要某些东西(硬件或软件?)到 运行 这个脚本。在 Apple M1 Max 芯片上 运行 PySpark 脚本需要特殊环境吗?如前所述,PySpark 似乎在同一台计算机上以 Python 的交互模式运行良好:

MacBook-Pro ~$ python3
Python 3.10.2 (v3.10.2:a58ebcc701, Jan 13 2022, 14:50:16) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyspark
>>> from pyspark.sql import SparkSession
>>> from pyspark.ml import *
>>> import numpy as np
>>> 

有没有办法解决这个错误,使各种 pyspark.ml 和其他导入在 Python 脚本中正常运行?也许 ~/.bash_profile 中的设置需要更改? _multiarray_umath.cpython-310-darwin.so 文件的不同版本是否可以解决问题?如果可以,我将如何获得它? (使用不同版本的 Python?)我正在寻求有关代码、设置、and/or 操作的建议。也许我忽略了一个简单的修复方法。

解决了。 尝试导入 numpy 时遇到的错误 c-extensions 涉及确保每个计算节点具有执行目标脚本所需的环境的挑战( test.py)。事实证明,这可以通过将必要的模块(在本例中,只有 numpy)压缩到压缩包 (.tar.gz) 中以用于 'spark-submit' 命令来执行 Python 脚本。我使用的方法涉及利用 conda-forge/miniforge 到 'pack' 所需的依赖项到一个文件中。 (这感觉像是一个黑客,但它奏效了。)

以下网站对开发解决方案很有帮助:

  1. Hyukjin Kwon 的博客,“如何在 PySpark 中管理 Python 依赖关系”https://databricks.com/blog/2020/12/22/how-to-manage-python-dependencies-in-pyspark.html
  2. "Python 包管理:使用 Conda": https://spark.apache.org/docs/latest/api/python/user_guide/python_packaging.html
  3. Alex Ziskind 的视频“python Apple Silicon 上的环境设置 | M1,M1 Pro/Max with Conda-forge”:https://www.youtube.com/watch?v=2Acht_5_HTo
  4. conda-forge/miniforge on GitHub:https://github.com/conda-forge/miniforge(对于 Apple 芯片,使用 Miniforge3-MacOSX-arm64 下载 OS X(arm64,Apple Silicon)。

实施解决方案的步骤:

  1. 按照 Alex 的建议在您的计算机上安装 conda-forge/miniforge(在我的例子中,是配备 Apple 芯片的 MacBook Pro)。您还不需要在计算机上激活任何 conda 环境。在安装过程中,我推荐这些设置:
Do you wish the installer to initialize Miniforge3
by running conda init? [yes|no] >>> choose 'yes'

If you'd prefer that conda's base environment not be activated on startup, 
set the auto_activate_base parameter to false: 
conda config --set auto_activate_base false  # Set to 'false' for now
  1. 安装 conda 后,进入包含 Python (PySpark) 脚本的目录(即您想要 运行 的文件——在此处讨论的情况下,'test.py').
  2. 输入 Spark 文档(请参阅上文 URL)中针对“使用 Conda”推荐的命令。在第一行(如下所示)包含您需要的模块的 space-separated 序列(在本例中,只有 numpy,因为问题涉及导入 numpy 失败 c-extensions)。这将在您所在的目录(包含 Python 脚本的目录)中创建您需要的 tarball,pyspark_conda_env.tar.gz(包含每个计算节点所需的所有模块和依赖项):
conda create -y -n pyspark_conda_env -c conda-forge numpy conda-pack
conda activate pyspark_conda_env
conda pack -f -o pyspark_conda_env.tar.gz

(如果需要,您可以插入 'pyarrow pandas numpy' 等,而不是仅在上面显示的三个模块的第一行插入 'numpy',如果您需要多个模块,例如这三个。Pandas 似乎依赖于 pyarrow。)

  1. 命令 conda activate pyspark_conda_env(以上)将激活您的新环境,所以现在是调查您的 conda 环境具有哪个版本的 Python 以及它存在的位置的好时机(您只需要做一次)。您将需要此信息来设置 ~/.bash_profile:
  2. 中的 PYSPARK_PYTHON 环境变量
(pyspark_conda_env) MacBook-Pro ~$ python --version
Python 3.10.2
(pyspark_conda_env) MacBook-Pro ~$ which python
/Users/.../miniforge3/envs/pyspark_conda_env/bin/python

如果您需要不同版本的 Python,您可以指示 conda 安装它(参见 Alex 的视频)。

  1. 确保您的 ~/.bash_profile(或类似配置文件)包含以下设置(filling-in 您刚刚发现的确切路径):
export PYSPARK_PYTHON=/Users/.../miniforge3/envs/pyspark_conda_env/bin/python

记得 'source' 对您的个人资料进行任何更改(例如,source ~/.bash_profile)或重新启动您的终端以使更改生效。

  1. 使用类似于此的命令 运行 你的目标脚本(假设你在上面讨论的同一目录中)。 Python 脚本现在应该成功执行,没有错误:
spark-submit --archives pyspark_conda_env.tar.gz test.py

还有其他几种方法可以使用 tarball 来确保它在 Spark 执行程序(节点)上自动解压缩到 运行 您的脚本。如果需要,请参阅上面讨论的 Spark 文档以了解它们。

  1. 为清楚起见,这里是适用于此安装的最终 ~/.bash_profile 设置,其中包括在 Spark 中 运行 Scala 脚本的能力。如果您不使用 Scala,SBT_HOME 和 SCALA_HOME 设置可能不适用于您。此外,您可能需要也可能不需要 PYTHONPATH 设置。 'How to install PySpark locally' (https://sigdelta.com/blog/how-to-install-pyspark-locally/).
  2. 中讨论了如何根据您的特定版本的 py4j 对其进行调整。
export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_321.jdk/Contents/Home
export SPARK_HOME=/Users/.../Spark2/spark-3.2.1-bin-hadoop2.7
export SBT_HOME=/Users/.../Spark2/sbt
export SCALA_HOME=/Users/.../Spark2/scala-2.12.15
export PATH=$JAVA_HOME/bin:$SBT_HOME/bin:$SBT_HOME/lib:$SCALA_HOME/bin:$SCALA_HOME/lib:$PATH
export PATH=$JAVA_HOME/bin:$SPARK_HOME:$SPARK_HOME/bin:$SPARK_HOME/sbin:$PATH
export PYSPARK_PYTHON=/Users/.../miniforge3/envs/pyspark_conda_env/bin/python
PYTHONPATH=$SPARK_HOME$\python:$SPARK_HOME$\python\lib\py4j-0.10.9.3-src.zip:$PYTHONPATH
PATH="/Library/Frameworks/Python.framework/Versions/3.10/bin:${PATH}"
export PATH

# export PYSPARK_DRIVER_PYTHON="jupyter"        # Not required
# export PYSPARK_DRIVER_PYTHON_OPTS="notebook"  # Not required

如果您对如何改进这些设置有任何建议,请在下方发表评论。

其他说明:

  1. 您的 Python 脚本仍应包含您需要的其他导入(在我的例子中,不需要在脚本本身中包含 numpy 导入——只有 numpy 在 tarball 中)。所以你的脚本可能包括这些,例如:
from pyspark.sql import SparkSession
from pyspark.sql.types import *
from pyspark.sql.functions import *
(etc.)
  1. 我的脚本不需要这个代码片段,它在 Spark 文档的示例中显示:
if __name__ == "__main__":
    main(SparkSession.builder.getOrCreate())
  1. 简单地创建一个包含模块列表的 'requirements.txt' 文件的方法(压缩并在 spark-submit 命令中使用而不使用 conda)正如这个线程 中所讨论的那样在我的情况下不起作用:
pip3 install -t dependencies -r requirements.txt
zip -r dep.zip dependencies # Possibly incorrect...
zip -r dep.zip .            # Correct if run from within folder containing requirements.txt 
spark-submit --py-files dep.zip test.py

有关此方法的更多详细信息,请参阅 Daniel Corin 的 'PySpark dependencies',该方法在某些情况下显然有效: https://blog.danielcorin.com/posts/2015-11-09-pyspark/

我有点猜测,但我认为这种方法可能不允许构建为 'wheels' 的包,因此不会构建您需要的所有依赖项。 Spark 文档在“使用 PySpark 本机功能”下讨论了这个概念。 (费用免费试用...您不需要 conda-forge/miniforge 这样做。)