直奔主题,为什么要做这件事情?

  • 服务器目前安装了CUDA 8.0、TensorFlow 1.3.0,并已有线上服务基于该版本环境配置开发,无法轻易修改本机环境版本
  • TensorFlow版本迭代很快,新版本使用更高版本的CUDA,不仅计算速度上更快,而且还有一些更加丰富的API可供使用,例如TensorFlow 1.8.0版本中的eager模式

所以通过查资料以及实验完成了这件事情,并记录下来以供有同样需求的朋友参考

文章修改记录:

  • 2018.11.25:统一路径信息、修改部分链接、增加全局pip源设置以及一些小问题修复

前言

文章开头说了为什么要做这件事,所以其实本质就是为了在不改变原有版本的基础上能够使用新版本,那么便需要调研多版本共存的可行性

经过调研,CUDA和TensorFlow均可以做到多版本共存,障碍只剩一个——显卡驱动。因为目前的显卡驱动384.66是支持CUDA 8.0的最新版本驱动,然而其不支持更高版本的CUDA。所以考虑使用支持更高版本CUDA的显卡驱动,并测试其是否向下兼容低版本CUDA。测试结果是可行的,这也才有了这篇文章

整篇文章分为两部分:使用方法、安装过程

  • 使用方法是将安装过程中创建虚拟环境并配置的过程使用脚本实现,以达到一步到位的便捷使用。脚本内容可以见文后
  • 安装过程叙述了为了达成多版本共存的目的所做的一些操作或者配置

本文所使用的Anaconda软件以root权限安装在/home/anaconda3目录下,可根据需要自行修改。安装后服务器上所有用户均可使用Anaconda来创建虚拟环境

本文由 @李君阳 与我一同完成

使用方法

若要创建指定CUDA版本的虚拟环境,请运行 create_virtual_env.sh 脚本(见文末,或查看该链接),根据提示输入虚拟环境名称、Python版本、CUDA版本即可

1
2
3
4
Enter the name of Anaconda virtual environment: 
Enter the version of Python (default is 3.6.5):
Enter the version of CUDA (8.0/9.0, default is 9.0):
Start to create the environment......

创建完成后会显示 Everything is DONE!

随后使用source activate XXX(或者conda activate XXX)命令进入虚拟环境,在虚拟环境中可以使用创建时指定的Python版本和CUDA版本,可自由使用pip安装自己所需要的Python包,不影响本机环境

要回到本机环境使用source deactivate(或者conda deactivate)退出虚拟环境

安装过程

1、安装显卡驱动

目前服务器上安装的显卡驱动版本为384.66,是支持CUDA 8.0的最新版本驱动。目前已知该驱动无法支持更高版本的CUDA,所以需要验证安装支持高版本CUDA的显卡驱动是否能正常向下兼容低版本CUDA

下载

http://www.nvidia.cn/Download/index.aspx 这里寻找对应的显卡驱动即可,这里选择:

  • Product Type: Tesla
  • Product Series: M-Series
  • Product: M40 24GB
  • Operating System: Linux 64-bit
  • CUDA Toolkit: 9.1
  • Language: English(US)

这里下载的文件名是:NVIDIA-Linux-x86_64-390.46.run

安装

首先先卸载旧版本驱动

1
./NVIDIA-Linux-x86_64-384.66.run --uninstall

然后对新驱动添加可执行权限,安装

1
2
chmod u+x NVIDIA-Linux-x86_64-390.46.run
./NVIDIA-Linux-x86_64-390.46.run

进入安装界面后一路同意就可以

验证是否支持低版本CUDA

使用CUDA自带的samples进行验证

1
2
3
4
cd /usr/local/cuda-8.0/samples/1_Utilities/deviceQuery
make clean
make
./deviceQuery

如果在最后看到以下信息,则证明该驱动能够支持低版本CUDA

1
2
deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 9.1, CUDA Runtime Version = 8.0, NumDevs = 2, Device0 = Tesla M40 24GB, Device1 = Tesla M40 24GB
Result = PASS

一些错误

  • ERROR: An NVIDIA kernel module ‘nvidia-uvm’ appears to already be loaded in your kernel. This may be because it is in use (for example, by an X server, a CUDA program, or the NVIDIA Persistence Daemon), but this may also happen if your kernel was configured without support for module unloading. Please be sure to exit any programs that may be using the GPU(s) before attempting to upgrade your driver. If no GPU-based programs are running, you know that your kernel supports module unloading, and you still receive this message, then an error may have occured that has corrupted an NVIDIA kernel module’s usage count, for which the simplest remedy is to reboot your computer.

