Clang 交叉编译针对 Jetson 的 ARM 架构下 CUDA 代码

一个来自群友的问题。如何使用在 x86 环境下,使用 clang 编译可以在 Jetson 开发板(ARM 架构) 运行的 CUDA 代码。下面的内容是在 Ubuntu 20.04 (x86_64) 测试的, 其他平台类似。然后使用的开发板是 Jetson Xavier NX 。

使用 clang 编译 CUDA 代码

首先 clang 是可以编译 CUDA 的 .cu 文件的。当然,依然需要 CUDA SDK,因为要链接 CUDA SDK 中的 动态库并使用 PTXAS 生成 GPU 设备相关的代码。

因此,如果只是使用 clang 编译本机可以运行的 CUDA 的程序的话。直接在 clang 中配置 GPU 架构的信息、CUDA SDK 信息和相关的链接库即可。例如要针对 CUDA samplevectorAdd 例程进行编译,则需要使用如下的命令:

clang++ --cuda-path=/usr/local/cuda-10.2/ --cuda-gpu-arch=sm_61 -I ../../../Common/ -L /usr/local/cuda-10.2/lib64/ -lcudart -ldl -lrt -pthread vectorAdd.cu -o vectorAdd.x86

其中 --cuda-path 是设置 CUDA SDK 的路径,然后 --cuda-gpu-arch 是设置 GPU 架构。我这里用的是一块 P106-100,所以使用的 sm_61。其他的 GPU 可以在英伟达 的 CUDA Compute Capability 里面查到。 -I ../../../Common/ 是添加 include 路径,然后 -L /usr/local/cuda-10.2/lib64/ 是添加 library 路径。-lcudart -ldl -pthread 是添加了要引用的库。其中 -lcudart 是 CUDA 相关的,其他的则是因为不添加就会报错,才添加的。

然后执行 ./vectorAdd.x86,就能说的输出:

[Vector addition of 50000 elements]
Copy input data from the host memory to the CUDA device
CUDA kernel launch with 196 blocks of 256 threads
Copy output data from the CUDA device to the host memory
Test PASSED
Done

使用 clang 交叉编译

如果是要使用 clang 交叉编译 ARM 代码的化,则需要安装专门的 clang 的交叉编译器。但是这个 Ubuntu 的官方软件源中并没有。这里本着省事的原则,(就是偷个懒),安装了 gcc-aarch64 和 g++-aarch64(apt install gcc-aarch64 g++-aarch64)。然后利用其中的工具链进行编译。 其中 clang 只是完成的支持 x86_64-pc-linux-gnu 这个平台:

$ clang -print-target-triple
> x86_64-pc-linux-gnu

首先,有一个和 CUDA 无关的测试代码 cross.cc:

#include<iostream>

int main() {
        std::cout << "Hello, ARM" << std::endl;
        return 0;
}

然后使用下面的命令进行编译:

clang++ -fuse-ld=/usr/bin/aarch64-linux-gnu-ld -target aarch64-linux-gnu cross.cc -o cross.aarch64 \
    -I /usr/lib/gcc-cross/aarch64-linux-gnu/9/../../../../aarch64-linux-gnu/include/c++/9 \
    -I /usr/lib/gcc-cross/aarch64-linux-gnu/9/../../../../aarch64-linux-gnu/include/c++/9/aarch64-linux-gnu \
    -I /usr/lib/gcc-cross/aarch64-linux-gnu/9/../../../../aarch64-linux-gnu/include \
    -I /usr/lib/gcc-cross/aarch64-linux-gnu/9/include

其中 -fuse-ld 是指定连接器的,这里因为 clang 并没有办法链接 aarch64 的程序,并且这里使用了 gcc-aarch64 的连接器。然后 -target 是指定目标架构,这里填写的是 aarch64-linux-gnu,也就是 Jetson 里面运行的系统的类型。然后因为是借用的 gcc 的东西。所以这里需要手动指定 include 的路径。其中具体的路径是通过 aarch64-linux-gnu-g++ -print-file-name=cc1plus 这个命令,找到 C/C++ 预处理的程序,然后运行 /usr/lib/gcc-cross/aarch64-linux-gnu/9/cc1plus -v < /dev/null 就会打印出来,这里使用到的 include 路径。

这样的,就能生成可以运行在 arm64 下的程序:

$ ./cross.aarch64
> Hello, ARM

混合在一起

然后其实就可以把上面的两个命令组合在一起,然后就可以交叉编译出在 ARM 设备上运行的 CUDA 代码了。但是,有一个问题,就是如何组合?

我才用的命令如下:

clang \
    -fuse-ld=/usr/bin/aarch64-linux-gnu-ld -target aarch64-linux-gnu vectorAdd.cu -o vectorAdd.aarch64 \
    --cuda-path=/usr/local/cuda-10.2/ --cuda-gpu-arch=sm_61 -lcudart \
    -L ../../../cuda/lib64/ \
    -I ../../../Common/ \
    -I /usr/lib/gcc-cross/aarch64-linux-gnu/9/../../../../aarch64-linux-gnu/include/c++/9 \
    -I /usr/lib/gcc-cross/aarch64-linux-gnu/9/../../../../aarch64-linux-gnu/include/c++/9/aarch64-linux-gnu \
    -I /usr/lib/gcc-cross/aarch64-linux-gnu/9/../../../../aarch64-linux-gnu/include \
    -I /usr/lib/gcc-cross/aarch64-linux-gnu/9/include

首先,要说明的是,这里更换了原先的链接路径,从 /usr/local/cuda-10.2/lib64/ 替换到了 ../../../cuda/lib64/。后面这个路径是 Jetson 设备上 CUDA SDK 通过 NFS 挂载到开发设备上的。里面有 ARM 架构的 CUDA 库。

然后,这里链接的库和上面的不太一样,因为不连接诸如 dl pthread 这样的库,没有报错。本着,偷懒的原则,不报错,能跑,就不管了。

最后,GPU 那边的 --cuda-gpu-arch 这个设置是要根据实际的 GPU 来调整的。我这里的设备是 Jetson Xavier NX,所以应该设置 sm_70,但是 设置 sm_61 也能跑,所以就不管了。

在 Jetson 开发板上运行就能得到:

$ ./vectorAdd.aarch64
> [Vector addition of 50000 elements]
> Copy input data from the host memory to the CUDA device
> CUDA kernel launch with 196 blocks of 256 threads
> Copy output data from the CUDA device to the host memory
> Test PASSED
> Done