流程
- 将test.cu代码进行分离,利用cudafe.exe 去分离CPU代码和GPU代码,我们可以在生成的中间文件可以看到test.cudafe1.cpp和test.cudafe1.gpu
- cicc.exe 将根据编译选项-arch=compute_xx将GPU代码编译成对应架构的test.ptx文件
- ptxas.exe 编译 test.ptx 到test.cubin,这个是根据编译选项-code=sm_xx定义的,比如test.sm_30.cubin.(这一步叫做PTX离线编译,主要的目的是为了将代码编译成一个确定的计算能力和SM版本,对应的版本信息保存在cubin中)
- fatbin.exe 编译test .cubin 和test. ptx到 text.fatbin.c 。 (这一步叫PTX在线编译,是将cubin和ptx中的版本信息保存在fatbin中)
- 调用系统的gcc/g++将host代码(test.cudafe1.cpp)和fatbin(text.fatbin.c)编译成对应的目标文件test.o 和test._dlink.o。
- 用c++编译器将目标文件链接起来生成可执行文件。
实验
nvcc --cuda test.cu --keep --dryrun
细节
- -arch=compute_XX, -code=sm_XX, 如果写两个的话必须这样来写,也就说compute_必须对应的是arch, code必须对应的是sm_,arch代表的是目标机器(vitural),而code代表的是真实机器(real),下面是具体的例子:
nvcc -o test test.cu -arch=compute_35 -code=sm_70 --keep --verbose
具体小步骤
- cicc *** -arch compute_35 -o test.ptx// 可以看出ptx是根据arch来设定的
- ptxas -arch=sm_70 -m64 “test.ptx” -o “test.cubin”//本地二进制文件是根据sm_70来设定,至于为用的是arch,这就是nvcc瞎写
- fatbinary --create=“test.fatbin” -64 “–image=profile=sm_70,file=test.cubin” --embedded-fatbin=“test.fatbin.c” --cuda(这里面没有将ptx放进去不知道什么情况,当只有arch一个参数的时候ptx和cubin都会放入fatbin.c里面)
使用
一般都是只使用一个-arch, 这样默认你的arch和code是同一个架构算力,比如-arch=sm_70,这个在nvcc -help中可以看到,就是为了方便而已,等效于-arch=compute_70, -code=sm_70, 感觉英伟达这搞得一点也不规范。
思考
- 经常可以看到说cuda的即时编译,可是我是没见到运行的时候出现编译过程,难道是我使用姿势不对?
fatbinary机制。exe二进制文件中会包含一个或多个体系结构的二进制代码以及PTX代码来完全避免JIT成本。CUDA运行时会在二进制文件中查找当前GPU架构的代码,并在找到时运行它。如果找不到二进制代码,但PTX可用,则驱动程序将编译PTX代码。这样,部署的CUDA应用程序可以在新GPU出现时支持它们。nvcc x.cu -arch=compute_10 -code=compute_10,sm_10,sm_13,比如这一段程序,编译出来的代码就会有10,13两个二进制,实际遇到了这类GPU就直接使用,否则就把compute_10.ptx运行时编译一波。
- 难道我一份exe每次在新机器A上重复使用时每次都要即时编译吗?这也太蠢了吧
缓存机制。 如果在code中没有指示出来实际运行的GPU, 理论来说你在A这个GPU上运行的时候,每次都要JIT,为此cuda做了一个优化,就是将它第一次接触过得GPU卡即时编译后缓存一起,也就是说第一次在A卡编译后,下次再运行就直接可以调用缓存里面的二进制了,理论上来说就是放在-code=sm_10, A,sm_20,不过A是在遇到A之后才编译的
注意
-arch 的配置一定要低于-code ,想想看,你一个-arch=compute_70的 **.ptx很难去编译出一个code=sm_30的二进制代码,因为很多架构是70的机器上的特性,在30的机器上压根都没有,比如tensorcore什么的,所以这个要注意。