使用nvidia-smi命令查看有哪些程序在使用显卡,kill掉。如果没有使用显卡的程序依旧报错,可以重启服务器

2、安装CUDA-9.0 & cuDNN-7.1

CUDA下载

注意:目前CUDA最新版本为9.1,但是TensorFlow的最新版本1.8还未支持CUDA 9.1,所以只能安装CUDA 9.0

https://developer.nvidia.com/cuda-downloads 这里寻找对应平台的文件下载即可。进入后默认显示最新版本CUDA,选择靠右侧的Legacy Releases,进入后选择CUDA Toolkit 9.0 (Sept 2017)

这里一些选项的选择为:

  • Operating System: Linux
  • Architecture: x86_64
  • Distribution: CentOS
  • Version: 7
  • Installer Type: runfile(local)

下面会显示两个安装文件,一个 Base Installer ,两个Patch。安装完Base后再安装Patch即可

直接复制下载链接,在服务器上wget下载即可

CUDA安装

添加可执行权限,安装

1
2
chmod u+x cuda_9.0.176_384.81_linux.run
./cuda_9.0.176_384.81_linux.run

接下来会有一系列选择

  • 是否安装显卡驱动:选no,因为已经装了最新的驱动
  • 是否安装CUDA 9.0:选yes
  • 输入安装位置:默认即可,因为默认位置就区分开了不同的CUDA版本
  • 是否创建软链:选no,因为要实现多版本CUDA共存。如果以前安装过CUDA并创建过软链,需要删除
  • 是否安装sample:选no,因为在安装路径下会有一份sample,这个是问是否要在自己的目录下再安装一份

然后再安装一下两个补丁即可:cuda_9.0.176.1_linux.run、cuda_9.0.176.2_linux.run

CUDA测试

进入samples目录,选择第一个例子进行测试

1
2
3
cd /usr/local/cuda-9.0/samples/1_Utilities/deviceQuery
make
./deviceQuery

如果在最后看到以下信息,则证明CUDA 9.0安装成功

1
2
deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 9.1, CUDA Runtime Version = 9.0, NumDevs = 2
Result = PASS

cuDNN下载

https://developer.nvidia.com/rdp/cudnn-download 选择对应的版本下载即可。不过需要先注册开发者账号后才可以下载

刚才安装的CUDA是9.0,所以我们选择

  • Download cuDNN v7.1.3 (April 17, 2018), for CUDA 9.0
  • cuDNN v7.1.3 Library for Linux

cuDNN安装

执行解压操作

1
2
3
mkdir cuda-9.0_cudnn
tar zxvf cudnn-9.0-linux-x64-v7.1.tgz -C ./cuda-9.0_cudnn
cd cuda-9.0_cudnn

解压后的文件夹是cuda。执行以下操作把文件复制到相应的位置

1
2
3
4
cp cuda/include/cudnn.h /usr/local/cuda-9.0/include/
cp cuda/lib64/libcudnn* /usr/local/cuda-9.0/lib64/
chmod a+r /usr/local/cuda-9.0/include/cudnn.h
chmod a+r /usr/local/cuda-9.0/lib64/libcudnn*

注意

以上过程基于服务器上已经有一个CUDA版本。如果服务器上未安装过CUDA,按照这个过程依次安装两个版本CUDA即可,只需注意不要创建软链。如果打算在本机环境使用CUDA,则需要配置环境变量。如果打算都在虚拟python环境下运行,则本机不需要配置相关环境变量,在之后启动虚拟环境时配置

3、安装Anaconda

下载安装

使用该下载链接下载Anaconda安装脚本,默认Python版本是3.6.5

添加可执行权限后,执行安装

安装完一堆包之后会询问是否要添加环境变量,这里选择no,稍后再添加。然后会继续询问是否安装VSCode,选择no。此时安装完成

因为我们使用Anaconda只是作为虚拟python环境管理,不需要用到其自带的python以及相关包,所以需要将Anaconda的bin放到path后,否则系统的python会失效。所以将下面这一行添加到需要使用Anaconda账户的.bashrc文件中

1
export PATH=$PATH:/home/anaconda3/bin

source一下或者退出重新登陆

