非root非源码安装高版本gcc的终极方法-Miniconda

3,644 阅读6分钟

前一篇文章那种方法虽然当前运行没有什么问题,但潜在风险很大,在生产环境断然是无法适用的,所以还是老老实实按照别人的建议,通过conda的方式来安装。可能有人已经非常熟悉这些流程和操作了,这里主要是为了记录一下,方便初次接触的人快速上手。

接触conda还是因为python虚拟环境,比如在python2的机器上想运行python3的工程和应用,conda就是一个非常不错的选择。docker非常庞大;virtialenv用起来非常繁琐,需要自己安装各种依赖库;conda则通过交互命令让环境隔离这一过程非常顺滑。现在通过能在隔离环境中使用gcc、ninja、cmake这些强大的编译工具,conda已经变得异常强大,不仅仅限于python包管理和部署了,还没有使用过的人可以赶紧体验一下。

conda是一系列工具的总称,常用的有Anaconda,相对比较庞大,但做环境移植相对比较方便,因为依赖库比较全面;Miniconda,可以说是精简后的anaconda;Miniforge,不同编译参数下的Miniconda,有些机器Miniconda安装不了,只有Miniforge才能成功!

基本使用

可以说非常简单明了,不过最好带上国内的channel地址,这个channel就是仓库镜像:

conda install gcc -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge

安装python依赖包的时候,一般通过:

pip install mumpy -i https://pypi.tuna.tsinghua.edu.cn/simple

这两个仓库地址是不同的!pypi一般是python包仓库地址,有些包两个仓库都有,有些只有一方才有, 比如cmakeninja通过pip就能安装,但gcc就只能通过conda安装。

指定版本

默认安装gcc是最新版本的gcc-12.1,相当高的版本了,可能不是我们需要的,安装指定版本的gcc自然就是我们的需要了,要安装指定版本先得罗列所有可安装版本:

$ conda search gcc
Loading channels: done
# Name                       Version           Build  Channel             
gcc                            8.5.0      h85e6f30_1  conda-forge         
gcc                            8.5.0      h85e6f30_1  anaconda/cloud/conda-forge
gcc                            8.5.0     h85e6f30_10  conda-forge         
gcc                            8.5.0     h85e6f30_10  anaconda/cloud/conda-forge
gcc                            8.5.0      h85e6f30_2  conda-forge         
gcc                            8.5.0      h85e6f30_2  anaconda/cloud/conda-forge
gcc                            8.5.0      h85e6f30_3  conda-forge         
gcc                            8.5.0      h85e6f30_3  anaconda/cloud/conda-forge
...

比如要安装8.5.9,仓库中没有那就安装不了,所以一定要写对版本号

$ conda install 'gxx=9.5.0' -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge

写成conda install 'gxx=9.5'可是安装不了的。版本指定还有不少花哨的用法,可以参照这篇帖子。c++的编译工具的名字和熟悉的gcc-c++不同,而是叫gxx

问题解决

1. cmake生成错误

