ラベル CUDA の投稿を表示しています。 すべての投稿を表示
ラベル CUDA の投稿を表示しています。 すべての投稿を表示

2009年3月13日金曜日

CUDA+Fortran

昨日から今日にかけてホワイトデーのお返しで菓子をあちこちからいっぱいもらったのでなんか嬉しい。もともと甘いもんスキーなので。先生方からもお返しもらってしまった。若干恐縮するなぁ。
研究室の工事があって、夕方から猛烈に接着剤のにおいがしだして、頭痛+アレルギー症状が劇化したので早々に帰ってきた。昔から溶剤なにおいとか防虫剤なにおいとか芳香剤なにおいが嫌いというか嗅ぐと気分悪くなったり頭痛くなる。なわけで今日はあんまり仕事してない。まぁ実は数値計算の新バージョンの計算コードの動作チェック(発狂しそうにめんどくさかった)も完了してあとはひたすら計算機のパワーに任せるだけの段階になったので気分的にのんびりしてたりする。

そんなわけで昨日に引き続きCUDAで遊ぶ。
計算物理のぎょーかいでは速度命だったり昔々の偉人がつくったコードを使いまわしたり、線形代数の神ライブラリLapackを使うことが多いのでいまだにFortranが幅を利かせている。他の分野ではFortranなにそれオイシイの?だろうけどなぁ…。自分もメインはFortran90でありこれまで作ってきたのも大半がそれベース。なのでその一部をCUDAで高速化しようと思うと、FortranからCUDAコードを呼び出す方法をお勉強しとかなあかん。
FortranとCはわりかし互換性がよくて、もともと相互にFortran中でCの関数を呼ぶとかC中でFortranのサブルーチンを呼び出すとかができる。CUDAはCがベースなので、Fortranから呼び出すことも実は比較的容易っぽい。

CUDAに限らず、FortranとCを混在させるときの注意点
・FortranからCの関数を呼ぶときには、呼び出される側のCの関数にexternを付けておく、最後に_を付けておく。
・Fortranはそもそもサブルーチン等の引数が参照渡しなのでC側では引数をポインタとして扱わないといけない。
・Fortranは配列が1から始まる、Cでは0から始まる。Fortranでは列優先なので(a,b)の次は(a+1,b)、Cでは行優先なので(a,b)の次は(a,b+1)。

とりあえず昨日の配列の和のコードをベースに作る
Fortranで配列を準備→CUDA側に引数で渡す、GPUで計算→Fortranサイドで出力チェックてなことをやってみようと。環境はLinux(CentOS5)+ifort

CUDA側(testf.cu)



#include <stdio.h>
#include <stdlib.h>
#include <cuda.h>
#include <cuda_runtime.h>


__global__ void wa ( int *array1, int *array2, int len)
{
int i;
for (i=0; i<len ;i++)
array1[i]= array1[i]+array2[i];
return;
}

extern "C" void cudafunc_ (int* arrayH1, int* arrayH2, int* Np)
{
int i;
int N;
int* arrayD1;
int* arrayD2;

size_t array_size;

N=*Np;

printf("inside the C code \n");
printf("input array \n");

printf("input of H1 \n");
for (i=0;i<N;i++)
printf("%d\n",arrayH1[i]);

printf("input of H2\n");
for (i=0;i<N;i++)
printf("%d\n",arrayH2[i]);


array_size = sizeof(int) * N;

cudaMalloc( (void **) &arrayD1, array_size);
cudaMalloc((void **) &arrayD2, array_size);

cudaMemcpy(arrayD1,arrayH1,array_size,cudaMemcpyHostToDevice);
cudaMemcpy(arrayD2,arrayH2,array_size,cudaMemcpyHostToDevice);

wa <<<dim3(1,1),dim3(1,1,1)>>>(arrayD1,arrayD2,N);

cudaMemcpy(arrayH1,arrayD1,array_size,cudaMemcpyDeviceToHost);

printf("output\n");
for (i=0;i<N;i++)
printf("%d\n",arrayH1[i]);


return ;

}




