这很奇怪... 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。
感谢。
答案 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本身无关。