ひびろぐ ver.2

Google検索したときの見た目を考慮してブログ名を変更してみた。馬鹿かと阿呆かと。

Archive for the ‘CUDA’


取れないバグ

バグがとれないときは、最低限の再現ケースを作成しましょう。



ほぼ最低限だけ記述した状態で再現するから困るんだよね、こういうのって。

おかしいなぁ

リファレンスを読み直したら、Warpの概念を間違えていた気がしてきた。というか考え直していたら元々どう考えていたかを忘れた。


だめじゃん。

MAC OS X用CUDAがリリースされていた件

CUDA Downloads - NVIDIA


MacBookProのキーボードがマシだったらマジ使う。HHKでも持ち歩こうかしらw

CUDAのアセンブラコード(と呼んでいいのか?)

先日の学会のときにCUDAをアプリケーション高速化側で使っている人たちがいたので話をしたんだが、彼らはいわゆるCUDAのアセンブラを眺めて最適化具合をチェックしていたらしい。

つーわけで、あえてスルーしていたアセンブラコードを眺めてみたんだが……

……うーん、これを眺めてチェックするのは正直勘弁してほしいなぁ。別に読めないわけではないけど……まぁある意味これさえ使えればある意味でやりたい放題できるから、意味はあるのかな(何

CUDA1.1の話

なんか計算と通信のオーバーラップができるようになったらしい。ということをドキュメントで読んだので研究情報垂れ流しblogに書いた。


んで、詳細を調べようと思って適当なキーワードでぐぐったら、さっき(二時間前)書いた自分の記事がTOPだった。

情報量0じゃねーか。オノレ。

Windowsの無茶な環境でCUDAを使うための方法

CUDAにはデバイスエミュレーションモードがあるのでCUDA対応GPUがなくてもプログラムの実験ができます。というお話。

  • 導入環境
    • WindowsXP 64bitEdition
    • RadeonX1600PRO
    • VisualStudio2005SP1

無茶をするなと。

以下導入メモ:

  1. http://developer.nvidia.com/object/cuda.html]から開発に必要なものを入手する。具体的にはCUDA ToolkitとCUDA SDK。32bitとか書いてあるけど気にしない。
  2. ToolkitとSDKを導入。特に文句は言われない。
    • Toolkitは C:\CUDA に導入されたのを確認。
    • SDKは C:\Program Files (x86)\NVIDIA Corporation\NVIDIA CUDA SDK に導入されたのを確認。
  3. Tookit導入時にご丁寧に C:\CUDA\bin にパスが通されていることに気がつく。いきなりnvcc.exeを実行可能。
  4. 適当なサンプルCUDAプログラムを準備。今回は自前の超単純なプログラムを利用することにする。
  5. nvccでコンパイルしてみるが、エラー。ヘッダファイルが見つからない旨を伝えられる。
    • nvcc cuda.cu
  6. エラーメッセージを参考に、Program Files以下のMicrosoft Visual Studio 8\VC\includeとNVIDIA Corporation\NVIDIA CUDA SDK\common\incをインクルードパスとして指定することにする。
    • nvcc cuda.cu -I”C:\Program Files (x86)\Microsoft Visual Studio 8\VC\include” -I”C:\Program Files (x86)\NVIDIA Corporation\NVIDIA CUDA SDK\common\inc”
  7. ヘッダファイルが見つからない旨は消えたが、clが無いといわれる。
  8. clと言ったらVCのアレなので、C:\Program Files (x86)\Microsoft Visual Studio 8\VC\binにパスを通してみる。
  9. 今度はmspdb80.dllが見つからないと怒られる。適当にファイル検索し、C:\Program Files (x86)\Microsoft Visual Studio 8\Common7\IDEに見つけたのでパスを通す。
  10. LINKエラー、libcpmt.libが開けないらしい。nvcc実行時引数に-Lオプションか。
  11. C:\Program Files (x86)\Microsoft Visual Studio 8\VC\libにターゲットを捕捉。-Lオプションを追加する。
    • nvcc cuda.cu -I”C:\Program Files (x86)\Microsoft Visual Studio 8\VC\include” -I”C:\Program Files (x86)\NVIDIA Corporation\NVIDIA CUDA SDK\common\inc” -L”C:\Program Files (x86)\Microsoft Visual Studio 8\VC\lib”
  12. __imp__cutCheckCmdLineFlag@12の未解決エラー。ライブラリのリンク不足なのは自明。名前からして恐らくcutilライブラリ。
  13. C:\Program Files (x86)\NVIDIA Corporation\NVIDIA CUDA SDK\common\libにcutil32.libを発見。-Lと-lのオプションを追加する。
    • nvcc cuda.cu -I”C:\Program Files (x86)\Microsoft Visual Studio 8\VC\include” -I”C:\Program Files (x86)\NVIDIA Corporation\NVIDIA CUDA SDK\common\inc” -L”C:\Program Files (x86)\Microsoft Visual Studio 8\VC\lib” -L”C:\Program Files (x86)\NVIDIA Corporation\NVIDIA CUDA SDK\common\lib” -lcutil32
  14. nvcc成功。しかし実行すると今度はcutil32.dllが無いと怒られる。ああそうか、dll使ってるのか。パス通ってないんだな。
  15. C:\Program Files (x86)\NVIDIA Corporation\NVIDIA CUDA SDK\common\libにdllを捕捉、パスを通す。
  16. 実行。There is no device supporting CUDA.で止まる。そりゃそうだ、Radeonだし。
  17. デバイスエミュレーションに望みを託す。nvccに-deviceemuオプションをつけてやればOK。
    • nvcc cuda.cu -I”C:\Program Files (x86)\Microsoft Visual Studio 8\VC\include” -I”C:\Program Files (x86)\NVIDIA Corporation\NVIDIA CUDA SDK\common\inc” -L”C:\Program Files (x86)\Microsoft Visual Studio 8\VC\lib” -L”C:\Program Files (x86)\NVIDIA Corporation\NVIDIA CUDA SDK\common\lib” -lcutil32 -deviceemu
  18. 実行。ちゃんと動く。
    • ちなみに-deviceemuの有無でバイナリサイズは変わらず。動的リンクだからでいいのかな。

というわけで無事実行に成功。

DirectXのREFみたいにnativeとemulationでできることできないことに差が生じる可能性は否定しないけど、その辺は追々ということで。

CUDAでの__device__なclassについて

__device__なclassを作るとどうなるかというお話。

普通に使えたらかなり便利じゃね?

とりあえず、

CUDA:

  1. __global__ void
  2. test(int *value)
  3. {
  4.   value[0] = hoge.func() - hoge.func2();
  5. }


なんてのを用意しておいてみる。

CUDA:

  1. class CHoge
  2. {
  3. public:
  4.   int value;
  5.   CHoge(){
  6.     value = 99;
  7.   }
  8.   ~CHoge(){
  9.   }
  10.   int func()
  11.   {
  12.     return 98;
  13.   }
  14.   int func2()
  15.   {
  16.     return value;
  17.   }
  18. }hoge;


でどうか。

結果は、hoge.func()とhoge.func2()の両方で

"cuda.cu", line 78: error: calling a host function from a

__device__/__global__ function is only allowed in device emulation

mode

value[0] = hoge.func() - hoge.func2();//value;

^

"cuda.cu", line 78: error: calling a host function from a

__device__/__global__ function is only allowed in device emulation

mode

value[0] = hoge.func() - hoge.func2();//value;

^

2 errors detected in the compilation of "/tmp/tmpxft_00007432_00000000-3.ii".

となり、関数が呼べない。まぁそりゃそうだろう。

ちょっと問題を縮小し、最低限の記述に落としてみる。

CUDA:

  1. class CHoge
  2. {
  3. public:
  4.   int func()
  5.   {
  6.     return 98;
  7.   }
  8.   int func2()
  9.   {
  10.     return 99;
  11.   }
  12. }hoge;


これでも同じエラー。

CUDA:

  1. class CHoge
  2. {
  3. public:
  4.   __device__ int func()
  5.   {
  6.     return 98;
  7.   }
  8.   __device__ int func2()
  9.   {
  10.     return 99;
  11.   }
  12. }hoge;


これだとうまく動いて-1が得られる。なるほど。

ちょっと趣向を変えて。

CUDA:

  1. class CHoge
  2. {
  3. public:
  4.   __device__ int func()
  5.   {
  6.     return 98;
  7.   }
  8.   __device__ int func2()
  9.   {
  10.     return 99;
  11.   }
  12. };
  13. class CHoge hoge1;
  14. class CHoge hoge2;
  15.  
  16. __global__ void
  17. test(int *value)
  18. {
  19.   value[0] = hoge1.func() - hoge2.func2();
  20. }


  • 1。ふむ。

……あれ?classの実体宣言には__device__つけていないんだけど、いいのかなぁ?


classに変数を持たせてみる。

CUDA:

  1. class CHoge
  2. {
  3. public:
  4.   int value;
  5.   __device__ int set(int n)
  6.   {
  7.     value = n;
  8.     return value;
  9.   }
  10.   __device__ int func()
  11.   {
  12.     return 1;
  13.   }
  14.   __device__ int func2()
  15.   {
  16.     return value;
  17.   }
  18. };
  19. class CHoge hoge1;
  20. class CHoge hoge2;
  21.  
  22. __global__ void
  23. test(int *value)
  24. {
  25.   hoge1.set(10);
  26.   hoge2.set(20);
  27.   value[0] = hoge1.func2() + hoge2.func2();
  28. }


すると、

"cuda.cu", line 62: error: identifier "hoge1" is undefined

(hoge1.value) = 10;

^

"cuda.cu", line 63: error: identifier "hoge2" is undefined

(hoge2.value) = 20;

^

"cuda.cu", line 64: error: identifier "hoge1" is undefined

(value[0]) = (((hoge1.value)) + ((hoge2.value)));

^

"cuda.cu", line 64: error: identifier "hoge2" is undefined

(value[0]) = (((hoge1.value)) + ((hoge2.value)));

^

4 errors detected in the compilation of "/tmp/tmpxft_000074c1_00000000-4.i".

……あれ?

CUDA:

  1. __device__ class CHoge hoge1;
  2. __device__ class CHoge hoge2;


に変更したら、今度は

"cuda.cu", line 62: warning: expression has no effect

(hoge1.value; }

^

"cuda.cu", line 63: warning: expression has no effect

(hoge2.value; }

^

というwarnigのみになり、実行結果も30と正しそうな感じに。

まぁなんだ、要するにインライン展開されまくりなんだろうな、きっと。

とりあえず関数も持てる構造体、程度には使えると。

(ええと、C++の場合は構造体に関数持たせられるんだっけ?そういえば。)

次。

class CHoge

にコンストラクタやらデストラクタやらを追加してみる。

CUDA:

  1. CHoge()
  2.   {
  3.     value = 1;
  4.   }


これで

"/usr/lib/gcc/i386-redhat-linux/4.1.1/../../../../include/c++/4.1.1/cstdlib", line 178: internal error:

can't generate code for non empty constructors or destructors on

device

div(long long __n, long long __d)

^

1 catastrophic error detected in the compilation of "/tmp/tmpxft_00007555_00000000-3.ii".

Compilation aborted.

意味がわからんエラーが出る。まぁコンストラクタやデストラクタはダメだよって言われてるわけだな。

残念ながらコンストラクタを__device__にしても状況は変わらないし、int value;を__device__にしても

"cuda.cu", line 45: error: memory qualifier on data member is not allowed

__attribute__((__device__)) int value;

^

とかエラーが変わるのみ。


うーん。

流石にまともにclassらしくclassを活用することはできなさそうだ。ちょっと残念。

それにコンストラクタとかが使えないのは微妙に不便だ。

コンストラクタっぽいことをする妥当なアイディアとしては、classを__device__に持っていることが前提だけど、初期化用の__device__関数を利用するのがいいのかな。

CUDA:

  1. class CHoge
  2. {
  3. public:
  4.   int value;
  5.   __device__ int set(int n)
  6.   {
  7.     value = n;
  8.     return value;
  9.   }
  10.   __device__ int func()
  11.   {
  12.     return 1;
  13.   }
  14.   __device__ int func2()
  15.   {
  16.     return value;
  17.   }
  18. };
  19. __device__ class CHoge hoge1;
  20. __device__ class CHoge hoge2;
  21.  
  22. __global__ void init()
  23. {
  24.   hoge1.set(10);
  25.   hoge2.set(20);
  26. }
  27.  
  28. __global__ void test(int *value)
  29. {
  30.   value[0] = hoge1.func2() + hoge2.func2();
  31. }
  32.  
  33.  
  34. 以下、ホスト側
  35.  
  36.   // 変数の初期化のために一度GPUを動かしてみる
  37.   dim3 threads0(1, 1, 1);
  38.   dim3 grid0(1, 1, 1);
  39.   init<<<grid0, threads0>>>();
  40.  
  41.   // 本番?の実行
  42.   dim3 threads(THREAD_X, 1, 1);
  43.   dim3 grid(BLOCK_X, 1, 1);
  44.   test<<<grid, threads>>>(d_sd1);



それにしてもなんだこのグダグダのエントリ。まとまりわりい。

CUDAコンパイラ(ry 補足

変数idを__shared__にすると思い通りに動くよ!


 い み が 。

CUDAコンパイラの最適化が腐っている のか?

アレな状況に陥ったので報告。本家forumで議論してきたほうがいいかもシレン。

CUDA:

  1. __global__ void
  2. test(int *value)
  3. {
  4.   int id;
  5.   id = blockIdx.x;
  6.   if(id==0){
  7.     value[0] = 1;
  8.   }else if(id==1){
  9.     value[1] = 2;
  10.   }
  11. }


grid(2,1,1)な実行後のvalueは何になるか。

答えは

value[0] = 0

value[1] = 0

……いや、それはおかしいだろう。

実は、例えば以下のように書くと想定どおりの答えが得られる。

CUDA:

  1. __global__ void
  2. test(int *value)
  3. {
  4.   int id;
  5.   id = blockIdx.x;
  6.   if(id==0){
  7.     value[0] = 1;
  8.   }else if(id==1){
  9.     value[id] = 2;
  10.   }
  11. }


これで答えは

value[0] = 1

value[1] = 2

となる。

CUDA:

  1. __global__ void
  2. test(int *value)
  3. {
  4.   int id;
  5.   id = blockIdx.x;
  6.   if(id==0){
  7.     value[id] = 1;
  8.   }else if(id==1){
  9.     value[1] = 2;
  10.   }
  11. }


でもいいや。

CUDA:

  1. __global__ void
  2. test(int *value)
  3. {
  4.   int id;
  5.   id = blockIdx.x;
  6.   if(id==0){
  7.     value[0] = id;
  8.   }else if(id==1){
  9.     value[1] = 2;
  10.   }
  11. }


なんてのもありらしい。

あくまで予想なんだけど、ifブロックの中で変数idが使われていないという理由で、コンパイラがid変数を最適化の課程で殺しているんじゃないだろうか。だとしたらコンパイラのバグだよなぁ……。

やっぱ本家にゴルァかな?勘違いかもしれないので一日熟成させよう。

block間での同期を取る3

このへんの続き。

前回は何故か__syncthreads()を使ったりと妙にキモイことをやっていたので、追実験。

atomic関数の存在も念頭に置いてみる。

まぁこんなケースを考えることにする。

CUDA:

  1. #define BLOCK_X 8      // grid(8,1,1) を意味する
  2.  
  3. __device__ int waitflag[8] = {0,0,0,0,0,0,0,0};
  4.  
  5. __global__ void gpu
  6. (int *mem0, int *mem1, unsigned int *_buf)
  7. {
  8.   int id = blockIdx.x;
  9.   // 実行時間をずらすための処理
  10.   if(id==3){
  11.     mem1[0]=0;
  12.     mem1[1]=0;
  13.     mem1[2]=0;
  14.   }
  15.   // 各blockで_buf[0]を叩く
  16.   atomicAdd(&_buf[0], id);
  17.  
  18.   // このへんで同期を取れないと、計算結果がblock毎に狂う可能性がある。
  19.   // 具体的には、id3以外のblockでは
  20.   // id3のatomicAddが終わる前に_buf[0]を読んでしまう可能性が高い。
  21.   // 恐らく、mem0は 26 26 26 29 26 26 26 26 になる(各要素ともデフォルトが1なので)。
  22.  
  23.   // 各blockからみえる計算結果を、
  24.   // global変数に書き戻して確認
  25.   mem0[id] = _buf[0];
  26.  
  27.   return;
  28. }


んで同期をどうやるか。

とりあえず考えたのが以下の実装。

CUDA:

  1. atomicAdd(&waitflag[0], 1);
  2.   while(waitflag[0]<BLOCK_X);


atomicAddで加算の保証が出来るので、あとは全blockが加算するのを待てばいいんじゃないかと。

うん。失敗した。無限ループっぽい。

直感的にはこれで行けそうなんだけど、何かひっかかるらしい。

CUDA:

  1. atomicAdd(&waitflag[0], 1);
  2.   while(waitflag[0]<BLOCK_X)__syncthreads();


という感じに__syncthreads();を入れると正しく動作する。

__syncthread()にそれらしい仕様はない気がするんだが……?

ところで、atomicAddは確実に読み出して加算ができる、ってのはいいんだけど、その処理の途中で別のblock/threadが値を読んでしまうことってあるんだろうか。いや、別に「0が読めるか1が読めるかわからないよ」ってのは構わないというか当然なんだけど、何故か全然違う値が読めたりすると危険だよなぁ……。

さて、胡散臭い__syncthreads()を使わずにちゃんとやるにはどうすればいいかなんだが、作業用の変数を追加して

CODE:

  1. __device__ int waitflag[8] = {0,0,0,0,0,0,0,0}; // 関数外宣言
  2.   int wait = 0; // 関数内宣言
  3.  
  4.   // 計算終了通知
  5.   waitflag[id] = 1;
  6.   // 全blockが計算終了に到達していることを確認する
  7.   while(wait <BLOCK_X){
  8.     wait = 0;
  9.     for(int i=0; i<BLOCK_X; i++){
  10.       wait += waitflag[i];
  11.     }
  12.   }


とやったらうまくいった。

安全を確保するならwaitflagの変更はatomicの方がいいのかもしれないけど、まぁ大丈夫だろう。

さて。以上の振る舞いから、今回のglobal memory spaceの変数の参照はコンパイラによって最適化されてしまっているのではないかという仮説が立てられる。

nvccの-ptxや-keepで中間コードが覗けるんだけど、これ見て判断するのはちょっとツライんだよなぁ。

最後。

仮設を踏まえて適当にうまく行く記述を考えてみた。

CUDA:

  1. atomicAdd(&waitflag[0], 1);
  2.   while(wait<BLOCK_X){wait=waitflag[0];}


明示的にglobal memory spaceからの再読み込みを行うような感じ。

実際、これで思い通りの動作は出来ている。

ちなみに、どの実装にしても複数回の実行にはちょっとだけ工夫が必要かもしれない。

作業用変数のクリアが必要だから。

とはいえ、作業用変数を多重化して、同期が終了するときに今使ったものとは別の作業用変数を初期化してやれば動きそうだから多分大丈夫だろう。たぶん。

以下、#ifで切ったプログラム全体。長いけど参考にベタッと貼っておく。

CUDA:

  1. // -*- C++ -*-
  2. /*
  3.   block間の同期を行ってみるテスト
  4. */
  5.  
  6. #include <stdlib.h>
  7. #include <stdio.h>
  8. #include <string.h>
  9. #include <math.h>
  10. #include <unistd.h>
  11.  
  12. #include <cutil.h>
  13.  
  14. #define N 8
  15. #define BLOCK_X N
  16. #define THREAD_X 1
  17.  
  18. //extern __device__ int* buf;
  19. __device__ int waitflag[8] = {0,0,0,0,0,0,0,0};
  20.  
  21. __global__ void gpu
  22. (int *mem0, int *mem1, unsigned int *_buf)
  23. {
  24.   // -------- -------- -------- --------
  25.   int wait = 0;
  26.   int id = blockIdx.x;
  27.   // 実行時間をずらすための処理
  28.   if(id==3){
  29.     mem1[0]=0;
  30.     mem1[1]=0;
  31.     mem1[2]=0;
  32.   }
  33.   // 各blockで_buf[0]を叩く
  34.   atomicAdd(&_buf[0], id);
  35.  
  36. #if SYNC==3
  37.   // 計算終了通知
  38.   waitflag[id] = 1;
  39.   // 全blockが計算終了に到達していることを確認する
  40.   while(wait <BLOCK_X){
  41.     wait = 0;
  42.     for(int i=0; i<BLOCK_X; i++){
  43.       wait += waitflag[i];
  44.     }
  45.   }
  46. #elif SYNC==4
  47.   atomicAdd(&waitflag[0], 1);
  48.   while(wait<BLOCK_X){wait=waitflag[0];}
  49. #elif SYNC==2
  50.   atomicAdd(&waitflag[0], 1);
  51.   while(waitflag[0]<BLOCK_X)__syncthreads();
  52. #else // NG
  53.   atomicAdd(&waitflag[0], 1);
  54.   while(waitflag[0]<BLOCK_X);//__syncthreads();
  55. #endif
  56.   // ここから、各blockが計算結果を確実に取得できる
  57.   // global変数に書き戻して確認
  58.   mem0[id] = _buf[0];
  59.   // 待ち合わせデータのチェックのため、
  60.   // global変数に書き戻して確認
  61.   mem1[id] = wait;
  62.  
  63.   return;
  64.  
  65.   // -------- -------- -------- --------
  66. }
  67.  
  68. // -------- -------- -------- -------- -------- -------- -------- --------
  69.  
  70. void
  71. runTest(int argc, char** argv)
  72. {
  73.   CUT_DEVICE_INIT();
  74.  
  75.   int* h_mem0;
  76.   int* h_mem1;
  77.   int* d_mem0;
  78.   int* d_mem1;
  79.   h_mem0 = (int*)malloc(sizeof(int)*N);
  80.   h_mem1 = (int*)malloc(sizeof(int)*N);
  81.   CUDA_SAFE_CALL(cudaMalloc((void**)&d_mem0, sizeof(int)*N));
  82.   CUDA_SAFE_CALL(cudaMalloc((void**)&d_mem1, sizeof(int)*N));
  83.   //CUDA_SAFE_CALL(cudaMemcpy(d_mem0, h_mem0, sizeof(int)*N, cudaMemcpyHostToDevice) );
  84.   //CUDA_SAFE_CALL(cudaMemcpy(d_mem1, h_mem1, sizeof(int)*N, cudaMemcpyHostToDevice) );
  85.   unsigned int* h_buf;
  86.   unsigned int* d_buf;
  87.   CUDA_SAFE_CALL(cudaMallocHost((void**)&h_buf, sizeof(int)*N));
  88.   int n;