如何编译PTX代码

时间:2013-11-15 23:17:14

标签: cuda nvcc ptx

我需要修改PTX代码并直接编译。原因是我想要一个接一个地有一些特定的指令,并且很难编写一个导致我的目标PTX代码的cuda代码,所以我需要直接修改ptx代码。 问题是我可以将其编译为(fatbin和cubin),但我不知道如何将这些(.fatbin和.cubin)编译为“X.o”文件。

6 个答案:

答案 0 :(得分:8)

有一种方法可以使用nvcc命令的有序序列来执行此操作,但我不知道它并且没有发现它。

然而,一种可能的方法是,虽然很麻烦,但是要中断并重新启动cuda编译序列,并在过渡期间(重启之前)编辑ptx文件。这是基于nvcc manual中提供的信息,我不认为这是一种标准方法,因此您的里程可能会有所不同。可能有许多场景我没有考虑过哪些不起作用或不可行。

为了解释这一点,我将提供一个示例代码:

#include <stdio.h>

__global__ void mykernel(int *data){

  (*data)++;
}

int main(){

  int *d_data, h_data = 0;
  cudaMalloc((void **)&d_data, sizeof(int));
  cudaMemcpy(d_data, &h_data, sizeof(int), cudaMemcpyHostToDevice);
  mykernel<<<1,1>>>(d_data);
  cudaMemcpy(&h_data, d_data, sizeof(int), cudaMemcpyDeviceToHost);
  printf("data = %d\n", h_data);
  return 0;
}

为此目的,我不再使用cuda error checking和其他细节,而是为了简洁。

通常我们可以按如下方式编译上面的代码:

nvcc -arch=sm_20 -o t266 t266.cu 

(假设源文件名为t266.cu)

相反,根据参考手册,我们将编译如下:

nvcc -arch=sm_20 -keep -o t266 t266.cu

这将构建可执行文件,但会保留所有中间文件,包括t266.ptx(其中包含mykernel的ptx代码)

如果我们此时只是运行可执行文件,我们会得到如下输出:

$ ./t266
data = 1
$

下一步是编辑ptx文件以进行我们想要的任何更改。在这种情况下,我们将内核添加2到data变量而不是添加1.相关的行是:

    add.s32         %r2, %r1, 2;
                              ^
                              |
                          change the 1 to a 2 here

现在是凌乱的部分。下一步是捕获所有中间编译命令,因此我们可以重新运行其中一些命令:

nvcc -dryrun -arch=sm_20 -o t266 t266.cu --keep 2>dryrun.out

(在这里使用stderr的linux重定向)。然后,我们要编辑dryrun.out文件,以便:

  1. 我们在创建ptx文件后保留所有命令,直到文件末尾。创建ptx文件的行将显示为指定-o "t266.ptx"
  2. 的行
  3. 我们删除每行开头的前导#$,因此实际上我们正在创建一个脚本。
  4. 当我执行上述两个步骤时,我最终得到了一个这样的脚本:

    ptxas  -arch=sm_20 -m64  "t266.ptx"  -o "t266.sm_20.cubin"
    fatbinary --create="t266.fatbin" -64 --key="xxxxxxxxxx" --ident="t266.cu" "--image=profile=sm_20,file=t266.sm_20.cubin" "--image=profile=compute_20,file=t266.ptx" --embedded-fatbin="t266.fatbin.c" --cuda
    gcc -D__CUDA_ARCH__=200 -E -x c++   -DCUDA_DOUBLE_MATH_FUNCTIONS   -D__CUDA_PREC_DIV -D__CUDA_PREC_SQRT "-I/usr/local/cuda/bin/..//include"   -m64 -o "t266.cu.cpp.ii" "t266.cudafe1.cpp"
    gcc -c -x c++ "-I/usr/local/cuda/bin/..//include"   -fpreprocessed -m64 -o "t266.o" "t266.cu.cpp.ii"
    nvlink --arch=sm_20 --register-link-binaries="t266_dlink.reg.c" -m64   "-L/usr/local/cuda/bin/..//lib64" "t266.o"  -o "t266_dlink.sm_20.cubin"
    fatbinary --create="t266_dlink.fatbin" -64 --key="t266_dlink" --ident="t266.cu " -link "--image=profile=sm_20,file=t266_dlink.sm_20.cubin" --embedded-fatbin="t266_dlink.fatbin.c"
    gcc -c -x c++ -DFATBINFILE="\"t266_dlink.fatbin.c\"" -DREGISTERLINKBINARYFILE="\"t266_dlink.reg.c\"" -I. "-I/usr/local/cuda/bin/..//include"   -m64 -o "t266_dlink.o" "/usr/local/cuda/bin/crt/link.stub"
    g++ -m64 -o "t266" -Wl,--start-group "t266_dlink.o" "t266.o"   "-L/usr/local/cuda/bin/..//lib64" -lcudart_static  -lrt -lpthread -ldl  -Wl,--end-group
    

    最后,执行上面的脚本。 (在linux中,您可以使用chmod +x dryrun.out或类似内容使此脚本文件可执行。)如果您在编辑.ptx文件时没有犯任何错误,则命令应该都已成功完成,并创建一个新{ {1}}可执行文件。

    当我们运行该文件时,我们会观察:

    t266

    表明我们的更改成功。

