记录一个conda相关的编译与链接错误 "undefined symbol:__cxa_call_terminate"
问题复现
之前在自己电脑上折腾conda环境时,安装了pytorch-quantization这个库之后运行时总会报一个错误:
from pytorch_quantization import cuda_ext ImportError: /home/blackcat/program/anaconda3/envs/modelopt/lib/python3.12/site-packages/pytorch_quantization/cuda_ext.cpython-312-x86_64-linux-gnu.so: undefined symbol: __cxa_call_terminate
这个问题只会在以下条件全部满足时出现:
- 使用conda环境(python=3.12.11);
- 使用源码编译安装了
pytorch-quantization。
之前在安装torch/torch相关的各种库时经常会遇到各种各样的未定义符号错误,一般都是因为运行时的cudart相关链接库版本和编译时的版本不匹配导致的,这种问题可以通过重编译解决。
但是这次的问题本身就是在自己编译后出现的,并且和一般的使用whl安装所导致的ABI不匹配问题也不一样。并且,最奇怪的一点在于,当我手动用python -m构建了一个虚拟环境之后再安装,就不会出现上述错误。
于是开始了一番折腾。
问题分析
首先,undefined symbol: __cxa_call_terminate是什么?
结论很简单,__cxa_call_terminate是一个经过名称修饰后的CPP ABI,一般定义在libstdc++.so内,libstdc++.so是个非常常见的动态链接库,linux环境下/usr/lib都有这个文件(还会有多个版本),因此可以确定这个问题和cudart无关,和cpp的编译相关。
1 | |
随后,开始寻找在手动安装库时的输出,看一下其调用g++或c++时的输出,发现其调用的是原生系统的g++。
1 | |
接着使用ldd查看报错的文件的链接依赖:
1 | |
发现编译得到的.so文件默认链接的不是系统的libstdc++.so而是在conda环境文件夹内的一个so文件。再通过readelf查看其rpath看看是否强制制定了动态库的寻找路径:
1 | |
果不其然,RPATH被指定为了conda路径下的一个文件夹,那么很有可能是这个被强制指定的.so文件的问题。并且,当我们去ldd查看使用-m指令生成的venv环境下编译好的同名文件时,其路径是系统/usr/lib内的文件,更加验证了这个猜想。
1 | |
问题解决
conda会为每个环境单独配置自己的/lib路径,以便在不同操作系统上对环境进行迁移与重现。如果系统本身的g++版本比较新,编译源码得到了所谓的__cxa_call_terminate ABI,而conda为环境提供的libstdc++.so版本较旧,没有__cxa_call_terminate ABI,便会出现上述错误。
而由于系统的/usr/lib和系统的g++一定是匹配的,所以在使用python原生的venv时便不会报错。那解决方案其实有以下可选几种:
- 推荐使用: 使用
conda install -c conda-forge gxx_linux-64指令安装相关编译工具,在编译时也使用conda自带的编译工具并匹配环境的lib; - 使用Python原生venv,即使用系统编译器和
/usr/lib路径; - 手动为编译好的文件修改链接地址,例如使用
LD_PRELOAD=/usr/lib/...。
在使用方法一,安装编译工具后,再对源码进行编译与安装,可以看到输出的日志内,使用的编译器已经换为了conda自己的编译器。
1 | |
此时编译后的包可以正常运行,问题解决。
总结
之前从来没想过conda会为每个环境分配各自的动态库环境,并且在某个conda环境内使用python/pip/setuptools进行安装时调用的却是操作系统默认的编译器,如果两者版本不匹配,就很有可能导致ABI问题。感觉在需要大量手动通过源码安装的环境下,conda已经没办法带来什么便捷了,还不如手动去创建一个venv开发(然后就发现自己的venv环境杂乱无章像个垃圾桶)。
同时关于python/pytorch和C++的交叉调用可以看看我博客内的另外两篇文章: 使用Pytorch的cpp_extension调用外部C函数和Linux环境下在CPP项目内引用torch相关库完成编译。
参考文献
参考了: