在 cython 中使用 MKL 失败,在来自 MKL 的共享对象中使用未定义的符号,但直接执行很好

问题描述

我正在使用英特尔数学内核库 (MKL) 用 C 语言编写物理模拟,并希望使用 cython 从 python 代码中直接调用它。 cython 编译本身可以工作(如果示例中不包含 MKL,则程序运行不会出错)并且如果我直接在 gcc 中使用

编译我的 C 代码

gcc -O3 -Wall -m64 -I"${MKLROOT}/include" bar.c -L${MKLROOT}/lib/intel64 -Wl,--no-as-needed -lmkl_intel_lp64 -lmkl_sequential -lmkl_core -lpthread -lm -ldl

效果很好。编译器标志由 MKL Link line Advisor 生成。 但是,如果我现在尝试使用 cython 编译相同的代码,则会收到错误消息

INTEL MKL ERROR: /opt/intel/oneapi/mkl/latest/lib/intel64/libmkl_avx2.so.1: undefined symbol: mkl_sparse_optimize_bsr_trsm_i8. Intel MKL Fatal error: Cannot load libmkl_avx2.so.1 or libmkl_def.so.1.

我还尝试将我的程序(没有主程序)编译到共享库(.so)中,以便在 cython 之外完成 MKL 链接,然后直接链接到它,但是在将该库摆弄成LD_LIBRARY_PATH 我刚刚又出现了同样的行为。

知道如何使链接正确吗?

我可以将使用过的 cblas_ 函数替换为不同的函数(例如 cblas_drot 和更多尝试过的函数)并得到相同的错误

我已经阅读了很多其他问题(很多关于 anaconda 中的 MKL,我的 MKL 是手动安装在 /opt 中的,如上面的路径所示),包括 this,它试图在 Java 中使用 MKL 和得到了同样的错误。 我可以重现关于 nm 语句(在 libmkl_avx2.so.1 中未定义,但在 libmkl_gnu_thread.so 中定义)的相同结果,但我未能将那个问题的答案应用于我的问题。如果我尝试在下面显示-lmkl_gnu_thread 脚本中添加 setup.py,我会得到不同的未实现的依赖项,通过还包括 -fopenmp 来修复旧错误...

更多信息和使用过的文件

LD_LIBRARY_PATH=/opt/intel/oneapi/mkl/latest/lib/intel64:/opt/intel/oneapi/compiler/2021.1.1/linux/lib:/opt/intel/oneapi/compiler/2021.1.1/linux/lib/x64:/opt/intel/oneapi/compiler/2021.1.1/linux/lib/emu:/opt/intel/oneapi/compiler/2021.1.1/linux/compiler/lib/intel64_lin:/opt/intel/oneapi/compiler/2021.1.1/linux/compiler/lib:/opt/intel/oneapi/tbb/2021.1.1/env/../lib/intel64/gcc4.8(由 mkl setvars 脚本设置,由 echo $LD_LIBRARY_PATH 获得) python 可能会对这个变量做些什么奇怪的事情?

readelf -d $MKLROOT/lib/intel64/libmkl_avx2.so.1 | grep NEEDED 0x0000000000000001 (NEEDED) 返回 Shared library: [libdl.so.2],表明它声称只依赖于 libdl (我没有找到我从哪里得到那个命令的问题,但它也是针对这个主题的,只是文件名中缺少 .1)

使用过的文件

setup.py

​​>

(包含第二个版本作为 comamnd,可以这样运行)

from Cython.distutils import build_ext
#from Cython.Build import cythonize
from distutils.extension import Extension
from distutils.core import setup
import numpy

extensions = [
    Extension("foo",["foo.pyx"],include_dirs=[numpy.get_include()],extra_compile_args=["-Wall","-m64","-I\"${MKLROOT}/include\""],extra_link_args=["-fopenmp","-L${MKLROOT}/lib/intel64","-Wl,--no-as-needed","-lmkl_gnu_thread","-lmkl_intel_lp64","-lmkl_sequential","-lmkl_core","-lpthread","-lm","-ldl"])
]

for e in extensions:
    e.cython_directives = {'language_level': "3"} #all are Python-3

# produce the same behavIoUr,first:
setup(ext_modules=cythonize(extensions))
#second
#setup(ext_modules=cythonize(extensions),#      cmdclass = {'build_ext':build_ext})

用于 python setup.py build_ext --inplace

根据评论

编辑一个版本更好地使用关键字。它给出了同样的错误

from Cython.Build import cythonize
from distutils.extension import Extension
from distutils.core import setup
import numpy

extensions = [
    Extension("foo",include_dirs=[numpy.get_include(),"\"${MKLROOT}/include\""],libraries=["mkl_intel_lp64","mkl_sequential","mkl_core","pthread","m","dl"],library_dirs=["${MKLROOT}/lib/intel64"],"-m64"],extra_link_args=["-Wl,])
]

for e in extensions:
    e.cython_directives = {'language_level': "3"} #all are Python-3

setup(ext_modules=cythonize(extensions))

foo.pyx

cimport numpy as np
import numpy as np
import ctypes

cdef extern from "bar.c":  
    void double_elements(int n,double* vec_y)  

def func(np.ndarray[np.double_t,ndim=1] y not None):
    double_elements(<int> y.size//2,<double*> <size_t> y.__array_interface__['data'][0])
    
    return y

bar.c

#include <mkl.h>
#include <stdio.h>

void double_elements(int n,double* x) {
    cblas_dscal(n,2.,x,1);
}


#ifndef PY_VERSION_HEX // compile the main only,if not using cython
int main() {
    double x[2] = {1.,2.};
    double_elements(2,x);
    printf("%g %g\n",x[0],x[1]);
    return 0;
}
#endif

运行.py

​​>

(用于测试,很无聊,只调用函数

import numpy as np
import foo

x = np.array([1.,2.])
y = foo.func(x)

print(x)
print(y)

解决方法

我已经通过使用 MKL 的静态链接解决了这个问题。因为我也没有在 setup.py 脚本中应用来自静态链接(它使用组)的所有命令,所以我已经切换到完全自己编译它

gcc -O3 -Wall -m64 -I"${MKLROOT}/include" -c bar.c -o build/bar.o

cythonize foo.pyx

# exchange /path/to/numpy/ by value given by numpy.get_include()
gcc -Wall -O2 -fstack-protector-strong -fwrapv -fstack-protector-strong -D_FORTIFY_SOURCE=2 -fPIC -I/path/to/numpy/core/include -I/usr/include/python3.8 -c foo.c -o build/foo.o -DNPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION

gcc -shared build/foo.o build/bar.o -o foo.cpython-38-x86_64-linux-gnu.so -Wl,--start-group ${MKLROOT}/lib/intel64/libmkl_intel_lp64.a ${MKLROOT}/lib/intel64/libmkl_sequential.a ${MKLROOT}/lib/intel64/libmkl_core.a -Wl,--end-group -lpthread -lm -ldl

这对应于安装脚本完成的编译步骤(不检查更改日期)并删除了一些编译标志(它使用了许多重复项)。