3

N上のN個のポイント間の距離の逆数の合計を計算するカーネルを作成しようとしています。Cのシリアルコーダは次のようになります。

    average = 0;
for(int i = 0; i < Np; i++){
    for(int j = i + 1; j < Np; j++){
        average += 1.0e0f/sqrtf((rx[i]-rx[j])*(rx[i]-rx[j]) + (ry[i]-ry[j])*(ry[i]-ry[j]));
    }
}
average = average/(float)N;

ここで、rxとryはそれぞれx座標とy座標です。

乱数ジェネレーターを使用するカーネルを介してポイントを生成します。カーネルの場合、4k(8k)ポイントでブロックあたり128(256)スレッドを使用しました。その上で、すべてのスレッドが内側の上の内側のループを実行し、次のように結果がreducesum関数に渡されます。

ポイントの生成:

__global__ void InitRNG ( curandState * state, const int seed ){
    int tIdx = blockIdx.x*blockDim.x + threadIdx.x;
    curand_init (seed, tIdx, 0, &state[tIdx]);
}

__global__
void SortPoints(float* X, float* Y,const int N, curandState *state){

    float rdmn1, rdmn2;

    unsigned int tIdx = blockIdx.x*blockDim.x + threadIdx.x;
    float range;
    if(tIdx < N){
        rdmn1 = curand_uniform(&state[tIdx]);
        rdmn2 = curand_uniform(&state[tIdx]);
        range = sqrtf(0.25e0f*N*rdmn1);
            X[tIdx] = range*cosf(2.0e0f*pi*rdmn2);
            Y[tIdx] = range*sinf(2.0e0f*pi*rdmn2);
    }
}

割引:

__device__
float ReduceSum2(float In){

    __shared__ float data[BlockSize];

    unsigned int tIdx = threadIdx.x;

    data[tIdx] = In;
    __syncthreads();

    for(unsigned int i = blockDim.x/2; i > 0; i >>= 1){

        if(tIdx < i){

            data[tIdx] += data[tIdx + i];   
        }

        __syncthreads();
    }

    return data[0];

}

カーネル:

__global__ 
void AvgDistance(float *X, float *Y, float *Avg, const int N){

    int tIdx = blockIdx.x*blockDim.x + threadIdx.x;
    int bIdx = blockIdx.x;

    float x , y;
    float d = 0.0f;
    if(tIdx < N){

        for(int i = tIdx + 1; i < N ; i++){

            x = X[tIdx] - X[i];
            y = Y[tIdx] - Y[i];

            d += 1.0e0f/(sqrtf(x*x + y*y));     
        }
        __syncthreads();
        Avg[bIdx] = ReduceSum2(d);

    }
}

カーネルは次のように構成および起動されます。

dim3 threads(BlockSize,BlockSize);
dim3 blocks(ceil(Np/threads.x),ceil(Np/threads.y));

InitRNG<<<blocks.x,threads.x>>>(d_state,seed);
SortPoints<<<blocks.x,threads.x>>>(d_rx,d_ry,Np,d_state);
AvgDistance<<<blocks.x,threads.x,threads.x*sizeof(float)>>>(d_rx,d_ry,d_Avg,Np);

最後に、データをホストにコピーして戻し、残りの合計を実行します。

Avg = new float[blocks.x];

CHECK(cudaMemcpy(Avg,d_Avg,blocks.x*sizeof(float),cudaMemcpyDeviceToHost),ERROR_CPY_DEVTOH);
float average = 0;

for(int i = 0; i < blocks.x; i++){
    average += Avg[i];
}
average = average/(float)Np;

4kポイントの場合、OK!結果は次のとおりです。

Average distance between points (via Kernel) = 108.615
Average distance between points (via CPU) = 110.191

この場合、合計が異なる順序で実行され、両方の結果が互いに異なる可能性があります、私にはわかりません...

しかし、8kになると、結果はまったく異なります。

Average distance between points (via Kernel) = 153.63
Average distance between points (via CPU) = 131.471

