MKL 和 openBLAS 交互 - 关于链接的问题

问题描述 投票:0回答:1

我正在使用动态链接到 BLAS 通用版本的二进制文件 (R), 例如(在很多情况下)这是 openBLAS。

现在,在 R 内部,我基本上使用

dlopen()
动态加载另一个共享库 (libtorch.so)。结果 libtorch 静态链接到 MKL BLAS。

我对静态和动态链接的理解是,这不应该是一个问题。即,因为 libtorch 静态链接到 MKL。当调用 libtorch 的代码时,它总是更喜欢它自己的符号,而不是其他可能动态加载的类似命名的符号。

确实,这似乎是常见的行为。例如,如果我从游戏中取出 BLAS 和 LibTorch,我可以编译一个链接到共享库 libA 的可执行文件,实现例如

print()
和另一个静态链接到
libB
的共享库
libA
。当从
libB
调用代码时,它将正确地从它自己的
libA
版本中调用定义。

但是 libtorch/MKL 和 openBLAS 不会发生这种情况。如果我编译一个动态链接到 libTorch 和 openBlas 的可执行文件,那么 libtorch 将开始使用 openBLAS 例程而不是静态链接的 MKL 例程。

例如:

#0  0x00007ffff5537da0 in sgemm_ () from /lib/x86_64-linux-gnu/libopenblas.so.0
#1  0x00007fffde5385d6 in at::native::cpublas::gemm(at::native::TransposeType, at::native::TransposeType, long, long, long, float, float const*, long, float const*, long, float, float*, long) () from /home/rstudio/data/torch/build-lantern/libtorch/lib/libtorch_cpu.so
#2  0x00007fffde67c139 in at::native::addmm_impl_cpu_(at::Tensor&, at::Tensor const&, at::Tensor, at::Tensor, c10::Scalar const&, c10::Scalar const&) () from /home/rstudio/data/torch/build-lantern/libtorch/lib/libtorch_cpu.so
#3  0x00007fffde67d475 in at::native::structured_mm_out_cpu::impl(at::Tensor const&, at::Tensor const&, at::Tensor const&) ()
   from /home/rstudio/data/torch/build-lantern/libtorch/lib/libtorch_cpu.so
#4  0x00007fffdf42309b in at::(anonymous namespace)::wrapper_CPU_mm(at::Tensor const&, at::Tensor const&) ()
   from /home/rstudio/data/torch/build-lantern/libtorch/lib/libtorch_cpu.so
#5  0x00007fffdf423123 in c10::impl::wrap_kernel_functor_unboxed_<c10::impl::detail::WrapFunctionIntoFunctor_<c10::CompileTimeFunctionPointer<at::Tensor (at::Tensor const&, at::Tensor const&), &at::(anonymous namespace)::wrapper_CPU_mm>, at::Tensor, c10::guts::typelist::typelist<at::Tensor const&, at::Tensor const&> >, at::Tensor (at::Tensor const&, at::Tensor const&)>::call(c10::OperatorKernel*, c10::DispatchKeySet, at::Tensor const&, at::Tensor const&) () from /home/rstudio/data/torch/build-lantern/libtorch/lib/libtorch_cpu.so
#6  0x00007fffdf1eaa70 in at::_ops::mm::redispatch(c10::DispatchKeySet, at::Tensor const&, at::Tensor const&) ()
   from /home/rstudio/data/torch/build-lantern/libtorch/lib/libtorch_cpu.so

即使 libtorch_cpu.so 包含它自己的

sgemm_
版本,也会发生这种情况,例如:

nm libtorch/lib/libtorch_cpu.so | grep "T sgemm_"
0000000006c531b0 T sgemm_
0000000006c53870 T sgemm_64
0000000006c53870 T sgemm_64_

我的问题是,在什么情况下动态加载库中的符号可以位于静态加载库前面?我肯定在这里遗漏了一些重要的东西,任何建议都会非常有帮助。

可重现的例子:
#include <torch/torch.h>
#include <iostream>
#include <cblas.h>

extern "C" void execute () {
  for (auto i = 1; i < 10; i++) {
    torch::Tensor tensor = torch::randn({2000, 2000});
    auto k = tensor.mm(tensor);  
  }
}

