如何在arm板(cubieboard)上编译安装tensorflow

历经三天四夜,睡眠不超过10小时,终于把tensorflow在arm架构cpu的cubieboard上成功run起来了。不得不吐槽cubieboard的资料实在太少,搜出来的相关资料全是树莓派的,但是还是给予了我不少启发作用。

作为全(zhen)宇(bu)宙(yao)第(B)一(lian)个成功把tensorflow移植到cubieboard的人(google翻100页都搜索不到),特此记录全过程,方便后来者乘凉。

开始正题

第一步 准备工作

如果没有JDK8,先安装JDK8,编译tensorflow需要用到bazel,而bazel依赖JDK8(必须是JDK8),必须配置环境变量。编辑完/etc/profile文件后,记得source /etc/profile,让环境变量生效。

1
2
3
4
export JAVA_HOME="/home/lib/jdk1.8.0_151"
export PATH="$PATH:$JAVA_HOME/bin"
export JRE_HOME="$JAVA_HOME/jre"
export CLASSPATH=".:$JAVA_HOME/lib:$JRE_HOME/lib"

接着安装bazel,我使用的是版本是0.6.1,是否选用更高版本自己看着办,跳坑我不管。
这里有个坑,吐槽bazel打包人员,居然没有在外层目录打包,unzip出来一堆东西,害我又去删掉。所以先mkdir

1
2
3
4
mkdir bazel-0.6.1
mv bazel-0.6.1-dist.zip ./bazel-0.6.1
cd bazel-0.6.1
unzip bazel-0.6.1-dist.zip

解压完成后,别急着执行compile.sh,先vim scripts/bootstrap/compile.sh文件,将第122行修改成

1
run "${JAVAC}" -J-Xms256m -J-Xmx384m -classpath "${classpath}" -sourcepath "${sourcepath}"

就是在这行中间加入-J-Xms256m -J-Xmx384m两个参数,否则执行compile.sh时Java会内存溢出。
现在放心大胆的./compile.sh吧。
哦,对了,compile.sh中用到了zip命令,没有的话apt-get install zip
完成之后将生成的文件copy到/usr/local/bin

1
cp output/bazel /usr/local/bin/bazel

大功告成,检测一下:

1
2
3
4
5
6
7
8
bazel version
.................................................................
Build label: 0.6.1- (@non-git)
Build target: bazel-out/local-opt/bin/src/main/java/com/google/devtools/build/lib/bazel/BazelServer_deploy.jar
Build time: Sat Jan 10 00:41:11 1970 (780071)
Build timestamp: 780071
Build timestamp as int: 780071
root@cubieboard4:/home/lib/bazel-0.6.1#

这里有个坑,千万要注意,修改系统时间到1980年之后,bazel里用到了timestamp, timestamp不支持1980年之前的时间,而你当前系统时间很可能是默认的1970-01-01,不注意的话后面编译tensorflow时会报错。

准备工作完成。

第二步 重头戏

我使用的是我当时最新的版本tensorflow1.4.0, 下载好后正常解压。

1
2
unzip v1.4.0.zip
cd tensorflow-1.4.0/

这次可以放心大胆直接解压,没有问题。
进入目录后,还是别忙着./configure, 因为会报错。

1
2
3
4
5
6
7
8
ERROR: error loading package '': Encountered error while reading extension file
'closure/defs.bzl': no such package '@io_bazel_rules_closure//closure': Error
downloading [http://mirror.bazel.build/github.com/bazelbuild/rules_closure/
archive/4af89ef1db659eb41f110df189b67d4cf14073e1.tar.gz, https://github.com/
bazelbuild/rules_closure/archive/4af89ef1db659eb41f110df189b67d4cf14073e1.tar
.gz] to /root/.cache/bazel/_bazel_root/337c5ffad05cc08b704a88b920a554a7/
external/io_bazel_rules_closure/4af89ef1db659eb41f110df189b67d4cf14073e1.tar
.gz

开始我一直以为是我大天朝的大局域网的原因,于是尝试让bazel使用代理,结果在bazel的源码src/main/cpp/blaze.cc1256行看到

1
2
3
4
5
6
static void PrepareEnvironmentForJvm() {
if (!blaze::GetEnv("http_proxy").empty()) {
PrintWarning("ignoring http_proxy in environment.");
blaze::UnsetEnv("http_proxy");
}
}

ignoring http_proxy in environment. What the hell,Man? 忽略代理? 黑人问号脸?

行不通又去找那几个Error downloading的文件下载地址写的位置,想着自己翻。。。那啥科学上网下载下来放到本地server服务里,然后把地址改成本机,结果发现这几个地址压根儿不需要科学上网就能下载,这时才意识到不对,又折腾了半天才弄明白人家代码非(xiang)常(ma)严(niang)谨, 加入了https的证书验证,里面有个http的地址也报错是因为访问后也自动转发到https的地址去了。
好吧,开始折腾证书,先通过Google Chrome依次从这几个网站把证书保存下来,再导入JDK。
http://mirror.bazel.build/的证书我取名为bazel.build
https://github.com/的证书我取名为github.com

