在 Amazon Ubuntu 上从源码构建 TensorFlow 失败:先排查内存与工具链

我最早在2017年写下这篇笔记。当时,我在一台配置较小的 Amazon Ubuntu 实例上构建 TensorFlow 失败,于是安装了 SWIG,怀疑系统内存耗尽,又降低了 Bazel 的并行度并添加了 swap。这个判断中真正有用的部分今天仍然成立:如果编译器进程没有留下有价值的报错就突然消失,它很可能因为机器内存不足而被系统终止。

不过,那次排查用到的具体命令已经过时。TensorFlow 的构建目标、Bazel 参数、支持的 Python 版本、编译器要求和 GPU 配置都发生了变化。这次修订保留了当年的经历,同时把今天推荐的安装方式与要求更高的源码构建方式分开说明。

首选官方 wheel

除非需要修改 TensorFlow 本身、启用非标准指令集,或者生成定制包,否则应优先使用官方 wheel。它速度更快、容易复现,也能避开大多数编译器和 Bazel 兼容性问题。

在受支持的 Linux 系统上:

sudo apt update
sudo apt install -y python3-pip python3-venv

python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install tensorflow

cd /tmp
python -c 'import tensorflow as tf; print(tf.__version__)'

如果使用 NVIDIA GPU,请先查看 TensorFlow 最新的 pip 指南和兼容性表。当前 Linux 软件包的安装方式是:

python -m pip install 'tensorflow[and-cuda]'
nvidia-smi
python -c 'import tensorflow as tf; print(tf.config.list_physical_devices("GPU"))'

如果 pip 报告找不到匹配的发行包,不要马上改为源码构建。先核对 CPU 架构和 Python 版本是否符合 TensorFlow 当前支持的配置。

先确认是不是真的内存不足

修改构建方式之前,先记录环境信息:

uname -a
uname -m
python3 --version
free -h
swapon --show
df -h /

退出码 137、单独一行 Killed,或者编译器进程突然消失,都可能表示系统触发了 OOM(内存不足)终止。在使用 systemd 的 Ubuntu 上,可以查看本次启动的内核日志:

sudo journalctl -k -b | grep -Ei 'out of memory|oom|killed process' || true

这一步需要证据。真正的编译错误要从工具链或源码入手;OOM 终止则需要减少并行任务、增加内存或配置 swap。

固定一个 TensorFlow 版本及其工具链

不要拿任意 checkout 配合系统中碰巧安装的 Bazel 和 Python 直接构建。先选定一个 TensorFlow 标签,再使用官方源码构建兼容性表中为该版本列出的工具版本。TensorFlow 还会把期望的 Bazel 版本记录在 .bazelversion 中,Bazelisk 可以据此自动选择版本。

git clone https://github.com/tensorflow/tensorflow.git
cd tensorflow

TF_TAG=v2.21.0  # 仅为示例;请选用当前环境支持的版本。
git checkout "$TF_TAG"
cat .bazelversion

创建干净的 Python 环境,并在选定的 checkout 中运行配置步骤:

sudo apt update
sudo apt install -y git python3-dev python3-pip python3-venv

python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
./configure

安装该标签明确要求的编译器、Bazel 或 Bazelisk;如需 GPU,再安装匹配版本的 CUDA 和 cuDNN。从 TensorFlow 2.13 开始,官方说明默认使用 Clang,但经测试的具体版本仍取决于所选 TensorFlow 版本。

我在2017年的笔记里曾在重试前安装 SWIG。那只是当时的推测性排查步骤,并不能证明根因;当前这条官方构建路径也没有把 SWIG 列为前置条件。

在小型 CPU 实例上保守构建

目前,CPU wheel 的官方目标是 //tensorflow/tools/pip_package:wheel。在内存有限的机器上,可以先只启用一个任务,并为 Bazel 设置保守的 RAM 调度预算:

bazel build 
  --config=opt 
  --jobs=1 
  --local_ram_resources=2048 
  --verbose_failures 
  --repo_env=USE_PYWRAP_RULES=1 
  --repo_env=WHEEL_NAME=tensorflow_cpu 
  //tensorflow/tools/pip_package:wheel

