我自己都不敢相信 2024 年了我还在为这个问题发愁…

笔者从 16 年就开始使用 TensorFlow 了,在大概 17 年的时候转投 PyTorch。TensorFlow 以前一直以安装困难,使用麻烦而饱受诟病。然而都 2024 年了,他还是熟悉的味道,不是用户需要,我是真不想咽下这一口的。不过硬吞还是吞了,不能白吃,顺便记录一下这一坨是如何被咽下去的。

PS 下面的安装过程全都在 Conda 虚拟环境中执行的。

记一次失败的经历

安装一个软件,第一反应是什么?当然是按照官方文档的流程来呀。我信心满满地按照中文官方文档直接使用 pip install tensorflow,然后使用文档中提供的测试命令来测试:

python -c "import tensorflow as tf;print(tf.reduce_sum(tf.random.normal([1000, 1000])))"

诶诶不对呀,这测试的代码咋没有测 GPU 呢?然后网上搜索一番,找到了正确测试 GPU 的代码:

python3 -c "import tensorflow as tf; print(tf.config.list_physical_devices('GPU'))"

结果当然是调用 GPU 失败了。遇到问题当然是优先思考是不是我自己有问题,毕竟对方是谷歌。而且在 TF 的旧版本中我已经知道,安装 TensorFlow,除了安装其本身的包之外,还需要自己单独安装 CUDA、cuDNN 等其他的依赖,但都 2024 了,就不能学学 PyTorch,把依赖都放在 Python Package 内吗?

然后我开始像以前一样使用 apt 从英伟达提供的官方软件源中安装 CUDAcuDNN,安装完毕,确认动态链接库的配置正确后,我尝试再次运行测试代码,然而还是失败了,并且会发出一个警告日志:

Cannot dlopen some GPU libraries. Please make sure the missing libraries mentioned above are installed properly if you would like to use GPU

大意就是「上面列出的这些动态链接库找不到,请安装这些东西后再试」。但奇葩就在于,他根本没给咱列出来,到底是哪些个依赖找不到,我尝试打开 TensorFlow 的 Debug 级别的日志,再跑测试代码,依然没给我显示到底是哪些库没有,就一直在那儿逼逼「上面列出的东西找不到了」。此刻他就像是一个领导,一直重复着「关键的问题在于找到问题的关键」,但就是不给你说问题是什么。官方论坛上的这个哥们儿跟我一样,也是这个问题的受害者。

在瞎猫抓耗子似的一通搜索之后,我偶然有幸、冥冥注定般般地打开了官方英文文档,然后我发现英文文档的测试的代码多出了针对 GPU的测试,但我明明记得中文的文档中是没有的。然后我详细看了英文文档,发现安装方式尽然跟官方中文文档的不一样!我只是通过右上角的语言切换按钮切换了语言而已啊,尽然连内容都变了。稍微认真分析就可知道,中文文档是一个旧版本(具体不知道是哪一版的),只有语言切换为英文看的才是最新的文档,此刻我的内心万马(那个马)奔腾,作为简中用户的我又一次感受到了不公。平复好心情后,继续阅读文档,发现正确的安装方式为:

pip install tensorflow[and-cuda]

虽然被官方中文过期文档坑让我有点愤怒,但现在更多的是喜悦:「啊哈!这肯定是一键安装的命令,看看这 [and-cuda],多么与时俱进的安装方式」。

我又信心满满地创建了一个新的虚拟环境,然后执行了这个命令,从不断滚动的日志中发现明显安装了更多来自 nvidia 的东西。「这下肯定对了」,然后当我测试的时候,他就像 4 月的阿森纳,在掉链子这件事情上从来不掉链子:还是报同样的错!此刻的我已经有点蚌埠住了,甚至一度想放弃:什么掏粪男孩,见鬼去吧!作为一名资深的 AI 环境搭建从业者,我什么时候受到过这种委屈?

冷静下来之后,我开始回朔整个事件,分析到底是哪出错了:

  1. 我确定 pip install tensorflow[and-cuda] 已经安装了所有的依赖,因为从日志中明显已经安装了很多来自 nvidiaCUDA
  2. 我确定报错的原因是找不到某些动态链接库,虽然他并没有给出具体的库名,但我有 9 成把握是 CUDA 相关的。
  3. 安装了又找不到?安装了但找不到…Hmmm….
  4. 所以还是动态链接库搜索路径问题?

