cuda错误对准推力device_vector调整大小

时间:2014-12-24 02:40:30

标签: c++ cuda thrust

这很奇怪... thrust :: device_vector.resize抛出cudaErrorMisalignedAddress,但只有当我第一次调用curandGenerateNormal并且起始地址未对齐到8个字节时才会这样:

#include <cuda_runtime.h>
#include <curand.h>
#include <thrust/device_vector.h>
#include <assert.h>

int main()
{
    thrust::device_vector<float> a(16), b(0);

    curandGenerator_t _prng;
    curandStatus_t curandStat = curandCreateGenerator(&_prng, CURAND_RNG_PSEUDO_DEFAULT);
    assert(curandStat == CURAND_STATUS_SUCCESS);

    bool breakCUDA = true;

    if (breakCUDA) {
        // this curand call (not 8-byte aligned) somehow breaks subsequent resize
        float *start_p1 = a.data().get() + 1;
        curandStat = curandGenerateNormal(_prng, start_p1, 2, 0.0f, 1.0f);
        assert(curandStat == CURAND_STATUS_SUCCESS);
    }
    else {
         // this one, using an 8-byte aligned pointer works fine
         float *start = a.data().get();
         curandStat = curandGenerateNormal(_prng, start, 2, 0.0f, 1.0f);
         assert(curandStat == CURAND_STATUS_SUCCESS);
    }

    // note: either call above returns CURAND_STATUS_SUCCESS

    // but this throws thrust::system_error with error cudaErrorMisalignedAddress
    // if the unaligned pointer was used before
    b.resize(16);
}

在我的实际代码中,我需要在第一个向量的不同段上使用不同的生成参数(0.0f,1.0f),并且段边界不一定是内存对齐的。

doc for curandGenerateNormal表示长度必须是均匀的(就像在两种情况下一样),但没有提及有关对齐的事情。

我现在有一个解决方法:检查我将要传递给curandGenerateNormal的指针是否与8个字节对齐,如果不是,我会生成一些临时内存并复制它。但是,如果有人对正在发生的事情有任何了解,我会很感激,所以我可以确保将来做正确的事情。是否还有其他推力或法术方法,我必须小心对齐?

这是Windows上的CUDA 6.5。

感谢。

1 个答案:

答案 0 :(得分:1)

我认为根本问题是curandGenerateNormal期望编写的数量与基本数据类型(float的两倍)对齐,在这种情况下。因此,当使用PRNG(例如默认的XORWOW生成器)时,传递给curandGenerateNormal的指针应该与数据类型的两倍对齐(即在这种情况下为8字节对齐,或者在情况下为16字节对齐)例如,curandGenerateNormalDouble。我不相信这个问题与推力有关。

虽然我所看到的问题没有详细记录,但可以在documentation you linked找到一些提示:

  

通常使用Box-Muller变换从伪随机生成器生成正态分布结果,因此要求n为偶数。

让我们考虑一个稍微不同的测试案例,以证明推力不是问题,并看看幕后发生了什么:

$ cat t625.cu
#include <curand.h>
#include <iostream>
#define DSIZE 4