1
2
3
4
5
6
cd /[Your JDK Home]/jre/lib/security/

#不要复制一次执行,keytool命令执行会要求输入密码,密码默认为changeit。
keytool -import -alias bazel.build -keystore cacerts -file ./bazel.build.cer -trustcacerts
#不要复制一次执行,keytool命令执行会要求输入密码,密码默认为changeit。
keytool -import -alias github.com -keystore cacerts -file ./github.com.cer -trustcacerts

证书弄好了,还没完。 接着配置虚拟内存,编译需要增加1G虚拟内存,否认编译过程中会因为内存不足而终止。

1
2
3
4
5
6
cd /var
mkdir image
touch swap
dd if=/dev/zero of=/var/image/swap bs=1024 count=1024000
mkswap /var/image/swap
swapon /var/image/swap

完成后执行free -m检查

1
2
3
4
             total       used       free     shared    buffers     cached
Mem: 1626 1274 351 0 64 773
-/+ buffers/cache: 436 1190
Swap: 999 93 906

Swap后显示999 则表示成功。
注意,重启后需要重新执行开启虚拟内存,swapon /var/image/swap。因为我只需要用于本次编译,所以不打算加入配置文件自动开启。

你以为这就可以开始编译tensorflow了? 你太天真了!
接着来,vim tensorflow/core/platform/platform.h48行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#elif defined(_WIN32)
#define PLATFORM_WINDOWS

#elif defined(__arm__)
#define PLATFORM_POSIX

// Require an outside macro to tell us if we're building for Raspberry Pi.
#if !defined(RASPBERRY_PI)
#define IS_MOBILE_PLATFORM //就是这行
#endif // !defined(RASPBERRY_PI)

#else
// If no platform specified, use:
#define PLATFORM_POSIX

树莓派的面子可真够大的,还针对树莓派做了特殊判断,不是树莓派的设备一律按移动设备处理。
我们当然不允许这么干了,所以把这句改成#define PLATFORM_POSIX,保存退出。

到此,才算终于可以执行./configure了,回到tensorflow目录执行去吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
./configure

WARNING: Running Bazel server needs to be killed, because the startup options are different.
You have bazel 0.6.1- (@non-git) installed.
Please specify the location of python. [Default is /usr/bin/python]: 直接Enter


Found possible Python library paths:
/usr/local/lib/python2.7/dist-packages
/usr/lib/python2.7/dist-packages
Please input the desired Python library path to use. Default is [/usr/local/lib/python2.7/dist-packages] 直接Enter

Do you wish to build TensorFlow with jemalloc as malloc support? [Y/n]: y
Do you wish to build TensorFlow with Google Cloud Platform support? [y/N] N
Do you wish to build TensorFlow with Hadoop File System support? [y/N] N
Do you wish to build TensorFlow with Amazon S3 File System support? [y/N] N
Do you wish to build TensorFlow with XLA JIT support? [y/N] N
Do you wish to build TensorFlow with GDR support? [y/N] N
Do you wish to build TensorFlow with VERBS support? [y/N] N
Do you wish to build TensorFlow with OpenCL support? [y/N] N
Do you wish to build TensorFlow with CUDA support? [y/N] N
Do you wish to build TensorFlow with MPI support? [y/N] N

除了第一个全部都不要,不怕死的当然也可以无视。
开始编译!你可以准备去睡一觉了,或者去吃几把鸡,因为编译将会花去12758.183s,足足三个半小时!!!!

1
bazel build -c opt --copt="-funsafe-math-optimizations" --copt="-ftree-vectorize" --copt="-fomit-frame-pointer" --local_resources 1024,1.0,1.0 --verbose_failures tensorflow/tools/pip_package:build_pip_package

--local_resources 1024,1.0,1.0 这个参数按理说应该可以提高速度,第一个是使用内存限制,第二个是使用CPU核心限制,第三个我也不知道。我尝试使用过4096,8.0,1.0,我配置了4个G的虚拟内存然后使用8个核心跑,编译到1,9xx/3,683] files 左右时,编译的8个进程全部锁死不动。也试过2048,4.0,1.02048,2.0,1.01024,2.0,1.0,通通死翘翘。无奈遇到所有前面讲到的问题时,又只能老老实实等待3个半小时,三天四夜就是这么来的!!!
对了,如果过程中出现问题,需要重新执行上述语句,需要先执行bazel clean!
好了,设置一个闹钟, 三个半小时以后回来你会看到

1
2
Target //tensorflow/tools/pip_package:build_pip_package up-to-date:
bazel-bin/tensorflow/tools/pip_package/build_pip_package

