【问题标题】:Separate the host-side and CUDA-device-side versions of library分离库的主机端和 CUDA 设备端版本
【发布时间】:2020-04-09 02:21:03
【问题描述】:

我有一个包含一些 __host__ __device__ 函数的库。我还有一个 #ifdef __CUDACC__ 小工具,它可以确保常规 C++ 编译器看不到 __host__ __device__,从而可以编译这些函数。

现在,我想在普通 C++ 静态库文件(Linux 上的.a)中使用库函数的编译主机端版本——我什至希望在 CUDA 不可用时可以编译该库;我希望在单独的静态库中编译设备端版本。

我快到了(我想),但遇到链接错误。以下是此类库的玩具源代码、测试程序(调用设备端和主机端版本的函数)和我使用的构建命令。

我做错了什么?


  • my_lib.hpp(库头):
#ifdef __CUDACC__
__host__ __device__
#endif
void foo(int*x, int* y);
int bar();
  • my_lib.cu(库来源):
#include "my_lib.hpp"

#ifdef __CUDACC__
__host__ __device__
#endif
void foo(int*x, int* y)  { *x = *y; }

int bar() { return 5; }
  • main.cu(测试程序):
#include "my_lib.hpp"

__global__ void my_kernel() {
  int z { 78 };
  int w { 90 };
  foo(&z,&w);
}

int main() {
  int z { 123 };
  int w { 456 };
  foo(&z,&w);
  my_kernel<<<1,1>>>();
  cudaDeviceSynchronize();
  cudaDeviceReset();
}

我的构建命令:

c++ -c -x c++ -o my_lib-noncuda.o my_lib.cu
ar qc my_lib-noncuda.a my_lib-noncuda.o
ranlib my_lib-noncuda.a
nvcc -dc -o my_lib-cuda.o my_lib.cu
ar qc my_lib-cuda.a my_lib-cuda.o
ranlib my_lib-cuda.a
nvcc -dc -o main.rdc.o main.cu
nvcc -dlink -o main.o main.rdc.o my_lib-cuda.a
c++ -o main main.o my_lib-noncuda.a -lcudart

以及我得到的错误 - 最后一个,链接,命令:

/usr/bin/ld: main.o: in function `__cudaRegisterLinkedBinary_39_tmpxft_00003f88_00000000_6_main_cpp1_ii_e7ab3416':
link.stub:(.text+0x5a): undefined reference to `__fatbinwrap_39_tmpxft_00003f88_00000000_6_main_cpp1_ii_e7ab3416'
/usr/bin/ld: main.o: in function `__cudaRegisterLinkedBinary_41_tmpxft_00003f69_00000000_6_my_lib_cpp1_ii_ab44b3f6':
link.stub:(.text+0xaa): undefined reference to `__fatbinwrap_41_tmpxft_00003f69_00000000_6_my_lib_cpp1_ii_ab44b3f6'
collect2: error: ld returned 1 exit status

注意事项:

  • 我在 Devuan GNU/Linux 上使用 CUDA 10.1 和 g++ 9.2.1。
  • 这是对已删除问题的“跟进”; @talonmies 评论说我最好准确地展示我的所作所为;这在一定程度上改变了问题。
  • 有点相关的问题:this one

【问题讨论】:

  • @talonmies:现在我向你展示我正在做的事情。
  • 该示例超出了您描述的错误。在该构建序列中的任何地方都不应该发出 main 并且应该也应该有一个 main not found 错误,除非我读错了
  • @talonmies:嗯,错误就是这样,虽然我明白你对main()-dc 的意思。问题是,如果我删除 -dc,我会得到一个编译错误:nvcc -o main.o main.cu 导致 Unresolved extern function '_Z3fooPiS_'
  • 我对你想做什么感到困惑。我以为您想将“普通 C++ 静态库文件”链接到 C++ 程序。但正如我所见,您希望在最后一步中使用 C++ 编译器仅用于链接,并且仍然存在所有 CUDA 内容。也许这就是你要找的东西:devblogs.nvidia.com/… - “高级用法:使用不同的链接器”部分
  • @Shadow:不完全是。我想要一个“拆分库”:一个包含主机端版本函数的.a 文件,无论CUDA如何,我都可以在常规C++链接中使用它;和另一个.a 文件,它是设备端版本的函数,我可以将其与从内核中调用这些函数的CUDA 代码链接。测试程序举例说明了第二种使用方式。

标签: c++ cuda linker static-libraries unresolved-external


【解决方案1】:

以下是创建两个库的方法,一个仅包含 CUDA 设备函数,另一个仅包含主机函数。 您可以省略“复杂的”#if#ifndef 守卫。但是,您的库中也会有“非 CUDA 代码”my_lib-cuda.a

对于其他问题,请参阅@talonmies 社区 wiki 答案或参考我已经在 cmets 中发布的链接:https://devblogs.nvidia.com/separate-compilation-linking-cuda-device-code/ - “高级用法:使用不同的链接器”部分。

my_lib.cu

#include "my_lib.hpp"

#ifdef __CUDA_ARCH__
__device__
#endif
#if (defined __CUDA_ARCH__) || (not defined __CUDACC__)
void foo(int*x, int* y)  { *x = *y; }
#endif

#ifndef __CUDACC__
int bar() { return 5; }
#endif

库的构建过程保持不变:(仅将 ar qc 更改为 ar rc 以替换现有文件,因此在不事先删除库的情况下重新构建时不会出错)