cmake ..
-- The CXX compiler identification is GNU 9.5.0
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - failed
-- Check for working CXX compiler: /home/zhouwenyuan/.conda/envs/gcc9.5/bin/c++
-- Check for working CXX compiler: /home/zhouwenyuan/.conda/envs/gcc9.5/bin/c++ - broken
CMake Error at /usr/share/cmake/Modules/CMakeTestCXXCompiler.cmake:59 (message):
  The C++ compiler

    "/home/zhouwenyuan/.conda/envs/gcc9.5/bin/c++"

  is not able to compile a simple test program.

  It fails with the following output:

    Change Dir: /home/zhouwenyuan/llama.cpp/build/CMakeFiles/CMakeTmp
    
    Run Build Command(s):/usr/bin/gmake -f Makefile cmTC_d422e/fast && /usr/bin/gmake  -f CMakeFiles/cmTC_d422e.dir/build.make CMakeFiles/cmTC_d422e.dir/build
    gmake[1]: Entering directory '/home/zhouwenyuan/llama.cpp/build/CMakeFiles/CMakeTmp'
    Building CXX object CMakeFiles/cmTC_d422e.dir/testCXXCompiler.cxx.o
    /home/zhouwenyuan/.conda/envs/gcc9.5/bin/c++    -o CMakeFiles/cmTC_d422e.dir/testCXXCompiler.cxx.o -c /home/zhouwenyuan/llama.cpp/build/CMakeFiles/CMakeTmp/testCXXCompiler.cxx
    Linking CXX executable cmTC_d422e
    /usr/bin/cmake -E cmake_link_script CMakeFiles/cmTC_d422e.dir/link.txt --verbose=1
    /home/zhouwenyuan/.conda/envs/gcc9.5/bin/c++ CMakeFiles/cmTC_d422e.dir/testCXXCompiler.cxx.o -o cmTC_d422e 
    /home/zhouwenyuan/.conda/envs/gcc9.5/bin/../lib/gcc/x86_64-conda-linux-gnu/9.5.0/../../../../x86_64-conda-linux-gnu/bin/ld: /home/zhouwenyuan/.conda/envs/gcc9.5/bin/../lib/gcc/x86_64-conda-linux-gnu/9.5.0/libstdc++.so: undefined reference to `memcpy@GLIBC_2.14'
    /home/zhouwenyuan/.conda/envs/gcc9.5/bin/../lib/gcc/x86_64-conda-linux-gnu/9.5.0/../../../../x86_64-conda-linux-gnu/bin/ld: /home/zhouwenyuan/.conda/envs/gcc9.5/bin/../lib/gcc/x86_64-conda-linux-gnu/9.5.0/libstdc++.so: undefined reference to `aligned_alloc@GLIBC_2.16'
    /home/zhouwenyuan/.conda/envs/gcc9.5/bin/../lib/gcc/x86_64-conda-linux-gnu/9.5.0/../../../../x86_64-conda-linux-gnu/bin/ld: /home/zhouwenyuan/.conda/envs/gcc9.5/bin/../lib/gcc/x86_64-conda-linux-gnu/9.5.0/libstdc++.so: undefined reference to `clock_gettime@GLIBC_2.17'
    collect2: error: ld returned 1 exit status
    gmake[1]: *** [CMakeFiles/cmTC_d422e.dir/build.make:99: cmTC_d422e] Error 1
    gmake[1]: Leaving directory '/home/zhouwenyuan/llama.cpp/build/CMakeFiles/CMakeTmp'
    gmake: *** [Makefile:127: cmTC_d422e/fast] Error 2

这是一个可忽略的错误,解决办法:

cmake .. -DCMAKE_CXX_COMPILER_WORKS=TRUE

2. 编译错误

$ make -j8
[  2%] Built target BUILD_INFO
[  5%] Building C object CMakeFiles/ggml.dir/ggml.c.o
[  7%] Building C object CMakeFiles/ggml.dir/k_quants.c.o
In file included from /home/zhouwenyuan/abc.c:7:
/home/zhouwenyuan/abc.h:26:15: error: expected declaration specifiers or '...' before 'sizeof'
   26 | static_assert(sizeof(block_q2_K) == 2*sizeof(ggml_fp16_t) + QK_K/16 + QK_K/4, "wrong q2_K block size/padding");
      |               ^~~~~~
In file included from /home/zhouwenyuan/abc.c:1:
/home/zhouwenyuan/abc.h:26:15: error: expected declaration specifiers or '...' before 'sizeof'
   26 | static_assert(sizeof(block_q2_K) == 2*sizeof(ggml_fp16_t) + QK_K/16 + QK_K/4, "wrong q2_K block size/padding");

解决办法:

替换abc.c中的static_assert_Static_assert

3. 链接错误

在编译过程中的链接阶段:

~/.conda/envs/gcc9.5/bin/../lib/gcc/x86_64-conda-linux-gnu/9.5.0/../../../../x86_64-conda-linux-gnu/bin/ld: ~/.conda/envs/gcc9.5/bin/../lib/gcc/x86_64-conda-linux-gnu/9.5.0/libstdc++.so: undefined reference to `memcpy@GLIBC_2.14'
~/.conda/envs/gcc9.5/bin/../lib/gcc/x86_64-conda-linux-gnu/9.5.0/../../../../x86_64-conda-linux-gnu/bin/ld: ~/.conda/envs/gcc9.5/bin/../lib/gcc/x86_64-conda-linux-gnu/9.5.0/libstdc++.so: undefined reference to `aligned_alloc@GLIBC_2.16'
~/.conda/envs/gcc9.5/bin/../lib/gcc/x86_64-conda-linux-gnu/9.5.0/../../../../x86_64-conda-linux-gnu/bin/ld: ~/.conda/envs/gcc9.5/bin/../lib/gcc/x86_64-conda-linux-gnu/9.5.0/libstdc++.so: undefined reference to `clock_gettime@GLIBC_2.17'

通过一番艰苦搜索,终于在这篇帖子找到了办法,原来是因为安装的glibc版本太低(2.12),但链接符号的库版本过高(2.14,2.16,2.17)导致了这个问题,所以只需安装高版本的glibc库即可。

解决办法:

conda install 'sysroot_linux-64=2.17' -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge

这一问题也让我突然明白,前面的两个错误应该也是引用了低版本的sysroot导致的,把代码和参数调回去,果然问题没有再出现。

