LLVM IRからGPUの実行可能ファイルを出力するコンパイルフローのまとめ
研究の過程で,cudaコードをclangに与えて出力されるLLVM IRを,GPUの実行可能ファイルまでコンパイルする必要があったので,そのコンパイルフローをまとめる.
環境
PCの環境は以下.
- OS: Ubuntu 18.04 LTS
- GPU: Geforce GTX TITAN RTX
- clang,LLVM 11.1.0
- CUDA 11.1
コンパイルフロー
まず,私の目的としては,cudaコード(.cu)をclangに与えて得られるLLVM IR(ホスト側とデバイス側の2つの.llファイル)に,最適化とかしてから,実行可能ファイルにコンパイルしたい. 色々試したところ,結局,「clangが実行可能ファイルを出力する過程をトレースすればいいじゃん」と気づき,おもむろに次のコマンドを試した.
clang++ -v mytest.cu -o mytest --cuda-gpu-arch=sm_75 -L/usr/local/cuda/lib64 -pthread -lcudart_static -ldl -lrt
ここで,mytest.cuにはホスト側とデバイス側のどちらのコードも書いてある.このコマンドによって,mytestというファイルが生成されるわけだが,-v
オプションを指定することで,どのように生成されたかが分かる.その結果は,あまりにも長いので全部は貼らないが,LLVM IRを経由して実行可能ファイルまでたどり着くためのコンパイルフローを以下にまとめる.
cu --> ll
まず,cudaコード(.cu)から,LLVM IR(.ll)を得るには,次のコマンドを実行する.
clang++ -S -emit-llvm mytest.cu --cuda-gpu-arch=sm_75
指定するアーキテクチャは適宜,自分の環境に合わせて読み替えてほしい.結果として,mytest.ll
(ホスト側)とmytest-cuda-nvptx64-nvidia-cuda-sm_75.ll
(デバイス側)のllファイルが得られる.
device.ll --> ptx
次に,デバイス側のllファイルをptxに変換する.LLVMの公式サイトを参考に,次のコマンドを実行する.
llc -mcpu=sm_75 mytest-cuda-nvptx64-nvidia-cuda-sm_75.ll -o mytest-cuda-nvptx64-nvidia-cuda-sm_75.ptx
ptx -> .o
次に,ptxファイルをオブジェクトファイルにコンパイルする.コマンドは以下.-v
オプションは,私がいつも指定しているだけで,必要はないだろう.
ptxas -m64 -O3 -v --gpu-name sm_75 -o mytest-cuda-nvptx64-nvidia-cuda-sm_75.o mytest-cuda-nvptx64-nvidia-cuda-sm_75.ptx
ptx, o --> fatbin
次に,ptxファイルとオブジェクトファイルから,デバイス側のバイナリを含むfatbinaryを生成する.
fatbinary -64 --create mytest-cuda-nvptx64-nvidia-cuda-sm_75.fatbin --image=profile=sm_75,file=mytest-cuda-nvptx64-nvidia-cuda-sm_75.o --image=profile=compute_75,file=mytest-cuda-nvptx64-nvidia-cuda-sm_75.ptx
fatbin, host.cu --> host_embedded_fatbin.ll
次に,ホスト側のllファイルに,先ほど作成したfatbinaryを埋め込む必要がある.このステップが重要で,埋め込みをしないでコンパイル-->リンクとやっていくと,カーネル関数が実行できなくなってしまう.実行するコマンドは,clangに-vオプションを渡したときのものを各自参考にしてほしい.clangに-fcuda-include-gpubinary
オプションを渡しているコマンドが,実行すべきコマンドである.-internal-isystem
オプションが満載で長すぎるので,適宜省略したものを,以下に記す.(...の部分は省略を表す)
clang++ -cc1 -triple x86_64-pc-linux-gnu -target-sdk-version=11.0 -aux-triple nvptx64-nvidia-cuda -emit-llvm ... -main-file-name mytest.cu ... -fcuda-include-gpubinary mytest-cuda-nvptx64-nvidia-cuda-sm_75.fatbin ... -x cuda mytest.cu -o mytest_embedded_fatbin.ll
ちなみに,clangの内部では,llファイルではなく,オブジェクトファイルを生成し,それをリンクすることで実行可能ファイルを生成している. 私は,ホスト側とデバイス側の両方のllファイルをいじりたいので,llファイルを出力している.
ll --> executable
最後は,↑で得たllファイルをただふつうにコンパイルするだけである.
clang++ mytest_embedded_fatbin.ll -o mytest -L/usr/local/cuda/lib64 -lcudart
以上のステップで,実行可能ファイルmytest
が手に入る.
まとめ
LLVM+CUDAの環境では,どうやらllファイルにfatbinaryを埋め込まなければカーネルが実行できないようだ.clang -v
でclangが何しているのか確認すれば,clangともお友達になれる気がする.
参考webサイト
Compiling CUDA with clang
User Guide for NVPTX Back-end
NVCC::CUDA Toolkit Documentation
CUDA Binary Utilities::CUDA Toolkit Documentation