答案 1 :(得分:3)

通常,当使用cubin或ptx文件处理时,使用CUDA驱动程序API而不是Runtime API;这样,您可以在运行时使用cuModuleLoadDataEx手动加载ptx或cubin文件。 如果你想坚持运行时API,你需要手动模仿NVCC的功能,但这不是(完全)记录的。我只发现了Nvidia forum entry如何做到这一点。

答案 2 :(得分:0)

您可以使用CUDA中的cuModuleLoad *函数在运行时加载cubin或fatbin:Here's the API

您可以使用它将PTX包含到您的构建中,尽管该方法有点复杂。例如,suricata 将其.cu文件编译为不同体系结构的PTX文件,然后将它们转换为包含PTX代码作为“C”数组的.h文件,然后在其中一个文件中包含它构建。

答案 3 :(得分:0)

我相当迟,但GPU Lynx正是这样做的:拿一个CUDA胖二进制文件,解析PTX,然后修改它,然后将结果发送给驱动程序,以便在GPU上执行。您也可以选择打印出修改过的PTX。

答案 4 :(得分:0)

这个nvcc命令序列似乎可以解决问题。有关详细信息,请参阅here

创建要修改的ptx文件

nvcc file1.cu file2.cu file3.cu -rdc=true --ptx

将ptx文件链接到目标文件

nvcc file1.ptx file2.ptx file3.ptx -dlink

我在Windows上执行此操作,因此弹出a_dlink.obj。正如文档所指出的那样,主机代码已被丢弃。运行

nvcc file1.cu file2.cu file3.cu -rdc=true --compile

创建目标文件。对于Windows,它们将是.obj,对于Linux,它们将是.o。然后创建一个库输出文件

nvcc file1.obj file2.obj file3.obj a_dlink.obj --lib -o myprogram.lib

然后运行

nvcc myprogram.lib

将在Windows上弹出可执行的a.exe或在Linux上弹出a.out。此过程也适用于cubinfatbin文件。只需用ptx替换这些名称。

答案 5 :(得分:0)

您可以使用NVTRC-很简单!

扩展@ArtemB的答案:

nVIDIA提供了一个实时编译(RTC)库。有一个示例说明如何将其用作CUDA示例;您可以访问它here

该示例实际上是从CUDA代码开始的,但是中间步骤是将PTX代码创建为纯C字符串(`char *)。从那里开始,这基本上就是您要做的:

char* ptx;
size_t ptxSize;

// ... populate ptx and ptxSize somehow ...

CUcontext context;
CUdevice cuDevice;

// These next few lines simply initialize your work with the CUDA driver,
// they're not specific to PTX compilation
cuInit(0);
cuDeviceGet(&cuDevice, 0); // or some other device on your system
cuCtxCreate(&context, 0, cuDevice);

// The magic happens here:
CUmodule module;
cuModuleLoadDataEx(&module, ptx, 0, 0, 0));

// And here is how you use your compiled PTX
CUfunction kernel_addr;
cuModuleGetFunction(&kernel_addr, module, "my_kernel_name");
cuLaunchKernel(kernel_addr, 
   // launch parameters go here
   // kernel arguments go here
);

注意:

  • 我已删除所有错误检查,以免弄乱示例代码-但检查代码中的错误
  • 您需要将程序与NVRTC库链接-它与主CUDA和CUDA驱动程序库分开。在Linux上,它称为libnvrtc.so