Fortran側(testcuda.f90)



program main
!test for CUDA+Fortran

!the length of array
integer,parameter :: N=8
integer :: arrayH1(1:N)
integer :: arrayH2(1:N)
integer :: i


!setting the input array
do i=1,N
arrayH1(i)=i
arrayH2(i)=i*2
end do

call cudafunc(arrayH1,arrayH2,N)

print*,"output in fortran code"

do i=1,N
print*,arrayH1(i)
end do

end program main


Makefileの例

all: CUDAfortran

# Define Fortran compiler
FC= ifort

CUDAfortran: testcuda.f90 testf.o
$(FC) -o CUDAfortran testcuda.f90 testf.o -L/usr/local/cuda/lib -lcudart

testf.o: testf.cu
nvcc -c testf.cu

clean:
rm testf.o CUDAfortran


うむ、なんとか動いた。これであれやこれやをGPUで計算できるわけだがなんと10秒ルールとやらがあるらしい。GPU上のジョブの時間制限10秒ってマジっすか?10秒で計算終わってもデータ転送に時間かかってるっぽいしなんだか用途の制限多いんじゃ…?まぁ面白そうではあるので引き続きいろいろ調べると思う。

2009年3月12日木曜日

CUDAその3

研究室にNIVIDA Tesla C1060が入ったやつが導入されたのでぼちぼちCUDAをいじってみる。

TeslaはGPUのくせに、ビデオ出力の機能がない。ひたすら数値演算に特化している。とりあえず、C1060だとマルチプロセッサが240あってしかも各プロセッサに8つのコアが含まれているらしい。超並列処理とかいっちゃってるわけだ。
GPGPUのウリはこの並列化らしい。各プロセッサの8つのコアはSIMT(single instruction multi thread)という形式で同じ内容の手続きを複数のスレッドに分けて実行する(SIMDと似ているがこっちのほうがややこしい処理を並列化できるんか)。スレッドは8*4=32が基本単位になっていて、32スレッドを1ワープと数えるらしい。変なの。

母体となるコードはCPU上で実行される。コードのなかでも並列化が肝になってくるところをGPU上で行うのが普通。GPU側で処理される手続きをカーネルと呼ぶ。
ふつーカーネルで行われる手続きは並列化される。並列化の基本単位はスレッドなんだが、CUDAではスレッドの塊とさらにその塊の集まったモノを定義する。スレッドの塊はスレッドブロック、スレッドブロックの集まりをグリッドという。ひとつのカーネルに対して、ひとつのグリッドが割り当てられる。スレッドブロックのインデックスは2次元まで、スレッドそのもののインデックスは3次元までとることができる。こんなに入れ子な仕組みにするのはたぶんGPUのハード側の特性(マルチプロセッサが並列+そのなかのコアが並列)になってるからだろう。それがメモリの階層性にも反映されている。メモリはグローバルメモリとシェアードメモリの2つに大別される。グローバルメモリはグリッド内すべてで共有であり、シェアードメモリはブロック内のみで共有される。シェアードメモリのほうが圧倒的にアクセスが早い。

CUDAのプログラムの大まかな流れは

CPUサイドでデータ処理

GPU上でメモリ確保(cudaMalloc)

GPUへデータ転送 (cudaMemorycpy)

GPUで計算(aaa<<>>(x,y,z))

GPUからデータ転送(cudaMemorycpy)

こんな感じ。めっちゃ簡単な一次元の配列を足し算するだけのをつくってみた。バージョンはCUDA v2.0。2.0になってちょこちょこ変更があったらしく、Web上の旧バージョンのサンプルとはヘッダファイルの名前が変わってたりというトラップあり。要注意。


#include <stdio.h>
#include <stdlib.h>
#include <cuda.h>
#include <cuda_runtime.h>