--jobs=1 会减少并发工作。--local_ram_resources=2048 告诉 Bazel 调度器,本地操作大约有 2 GB RAM 可用;它不是操作系统强制执行的内存上限。应根据实例配置调整,不能盲目照抄这个数值。

2017年的命令使用了 --local_resources 和旧目标 //tensorflow/tools/pip_package:build_pip_package。它们只能作为历史记录保留,都不是当前官方文档中的命令。

如果要构建 GPU wheel,TensorFlow 当前的源码构建指南还需要额外的 CUDA 配置,包括 --config=cuda--config=cuda_wheelWHEEL_NAME=tensorflow。不要混用不同时期的 CPU 与 GPU 参数,应完整遵循所选版本对应的官方指南。

只把 swap 当作备用方案

有时降低并行度就足够了。如果机器仍然耗尽内存,升级实例通常比让编译过程大量使用 swap 更快、更可靠。对于临时或低成本机器,swap 文件仍可能有用,但要先检查主机状态:

free -h
swapon --show
df -h /
test ! -e /swapfile || { echo '/swapfile already exists; stop and inspect it'; exit 1; }

Ubuntu 的 swapon 文档说明,在会暴露空洞文件的文件系统上,使用 fallocate 创建的文件可能被拒绝;Btrfs 也需要特殊处理。对于常规文件系统,写入全零的文件更具兼容性:

sudo dd if=/dev/zero of=/swapfile bs=1MiB count=4096 status=progress
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
swapon --show

不要对已经存在或用途不明的路径执行 mkswap。如果希望重启后继续使用该 swap 文件,请只在 /etc/fstab 中添加一条记录:

/swapfile none swap sw 0 0

重启之前先验证配置文件:

sudo findmnt --verify --verbose

如果根文件系统是 Btrfs、采用特殊加密方式,或者受镜像策略管理,请改用对应平台记录的 swap 配置流程。

安装并验证 wheel

当前构建会把 wheel 写入 wheel_house 目录。将生成的软件包装进当前虚拟环境,然后到源码 checkout 之外测试,避免本地源码文件遮蔽已经安装的软件包:

python -m pip install bazel-bin/tensorflow/tools/pip_package/wheel_house/*.whl

(
  cd /tmp
  python -c 'import tensorflow as tf; print(tf.__version__)'
)

常见症状排查表

症状首先检查下一步操作
Killed、退出码137,或内核日志出现 OOM 记录free -hswapon --showjournalctl -k减少 --jobs,设置合理的 RAM 预算,升级实例,或谨慎配置 swap
pip 报告 No matching distribution foundPython 版本、CPU 架构、操作系统和 TensorFlow 支持表改用受支持的 Python/平台组合;源码构建不是首选修复方法
Bazel 版本错误所选标签的 .bazelversion 和官方构建兼容性表使用 Bazelisk,或安装经过测试的准确 Bazel 版本
编译器或头文件错误完整的 --verbose_failures 输出,以及该版本测试过的编译器让编译器与依赖项和所选 TensorFlow 标签保持一致
找不到 CUDA,或 GPU 列表为空驱动是否可见、nvidia-smi 和当前 CUDA/cuDNN 要求从头到尾遵循同一个 TensorFlow 版本的官方 GPU 路径
只有在 checkout 内才能 import,或在其中行为异常当前工作目录和 python -m pip show tensorflow/tmp 等源码树以外的目录测试

从最初的失败中学到什么

2017年那次排查中真正有用的结论并不是“安装 SWIG”,而是:小型云实例上的源码构建失败,必须先分类,再去改软件包和参数。先证明编译器是否被内核终止,再让构建工具链与一个明确的 TensorFlow 版本保持一致;如果定制构建没有清晰收益,就使用官方 wheel。

官方资料

如果想了解历史背景,TensorFlow 早期 Issue 中关于编译时内存压力源码构建失败的讨论,也说明了这个问题为什么经常出现在小型机器上。

Leave a Reply