在ubuntu上编译TensorFlow源码
记录一下三个下午的劳动成果。
系统是ubuntu18.04,在win10上用hyper-v装的虚拟机,tf用的是最新的master
首先获取源码
这没啥可说的,但是目前出于不可抗力,连官方文档都打不开了,我记得以前是可以的,于是只能从git上再整一个文档源码来看
这个文档似乎是可以编译成好看的格式的,没研究明白咋搞,我看都是md格式的,于是直接用自己的md编辑器打开来看了,关于从源码编译参考
这个文档的前半部分都讲的是装环境然后编译,但后面讲了用docker的方法,我感觉更加简单一些。
说来惭愧,一直都有听说docker,但这次才真的看了一下到底是个什么东西,以前一直理解为了虚拟机,现在才发现它更像是头文件,为你提供一个只读的环境来帮你运行代码,用它来编译就可以避免各种依赖的安装和版本不对的问题,还是蛮不错的。
同级目录下还有个docker.md,但是这个侧重于使用docker直接运行你的tf代码,关于编译源码这部分还是source.md里说的更清楚
接下来是安装docker,tf文档指向的docker文档链接有点问题,docker那边似乎在推广一个桌面版本(有ui的那种),所以目录层级变了,现在shell下的安装教程位于 https://docs.docker.com/engine/install/ubuntu/
docker官方说的还是挺清楚的,照做就行不需要取舍,这里就不贴了。
唯一的问题是这套操作需要root权限,不过我感觉真到需要的场景下是可以让管理员给装的,所以就不研究怎么在用户权限下整了。
但是装好之后,docker每次运行都要sudo权限,这就很蛋疼了,关于这个问题官方文档也有讲 https://docs.docker.com/engine/install/linux-postinstall/
我在这个问题卡了好久,一开始是没找到官方的说明,博客上说的都差不多,但是照做之后全都不好使,后来仔细看了官方的才发现我一直漏了第三步,用户组搞完之后一定得重启一下,可能由于我这是桌面版ubuntu,不是光重启shell就行,整个机器重启之后就可以了。
docker搞定之后就到tf了,首先是下载官方提供的编译环境
这里pull的devel版本就是专门为了编译源码用的,它内部甚至并没有一个编好的tf,而是直接放了源码进去,但由于docker镜像是只读的,按我理解,他自己的源码编了也是白编,重启就没了(指中间结果),好在tf文档提供了另一种更合理的方法(不懂为什么更合理的放在第二个说)
这里就是把你自己的源码文件夹挂载进container的/tensorflow位置,这样直接用docker的环境编译你自己的代码就好了,docker开着的时候外面还可以用IDE改代码,非常合理。
除了挂载了源码,这里还挂载了一个当前文件夹,是我新建的空文件夹,放到了container的/root/.cache,文档里使用的是mnt,原本目的是让编译出来的whl文件有个去处。
但是在我成功编译完第一次之后,再进入docker发现并不是按我想象中的改哪编哪,而是直接从头开始编译了,研究半天发现,bazel在编译的时候会将他生成的文件(下载的、编译的、以及最终结果)放在"bazel-"开头的文件夹里,这些文件夹看似在代码目录下(这里就是/tensorflow),实际上是软链到了/root/.cache/bazel/下,于是docker一关闭东西就都无啦,白编译了8h焯。
我懒得找怎么给bazel替换临时地址了,直接把我的工作环境目录(也就是新建的文件夹)挂载成了.cache文件夹(检查过了docker本身并没有这个地址),问题就解决了,后续编译的结果也往这里放就可以了。
现在我们就打开了一个环境极度舒适的bash,然后按照普通的编译方法直接操作就好了(最终结果不能往/mnt放了)。
在configure的时候额外功能我一律选No,如果需要gpu版或者其他功能的话,在下载docker的时候就得选对应的版本,关于后缀在docker.md里有详细说明,或者直接去docker网站上看列表(hub.docker.com/r/tensorflow/tensorflow/tags/),我这里只编译了纯cpu版本。
第二步的编译是最耗时的,我用家用机器(i9-9900k)需要编8h,编译的时候看cpu消耗感觉它只用了单核,查了下可以加"--jobs 4",但是我的虚拟机创建的时候只有一个核emmm,大意了,反正是一次性的就干等好了。
第二步这里经常会失败退出,原因是bazel在编译的时候需要联网下载依赖,大部分依赖来源都是github,然而出于某种神奇原因,这网站经常连不上,连外国人也连不上,所以tf自己整了个镜像网址,可以从自家仓库下载这些东西,只是这个镜像网址我们自然是访问不了的了,第一次fetch得到404之后,tf会贴心的帮我们重新尝试github,于是连不连得上就得看命了,失败了就反复重试,早晚会好的,毕竟下载的过程不占大头,可以人盯一会儿,等开始编cc文件了就可以挂机了。
只要编成功了第一次,以后就快了,即便重启docker也没有问题。
第三步执行过后,我们就得到了tf的whl包,正常来说直接拿到需要的地方去pip install就好了,但是作为开发来说,我需要改代码然后重复编译,不太想每次用pip去搞本机环境,于是就想着再回到docker里去运行。
然而奇葩的问题是,负责编译的devel镜像是没法执行的,缺少一些依赖的python库,而负责运行的其他镜像又没有bazel没法编译,所以这两个操作只能搞两个镜像了,因为编译的是master,所以我又搞了个nightly的docker环境。
whl包其实就是个压缩文件,可以原地解压缩打开
解压得到的tensorflow文件夹就是可以直接用来import的python包了,尝试修改tensorflow/__init__.py里的__version__变量,再尝试print(tf.__version__)是可以看到变化的。
虽说nightly环境里是自带编译好的tf的,但是import会优先本地文件,所以只要处在当前目录,就可以保证使用的是测试代码。
一个小问题就是,这个whl包如果是由docker来unzip,那么对于host环境(使用IDE的地方)来说这些文件是只读的,每次改动都需要给权限,如果是在host下unzip就没事,不过编译和运行都是在docker的,unzip还要再单开一个就太麻烦了,好在时常需要改动的并不是打包后的这些文件,所以可以容忍这个问题。
我这里忽略了文档里的chown这步,不加上的话,外面依旧可以解压可以改动,在docker里加上了在里面解压外面依旧改不了,所以不大懂原本这个chown是干嘛用的,既然不需要chown了,外面也就不用传$HOST_PERMS了。
最终留下了这么几个脚本
启动编译环境(改了进docker后的位置)
编译(第二次开始大概需要一两分钟,对于这么大个项目来说还可以了,以后需要频繁修改某个特定文件的时候可以直接cp)
启动运行环境
在运行环境跑自己的代码(docker里的python是python3.8.10)