#define N (32)

__global__ void wa ( int *array1, int *array2, int len)
{
int i;
for (i=0; i<len ;i++)
array1[i]= array1[i]+array2[i];
return;
}

int main (int argc,char *argv[])
{
int i;
int arrayH1[N];
int arrayH2[N];
int arrayoutput[N];

int* arrayD1;
int* arrayD2;

size_t array_size;
printf("input array \n");
for (i=0; i<N;i++) arrayH1[i]=i;
for (i=0; i<N;i++) arrayH2[i]=i*2;

printf("input of H1 \n");
for (i=0;i<N;i++)

printf("%d\n",arrayH1[i]);

printf("input of H2\n");
for (i=0;i<N;i++)

printf("%d\n",arrayH2[i]);


array_size = sizeof(int) * N;

cudaMalloc( (void **) &arrayD1, array_size);
cudaMalloc((void **) &arrayD2, array_size);

cudaMemcpy(arrayD1,arrayH1,array_size,cudaMemcpyHostToDevice);
cudaMemcpy(arrayD2,arrayH2,array_size,cudaMemcpyHostToDevice);
wa <<<dim3(1,1),dim3(1,1,1)>>>(arrayD1,arrayD2,N);

cudaMemcpy(arrayoutput,arrayD1,array_size,cudaMemcpyDeviceToHost);

printf("output\n");
for (i=0;i<N;i++)

printf("%d\n",arrayoutput[i]);


return 0;

}



なんか(void**)が??だったけど、CUDAではこういう型もあるっぽい。void*はvoid型ポインタだからその拡張か?C言語自体あんまりしらんのでなかなか目新しいのが満載だ。今回つかったのは単純のためまったく並列化もなんもせんと1ブロック1スレッド。さて次は並列化を学習しよう。

2009年1月20日火曜日

CUDAをコマンドラインから使えるようにしよう

VisualStudioが意味フなのでWindowsでもコマンドラインから使えるようにしたいなぁと。どうせ本番はLinux機の可能性100パーですし。
ちなみにOSはWindowsXPです。
コマンドラインから使えるようにするには環境変数をいろいろいじらんとあかんみたいだ。
先人の知恵がつまった参考文献3つで言っていることが違うぜ、どうしよう
http://tech.ckme.co.jp/cuda_inst.shtml
http://exth.net/~ohshima/wordpress/2007/10/30/5/
http://chihara.naist.jp/people/STAFF/imura/computer/OpenGL/cuda1/disp_content

環境変数INCLUDEなるものが見当たらないので、まず真ん中の人がやっているのと同様に、システムのPATHをいじることにする。
とりあえずPATHに


C:\Program Files\Microsoft Visual Studio 8\VC\bin;C:\Program Files\Microsoft Visual Studio 8\Common7\IDE;C:\Program Files\NVIDA Corporation\NVIDA CUDA SDK\common\lib


を足してみる。
で、昨日ためして動いたbandWidthTest.cuをコンパイルして実行すべく


nvcc -o test.exe bandWidthTest.cu -I"C:\Program Files\Microsoft Visual Studio 8\VC\include" -I"C:\Program Files\NVIDIA Corporation\NVIDIA CUDA SDK\common\inc" -L"C:\Program Files\Microsoft Visual Studio 8\VC\lib" -L"C:\Program Files\NVIDIA Corporation\NVIDIA CUDA SDK\common\lib" -lcutil32


てなバッチファイルを作って投下。
test.exe無事生成
が、cutil32.dll見つからないというエラーメッセージ出て動かず