另外这篇帖子也给人启发,原来Miniforge中那么多的发布版本,其中的那个Mamba能够更快地解析远程仓库信息,难怪conda每次运行install都那么慢!而且miniconda和miniforge的解析也容易出现问题,比如上面的错误,安装gxx-9.5.0并竟然没有自动引用高版本的sysroot


7.21新问题

果然还是有问题!在本地已经正确运行的情况下,我们用conda pack打包当前环境并移植到生产隔离环境,出现了2个问题:

4. 运行错误: version GLIBCXX_3.4.20 not found

安装好gxx之后,本地编译了一个可执行文件main进行验证,编译链接现在十分顺利,但运行可执行文件的时候报了以下错误:

bin/main: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20` not found (required by bin/main)
bin/main: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21` not found (required by bin/main)
bin/main: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.22` not found (required by bin/main)

但是miniconda环境中的libstdc++.so.6明明是有的:

$ strings /opt/miniconda/lib/libstdc++.so.6 | grep GLIBCXX_3.4.2
GLIBCXX_3.4.20
GLIBCXX_3.4.21
GLIBCXX_3.4.22

显然,可执行文件运行的时候只在系统目录/lib64/下查找了libstdc++.so.6文件,却没有在conda环境中的目录/opt/miniconda/lib查找这个文件,真是匪夷所思!conda理应为我们设置好查找目录的,然而可执行文件就是没有在预期的目录中查找.so文件。当然这个问题本身解决起来其实很容易。

解决办法:

$ LD_LIBRARY_PATH=/opt/miniconda/lib bin/main ...

后来我们发现在正确运行可执行文件的机器上,可执行文件中包含了/opt/miniconda/lib路径,而错误运行的机器上编译的可执行文件没有包含这个加载路径:

$ strings bin/main | grep 'miniconda'
/opt/miniconda/lib

百思不得其解!我们没有改动任何东西,就只是用了打包工具打包的呀,为什么编译的时候没有把加载路径生成到可执行文件中呢?尝试了很多次很多方法,我们终于怀疑是conda pack有问题

把miniconda中表示环境的目录直接用tar压缩的方式,移植到隔离环境,竟然一点问题没有!重新编译的可执行文件中也正确包含了共享库加载路径!现在还不清楚,conda pack究竟做了什么导致了这个问题,反正用手动压缩的打包方式,运行是没有任何问题的。真是让人恨不得把这机器给砸了!总之,最好不要用conda pack这个工具去迁移环境!

还发现个复现这个问题的方式:如我们把环境的目录abc压缩,然后在移植环境里解压后改名成abcd也会出现找不到共享库的问题,这种方式对于理解系统搜索共享库目录有一定帮助。

5. 运行错误: Could not open shared library libcudart.so.12

这个问题不是直接运行二进制可执行文件遇到的,而是在运行python程序时遇到的。项目用到了一个python依赖包a,a在conda的隔离环境下通过在线安装的方式在验证机器上顺利安装并且运行python程序没有问题。但移植到生产环境刚报无法打开共享库文件libcudart.so.12的错误。

这里有一个重要的条件:依赖包a是.tar.gz格式的,而不是常见的whl格式。很多人可能并没有注意格式差别的问题,只要能够顺利安装并正确运行就行,然而通过这次的问题才明白这两种格式是为了解决什么问题的。

.tar.gz格式就是源码安装的格式,或者说要需要编译安装,而相应的,.whl格式则相当于预编译,安装时直接将文件中的二进制文件拷贝到python的site-packages目录下。所以安装.whl格式依赖包一般非常快。

这个问题是这样的,依赖包a编译生成了liba.so,在python程序启动后liba.so是正确加载的,但liba.so依赖的libcudart.so.12却没有找到!在cuda库目录下(一般为/usr/local/cuda/lib)也确实没有libcudart.so.12,只有libcudart.so.11。我们这才想起来,验证python依赖的机器上的CUDA是12.1而移植机器上的CUDA是11.4!这一发现让我们意识到环境一致性问题的重要,既然是移植就应当保证环境的完全一致才对。只不过有时候因为各种各样的情况,稍不注意就会导致环境的不一致,关键是这一过程不易察觉,真是防不胜防。

解决办法:

在验证机器上降级CUDA版本。这个问题有时也不那么好解决,因为显卡硬件不同,没法确定低版本的CUDA能不能正确运行在验证机器上,所幸我们这次降级没有造成别的问题。

在验证机器上正确安装后,首先跑一遍程序,确保一切正常运行。接着只需要打包site-packages目录下的依赖包a的目录,然后在迁移机器上直接替换即可。再运行验证一遍,终于没有再出现其它问题了!结束吧,再也不想搞环境迁移这一坨了!