c++ -c -x c++ -o my_lib-noncuda.o my_lib.cu
ar rc my_lib-noncuda.a my_lib-noncuda.o
ranlib my_lib-noncuda.a
nvcc -dc -o my_lib-cuda.o my_lib.cu
ar rc my_lib-cuda.a my_lib-cuda.o 
ranlib my_lib-cuda.a 

构建 CUDA 程序:(通过仅使用 nvcc 而不是 c++ 进行简化,或者查看 @talonmies 社区 wiki 答案)

nvcc -dc main.cu -o main.o
nvcc main.o my_lib-cuda.a my_lib-noncuda.a -o main

如上所述,如果您还省略了my_lib.cu 中的#if#ifndef,则可以省略指向my_lib-noncuda.a 的链接。

构建一个 C++ 程序:(假设在 main.cu 的 CUDA 代码周围有 #ifdef __CUDACC__ 守卫)

c++ -x c++ -c main.cu -o main.o
c++ main.o my_lib-noncuda.a -o main

【讨论】:

  • 问题是,如果我采用这种方法,我会遇到冲突。也就是说,假设我的应用程序直接使用设备端的 my_lib 和主机端的 other_lib。现在, other_lib 又在主机端使用 my_lib。当我尝试将所有内容链接在一起时,将在两个冲突的地方找到主机端功能,不是吗?
  • 如果你像我一样做,my_lib 将只包含设备或主机代码。所以你的例子没有意义。冲突始终是你必须注意的事情,而且我所做的没有额外的潜在冲突。
  • 所以,这失败了。也就是说,用于构建 CUDA 程序的两行中的第二行失败,并显示:nvlink error : Multiple definition of '_Z3fooPiS_' in 'my_lib-cuda.a:my_lib-cuda.rdc.o', first defined in 'my_lib-cuda.a:my_lib-cuda.o' nvlink fatal : merge_elf failed - 除非您的更改与 @talonmies 所写的内容相关。
  • 哦。你说得对。但前提是我重复一些编译步骤。在第一次编译时,一切对我来说都很好。但这似乎是创建图书馆的问题。在第二次运行时,my_lib-cuda.a 没有被重写,而是被附加。
  • 确实,问题在于我在问题中创建两个库的方式。我需要做点别的……但请记住,主机和设备代码的分离是问题的症结所在,而不是示例程序的编译。
【解决方案2】:

让我们将您的示例修改为我认为您的实际用例。修改将main()放入.cpp文件,由g++编译,将CUDA代码放入单独的.cu文件,由nvcc编译。这对于使您的两个库设置工作很重要;并且合理,因为“main 包含需要单独编译和链接的 CUDA 内核”是 nvcc 编译模型的特殊极端情况。

重组后的代码:

main.cu

include "my_lib.hpp"

__global__ void my_kernel() {
  int z { 78 };
  int w { 90 };
  foo(&z,&w);
}

int cudamain()
{
  my_kernel<<<1,1>>>();
  return 0;
}

main.cpp

#include <cuda_runtime_api.h>
#include "my_lib.hpp"

extern int cudamain();

int main() {
  int z { 123 };
  int w { 456 };
  foo(&z,&w);
  cudamain();
  cudaDeviceSynchronize();
  cudaDeviceReset();
}

所有其他文件都保留在问题中。

现在构建程序所需的命令是:

c++ -c -x c++ -o my_lib-noncuda.o my_lib.cu
ar qc my_lib-noncuda.a my_lib-noncuda.o
ranlib my_lib-noncuda.a

nvcc -std=c++11 -dc -o my_lib-cuda.rdc.o my_lib.cu
ar qc my_lib-cuda.a my_lib-cuda.rdc.o
ranlib my_lib-cuda.a

# Until this line - identical to what you have tried in your question

nvcc -std=c++11 -c -rdc=true main.cu -o main.cu.o 
nvcc -dlink -o main.o main.cu.o my_lib-cuda.a

c++ -std=c++11 -o main main.cpp main.o main.cu.o -I/path/to/cuda/include \
    -L/path/to/cuda/lib64 my_lib-cuda.a my_lib-noncuda.a -lcudart -lcudadevrt

需要牢记的重要一点是主机端组件需要在构建中进行。因此,您必须将 CUDA 主机代码的nvcc 输出传递给主链接,并且您还必须将您的 CUDA 端库添加到主链接。否则,您的代码的主机端运行时 API 支持将丢失。另请注意,您必须链接设备运行时库才能使其工作。

【讨论】:

  • 这里不需要链接my_lib-noncuda.a,不是吗?
  • 是的。 main调用host/device函数的host版本
  • 我只是复制了确切的代码和编译命令,只删除了针对my_lib-noncuda.a 的链接。我没有错误。
  • 老实说,我没有看主机方面,只是缺少依赖项和缺少 main
  • @talonmies;库仍然存在重复 - 这是我自己在问题中构建尝试的一个问题。就像@Shadow 建议的那样——my_lib-cuda.a 拥有主机端和设备端版本的foo(),以及主机端版本的bar()。如果您在最后一个构建命令中切换链接顺序 - 首先是 -cuda.a,然后是 -noncuda.a(这很可能在构建系统生成器中无意中发生或只是天真地发生) - 您会收到错误:
猜你喜欢
  • 2020-08-20
  • 2010-12-16
  • 1970-01-01
  • 2012-11-21
  • 2015-04-23
  • 2012-07-14
  • 2021-01-27
  • 1970-01-01
  • 2012-03-16
相关资源
最近更新 更多