源码编译不需要眼泪! MacOS 成功编译 TensorFlow 源码!
最近打算开始研究下 TensorFlow, 想着以后可能要写一些 Ops, 因此有必要从源码开始编译 TF. 预想到可能会遇到很多问题, 而事实上 TF 果然诚不我欺, 给我留下了一大堆让人摸不着头脑的编译 Errors, 好在凭着自己的智慧、勇气和坚持 😋, 花了数个小时终于编译成功 ✌️✌️✌️. 此文用来纪念那些为编译 TF 而逝去的的美好时光以及凋零的脑细胞 🤣.
广而告之
可以在微信中搜索 “珍妮的算法之路” 或者 “world4458” 关注我的微信公众号;另外可以看看知乎专栏 PoorMemory-机器学习, 以后文章也会发在知乎专栏中;
重要的事提前说
编译完成, 总结避免重复采坑的经验如下, 可以进行参考:
- 我编译的 TensorFlow 版本为 v1.10.0;
- Bazel 版本很重要, 目前用的是 1.2.1;
- 不建议用 Anaconda 进行编译, 可以先创建一个 virtualenv, 如:
conda create -n tf1.10 python=3.5
, 默认安装了3.5.6
的 Python. (不要采用3.7
的 Python, 会有各种问题). - 可以先用
pip install tensorflow==v1.10.0
对 TensorFlow 进行安装, 这样 pip 会自动下载所需要的 Python 库, 而且还是正确的版本, 避免自己手动安装. (如果需要手动安装, 可以在文件tensorflow/tools/pip_package/setup.py
中查看REQUIRED_PACKAGES
. )
完整过程
TF 源码下载
到 https://github.com/tensorflow/tensorflow 下载最新的 TF 源码:
git clone --recursive https://github.com/tensorflow/tensorflow
我目前下载的源码, 其 master 分支的 commit id 为: 3fa68e2d8835f4dd231b9c767517a9dcec26c6a9
. 由于我想编译的是 v1.10.0
版本的 TF, 故使用:
git checkout v1.10.0
命令切换到 v1.10.0
分支.
Bazel 安装
TensorFlow 项目采用 Bazel 进行编译, 需要先安装 Bazel. 注意 Bazel 的版本要和 TensorFlow 版本匹配, 我证明了 Bazel 1.2.1
版本是可以没有问题的. MacOS 一般采用 HomeBrew 来安装软件, 参考 https://docs.bazel.build/versions/master/install-os-x.html 可以获得用 HomeBrew 安装 Bazel 的方法. 但该方法默认安装最新版的 Bazel. 为了安装特定版本的 Bazel, 这里提一个我发现的 Trick.
- 卸载
bazel
的方法是:brew uninstall bazel
. - 首先到 https://github.com/bazelbuild/homebrew-tap/blob/master/Formula/bazel.rb, 下载该
bazel.rb
文件到本地:
# bazel.rb
class Bazel < Formula
desc "Fast, scalable, multi-language and extensible build system"
homepage "https://bazel.build/"
url "https://releases.bazel.build/2.0.0/release/bazel-2.0.0-installer-darwin-x86_64.sh", :using => :nounzip
version "2.0.0"
sha256 "c675fa27d99a3114d681db10eb03ded547c40f702b2048c99b8f4ea8e89b9356"
bottle :unneeded
def install
chmod 0555, "bazel-#{version}-installer-darwin-x86_64.sh"
system "./bazel-#{version}-installer-darwin-x86_64.sh", "--prefix=#{buildpath}"
bin.install "lib/bazel/bin/bazel" => "bazel"
bin.install "lib/bazel/bin/bazel-real" => "bazel-real"
bash_completion.install "lib/bazel/bin/bazel-complete.bash"
zsh_completion.install "lib/bazel/bin/_bazel"
end
test do
touch testpath/"WORKSPACE"
(testpath/"Main.java").write <<~EOS
public class Main {
public static void main(String... args) {
System.out.println("Hello world!");
}
}
EOS
(testpath/"BUILD").write <<~EOS
java_binary(
name = "main",
srcs = ["Main.java"],
main_class = "Main",
)
EOS
system bin/"bazel", "build", "//:main"
system "bazel-bin/main"
end
end
观察该脚本可以发现, 它其实是下载 bazel-<version>-installer-darwin-x86_64.sh
这个脚本来实现对 Bazel 的安装, 但需要我们额外提供 sha256
. 我们可以将 url
这个字段中的 2.0.0
全部改为 1.2.1
, 同时将 version
中的 2.0.0
也改成 1.2.1
, 注释 bash_completion.install
以及 zsh_completion.install
两行 (用 #
可以进行注释),
然后运行:
brew install bazel.rb
命令报错, 提示信息如下:
替换 bazel.rb
中的 sha256
值, 得到文本如下:
class Bazel < Formula
desc "Fast, scalable, multi-language and extensible build system"
homepage "https://bazel.build/"
url "https://releases.bazel.build/1.2.1/release/bazel-1.2.1-installer-darwin-x86_64.sh", :using => :nounzip
version "1.2.1"
#sha256 "c675fa27d99a3114d681db10eb03ded547c40f702b2048c99b8f4ea8e89b9356"
sha256 "59e469bf1d8d1615b67856ea17e761be05e9c92b462e55c0354cd78145b480d5"
bottle :unneeded
def install
chmod 0555, "bazel-#{version}-installer-darwin-x86_64.sh"
system "./bazel-#{version}-installer-darwin-x86_64.sh", "--prefix=#{buildpath}"
bin.install "lib/bazel/bin/bazel" => "bazel"
bin.install "lib/bazel/bin/bazel-real" => "bazel-real"
#bash_completion.install "lib/bazel/bin/bazel-complete.bash"
#zsh_completion.install "lib/bazel/bin/_bazel"
end
test do
touch testpath/"WORKSPACE"
(testpath/"Main.java").write <<~EOS
public class Main {
public static void main(String... args) {
System.out.println("Hello world!");
}
}
EOS
(testpath/"BUILD").write <<~EOS
java_binary(
name = "main",
srcs = ["Main.java"],
main_class = "Main",
)
EOS
system bin/"bazel", "build", "//:main"
system "bazel-bin/main"
end
end
再次运行: brew install bazel.rb
, 成功安装 Bazel.
另外, Bazel 的 Github 库也 Release 了 1.2.1
版本, 如: https://github.com/bazelbuild/bazel/releases/tag/1.2.1, 然而按照我上面的方式做, 可以用 HomeBrew 来管理 Bazel, 方便卸载或安装其他的版本.
Python 虚拟环境
如果安装了 Anconda 发行版的话, 建议创建一个 Python 虚拟环境来编译 TF. 暂时无法断言 Anaconda 对 TF 的编译有不良影响, 但采坑经验告诉我最好还是额外创建一个虚拟环境. 另外, Python 的版本也至关重要, 选择 Python 3.5
.
# 进入 tf 源码目录
cd tensorflow/
# 创建虚拟环境, Python 版本最后为 3.5.6
conda create -n tf1.10 python=3.5
# 激活虚拟环境
conda activate tf1.10
编译 TF 还需要一些 Python 包, 可以通过:
pip install tensorflow==v1.10.0
进行安装, 避免了手动安装的麻烦~.
TensorFlow 的编译
先做一些准备工作:
## 虽然上面已经说过了, 再说一遍, 先进入 TensorFlow 源码目录
cd tensorflow/
git checkout v1.10.0
然后参照 Building and Installing TensorFlow from Source on Your Mac , 下载 build_tf.sh 脚本到当前目录中, 增加执行权限并运行该文件, 即可正常编译 TensorFlow.
chmod +x build_tf.sh
为了避免该脚本的链接失效, 将内容拷贝至此处:
# Check whether script is executing in a VirtualEnv or Conda environment
if [ -z "$VIRTUAL_ENV" ] && [ -z "$CONDA_PREFIX" ] ; then
echo "VirtualEnv or Conda env is not activated"
exit -1
fi
# Set the virtual environment path
if ! [ -z "$VIRTUAL_ENV" ] ; then
VENV_PATH=$VIRTUAL_ENV
elif ! [ -z "$CONDA_PREFIX" ] ; then
VENV_PATH=$CONDA_PREFIX
fi
# Set the bin and lib directories
VENV_BIN=$VENV_PATH/bin
VENV_LIB=$VENV_PATH/lib
# bazel tf needs these env vars
export PYTHON_BIN_PATH=$VENV_BIN/python
export PYTHON_LIB_PATH=`ls -d $VENV_LIB/*/ | grep python`
# Set the native architecture optimization flag, which is a default
COPT="--copt=-march=native"
# Determine the available features of your CPU
raw_cpu_flags=`sysctl -a | grep machdep.cpu.features | cut -d ":" -f 2 | tr '[:upper:]' '[:lower:]'`
# Append each of your CPU's features to the list of optimization flags
for cpu_feature in $raw_cpu_flags
do
case "$cpu_feature" in
"sse4.1" | "sse4.2" | "ssse3" | "fma" | "cx16" | "popcnt" | "maes")
COPT+=" --copt=-m$cpu_feature"
;;
"avx1.0")
COPT+=" --copt=-mavx"
;;
*)
# noop
;;
esac
done
# First ensure a clear working directory in case you've run bazel previously
bazel clean
# Run TensorFlow configuration (accept defaults unless you have a need)
./configure
# Build the TensorFlow pip package
bazel build -c opt $COPT -k //tensorflow/tools/pip_package:build_pip_package
bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg
我的小 MBP 花了好一段时间才将 TensorFlow 编译好, 编译成 .whl
文件保存到 /tmp/tensorflow_pkg
目录中.
至此, MacOS 上编译 TensorFlow 这一旅程终于结束了. 今早运行脚本之后和朋友逛了国家博物馆, 回来后发现编译成功了! 我无法想象拖着疲惫的身躯回到家后看到电脑屏幕显示的是 Build Failed
字样的结果.
Issues
编译过程中遇到了大量的问题, 编译成功之后就选择性地忘了曾经的伤痛, 而下面的网页则帮我记录了这些伤痕, 它们是否能称为其他人的良药呢… 🤣
- https://github.com/bazelbuild/homebrew-tap/issues/39 有关 HomeBrew 以及 Bazel 安装的资料
- https://github.com/tensorflow/tensorflow/issues/23096 TF 编译过程遇到的错误, 可以用来寻求安慰, 了解自己并不孤独
- 从源码编译安装TensorFlow 作者语言风趣, 代码高亮的配色 monokai 非常漂亮; 我现在更喜欢 Solarized.
- TensorFlow 源码大坑(0) 前言 和本文关系不大, 记录于此做个书签;
- Building and Installing TensorFlow from Source on Your Mac 优先看这个博客, 发现太晚了!
- https://github.com/tensorflow/tensorflow/issues/21045 关于 C++ Error 的, Bazel 还提供
--cxxopt=-std=c++11
选项. 最终我没有用该选项, 但确实能消除很多 C++ Errors.