恭喜你编译完成了!接着执行

1
bazel-bin/tensorflow/tools/pip_package/build_pip_package /tmp/tensorflow_pkg

这时将会把用于pip安装的whl文件生成到/tmp/tensorflow_pkg目录

1
pip install /tmp/tensorflow_pkg/tensorflow-1.4.0-cp27-cp27mu-linux_armv7l.whl

然后你就又会看到让人崩溃的红字。。。is not a supported wheel on this platform holy shit!!!

1
2
tensorflow-1.4.0-cp27-cp27mu-linux_armv7l.whl is not a supported wheel on this platform.
Storing debug log for failure in /root/.pip/pip.log

进入/root/.pip/pip.log日志查看

1
2
3
4
5
6
7
8
9
10
11
/usr/bin/pip run on Tue Dec 12 03:13:40 2017
tensorflow-1.4.0-cp27-cp27mu-linux_armv7l.whl is not a supported wheel on this platform.
Exception information:
Traceback (most recent call last):
File "/usr/lib/python2.7/dist-packages/pip/basecommand.py", line 122, in main
status = self.run(options, args)
File "/usr/lib/python2.7/dist-packages/pip/commands/install.py", line 257, in run
InstallRequirement.from_line(name, None))
File "/usr/lib/python2.7/dist-packages/pip/req.py", line 167, in from_line
raise UnsupportedWheel("%s is not a supported wheel on this platform." % wheel.filename)
UnsupportedWheel: tensorflow-1.4.0-cp27-cp27mu-linux_armv7l.whl is not a supported wheel on this platform.

继续vim /usr/lib/python2.7/dist-packages/pip/req.py166行

1
2
3
4
5
6
7
8
9
10
11
12
13
if link and req is None:
url = link.url_without_fragment
req = link.egg_fragment #when fragment is None, this will become an 'unnamed' requirement

# Handle relative file URLs
if link.scheme == 'file' and re.search(r'\.\./', url):
url = path_to_url(os.path.normpath(os.path.abspath(link.path)))

# fail early for invalid or unsupported wheels
if link.ext == wheel_ext:
wheel = Wheel(link.filename) # can raise InvalidWheelFilename
if not wheel.supported():
raise UnsupportedWheel("%s is not a supported wheel on this platform." % wheel.filename)

然后使用本地IDE查看wheel.supported()的源码发现

1
2
3
4
5
def supported(self, tags=None):
"""Is this wheel supported on this system?"""
if tags is None: # for mock
tags = pep425tags.supported_tags
return bool(set(tags).intersection(self.file_tags))

找到关键点pep425tags.supported_tags,回到设备上执行查看

1
2
3
python
from pip import pep425tags
print str(pep425tags.supported_tags)

得到结果

1
[('cp27', 'none', 'linux_armv7l'), ('cp27', 'none', 'any'), ('cp2', 'none', 'any'), ('cp26', 'none', 'any'), ('cp25', 'none', 'any'), ('cp24', 'none', 'any'), ('cp23', 'none', 'any'), ('cp22', 'none', 'any'), ('cp21', 'none', 'any'), ('cp20', 'none', 'any'), ('py27', 'none', 'any'), ('py2', 'none', 'any'), ('py26', 'none', 'any'), ('py25', 'none', 'any'), ('py24', 'none', 'any'), ('py23', 'none', 'any'), ('py22', 'none', 'any'), ('py21', 'none', 'any'), ('py20', 'none', 'any')]

再次观察生成的whl文件,tensorflow-1.4.0-cp27-cp27mu-linux_armv7l.whl,找到原因,文件名里包含cp27mu,表示需要宽Unicode支持,当前pip版本对Unicode支持为none,遂查看pip版本发现为1.5.x,升级pip即可解决。

1
pip install --upgrade pip

我这里升级完成后,pip名称变成了pip2,所以使用pip2再次支持install

1
pip2 install /tmp/tensorflow_pkg/tensorflow-1.4.0-cp27-cp27mu-linux_armv7l.whl

终于看到了久违的successfully!!

1
Successfully installed backports.weakref-1.0.post1 bleach-1.5.0 enum34-1.1.6 futures-3.2.0 html5lib-0.9999999 markdown-2.6.10 tensorflow-1.4.0 tensorflow-tensorboard-0.4.0rc3

执行ldconfig,刷新一下动态链接库后马上测试一下。

1
2
3
4
5
import tensorflow as tf

hello = tf.constant('Hello, TensorFlow!')
sess = tf.Session()
print sess.run(hello)

泪牛满面的看到控制台终于输出了Hello, TensorFlow!!

至此,tensorflow移植成功!Thanks for your reading。

文章目录
  1. 1. 开始正题
    1. 1.1. 第一步 准备工作
    2. 1.2. 第二步 重头戏
,