然后执行ln -s /home/anaconda3/etc/profile.d/conda.sh /etc/profile.d/conda.sh,这是为了服务器上所有用户都可以使用conda activateconda deactivate来进入和退出虚拟环境(即source和conda都可以)

修改国内镜像仓库

因为Anaconda默认使用国外的下载地址,为了提高国内的下载速度,可以使用国内的清华镜像源或者中科大镜像源,二选一即可,执行三条命令

加入–system是配置全局的conda config。若个人账户想修改,则取消–system即可,此时在个人账户下会覆盖全局配置(类似 git config )。个人账号相关配置会写在 ~/.condarc 里面

  • 清华Anaconda镜像
1
2
3
conda config --system --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
conda config --system --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
conda config --system --set show_channel_urls yes
  • 中科大Anaconda镜像
1
2
3
conda config --system --add channels http://mirrors.ustc.edu.cn/anaconda/pkgs/free/
conda config --system --add channels http://mirrors.ustc.edu.cn/anaconda/pkgs/main/
conda config --system --set show_channel_urls yes

创建虚拟环境

基本命令为 conda create --name XXX,–name可缩写为-n

如果在创建虚拟环境时报错(跟urls.txt或者environments.txt有关,一般是第一次使用Anaconda时会出现),参考下方一些错误部分

关于创建虚拟环境,分以下三种情况陈述

  • conda create --name test1

此时虚拟环境并不起作用,进入test1后会发现实际上使用的依旧是本机Python环境,并且在test1中使用pip安装或者删除包均是对本机环境操作

所以不要使用这种方式

  • conda create --name test2 python=X.X.X

该命令会创建一个名为test2的纯净虚拟环境,并且会安装python X.X.X版本,无其他Python包。进入test2后,使用python -Vpip -V可以看到使用的的确是该虚拟环境,并且在test2中使用pip安装或者删除包均是对该虚拟环境进行操作,不会影响本机环境

这里python的入参有多种

如果只写python,则安装的是Anaconda自带的Python版本(在这里是3.6.5)

如果是python=2或者python=3,则安装的是python2或者python3的最新版本(目前是2.7.15和3.6.5)

如果写了二级版本号,例如python=2.6或者python=3.5,则安装的是python2.6或者python3.5分支的最新版本(目前是2.6.9和3.5.5)

如果写了最小的版本号,例如python=2.7.8或者python=3.6.2,则安装的便是输入的特定版本

  • conda create --name test3 python=2.7 numpy

该命令会创建一个名为test3的纯净虚拟环境,并且会安装python2.7的最新版本,在此基础上还会额外安装numpy包。当然numpy包也可以类似Python一样指定版本,例如numpy=1.10,则会安装1.10.4版本。不指定则安装的就是最新版本。其他同上

  • conda create --name test4 numpy

该命令会创建一个名为test4的纯净虚拟环境,并且会安装Anaconda自带的Python版本(在这里是3.6.5),在此基础上还会额外安装numpy包。其他同上

启动和退出虚拟环境

  • 启动
1
source activate XXX(或者conda activate XXX)

启动成功后在命令行前会有虚拟环境的名字(XXX)

  • 退出
1
source deactivate(或者conda deactivate)

其他常用指令

查看有哪些虚拟环境:conda env list

删除虚拟环境:conda env remove -n XXX

多版本TensorFlow共存

在不同的虚拟环境中安装不同版本的TensorFlow即可。若不同版本的TensorFlow依赖的CUDA版本不同,则参照下一小节“给虚拟环境指定CUDA版本”来操作

设置全局pip源配置

进入到虚拟环境后,使用pip安装Python包的时候会发现其从原始的国外地址进行下载。有时下载速度较慢,所以我们要更新pip源为国内源

编辑 /etc/pip.conf文件(若无则创建),添加以下内容

1
2
3
[global]
trusted-host = mirrors.aliyun.com
index-url = http://mirrors.aliyun.com/pypi/simple

该方式可以进行全局pip源配置,便于服务器上所有用户均可使用。若只要某些用户使用,那么编辑的就是~/.pip/pip.conf文件

一些错误

  • NotWritableError: The current user does not have write permissions to a required path.

    path: /home/username/.conda/pkgs/urls.txt

    uid: 1001

    gid: 1002

    If you feel that permissions on this path are set incorrectly, you can manually change them by executing

    $ sudo chown 1001:1002 /home/username/.conda/pkgs/urls.txt

    In general, it’s not advisable to use ‘sudo conda’.