私には、カーネルとシリアルコードの両方が同じように書かれているように見えます。浮動小数点数のCUDA計算の精度に不信感を抱く原因は何ですか。これは意味がありますか?または、一部のスレッドがXとYから同時に同じデータをロードすると、グローバルメモリへのアクセスによって競合が発生しますか?または、私がカーネルを書いた方法が何らかの形で「間違っている」(つまり、両方の結果が互いに異なる原因になっていることをしているのでしょうか?)。

4

1 に答える 1

7

実は、私が言えることから、問題はCPU側にあるようです。あなたのコードに基づいてサンプルコードを作成しました。

結果を再現することができました。

まず、、、およびのすべてのインスタンスを対応するダブルバージョンに切り替えsinfましcosfsqrtf。これは結果に違いはありませんでした。

float次に、typedefを含めたので、精度を簡単に切り替えて、コード内のdouble関連するすべてのインスタンスをtypedefに置き換えることができました。floatmytype

typedefがfloatでデータサイズが4096のコードを実行すると、次の結果が得られます。

GPU average = 108.294922
CPU average = 109.925285

typedefがdoubleでデータサイズが4096のコードを実行すると、次の結果が得られます。

GPU average = 108.294903
CPU average = 108.294903

typedefがfloatでデータサイズが8192のコードを実行すると、次の結果が得られます。

GPU average = 153.447327
CPU average = 131.473526

typedefがdoubleでデータサイズが8192のコードを実行すると、次の結果が得られます。

GPU average = 153.447380
CPU average = 153.447380

少なくとも2つの観察があります:

  1. GPUの結果は、小数点以下5桁を除いて、floatとdoubleの間で変化しません。
  2. CPUの結果はfloatとdoubleの間で1〜20%程度異なりますが、doubleを選択すると、GPUの結果と正確に一致します(とにかく小数点以下6桁まで)。

これに基づいて、CPUは可変で疑わしい動作を提供していると思います。

参考までに私のコードは次のとおりです。

#include <stdio.h>
#include <curand.h>
#include <curand_kernel.h>
#define DSIZE 8192
#define BlockSize 32
#define pi 3.14159f


#define cudaCheckErrors(msg) \
    do { \
        cudaError_t __err = cudaGetLastError(); \
        if (__err != cudaSuccess) { \
            fprintf(stderr, "Fatal error: %s (%s at %s:%d)\n", \
                msg, cudaGetErrorString(__err), \
                __FILE__, __LINE__); \
            fprintf(stderr, "*** FAILED - ABORTING\n"); \
            exit(1); \
        } \
    } while (0)

typedef double mytype;

__global__ void InitRNG ( curandState * state, const int seed ){
    int tIdx = blockIdx.x*blockDim.x + threadIdx.x;
    curand_init (seed, tIdx, 0, &state[tIdx]);
}

__global__
void SortPoints(mytype* X, mytype* Y,const int N, curandState *state){

    mytype rdmn1, rdmn2;

    unsigned int tIdx = blockIdx.x*blockDim.x + threadIdx.x;
    mytype range;
    if(tIdx < N){
        rdmn1 = curand_uniform(&state[tIdx]);
        rdmn2 = curand_uniform(&state[tIdx]);
        range = sqrt(0.25e0f*N*rdmn1);
            X[tIdx] = range*cos(2.0e0f*pi*rdmn2);
            Y[tIdx] = range*sin(2.0e0f*pi*rdmn2);
    }
}

__device__
mytype ReduceSum2(mytype In){

    __shared__ mytype data[BlockSize];

    unsigned int tIdx = threadIdx.x;

    data[tIdx] = In;
    __syncthreads();

    for(unsigned int i = blockDim.x/2; i > 0; i >>= 1){

        if(tIdx < i){

            data[tIdx] += data[tIdx + i];
        }

        __syncthreads();
    }

    return data[0];

}

__global__
void AvgDistance(mytype *X, mytype *Y, mytype *Avg, const int N){

    int tIdx = blockIdx.x*blockDim.x + threadIdx.x;
    int bIdx = blockIdx.x;

    mytype x , y;
    mytype d = 0.0f;
    if(tIdx < N){

        for(int i = tIdx + 1; i < N ; i++){

            x = X[tIdx] - X[i];
            y = Y[tIdx] - Y[i];

            d += 1.0e0f/(sqrt(x*x + y*y));
        }
        __syncthreads();
        Avg[bIdx] = ReduceSum2(d);

    }
}

