在无 ROOT 权限的机器上安装 Caffe

都 8102 年了,还是有人在用 Caffe,这群计算机科学家们真是一点都不酷。最近发现一个项目,其中的 Caffe 还只能用 cuDNN 5.1 来编译,这也太共和党了吧。不过借着这次机会,我又把在无 ROOT 权限的机器上编译奇怪的程序的两种方法实践了一遍,在此记录一下吧。

无 ROOT 权限的机器最让人头疼的当然就是不能使用 aptyum 来安装软件了,这让本来可以一步到位的操作变得比登天还难。经过几个月来在这些机器上的摸索,我发现了三种可能的方式,达到在无 ROOT 权限的机器上编译复杂程序的目的:

  • 暴力全局复制
  • 手动编译依赖
  • Anaconda 替代

一、暴力全局复制

一开始我的想法很简单,虽然服务器的 ROOT 权限我搞不到,但是我在我的虚拟机里面那就可以随便乱搞了呀。于是我就用 Hyper-V 创建了一个新的 Ubuntu 16.04 TLS 虚拟机,使用 apt 把该装的软件都装好,然后用 rsync 同步到服务器上。由于之后经常还会改动(比如新安装一些软件,或者更改一些软件的版本),所以写了个一键同步的脚本,在本机上 apt install 完了直接运行脚本进行增量同步,速度十分理想。具体说来:

本机的 Ubuntu 系统软件一般安装在 /usr 目录,但为了更粗暴一点我直接将 /usr/lib/lib64/bin/sbin 全部同步过去。服务器上这些文件夹我就放在 /home/myname/env 目录下,假设编译程序需要用到 glog 库,那么在本机执行

apt install libgoogle-glog-dev

并且同步完成之后,服务器上 /home/myname/env/usr 目录下面就会包含 glog 库的文件了,这样就可在编译的时候根据需要加入:

-I/home/myname/env/usr/include
-L/home/myname/env/usr/lib

我用这个方法成功安装过 Caffe2,感觉十分方便。附上我的同步脚本:

#!/bin/bash
DIRS=(/bin /sbin /lib /lib64 /usr)
for DIR in ${DIRS[@]};
do
  rsync -rlptv $DIR myname@lab:~/env
done

二、手动编译依赖

这个方法其实也挺暴力的,但是相比起来就有点笨了,不过能解决问题就好。以下是整个过程:

安装 Anaconda 并创建新环境

安装 Anaconda 时推荐到清华的镜像源下载,速度超快。安装好之后按照这里的指南把 conda 的官方仓库源替换掉,就能愉快地使用了。

新建一个 Python 2 环境:

conda create -n caffe python=2
source activate caffe

安装一些库:

conda install -c serge-sans-paille gcc_49
conda install -c anaconda cmake
conda install -c menpo opencv

由于 cuDNN 不支持 gcc 5.0 及以上的版本编译,并且在我的印象中好像 gcc 4.8 也会有点问题,所以我先尝试用 gcc 4.9 编译。

然后克隆 Caffe 源代码,并将待编译的依赖库统一放置在 $THIRD 目录里面:

export CUDA_ROOT=/home/myname/env/usr/local/cuda-7.5_cudnn-5.1
export PS_ENV=/home/myname/env/anaconda3/envs/ps
export PROJ_HOME=/home/myname/src/person_search
export THIRD=$PROJ_HOME/caffe/thirdparty
git clone --recursive https://github.com/ShuangLI59/person_search.git $PROJ_HOME
mkdir $THIRD

编译 GFlags

需用 gcc 4.9 编译,否则待会儿链接 Caffe 的时候会报错(要保证依赖库和 Caffe 本身同时使用 gcc 4.x 或同时使用 gcc 5.x 编译)。为了保险起见,我在编译所有的库的时候都加上了 -fPIC 选项(虽然我之后尽量都使用静态链接库而不是动态链接库,应该不会存在这个问题)。最后将编译好的库文件放在 $GFLAGS_ROOT_DIR 目录。

cd $THIRD
export GFLAGS_ROOT_DIR=$THIRD/gflags-master/gflags

wget https://github.com/schuhschuh/gflags/archive/master.zip
unzip master.zip
cd gflags-master
mkdir build && cd build
CFLAGS="-fPIC" CXXFLAGS="-fPIC" cmake .. -DCMAKE_CXX_COMPILER=g++-4.9 -DCMAKE_C_COMPILER=gcc-4.9 -DCMAKE_INSTALL_PREFIX:PATH=$THIRD
make VERBOSE=1
make && make install