int main(){

  curandGenerator_t _prng;
  curandStatus_t curandStat = curandCreateGenerator(&_prng, CURAND_RNG_PSEUDO_DEFAULT);
  float *h_a, *d_a;
  h_a = (float *)malloc(DSIZE*sizeof(float));
  cudaMalloc(&d_a, DSIZE*sizeof(float));
  cudaMemset(d_a, 0, DSIZE*sizeof(float));
  float *start_p1 = d_a+ 1;
  curandStat = curandGenerateNormal(_prng, start_p1, 2, 0.0f, 1.0f);
  cudaMemcpy(h_a, d_a, DSIZE*sizeof(float), cudaMemcpyDeviceToHost);
  for (int i = 0; i < DSIZE; i++)
    std::cout << h_a[i] << std::endl;
  return 0;
}
[user2@dc20 misc]$ vi t625.cu
[user2@dc20 misc]$ nvcc -arch=sm_20 -o t625 t625.cu -lcurand
[user2@dc20 misc]$ cuda-memcheck ./t625
========= CUDA-MEMCHECK
========= Invalid __global__ write of size 8
=========     at 0x000003e8 in void gen_sequenced<curandStateXORWOW, float2, normal_args_st, __operator_&__(float2 curand_normal_scaled2<curandStateXORWOW>(curandStateXORWOW*, normal_args_st))>(curandStateXORWOW*, float2*, unsigned long, unsigned long, normal_args_st)
=========     by thread (0,0,0) in block (0,0,0)
=========     Address 0x13047c0004 is misaligned
=========     Saved host backtrace up to driver entry point at kernel launch time
=========     Host Frame:/usr/lib64/libcuda.so.1 (cuLaunchKernel + 0x2c5) [0x14ad95]
=========     Host Frame:/usr/local/cuda/lib64/libcurand.so.6.5 [0x726d8]
=========     Host Frame:/usr/local/cuda/lib64/libcurand.so.6.5 [0x9b923]
=========     Host Frame:/usr/local/cuda/lib64/libcurand.so.6.5 [0xfc95]
=========     Host Frame:/usr/local/cuda/lib64/libcurand.so.6.5 (curandGenerateNormal + 0x1ee7) [0x3b987]
=========     Host Frame:./t625 [0x27a2]
=========     Host Frame:/lib64/libc.so.6 (__libc_start_main + 0xfd) [0x1ecdd]
=========     Host Frame:./t625 [0x2639]
=========
========= Program hit cudaErrorLaunchFailure (error 4) due to "unspecified launch failure" on CUDA API call to cudaMemcpy.
=========     Saved host backtrace up to driver entry point at error
=========     Host Frame:/usr/lib64/libcuda.so.1 [0x2ef613]
=========     Host Frame:./t625 [0x37fdf]
=========     Host Frame:./t625 [0x27c2]
=========     Host Frame:/lib64/libc.so.6 (__libc_start_main + 0xfd) [0x1ecdd]
=========     Host Frame:./t625 [0x2639]
=========
1.14162e-37
0
7.40782e-38
0
========= ERROR SUMMARY: 2 errors
$

(我在linux上工作,但我不希望这里的windows和linux有任何区别。)

上述程序产生的错误基本相同。因此,我们可以得出结论,没有必要推力来看问题。仔细查看cuda-memcheck输出,我们看到:

========= Invalid __global__ write of size 8
=========     at 0x000003e8 in void gen_sequenced<curandStateXORWOW, float2, normal_args_st, __operator_&__(float2 curand_normal_scaled2<curandStateXORWOW>(curandStateXORWOW*, normal_args_st))>(curandStateXORWOW*, float2*, unsigned long, unsigned long, normal_args_st)
=========     by thread (0,0,0) in block (0,0,0)
=========     Address 0x13047c0004 is misaligned

gen_sequenced是主机API函数curandGenerateNormal中包含的内核调用。它试图写一个8字节的数量,必须(通过CUDA要求)在8字节对齐的边界上。正如您已经指出的那样,在失败的情况下,传递的指针是4字节对齐但不是8字节对齐。此外,我们看到引擎盖下的内核使用float2数量。毫无疑问,这是一项优化,因为已知数量n必须是偶数。 <{1}}数量只能 在8字节边界上访问。

因此,虽然似乎没有明确记录,但结论似乎是针对本声明所涵盖的案例:

  

通常使用Box-Muller变换从伪随机生成器生成正态分布结果,因此要求n为偶数。

传递的指针必须与基本数据类型的两倍对齐。我将向NVIDIA提交通知,要求更新文档。

关于错误报告,将异步检测CUDA内核检测到的实际错误(未对齐指针),并且在内核启动时不会报告(float2内核,在这种情况下)。当检查CUDA错误状态时,将在稍后的某个时间点报告。这可以解释为什么curand函数本身返回一个正结果。 Thrust内置了运行时错误处理,因此先前发生的这种类型的CUDA错误将被Thrust“捕获”并报告,即使(在这种情况下)它可能与Thrust本身无关。