我具体看了 pip 的安装日志,找到其中一个 nvidia 开头的包名(比如 nvidia-cudnn-cu11),然后使用 pip show nvidia-cudnn-cu11 找到安装路径,然后发现所有 CUDA 相关的依赖都被安装在了 site-packages/nvidia 目录下:

# in site-packages/nvidia
tree -L 1  
.
├── __init__.py
├── __pycache__
├── cublas
├── cuda_cupti
├── cuda_nvrtc
├── cuda_runtime
├── cudnn
├── cufft
├── curand
├── cusolver
├── cusparse
├── nccl
├── nvjitlink
└── nvtx

除了我所熟悉的 cuda_runtimecudnn 等,还有很多其他的东西,而这些目录下都有动态链接库文件。一不做二不休,直接把所有的目录全部加到 LD_LIBRARY_PATH 中,然后再次测试,终于成功了!

export LD_LIBRARY_PATH=`find {CONDA_ENV_PATH}/envs/py311/lib/python3.11/site-packages/nvidia -name "lib" | tr '\n' ':'`

python3 -c "import tensorflow as tf; print(tf.config.list_physical_devices('GPU'))"

但现在算安装成功了吗?并没有。首先,我需要配置 LD_LIBRARY_PATH,或是修改 /etc/ld.so.conf 才能正常使用,这多少有点别扭。其次这种安装方式有一个问题,如果想在同一个环境中同时安装 PyTorch 和 TensorFlow,是会出问题的,因为他们依赖不同版本的 cuda 包,比如现在装好 TensorFlow 后,再去安装 PyTorch,这些 cuda 相关的软件包会根据 PyTorch 的依赖配置被重新安装且覆盖原来的版本,这可能会导致使用 TensorFlow 时候出现问题。但好在问题的 Root Cause 算是已经找到了。

为什么这么晚才意识到这个问题

我安装过很多不同时期,不同版本的 PyTorch,早期的 PyTorch 不会安装英伟达发布的 Python 依赖(因为那个时候英伟达还没发布过基于 Python Package 的 CUDA),因此 PyTorch 是把所有 CUDA 依赖自行编译成了几个动态链接库(.so)文件中,然后随着 PyTorch 的 Python 的包一起安装。因此安装 PyTorch 是不需要单独安装 CUDA 的。而现在英伟达发布了 CUDA 的 Python 包,所以现在安装 PyTorch 的时候,会直接安装英伟达的 Python CUDA 包了,这种方式跟当前 tensorflow[and-cuda] 的安装方式是一样的。然而,使用 PyTorch 是不需要自己去修改 LD_LIBRARY_PATH 的,这大概率是因为使用 PyTorch 的时候,他会自动把这些动态链接库加到环境变量中,但怎么也没想到 TensorFlow 不会这么做。

稍微好一些的安装方式

对于不需要同时安装 TensorFlow 和 PyTorch 的环境的场景,可以就使用上面的方法,通过修改 LD_LIBRARY_PATH 来解决问题。因为 Featurize 上的环境需要同时安装 PyTorch 和 TensorFlow,因此还需要找到另外的办法。

我的打算是保证 PyTorch 的 CUDA 依赖,然后 TensorFlow 使用旧的手动安装 CUDA 的方法来安装,这样的好处是:

  1. PyTorch 的 CUDA 会从 Python 的包中获取,然后 TensorFlow 的 CUDA 会从系统环境 /usr/loca/cuda 中获取,这样就不会冲突了。
  2. 系统本身就需要安装一个 CUDA 环境。

而 TensorFlow 需要的 CUDA 相关的依赖也比较清晰了,从 sites-packages/nvidia 目录下就可以找到所有依赖,或者从官方源码的 setup.py 中也能找到 [and-cuda] 所需要的依赖。整个安装过程如下:

# 首先跟着 nvidia 官方文档添加官方 apt 源,否则找不到 cuda-12-3 和 libcudnn8
pip install tensorflow # 这里主要不要加 [and-gpu]
sudo apt-get install cuda-12-3 libcudnn8

对,就是这么简单的两行,就可以愉快地使用 TensorFlow 了。之所以之前搞了很久,就是因为安装了错误的 CUDA 和 cuDNN 版本。对照着官方源码中的 setup.py安装对应的版本就成功解决这个问题了。