「CUDAはIEEE754対応だよ!計算精度はCPUと変わらないよ!!!」みたいなことをこれまでに何度か聞いていたような気がするので,実際のところどうなのかを試してみた.これが直接的に論文のネタになるとは思っていないので,公開しておきます.
対象問題
誤差が出る問題として何を使うかちょっと悩んだんだけど,手元の数値計算の教科書(新版 入門数値計算)の最初に掲載されていた「ニュートン・ラフソン法」を実装してみることにした.
もはや授業を受けたときの頃は全然覚えていないんだけど, f(x) = x^3 -2x -5 = 0の実根alphaを求める という問題.「組み立て除法」ってのを使う.
ソースコード
難しいことは考えずに,ガツッと決め撃ちした.
void kernel(TYPE *rs, TYPE *ep)
{
TYPE X3 = 1.0f;
TYPE X2 = 0.0f;
TYPE X1 = -2.0f;
TYPE X0 = -5.0f;
TYPE x3 = 0.0f;
TYPE x2 = 0.0f;
TYPE x1 = 0.0f;
TYPE x0 = 0.0f;
TYPE z0 = INIT;
TYPE z = z0;
TYPE a, b, c, d, e, f, g;
int i;
for(i=0; i<NLOOP; i++){
x3 = X3;
x2 = X2;
x1 = X1;
x0 = X0;
a = x3;
b = a * z;
c = x2 + b;
d = c * z;
e = x1 + d;
f = e * z;
g = x0 + f;
x3 = a;
x2 = c;
x1 = e;
z = g;
a = x3;
b = a * z0;
c = x2 + b;
d = c * z0;
e = x1 + d;
rs[i] = z0 - z/e;
ep[i] = (z/e)*(z/e);
z0 = rs[i];
z = z0;
}
}
CPUもGPUも同じ計算カーネル.(GPUのカーネルにはもちろん__global__がつきますが.)
速度はどうでも良いし並列性もどうでも良い.Grid/Blockのパラメタも(1,1)(1,1,1)な設定でOK.
型TYPEはfloatとdoubleとを切り替えてコンパイルできるようにしている.初期値INITも入れ替えられるようにしておいた.
実行環境など
Core i7 + TeslaC1060.CentOS5.x x86_64.cudaのバージョンは2.21.-m 64で64bitのバイナリを吐かせている.doubleを使いたいので,もちろん-arch=sm_13.
実行結果
教科書では初期値2.0でやってたんだけど,収束の具合が見たいので適当に1.0にしてやってみた.ループ回数は特に考えも無しに適当に20回.
はじめにfloatでやった結果から.結果と誤差,それぞれ右下に行くほど繰り返し数が多い際の値.
結果:CPU 7.000000e+00 4.765517e+00 3.348703e+00 2.531600e+00 2.173916e+00 2.097884e+00 2.094558e+00 2.094552e+00 2.094551e+00 2.094552e+00 2.094551e+00 2.094552e+00 2.094551e+00 2.094552e+00 2.094551e+00 2.094552e+00 2.094551e+00 2.094552e+00 2.094551e+00 2.094552e+00
結果:GPU 7.000000e+00 4.765517e+00 3.348703e+00 2.531600e+00 2.173916e+00 2.097884e+00 2.094558e+00 2.094552e+00 2.094552e+00 2.094552e+00 2.094552e+00 2.094552e+00 2.094552e+00 2.094552e+00 2.094552e+00 2.094552e+00 2.094552e+00 2.094552e+00 2.094552e+00 2.094552e+00
誤差:CPU 3.600000e+01 4.992913e+00 2.007363e+00 6.676576e-01 1.279377e-01 5.780894e-03 1.106205e-05 3.943923e-11 1.642639e-14 4.562891e-14 1.642639e-14 4.562891e-14 1.642639e-14 4.562891e-14 1.642639e-14 4.562891e-14 1.642639e-14 4.562891e-14 1.642639e-14 4.562891e-14
誤差:GPU 3.600000e+01 4.992913e+00 2.007363e+00 6.676576e-01 1.279377e-01 5.780877e-03 1.106177e-05 3.784591e-11 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00 0.000000e+00
結果を見ると同じに見えるけど,実は途中で少しだけ値が変わってしまっている.
そして誤差を見るとGPUは途中から誤差が無くなってしまっている.なんだか変だ.
とはいえここまではfloat型.本番?はdouble型だよね.
結果:CPU 7.000000e+00 4.765517e+00 3.348703e+00 2.531600e+00 2.173916e+00 2.097884e+00 2.094558e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00
結果:GPU 7.000000e+00 4.765517e+00 3.348703e+00 2.531600e+00 2.173916e+00 2.097884e+00 2.094558e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00 2.094551e+00
誤差:CPU 3.600000e+01 4.992913e+00 2.007363e+00 6.676575e-01 1.279377e-01 5.780895e-03 1.106208e-05 3.886632e-11 4.787812e-22 2.532912e-32 2.532912e-32 2.532912e-32 2.532912e-32 2.532912e-32 2.532912e-32 2.532912e-32 2.532912e-32 2.532912e-32 2.532912e-32 2.532912e-32
誤差:GPU 3.600000e+01 4.992913e+00 2.007363e+00 6.676575e-01 1.279377e-01 5.780895e-03 1.106208e-05 3.886632e-11 4.787810e-22 3.447355e-33 3.447355e-33 3.447355e-33 3.447355e-33 3.447355e-33 3.447355e-33 3.447355e-33 3.447355e-33 3.447355e-33 3.447355e-33 3.447355e-33
計算結果を見る限りではCPUとGPUの差は見受けられなかった.でも誤差を見ると違いがある.
なんだろうね,これ.
考察
というわけでして,CPUとGPUでは(double型で計算しても)実行結果に差が生じた.
原因はちょっとまだわかっていない.レジスタの違い?
そもそも,こうやって計算結果(誤差計算結果)の違いを見てしまうと,使い方が悪いだけなのか(GPUには問題はないのか)という疑問が生じるところ.実はGPUもちゃんとIEEE754には対応していて,CPUはさらに高精度な計算をしている?それはちょっとおかしいよねえ.
まぁとりあえずこんなところで.
数値計算に詳しい人に聞いてみるか,何も考えずにNVIDIAのForumに投げ込むか.でもForumに投げ込むにはちょっと問題(ソースコード)がシンプルじゃないしなあ.もっとプリミティブな例を作成してみようかしら?
最近のコメント