int main() {
  
  int m = 3; // rows of A
  int n = 3; // cols of A
  
  // Matrix A (m x n) in row-major order
  double A[] = {1.0, 2.0, 3.0,
                4.0, 5.0, 6.0,
                7.0, 8.0, 9.0};
  
  // Vector x (size n)
  double x[] = {1.0, 1.0, 1.0};
  
  // Result vector y (size m), initially zero
  double y[] = {0.0, 0.0, 0.0};
  
  // Scalar multipliers
  double alpha = 1.0, beta = 0.0;
  
  // Perform y = alpha * A * x + beta * y
  cblas_dgemv(CblasRowMajor, CblasNoTrans, m, n, alpha, A, n, x, 1, beta, y, 1);

  execute();
  
  return 0;
}

使用 CMakeLists.txt

set(CMAKE_POSITION_INDEPENDENT_CODE ON)
cmake_minimum_required(VERSION 3.0 FATAL_ERROR)
project(example)

find_package(Torch REQUIRED)
find_package(BLAS)

add_executable(example example.cpp)
target_link_libraries(example "${TORCH_LIBRARIES}" "${BLAS_LIBRARIES}")
set_property(TARGET example PROPERTY CXX_STANDARD 17)

LibTorch可以从pytorch网站直接下载链接

运行

mkdir build && cd build
cmake .. -DCMAKE_PREFIX_PATH=<path to libtorch>
cmake --build .
c++ c blas intel-mkl linkage
1个回答
0
投票

我的问题是,在什么情况下动态加载库中的符号可以位于静态加载库前面?我肯定在这里遗漏了一些重要的东西,任何建议都会非常有帮助。

我认为您的情况并非如此。 事实上,我认为它不会发生——静态链接在链接时而不是运行时解析符号。 相反,我认为您正在使用的 LibTorch 共享库的性质存在混淆。 我的检查表明它嵌入了许多 MKL 例程,并为它们提供了正常和动态符号。 这并不意味着库中其他地方对这些符号的引用是预先解决的。 事实上,它表明 LibTorch DSO 是针对 MKL DSO 构建的,这排除了您所想到的静态链接。 LibTorch 还提供

sgemm

sgemm_
作为(可重定位)动态符号,因此您可以预期,即使是在同一个 DSO 中,动态链接器也会解析对这些符号的引用。
动态链接很复杂,有时也不直观。  我通常参考 Ulrich Drepper 的论文《如何编写共享库》,它比 ELF 规范本身更容易理解。  由于它与您的问题相关,因此要考虑的细节是动态符号解析的

查找范围

,Drepper 在其第 1.5.4 节中对此进行了讨论。 对于任何给定的符号查找,查找范围可以被视为要搜索的已加载共享对象的有序序列。 其中 SO 的顺序受多种因素影响,但主要因素是每个共享对象中 DT_NEEDED 条目的顺序。 首先近似,可执行文件首先是,然后是它自己的直接 DT_NEEDED 对象,然后是

他们的

DT_NEEDED
对象,
etc
,按广度优先顺序。 如果您想观察共享库本身提供符号,但其自己对该符号的引用解析为由不同对象提供的符号,那么实现它的最简单方法是让可执行文件提供相同的符号。 示例:
main.c

#include <stdio.h> int share(void); int do_something(void) { printf("in main\n"); } int main(void) { share(); return 0; }

共享.c

#include <stdio.h>

int do_something(void) {
    printf("in shared\n");
}

int share(void) {
    return do_something();
}

生成文件

CFLAGS= -fpic
LIBS = -L. -ldemo
MAIN_OBJS = main.o

all: prog libdemo.so

prog: $(MAIN_OBJS) libdemo.so
    $(CC) -o $@ $(MAIN_OBJS) $(LIBS)

libdemo.so: shared.o
    $(CC) -o $@ -shared $^

命令: $ make cc -fpic -c -o main.o main.c cc -fpic -c -o shared.o shared.c cc -o libdemo.so -shared shared.o cc -o prog main.o -L. -ldemo $ LD_LIBRARY_PATH=$(pwd) ./prog in main $

请注意,在这种情况下,主程序 
prog

和共享库

libdemo.so
 都提供函数 
do_something()

。 当调用共享库中的函数

shared()
时,调用的是主程序中的
do_something()
当相关符号由不同的共享库定义而不是由主可执行文件定义时,可以实现类似的效果。  无论哪个共享库在程序的直接和间接依赖项的广度优先遍历中首先出现,它都会(通常)为所有涉及的 DSO 提供定义。  通常,这就是您想要的。
还有一些我没有提到的额外考虑因素,
dlopen()
特别提出了其中的几个,但它们只是在我所呈现的相同一般情况下添加了可能的调制和例外情况。 它们不会从根本上改变动态名称解析行为。

© www.soinside.com 2019 - 2024. All rights reserved.