Jetson NanoではじめてのCUDAプログラミング + ImageJで可視化アニメーション
1. はじめに
本記事では、とても入手しやすいJetson Nano(NVIDIA社)を使って、はじめてのGPGPU(General-Purpose computing on Graphics Processing Units)に取り組みます。GPUは本来、画像処理・グラフィック向けの演算装置ですが、そのGPUに、それ以外の汎用的な演算処理(例えば数値計算など)を実行させる応用的技術をGPGPUと言います。 ここでは特に、CUDA(Compute Unified Device Architecture:クーダ)を導入して、GPGPUの入門的な演習を行います。CUDAはWikipediaによると、
NVIDIAが開発・提供している、GPU向けの汎用並列コンピューティングプラットフォーム(並列コンピューティングアーキテクチャ)およびプログラミングモデルである。
ざっくりとした言い方になおすなら、GPGPUを実施するためにNVIDIA社が頑張って開発してくれてなおかつ無料配布してくれているとても有難い開発プラットフォーム ということになるかと思います。 難しいことは実際に動かしながら覚えていくこととして、GPGPU & CUDAプログラミングに早速挑戦します。
2. Jetson NanoへのCUDAの導入
普通にJetson Nanoのセットアップが終了していれば、元々CUDAが入ってます。私はJetsonイメージをmicroSDに焼いて普通の手順にてセットアップ完了させましたが、OKでした。ちなみに2020年5月時点で、CUDAのバージョンは10.2でした。というわけで、今回の入門演習をとりあえず実施するだけで他に特別な操作をしない場合、特に何も手順はいらずにCUDAがすぐに使えそうです。
3. CUDAで拡散方程式をGPGPU
早速入門演習として、実際にCUDAを使って、CPUではなくGPUで数値計算を実行(GPGPU)します。参考図書としては、こちらを使いました。が、2009年が初版と少し前の書籍なので、その間にCUDAのバージョンが上がり、そっくりそのままサンプル・プログラムを実行しただけでは上手くいきませんでした。少し、現在のバージョンに合ったプログラムおよび環境設定に一部修正する必要があります。その変更点や修正箇所を本記事ではメインに書いていきますので、手元にこの書籍があると、照らし合わせながら考えられるので、いずれにせよ理解が進みやすいと思います。
なお、紹介した書籍の中に、サンプル・プログラムをダウンロードできるサイト情報が明記されていますので、サンプル・プログラムをダウンロードしたい方は書籍で詳しい情報を確認してください。
書籍のp.169~記載の「差分法による偏微分方程式のGPU計算」を演習として取り上げます。この問題では、拡散方程式を「水の入ったコップの中にインクを落としたとき」の拡散現象を想定してGPUで数値シミュレーションしています。データの可視化に際して、bmp出力画像を生成するのはCPUの方が得意なので、それはCPU側で対応し、役割を分担しています。
※実際に、数値シミュレーションが完了するまでの修正箇所も含めた手順を、以下の①~⑤に示します。
① ダウンロードしたzipフォルダを好きな場所に解凍する。仮に、そのときのフォルダ名をここではprob_jetson
とする。
② 現在ではCUDAのバージョンが上がったのにともなって、昔のバージョンで入っていたヘッダファイルcutil.h
が無くなったようなので、ココから復活させる(少し野暮ったい方法ですが)。rawデータを全文コピペして適当な好きなエディタを使い、cutil.h
の名前でひとまずどこでもいいので保存する。
③ 保存したヘッダファイルcutil.h
を、/usr/local/cuda-10.2/include
のディレクトリ(つまり保存先ディレクトリ)に sudo cp (保存元) (保存先)
コマンド等でコピーする。
④とりあえずの対策として、main.cu
のコードの中身を以下のように一部コメントアウトする(指定箇所をコメントアウトすると、GPU使用時の稼働時間のログが表示されなくなるが、一番肝心な出力のbmpファイルには影響しないと思われるので、とりあえずコメントアウトでエラー回避)。
//40 ~ 44 行目付近 /* unsigned int timer; cutCreateTimer(&timer); cutResetTimer(timer); cutStartTimer(timer); */ //55 行目付近 // printf("TIME = %9.3e\n",time); //60 ~ 65 行目付近 /* cutStopTimer(timer); float elapsed_time = cutGetTimerValue(timer)*1.0e-03; printf("Elapsed Time= %9.3e [sec]\n",elapsed_time); printf("Performance= %7.2f [MFlops]\n",flops/elapsed_time*1.0e-06); */
③ ~/prob_jetson$ make
で、フォルダ内にある Makefile をmakeする。すると、diffusion2d.o
, etc.o
, libbmp.o
, main.o
の合計4つのオブジェクトファイル(~~~.o)ができる。しかし、最終的にはエラーが発生し、make: ***[a.out] Error 1
とターミナルに表示される。つまり、Makefile 内で指定されているa.out
の記述が、リンクエラーが恐らくの原因で出力されない。Makefileを精密に書けるほど詳しくないので、応急処置で以下のシェルスクリプトを作成し対応する。何回も打つのは億劫なのと忘れるのでシェルに。
mycommand.sh
nvcc diffusion2d.o etc.o main.o libbmp.o -L/usr/local/cuda-10.2 -o run
④ 手順③で作成済みのmycommand.sh
に下記コマンドで権限を変更し実行すると...
~/prob_jetson$ chmod 755 mycommand.sh ~/prob_jetson$ ./mycommand.sh
この段階で、フォルダ内(prob_jetson
)に最終実行のためのファイルrun
が生成されるはず。
⑤ あとは、このファイルrun
を走らせるだけ。
~/prob_jetson$ ./run
フォルダ内に、水中のインクの拡散の様子をシミュレーションしたbmpファイルが40枚ほど時系列に沿って次々出力される!!
4. Jetson NanoのUbuntuに画像処理ソフトImageJを入れる
次は、Jetson NanoのUbuntuに、ImageJという生物画像解析の分野で有名なソフトをインストールし、それを使って先ほど出力したbmp画像群をひとつのアニメーションにサクッと変換してみます。インクが水中でじわじわと広がってゆく拡散の様子がよりわかりやすく観察できそうです。参考図書では別の可視化ソフトが紹介されていますが、今回は特に理由はないのですが、ImageJを使ってみます。ImageJダウンロードサイトの Linux から適切なものをダウンロードして下さい。 ダウンロードしたら解凍します。ImageJを置くディレクトリはどこでも良かったはずです。これも、毎回コマンドを忘れてしまうとあれなのでImageJ実行のためのシェルスクリプトを一応用意しておきます。 start.sh
java -jar ij.jar
続いて、権限の変更です。
~/ImageJ$ chmod 755 start.sh
その後、ImageJを起動します。
~/ImageJ$ ./start.sh
ImageJを起動したら、メニューバーの"File" -> "Import" -> "Image Sequence..."を選択して、No.0の画像を選びます。
すると、次のような"Sequence Option"ウィンドウが開くので、図のように設定し、"OK"を押して決定します。先ほど連番で40枚ほど出力されたbmp形式の画像ファイルが一気にひとまとまりでImageJに読み込まれます。
"File" -> "Save As" -> "Gif..." 等のファイル形式の指定で画像群がひとまとまりになったアニメーションに変換されました!!
アップロードする用に、今回は.gif形式に変換しましたが、.aviなどのその他形式への保存にももちろん対応しています。アニメーションにしたGifがこちらです。
5. まとめ
Jetson Nanoにインストール済みのCUDAを使って、拡散方程式の数値計算を演習に、入門編としてはじめてのGPGPUを実施しました。bmp形式で出力された画像群を、ImageJを導入してアニメーションに変換して可視化しました。今後は、より高度なCUDAプログラミングを勉強しつつやっていけたらと思ってます。