CUDAと浮動小数点の誤差に関する実験メモ

マルチポスト元→http://exth.net/~tgbt/wordpress/2009/08/14/2433/


「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に投げ込むにはちょっと問題(ソースコード)がシンプルじゃないしなあ.もっとプリミティブな例を作成してみようかしら?