编译 GLog

cd $THIRD
export GLOG_ROOT_DIR=$THIRD/glog-0.3.3/glog

wget https://github.com/google/glog/archive/v0.3.3.tar.gz
tar zxvf v0.3.3.tar.gz
cd glog-0.3.3
mkdir build && cd build
../configure CC=gcc-4.9 CXX=g++-4.9 CFLAGS="-fPIC" CXXFLAGS="-fPIC" --prefix=$GLOG_ROOT_DIR
make && make install

编译 LMDB

cd $THIRD
export LMDB_ROOT=$THIRD/lmdb-LMDB_0.9.22/lmdb

wget https://github.com/LMDB/lmdb/archive/LMDB_0.9.22.tar.gz
tar LMDB_0.9.22.tar.gz
cd lmdb-LMDB_0.9.22
mv libraries/liblmdb/* .

修改 Makefile 文件的开头部分,把编译器改成 gcc 4.9,加入 -fPIC 选项,修改安装位置。

CC  = gcc-4.9
AR  = ar
W   = -W -Wall -Wno-unused-parameter -Wbad-function-cast -Wuninitialized
THREADS = -pthread
OPT = -O2 -g -fPIC
CFLAGS  = $(THREADS) $(OPT) $(W) $(XCFLAGS)
LDLIBS  =
SOLIBS  =
SOEXT   = .so
prefix  = $(LMDB_ROOT)

编译安装:

make && make install

编译 Snappy

LevelDB 依赖 Snappy。

cd $THIRD
export SNAPPY_ROOT=$THIRD/snappy-1.1.7/snappy

wget https://github.com/google/snappy/archive/1.1.7.tar.gz
tar zxvf 1.1.7.tar.gz
cd snappy-1.1.7
mkdir build && cd build
CFLAGS="-fPIC" CXXFLAGS="-fPIC" cmake .. -DCMAKE_CXX_COMPILER=g++-4.9 -DCMAKE_C_COMPILER=gcc-4.9 -DCMAKE_INSTALL_PREFIX:PATH=$SNAPPY_ROOT
make && make install

编译 LevelDB

cd $THIRD
export LEVELDB_ROOT=$THIRD/leveldb-1.20

wget https://github.com/google/leveldb/archive/v1.20.tar.gz
tar zxvf v1.20.tar
cd leveldb-1.20
CXX=g++-4.9 CXXFLAGS="-fPIC" LIBS="-L$SNAPPY_ROOT/lib" make

编译完成后静态链接库和动态链接库分别存放在 out-staticout-shared 目录下。

编译 zlib

zlib 库和 libpng 库本不是 Caffe 编译指南上要求的,一般安装其它库的时候就会顺带安装上。但是我在最后编译时遇到了 libpng 报错,按照网上的方法试了也没用。找了好久终于发现有人遇到了类似的问题,说是要特定版本的 zlib 和 libpng 才行,于是又有了一下步骤:

cd $THIRD
export ZLIB_ROOT=$THIRD/zlib-1.2.8/zlib

wget https://sourceforge.net/projects/libpng/files/zlib/1.2.8/zlib-1.2.8.tar.gz
tar zxvf zlib-1.2.8.tar.gz
cd zlib-1.2.8
mkdir build && cd build
CFLAGS="-fPIC" CXXFLAGS="-fPIC" cmake .. -DCMAKE_CXX_COMPILER=g++-4.9 -DCMAKE_C_COMPILER=gcc-4.9 -DCMAKE_INSTALL_PREFIX:PATH=$ZLIB_ROOT
make && make install

编译 libpng

使用自己编译的库之前最好把 conda 安装的删掉:

conda remove libpng

然后开始编译。

cd $THIRD
export LIBPNG_ROOT=$THIRD/libpng-1.6.34/libpng

wget https://ftp.osuosl.org/pub/blfs/conglomeration/libpng/libpng-1.6.21.tar.xz
tar xJvf libpng-1.6.21.tar.xz
cd libpng-1.6.34
mkdir build && cd build
LDFLAGS="-L$ZLIB_ROOT/lib/" ../configure CC=gcc-4.9 CXX=g++-4.9 CFLAGS="-fPIC" CXXFLAGS="-fPIC" --prefix=$LIBPNG_ROOT
make && make install

编译 HDF5

cd $THIRD
export GLOG_ROOT_DIR=$THIRD/glog-0.3.3/glog

wget https://github.com/google/glog/archive/v0.3.3.tar.gz
tar zxvf v0.3.3.tar.gz
cd glog-0.3.3
mkdir build && cd build
../configure CC=gcc-4.9 CXX=g++-4.9 CFLAGS="-fPIC" CXXFLAGS="-fPIC" --prefix=$GLOG_ROOT_DIR
make && make install

编译 Protobuf

cd $THIRD
export PROTOBUF_ROOT=$THIRD/protobuf-2.6.1/protobuf

wget https://github.com/google/protobuf/releases/download/v2.6.1/protobuf-2.6.1.tar.bz2
tar xjvf protobuf-2.6.1.tar.bz2
cd protobuf-2.6.1
mkdir build && cd build
../configure CC=gcc-4.9 CXX=g++-4.9 CFLAGS="-fPIC" CXXFLAGS="-fPIC" --prefix=$PROTOBUF_ROOT
make && make install

编译 Boost

这里用了指南中所要求的的最低版本的 Boost。

cd $THIRD
export BOOST_ROOT=$THIRD/boost_1_55_0/release

wget http://sourceforge.net/projects/boost/files/boost/1.55.0/boost_1_55_0.tar.bz2
tar xjvf boost_1_55_0.tar.bz2
cd boost_1_55_0
CXXFLAGS="-fPIC" ./bootstrap.sh --with-libraries=all --with-toolset=gcc
# 指定 gcc 版本:
echo "using gcc : 4.9 : g++-4.9 ;" >> tools/build/v2/user-config.jam
./b2 --toolset=gcc-4.9
./b2 install --prefix=$BOOST_ROOT

开始编译

cd $THIRD/..

mkdir build && cd build

export PATH=$LIBPNG_ROOT/bin:$HDF5_ROOT/bin:$PROTOBUF_ROOT/bin:$PATH

cmake .. -DCMAKE_CXX_COMPILER=g++-4.9 -DCMAKE_C_COMPILER=gcc-4.9 -DUSE_MPI=ON -DGFLAGS_INCLUDE_DIR=$GFLAGS_ROOT_DIR/include -DGFLAGS_LIBRARY=$GFLAGS_ROOT_DIR/lib/libgflags.a -DGLOG_INCLUDE_DIR=$GLOG_ROOT_DIR/include -DGLOG_LIBRARY=$GLOG_ROOT_DIR/lib/libglog.a -DLMDB_INCLUDE_DIR=$LMDB_ROOT/include -DLMDB_LIBRARIES=$LMDB_ROOT/lib/liblmdb.a -DLevelDB_INCLUDE=$LEVELDB_ROOT/include -DLevelDB_LIBRARY=$LEVELDB_ROOT/out-static/libleveldb.a -DSnappy_INCLUDE_DIR=$SNAPPY_ROOT/include -DSnappy_LIBRARIES=$SNAPPY_ROOT/lib/libsnappy.a -DCUDA_TOOLKIT_ROOT_DIR=$CUDA_ROOT -DCUDA_ARCH_NAME=Manual -DPYTHON_LIBRARY=$PS_ENV/lib/libpython2.7.so -DProtobuf_INCLUDE_DIR=$PROTOBUF_ROOT/include -DProtobuf_LIBRARY=$PROTOBUF_ROOT/lib/libprotobuf.a -DCMAKE_POSITION_INDEPENDENT_CODE=ON -DProtobuf_USE_STATIC_LIBS=ON

make -j36

不出意外,应该是没问题了(然而现实中最不缺少的就是意外

三、Anaconda 替代

其实上面的过程也用到了这个特性,即用 conda install 来代替 apt install。这个方法理论上可以行得通,但是我目前没有成功实践过。具体到这次 Caffe 的编译,由于它需要 cuDNN 5.1,而 cuDNN 5.1 不支持 gcc 5.0 及以上的版本,又因为 gcc 5 和 gcc 4 之间处理标准库的方式又不一样,导致分别用它们编译好的库文件很难链接起来,因此最好所有的库文件都用同一个版本的编译器编译。在 Anaconda 上面找到的现成的库大多都是用 gcc 5 以上的编译器编译的,安装好了编译的时候也会报错。如果没有 cuDNN 必须要 5.1 这个非人的限制,那么理论上全部都用 gcc 5 来编译应该是可行的。

此方法可以说是最无脑的了,缺点就是在 Anaconda 仓库里面找到合适版本的库很难,然后就是 conda 的安装速度真的特别慢。