そこで最後の文献を参考にして
C:\Program Files\NVIDIA Corporation\NVIDIA CUDA SDK\common\lib\cutil32.dllをC:\Windows\system32へコピーすると動いた…いいのかこれで?
たぶんファイル構造をつらつら見るに、
C:\Program Files\NVIDIA Corporation\NVIDIA CUDA SDK\bin\win32\Release
にPATH通しといても同様に動くはず。このwin32\Releaseやwin32\DebugはVisualStudioのコンパイラ経由でDebugなりReleaseなりするときに必要なDLLとかを全部ほりこんであるぽいのでPATH通しとくのがいいんじゃないかなぁ。やっとこ

なんにしろ無事うごいたっぽいのでCUDAが出始めのころに試して記録を残してくれた先人たちに感謝感謝。

2009年1月19日月曜日

CUDAを試す

どうやらCFSは順調に動いたようで磁場下での1粒子励起スペクトルが綺麗に出た。今までの方法と段違いにきれいな結果だ。ひじょーに満足。作るの苦労した分、上手くいくとめちゃうれしいわぁ~。これで夏の国際会議のネタが確保できる見通しもついた。

ほっとしたので、自宅機で噂のCUDAを試す環境を作ってみる。CUDAって何て読むんだろ。きゅーだ?くだ?
研究室でも最新版Teslaを試そうぜっていう話がでてたんだが、取引先の業者さんがまだ性能評価終わってないらしい。Teslaは結構熱がでるとかでファンをどうするとかGPUコンピューティングするなら帯域がどーとかそれなりに相性とか考えなあかんぽいとか。評価終わるの待ってて予算執行がぎりぎりになるんもあれだし、導入はたぶん来年度かねぇ。それまで自宅のにはせっかくCUDAが動かせるGeForce9800GTが入っているので試しにいじってみようかと。GPUつかったら画面が出力できないとかOTLなことになるんじゃないのとかいろいろ無駄におそれたがべつにそんなことはないらしい。
http://tech.ckme.co.jp/cuda_inst.shtml
に詳しくインストールの仕方が書いてあるのでとりあえず試す。
ドライバは対応してるのがもともと入っているのでいじらない(グラボのドライバってなんかいじってえらい目にあった知人の話聞いて以来怖い)
NVIDAのページhttp://www.nvidia.com/object/cuda_get.htmlからcuda toolkit cuda sdkのバージョン2.0をダウンロード。バージョン2.0を動かすためにはWindowsではVisual Studio 2005C++が必要らしいのでそれもMicrosoftのページからダウンロード。最新版は2008らしいがCUDAの2.0は2005じゃないといかんらしい。あとDirectXつかうサンプルもあるらしく、DirectX SDKも入れといたほうがいいらしいが、入れ忘れた。まぁとりあえず放置。

CUDAについてはtoolkit → sdkの順でインストールすべしとのこと。インストールはデフォルト通りにしといた。後でみたらいろいろパスが増えとる。参考資料によると、CUIでコンパイル、ビルドするにはパスをいろいろいじる必要があるらしいが、今日のところはとりあえず動くかどうかをチェックしたいのでまたとりあえず放置。
デフォルト通りにすると
C:\Program Files\NVIDIA Corporation\NVIDIA CUDA SDK\projects\simpleTemplates
下にsimpleTempletes.slnなるものあり。
とりあえずこれが動くかどうかでCUDAが無事インストールされたかどうかがわかる模様。
これをクリックするとVisual Studioが立ち上がる
ビルド→ソリューションのビルド
なんか一応正常終了したっぽい
Visual Studio使ってないもんでよくわからんのだが、たぶん緑の矢印が「実行」てのは共通だろうとあたりを付けてとりあえずそれを押す(これデバッグらしいが)

とりあえず動いたっていう解釈でいいのか…?謎いわ。やっぱLinuxのほうがgccで直コンパイル+ビルドできて見通しつけやすそう。sampleにMonteCarloあんのが気になる
結果がWebにあがってたbandWidthTestも試す

出てる項目大体いっしょぽいなぁ。ビルド時に警告でたけど動いてるってことか、よーわからん。CUDAなかなか手ごわそうじゃ。