MacBook Pro运行PySpark时NumPy C-extensions导入失败
结论
这是进程架构不一致引起的动态库加载失败,通常与 SparkSession 配置、缺少 import 或 PYTHONPATH 无关。
错误信息:
have 'arm64', need 'x86_64'
表示当前 NumPy 扩展 _multiarray_umath...so 的架构是 arm64,而执行 spark-submit 时启动的 Python 进程运行在 x86_64 模式。x86_64 Python 进程无法加载 arm64 动态库。
这套环境中首先要排查 Intel 版 JDK。spark-submit 会先启动 JVM,再由 Spark 的 JVM 进程启动 Python。如果 JVM 是 x86_64,macOS 可能会让 universal Python 选择 x86_64 slice。直接从原生 Terminal 启动 Python 时,Python 使用 arm64 slice,因此可以正常导入 NumPy。
建议将整套软件统一为原生 arm64 架构:
arm64 JDK
→ arm64 Python
→ arm64 NumPy、pandas
不要单独替换 _multiarray_umath.cpython-310-darwin.so。NumPy 包含多个编译扩展和底层依赖,只复制一个 .so 文件很容易引发新的 ABI 或架构冲突。
确认实际运行架构
除了检查 Python 文件是否为 universal binary,还需要确认进程启动后实际选择了哪种架构。
检查 Terminal 中的 Python
python3 -c 'import platform, sys; print(sys.executable); print(platform.machine())'
在 Apple silicon 原生环境中,输出应包含:
arm64
检查 Java
echo "$JAVA_HOME"
file "$JAVA_HOME/bin/java"
java -XshowSettings:properties -version 2>&1 | grep os.arch
如果输出类似:
Mach-O 64-bit executable x86_64
或者:
os.arch = x86_64
说明 Spark 当前使用的是 Intel 版 Java。它可以通过 Rosetta 2 正常运行 Spark SQL,但后续启动的 universal Python 可能会选择 x86_64。
检查 spark-submit 中的 Python
新建一个最小测试脚本:
import os
import platform
import sys
print("executable:", sys.executable)
print("machine:", platform.machine())
print("PYSPARK_PYTHON:", os.environ.get("PYSPARK_PYTHON"))
import numpy as np
print("NumPy:", np.__version__)
print("NumPy path:", np.__file__)
运行:
spark-submit check_arch.py
如果普通 python3 check_arch.py 显示 arm64,而 spark-submit check_arch.py 显示 x86_64,或在导入 NumPy 时失败,就可以确认两种启动方式使用了不同架构。
推荐方案:换成 arm64 JDK
安装适用于 Apple silicon 的 JDK。Spark 3.2.1 需要使用该版本支持的 Java,并确认下载包明确标注为 macOS ARM64、AArch64 或 Apple silicon。版本号本身不能说明架构。
安装后列出系统识别到的 JDK:
/usr/libexec/java_home -V
将 JAVA_HOME 指向 arm64 JDK 的实际目录。例如:
export JAVA_HOME="/Library/Java/JavaVirtualMachines/<arm64-jdk>/Contents/Home"
export PATH="$JAVA_HOME/bin:$PATH"
将 <arm64-jdk> 替换为本机的实际安装目录。修改后重新打开 Terminal,或重新加载配置:
source ~/.bash_profile
再次验证:
file "$JAVA_HOME/bin/java"
java -XshowSettings:properties -version 2>&1 | grep os.arch
输出应为 arm64 或 aarch64,具体名称取决于 JDK。
显式指定 PySpark 使用的 Python
设置 PYSPARK_PYTHON=python3 时,系统会根据 PATH 查找解释器。如果机器上安装了多个 Python,建议直接填写绝对路径:
export PYSPARK_PYTHON="/Library/Frameworks/Python.framework/Versions/3.10/bin/python3"
在本地模式下,还可以让 driver 使用同一个解释器:
export PYSPARK_DRIVER_PYTHON="$PYSPARK_PYTHON"
相关配置可以整理为:
export JAVA_HOME="/Library/Java/JavaVirtualMachines/<arm64-jdk>/Contents/Home"
export SPARK_HOME="/Users/.../Spark2/spark-3.2.1-bin-hadoop2.7"
export PYSPARK_PYTHON="/Library/Frameworks/Python.framework/Versions/3.10/bin/python3"
export PYSPARK_DRIVER_PYTHON="$PYSPARK_PYTHON"
export PATH="$JAVA_HOME/bin:$SPARK_HOME/bin:$SPARK_HOME/sbin:/Library/Frameworks/Python.framework/Versions/3.10/bin:$PATH"
export PYTHONPATH="$SPARK_HOME/python:${PYTHONPATH:-}"
这里的配置用于排查本机 local 模式。使用 spark-submit 的集群部署模式时,不要随意设置 PYSPARK_DRIVER_PYTHON。
修改后检查各个程序的实际入口:
which java
which python3
which spark-submit
"$JAVA_HOME/bin/java" -XshowSettings:properties -version 2>&1 | grep os.arch
"$PYSPARK_PYTHON" -c 'import platform, sys; print(sys.executable); print(platform.machine())'
在同一个 Python 环境中重新安装依赖
先确认 pip 属于准备交给 PySpark 使用的解释器:
"$PYSPARK_PYTHON" -m pip --version
"$PYSPARK_PYTHON" -m pip show numpy pandas
不要直接使用 pip 或 pip3,它们可能对应另一个 Python 安装。
如果现有环境中混装过不同架构的软件包,建议创建一个干净的虚拟环境:
"$PYSPARK_PYTHON" -m venv "$HOME/venvs/pyspark-arm64"
source "$HOME/venvs/pyspark-arm64/bin/activate"
python -m pip install --upgrade pip
python -m pip install numpy pandas
然后让 PySpark 使用该虚拟环境:
export PYSPARK_PYTHON="$HOME/venvs/pyspark-arm64/bin/python"
export PYSPARK_DRIVER_PYTHON="$PYSPARK_PYTHON"
验证安装结果:
"$PYSPARK_PYTHON" -c 'import platform, numpy, pandas; print(platform.machine()); print(numpy.__version__); print(pandas.__version__)'
也可以检查 NumPy 扩展的架构:
file "$("$PYSPARK_PYTHON" -c 'import numpy.core._multiarray_umath as m; print(m.__file__)')"
最小验证程序
统一环境后,先运行一个不涉及 Hive 和数据表的脚本:
import platform
import sys
import numpy as np
import pandas as pd
from pyspark.ml.feature import StringIndexer
from pyspark.sql import SparkSession
print("Python:", sys.executable)
print("Architecture:", platform.machine())
print("NumPy:", np.__version__)
print("pandas:", pd.__version__)
spark = (
SparkSession.builder
.master("local[*]")
.appName("import-test")
.getOrCreate()
)
df = spark.createDataFrame(
[("a",), ("b",), ("a",)],
["category"],
)
model = StringIndexer(
inputCol="category",
outputCol="category_index",
).fit(df)
model.transform(df).show()
spark.stop()
运行:
spark-submit import_test.py
这个程序可以单独验证架构和模块导入,避免 Hive、数据库目录或表创建等操作干扰排查。
为什么 pyspark.sql 正常,pyspark.ml 却失败
导入基本的 pyspark.sql 主要涉及 Python 和 JVM 代码,不一定会立即加载 NumPy 的原生扩展。
pyspark.ml 的部分模块会在导入时加载 NumPy,pandas 也依赖编译后的扩展。执行这些 import 后,macOS 动态加载器才会检查 Python 进程与 .so 文件的架构是否一致。
因此,问题不在 pyspark.ml 的 import 写法。它只是更早触发了原生扩展加载,从而暴露出混合架构环境。
如果必须保留 Intel 版 Java
也可以将整套 Python 环境统一为 x86_64:
x86_64 JDK
→ x86_64 Python
→ x86_64 NumPy、pandas
这通常需要在 Rosetta 环境中安装并运行 Intel 版 Python 及其依赖。x86_64 Python 无法加载 arm64 NumPy。
必须依赖 Intel 原生库的旧环境可以采用这套方案。不过在 M1 Max 上,使用原生 arm64 JDK、Python 和依赖通常更容易维护,也能避免混用 Rosetta 与原生组件。
注意事项
- universal Python 虽然同时包含两种架构,但一次运行只能选择其中一种,也只能加载对应架构的动态库。
need 'x86_64'表示当前 Python 进程需要x86_64库,并非 M1 Max 硬件要求使用x86_64。- Python 3.10.1 与 3.10.2 的补丁版本差异无法解释这条明确的 Mach-O 架构错误。
PATH只决定选用哪个可执行文件,不能让正在运行的x86_64进程加载arm64扩展。PYTHONPATH用于查找 Python 模块,无法解决二进制架构冲突。- 不要手动下载并覆盖单个 NumPy
.so文件。应通过目标解释器的python -m pip或环境管理器重新安装完整软件包。 - 修改环境后,启动新的 Terminal,再检查
JAVA_HOME、Java 架构以及spark-submit中的platform.machine(),以免继续使用旧的环境变量。
备注:内容仅供参考。