mytype cpu_avg(const mytype *rx, const mytype *ry, const int size){
  mytype average = 0.0f;
  for(int i = 0; i < size; i++){
    for(int j = i + 1; j < size; j++){
        average += 1.0e0f/sqrt((rx[i]-rx[j])*(rx[i]-rx[j]) + (ry[i]-ry[j])*(ry[i]-ry[j]));
    }
  }
  average = average/(mytype)size;
  return average;
}

int main() {

  int Np = DSIZE;
  mytype *rx, *ry, *d_rx, *d_ry, *d_Avg, *Avg;
  curandState *d_state;
  int seed = 1;

  dim3 threads(BlockSize,BlockSize);
  dim3 blocks((int)ceilf(Np/(float)threads.x),(int)ceilf(Np/(float)threads.y));
  printf("number of blocks = %d\n", blocks.x);
  printf("number of threads= %d\n", threads.x);
  rx = (mytype *)malloc(DSIZE*sizeof(mytype));
  if (rx == 0) {printf("malloc fail\n"); return 1;}
  ry = (mytype *)malloc(DSIZE*sizeof(mytype));
  if (ry == 0) {printf("malloc fail\n"); return 1;}

  cudaMalloc((void**)&d_rx, DSIZE * sizeof(mytype));
  cudaMalloc((void**)&d_ry, DSIZE * sizeof(mytype));
  cudaMalloc((void**)&d_Avg, blocks.x * sizeof(mytype));
  cudaMalloc((void**)&d_state, DSIZE * sizeof(curandState));
  cudaCheckErrors("cudamalloc");



  InitRNG<<<blocks.x,threads.x>>>(d_state,seed);
  SortPoints<<<blocks.x,threads.x>>>(d_rx,d_ry,Np,d_state);
  AvgDistance<<<blocks.x,threads.x,threads.x*sizeof(mytype)>>>(d_rx,d_ry,d_Avg,Np);
  cudaCheckErrors("kernels");


  Avg = new mytype[blocks.x];

  cudaMemcpy(Avg,d_Avg,blocks.x*sizeof(mytype),cudaMemcpyDeviceToHost);
  cudaMemcpy(rx, d_rx, DSIZE*sizeof(mytype),cudaMemcpyDeviceToHost);
  cudaMemcpy(ry, d_ry, DSIZE*sizeof(mytype),cudaMemcpyDeviceToHost);
  cudaCheckErrors("cudamemcpy");
  mytype average = 0;

  for(int i = 0; i < blocks.x; i++){
    average += Avg[i];
  }
  average = average/(mytype)Np;

  printf("GPU average = %f\n", average);
  average = cpu_avg(rx, ry, DSIZE);
  printf("CPU average = %f\n", average);

  return 0;
}

RHEL 5.5、CUDA 5.0、IntelXeonX5560で実行しています

コンパイル:

nvcc -O3 -arch=sm_20 -lcurand -lm -o t93 t93.cu

編集: 変動性がCPU側にあることを観察した後、次のようにCPU平均化コードを変更することでCPUの変動性のほとんどを排除できることがわかりました。

mytype cpu_avg(const mytype *rx, const mytype *ry, const int size){
  mytype average = 0.0f;
  mytype temp = 0.0f;
  for(int i = 0; i < size; i++){
    for(int j = i + 1; j < size; j++){
        temp += 1.0e0f/sqrt((rx[i]-rx[j])*(rx[i]-rx[j]) + (ry[i]-ry[j])*(ry[i]-ry[j]));
    }
    average += temp/(mytype)size;
    temp = 0.0f;
  }
  return average;
}

ですから、CPU側の中間結果に問題があると思います。GPUの結果に表示されないのは興味深いことです。これは、GPU平均の最終的な合計がCPUで行われるため(したがって、個々のGPUブロックの結果はサイズによって縮小されます(例:8192))、これらの精度は中程度の精度である可能性があります。最終部門。CPU平均計算をインライン化すると、別の何かが再び観察される場合があります。

于 2013-03-13T02:12:45.897 に答える