自己创建该文件。类似的还有 /home/username/.conda/environments.txt

4、给虚拟环境指定CUDA版本

处理环境变量

假设此时服务器上安装了两个CUDA版本,分别为CUDA 8.0(/usr/local/cuda-8.0)和CUDA 9.0(/usr/local/cuda-9.0),并且在本机上配置了一个版本(例如8.0)的环境变量($CUDA_HOME, $LD_LIBRARY_PATH)。没配置更好,这里配置是为了验证切换虚拟环境时指定的CUDA版本是否发生相应的改变

首先创建一个虚拟环境,让这个虚拟环境使用CUDA 9.0的lib

1
conda create -n cuda_test python=3

然后设定启动虚拟环境以及退出时需要执行的脚本,为了使虚拟环境启动时环境变量不同于本机环境,退出时又恢复为本机环境

1
2
mkdir -p /home/username/.conda/envs/cuda_test/etc/conda/activate.d
mkdir -p /home/username/.conda/envs/cuda_test/etc/conda/deactivate.d
  • 编辑启动脚本 vim /home/username/.conda/envs/cuda_test/etc/conda/activate.d/activate.sh

输入以下内容

1
2
3
4
ORIGINAL_CUDA_HOME=$CUDA_HOME
ORIGINAL_LD_LIBRARY_PATH=$LD_LIBRARY_PATH
export CUDA_HOME=/usr/local/cuda-9.0
export LD_LIBRARY_PATH=$CUDA_HOME/lib64:$LD_LIBRARY_PATH

添加执行权限 chmod +x /home/username/.conda/envs/cuda_test/etc/conda/activate.d/activate.sh

  • 编辑退出脚本 vim /home/username/.conda/envs/cuda_test/etc/conda/deactivate.d/deactivate.sh

输入以下内容

1
2
3
4
export CUDA_HOME=$ORIGINAL_CUDA_HOME
export LD_LIBRARY_PATH=$ORIGINAL_LD_LIBRARY_PATH
unset ORIGINAL_CUDA_HOME
unset ORIGINAL_LD_LIBRARY_PATH

添加执行权限 chmod +x /home/username/.conda/envs/cuda_test/etc/conda/deactivate.d/deactivate.sh

测试

首先先在本机查看环境变量 CUDA_HOME 和 LD_LIBRARY_PATH

echo $CUDA_HOME 结果为/usr/local/cuda-8.0

echo $LD_LIBRARY_PATH 结果为/usr/local/cuda-8.0/lib64

进入虚拟环境

1
source activate cuda_test

查看此时的环境变量

echo $CUDA_HOME 结果为/usr/local/cuda-9.0

echo $LD_LIBRARY_PATH 结果为/usr/local/cuda-9.0/lib64:/usr/local/cuda-8.0/lib64

退出虚拟环境

1
source deactivate

再次查看环境变量,会发现恢复为/usr/local/cuda-8.0和/usr/local/cuda-8.0/lib64,证明在虚拟环境中指定CUDA版本lib成功

PS:也可以通过在虚拟环境中安装1.8.0版本的tensorflow-gpu来验证,因为其必须使用CUDA 9.0才能正确import,否则会报错ImportError: libcublas.so.9.0: cannot open shared object file: No such file or directory

5、小结

至此,完成以上操作后,在服务器上即可达到多版本CUDA、TensorFlow共存的目的

注意事项

通过第四小节可以看到,可以通过编写虚拟环境的启动脚本和退出脚本来管理虚拟环境中的环境变量,使其启动时不同于本机环境,退出时又恢复为本机环境。但是如果需要在脚本中对PATH环境变量做改变的话,会发生一些问题——退出时没有正确恢复PATH环境变量

在对虚拟环境启动和退出的过程做了梳理后,可以有办法解决这个问题(使用上述ORIGINAL_的方式做中间转换是无法解决的)

假设启动前PATH内容为/usr/bin

启动过程:

  1. 在启动时,conda会先修改PATH,在本机PATH的前面加上/home/username/.conda/envs/cuda_test/bin: ,这是为了在虚拟环境中能够使用虚拟环境自己的Python。此时PATH内容为PATH=/home/username/.conda/envs/cuda_test/bin:/usr/bin,然后export该PATH
  2. 然后执行添加的启动脚本activate.sh。假如在其中对PATH做了添加,此时PATH内容为PATH=/xxxx/bin:/home/username/.conda/envs/cuda_test/bin:/usr/bin。进入虚拟环境中echo $PATH得到的内容也是/xxxx/bin:/home/username/.conda/envs/cuda_test/bin:/usr/bin
  3. 然后conda会将此时的PATH在某个地方备份记录一下,并且将其在第一步添加的内容删掉。也就是说这时conda会保留一个内容为/xxxx/bin:/usr/bin的PATH

