GPUを手っ取り早く活用するためのライブラリ、ArrayFireのご紹介
この記事は GPGPU Advent Calender の1日目の記事です
参加者募集中です。マジで。
GPGPU Advent Calendar人いなさすぎじゃね?大丈夫?
とものすごいいろんな方からご心配いただきましたが大丈夫じゃないです
まぁ、それがある意味でGPGPUの現状なのではないかという気がしていて、盛り上げていくためにいろいろやりたいですね、というところです。
さて、1日目は、「GPUを手っ取り早く活用するためのライブラリ、ArrayFireのご紹介」をしたいと思います
はじめに
GPGPUもだいぶ普及してきたとはいえ、耳にするのはやはり敷居の高さです
「CUDAとかOpenCLとか難しいでしょ?」という話はよく聞きますし、そのたびに「まぁそうですねぇ……」と返すのが様式美というかなんというか
難しい要因として、
などなどが挙げられます
統括すると、速くはしたいけど、自分の手間は押さえたい。
そんなときにおススメなのが、このArrayFireです
ArrayFireとは?
様々な行列演算をサポートしたライブラリです
公式サイトは
http://www.accelereyes.com/products/arrayfire
CUDA / OpenCLに対応しており、現在はCUDA版のみ公開されています(おかげで当初の予定が狂った)
無料版の場合、
- 行列の四則演算
- 線形代数学演算
- 画像処理
- 信号処理
のメソッドが用意されています
有料版の場合は、上記のものに加え
- 疎行列演算
が追加されます
また、無料版の場合は対応しているのは単精度のみですが、有料になると倍精度もサポートします
ダウンロードには登録が必要です。
現行最新verは1.9です
対応している環境は、Windows, Mac, Linux.
インストールの方法などは各環境依存なので、ここでは割愛します
また、この記事では行列(の、ほんのさわりについて)のみに限定してお話をしたいと思います。もっと細かいのがいい!という人はリクエストください
はじめてのArrayFire
本記事では、以下の環境で実行しています
また、ArrayFireのインストールディレクトリ,CUDAのインストールディレクトリを、それぞれ以下のように設定しています
/usr/local/ArrayFire /usr/local/cuda-5.0
ほかのOSの場合などは、以下のコマンド、パスを適宜読み替えてください
でははじめてのArrayFire
#include <arrayfire.h> int main() { af::info(); return 0; }
簡単ですね
さて、ではコンパイルします
$ g++ getInfo.cpp -I /usr/local/cuda-5.0/include/ -I /usr/local/ArrayFire/include/ -L /usr/local/ArrayFire/lib64/ -laf
肝心なのは、libaf.soをリンクすることによって、ArrayFireが使えるようになるということでスね
出来上がったものを実行すると、次のような結果が表示されるかと思います
$ ./a.out ArrayFire v1.9 (build bbeef42) by AccelerEyes (64-bit Linux) License: Server (27000@server.accelereyes.com) CUDA toolkit 5.0, driver 310.19 GPU0 GeForce GTX 580, 3072 MB, Compute 2.0 (single,double) Memory Usage: 3005 MB free (3072 MB total)
おめでとうございます!初めてのArrayFireですよ!
……といってもArrayFireらしいこと何一つしていませんので、もっとらしいことをやりましょう
できる!ArrayFire 入門編
大変うれしいことに、ArrayFireではデータをたった一つのクラスで表現しています
それがarrayクラスです。
さっそくarrayクラスを使うようなコードを書いてみましょう
#include <arrayfire.h> int main() { af::info(); af::array a = af::randu(4, 4); af::array b = af::randu(4, 4); af::array c = a + b; af::print(a); af::print(b); af::print(c); return 0; }
これを先ほどのようにコンパイルして、実行すると次のような結果が出るかと思います
$ ./a.out ArrayFire v1.9 (build bbeef42) by AccelerEyes (64-bit Linux) License: Server (27000@server.accelereyes.com) CUDA toolkit 5.0, driver 310.19 GPU0 GeForce GTX 580, 3072 MB, Compute 2.0 (single,double) Memory Usage: 3005 MB free (3072 MB total) a = 0.7402 0.9251 0.4702 0.7140 0.9210 0.4464 0.5132 0.3585 0.0390 0.6673 0.7762 0.6814 0.9690 0.1099 0.2948 0.2920 b = 0.3194 0.2080 0.2343 0.5786 0.8109 0.6110 0.8793 0.5538 0.1541 0.3073 0.6462 0.3557 0.4452 0.4156 0.9264 0.7229 c = 1.0596 1.1331 0.7045 1.2926 1.7319 1.0573 1.3925 0.9124 0.1931 0.9746 1.4224 1.0371 1.4141 0.5255 1.2212 1.0149
あっさり行列の足し算ができました。簡単ですね。
arrayクラスの生成を行っているのは
af::array a = af::randu(4, 4); af::array b = af::randu(4, 4);
ですが、右辺のrandu()関数は、ご存じのとおり[0,1]の乱数を生成する関数です
ほかにも以下のような関数が用意されています
array constant (float, unsigned nx, unsigned ny, dtype ty=f32) array identity (unsigned nx, unsigned ny, dtype ty=f32) array randu (unsigned nx, unsigned ny, dtype ty=f32) array randn (unsigned nx, unsigned ny, dtype ty=f32) array rand (unsigned nx, unsigned ny, dtype ty=u32)
上から、
- nx,nyの行列を生成し、第一引数を全要素にセットする
- nx,nyの行列の単位行列を生成する
- nx,nyの行列を生成し、U[0,1]の乱数を各要素に対しセットする
- nx,nyの行列を生成し、N[0,1]の乱数を各要素に対しセットする
- nx,nyの行列を生成し、各ビット0または1の乱数を生成しセットする
という風になっています
最後の引数のdtypeはデータタイプで、所謂型と呼ばれるものです。ArrayFireでは、データタイプとして
- f32
- 32bit浮動小数点
- c32
- f64
- 64bit浮動小数点
- c64
- b8
- ブーリアン型(0 = false, 非0 = true)
- s32
- 符号付32bit整数
- u32
- 符号なし32bit整数
をサポートしています。
ただし、先述のとおりf64,c64は有料版のみのサポートです
基本的に行列演算はオペレータで書くことができますが、行列の乗算だけは気を付けなければなりません
乗算をオペレータで書くと……
#include <arrayfire.h> int main() { af::info(); float aa[] = {0.0f, 1.0f, 2.0f, 3.0f}; float ab[] = {4.0f, 5.0f, 6.0f, 7.0f}; af::array a(2, 2, aa); af::array b(2, 2, ab); af::array c = a * b; af::print(a); af::print(b); af::print(c); return 0; }
a = 0.0000 2.0000 1.0000 3.0000 b = 4.0000 6.0000 5.0000 7.0000 c = 0.0000 12.0000 5.0000 21.0000
何かがおかしい。
ちなみに、arrayクラスのコンストラクタで配列のポインタを渡すことで初期化もできます。それが上の使い方ですね
閑話休題。
cの結果だと、a,bの各要素を乗算した値になっています。つまりは
c[i] = a[i] * b[i]
こういうケースが必要な場合はオペレータで表記できますが、行列の乗算をしてほしい場合はこうじゃねーんだよ!とPCに殴り掛かること請け合い
行列の乗算をしたいときは、次の関数を使用します
c = af::matmul(a, b);
a = 0.0000 2.0000 1.0000 3.0000 b = 4.0000 6.0000 5.0000 7.0000 c = 10.0000 14.0000 19.0000 27.0000
よし。正しく乗算の結果が得られました。
……ところで。
この乗算のパフォーマンス、はたしていかほどのものなのでしょうか?
いくら手軽に書けるとはいえ、CPUよりも遅いとかになっていたらわざわざライブラリを使う必要はありません。
それでは測ってみようじゃありませんか。そのパフォーマンスとやらを!
ArrayFireの実力 入門編
行列の乗算について、FLOPSを算出します
今回使ったGPUはGTX 580です。580のCUDA coreは積和演算を1clockでできるので2opで、
512core * 2 op * 1544Mhz = 1581GFLops
……さすが速いな
さて、実効性能はどんなもんでしょうね
#include <arrayfire.h> #include <iostream> af::array A; static void calc(void) { af::array dst = af::matmul(A, A); dst.eval(); } int main() { for(int i = 128; i <= 4196; i += 128) { A = af::randu(i, i); double time = af::timeit(calc); double flops = 2.0 * powf(i, 3) / (time * 1.0e9); std::cout << i << " * " << i << " time: " << time << ", GFlops: " << flops << std::endl; } return 0; }
こういうコードをコンパイルして実効します
結果は
行列サイズ | FLOPS |
---|---|
128 * 128 | 278.781 |
256 * 256 | 409.526 |
384 * 384 | 454.729 |
512 * 512 | 729.838 |
640 * 640 | 588.356 |
768 * 768 | 675.322 |
896 * 896 | 667.429 |
1024 * 1024 | 758.828 |
1152 * 1152 | 707.298 |
1280 * 1280 | 737.136 |
1408 * 1408 | 730.231 |
1536 * 1536 | 758.927 |
1664 * 1664 | 744.276 |
1792 * 1792 | 756.01 |
1920 * 1920 | 753.608 |
2048 * 2048 | 771.99 |
2176 * 2176 | 758.935 |
2304 * 2304 | 766.328 |
2432 * 2432 | 763.542 |
2560 * 2560 | 771.544 |
2688 * 2688 | 765.283 |
2816 * 2816 | 769.794 |
2944 * 2944 | 767.804 |
3072 * 3072 | 775.545 |
3200 * 3200 | 769.943 |
3328 * 3328 | 772.364 |
3456 * 3456 | 771.217 |
3584 * 3584 | 774.082 |
3712 * 3712 | 773.062 |
3840 * 3840 | 772.706 |
3968 * 3968 | 773.953 |
4096 * 4096 | 780.347 |
50%…?
とはいえ、ArrayFireって実はcuBLASのラッパーなんですが
それでも、CPUと比較してこれだけの演算性能がかなりお手軽に得られるというのは、結構メリットのあることなのではないでしょうか?
こんくるーじょん
ArrayFireのほんの表層をご紹介しました。
パフォーマンス的にcuBLASとほぼ同等と考えていますが、cuBLASのベンチも後で載せておきます
なんで表層だけかというと、あと二回ぐらい書かなきゃいけなさそうだったのでぶっちゃけ時間稼ぎです
次はLinearなところとかもお話します
ということで、むしろGPGPU Advent Calendarは明日からが本番!
w_o先生がもっとこゆい話をしてくれる…!
では自壊次回もよろしくお願いします
ついしん.
ほかのひともご参加くだされば本当に泣いて喜びます