退出过程:

  1. 首先执行添加的退出脚本deactivate.sh
  2. 将启动第三步存下来的PATH拿出来直接export。所以在deactivate.sh中使用ORIGINAL_的方式做恢复是无效的

当恢复到本机环境时,PATH的内容为/xxxx/bin:/usr/bin的PATH,与启动前不一样

启动过程主要执行的是/home/anaconda3/etc/profile.d/conda.sh中的_conda_activate()方法。退出过程主要执行的是/home/anaconda3/etc/profile.d/conda.sh中的_conda_deactivate()方法

目前想到的一种解决方案如下:(未尝试)

  1. 在退出脚本deactivate.sh的头部添加FIX_PATH=$CUDA_HOME
  2. /home/anaconda3/etc/profile.d/conda.sh_conda_deactivate()方法中的eval "$ask_conda"之后,在PATH中正则匹配FIX_PATH的内容并将其删掉,再重新export PATH

Reference

https://www.jianshu.com/p/4ac737fd6b45

https://blog.kovalevskyi.com/multiple-version-of-cuda-libraries-on-the-same-machine-b9502d50ae77

https://www.digitalocean.com/community/tutorials/how-to-install-the-anaconda-python-distribution-on-ubuntu-16-04

create_virtual_env.sh

链接:https://gist.github.com/bluesmilery/b1b4a840fcfabcda8ae26144988480d2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#!/bin/bash
# Used to create a Anaconda virtual environment for CUDA and TensorFlow.

echo ""
echo "This script is used to create a Anaconda virtual environment for CUDA and TensorFlow."

echo ""
read -p "Enter the name of Anaconda virtual environment: " ENV_NAME
if [ -z ${ENV_NAME} ]; then
echo ""
echo "Need a virtual environment name!" >&2
exit 1
fi

echo ""
read -p "Enter the version of Python (default is 3.6.5): " PYTHON_VERSION
if [ -z ${PYTHON_VERSION} ]; then
PYTHON_VERSION="3.6.5"
fi

echo ""
read -p "Enter the version of CUDA (8.0/9.0, default is 9.0): " CUDA_VERSION
if [ -z ${CUDA_VERSION} ]; then
CUDA_VERSION="9.0"
elif [ ${CUDA_VERSION} != "8.0" ] && [ ${CUDA_VERSION} != "9.0" ]; then
echo ""
echo "CUDA version must be 8.0 or 9.0!" >&2
exit 1
fi

echo ""
echo "Start to create the environment......"
echo ""

cmd_create="conda create -n ${ENV_NAME} python=${PYTHON_VERSION}"
eval ${cmd_create}

ACTIVATE_PATH=$HOME/.conda/envs/${ENV_NAME}/etc/conda/activate.d
DEACTIVATE_PATH=$HOME/.conda/envs/${ENV_NAME}/etc/conda/deactivate.d

activate_string="
ORIGINAL_CUDA_HOME=\$CUDA_HOME
ORIGINAL_LD_LIBRARY_PATH=\$LD_LIBRARY_PATH

export CUDA_HOME=/usr/local/cuda-$CUDA_VERSION
export LD_LIBRARY_PATH=\$CUDA_HOME/lib64:\$LD_LIBRARY_PATH"

deactivate_string='
export CUDA_HOME=$ORIGINAL_CUDA_HOME
export LD_LIBRARY_PATH=$ORIGINAL_LD_LIBRARY_PATH

unset ORIGINAL_CUDA_HOME
unset ORIGINAL_LD_LIBRARY_PATH'

mkdir -p ${ACTIVATE_PATH}
echo "${activate_string}" > ${ACTIVATE_PATH}/activate.sh
chmod +x ${ACTIVATE_PATH}/activate.sh

mkdir -p ${DEACTIVATE_PATH}
echo "${deactivate_string}" > ${DEACTIVATE_PATH}/deactivate.sh
chmod +x ${DEACTIVATE_PATH}/deactivate.sh

echo "Everything is DONE!"
echo ""