curand_init的偏移参数

时间:2014-09-13 21:20:41

标签: cuda

我在理解curand_init的偏移参数时遇到了问题。

cuRAND指南说:

  

offset参数用于在序列中向前跳过。如果offset = 100,则   生成的第一个随机数将是序列中的第100个。这允许   多次运行同一程序以继续从同一程序生成结果   序列没有重叠。

这似乎是

中说明的先行概念
T. Bradley, J. du Toit, R. Tong, M. Giles, P. Woodhams, "Parallelization Techniques for Random Number Generators", GPU Computing Gems, Emerald Edition.

请考虑以下代码:

#include <stdio.h>
#include <curand.h>
#include <curand_kernel.h>

#define BLOCKSIZE 256

/**********/
/* iDivUp */
/**********/
int iDivUp(int a, int b){ return ((a % b) != 0) ? (a / b + 1) : (a / b); }

/***********************/
/* CUDA ERROR CHECKING */
/***********************/
#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, char *file, int line, bool abort=true)
{
    if (code != cudaSuccess) 
    {
        fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line); 
        if (abort) exit(code);
    }
}

/********************************************************/
/* KERNEL FUNCTION FOR TESTING RANDOM NUMBER GENERATION */
/********************************************************/
__global__ void testrand1(unsigned long seed, float *a, int N){
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    curandState state;
    if (idx < N) {
        curand_init(seed, idx, 0, &state);
        a[idx] = curand_uniform(&state);
    }
}

/********/
/* MAIN */
/********/
int main() {

    const int N = 10;

    float *h_a  = (float*)malloc(N*sizeof(float));
    float *d_a; gpuErrchk(cudaMalloc((void**)&d_a, N*sizeof(float)));

    testrand1<<<iDivUp(N, BLOCKSIZE), BLOCKSIZE>>>(1234, d_a, N);
    gpuErrchk(cudaPeekAtLastError());
    gpuErrchk(cudaDeviceSynchronize());

    gpuErrchk(cudaMemcpy(h_a, d_a, N*sizeof(float), cudaMemcpyDeviceToHost));

    for (int i=0; i<N; i++) printf("%i %f\n", i, h_a[i]);

    getchar();
}

运行此代码会生成:

0 0.145468
1 0.820181
2 0.550399
3 0.294830
4 0.914733
5 0.868979
6 0.321921
7 0.782857
8 0.011302
9 0.285450

现在,如果我使用

curand_init(seed, idx+2, 0, &state);

我收到:

0 0.550399
1 0.294830
2 0.914733
3 0.868979
4 0.321921
5 0.782857
6 0.011302
7 0.285450
8 0.781606
9 0.233840

表示生成的序列从与seed = 1234关联的随机数生成器(RNG)序列的第三个元素开始。根据{{​​1}}指南中的定义,我会说那个

cuRAND

应该相当于

curand_init(seed, idx+2, 0, &state);

不幸的是,如果我使用上述行,我会收到:

curand_init(seed, idx, 2, &state);

与上述不同。

在同一指南的第0 0.870710 1 0.511765 2 0.782640 3 0.620706 4 0.554513 5 0.214082 6 0.118647 7 0.993959 8 0.104572 9 0.231619 页上,在讨论11时,会写出:

  

状态设置将是curand_init()从种子状态调用2^67 # sequence + offset后的状态。

我将其解释为curand()在与特定种子关联的offset个可用序列中的偏移量。

任何人都可以帮助我了解2^67中<{1}}参数的使用情况?

1 个答案:

答案 0 :(得分:5)

curand_init(seed, idx+2, 0, &state);

应该等同于:

curand_init(seed, idx, 2, &state);

因为sequences generated with the same seed and different sequence numbers will not have statistically correlated values。请注意,基于偏移参数,此语句没有其他限定条件。

生成的整体序列的周期大于2 ^ 190。在整个序列中,有一些子序列由curand_init调用的第二个参数标识。这些子序列彼此独立(并且没有统计相关性)。这些子序列在整个序列中大约相隔2 ^ 67个数字。偏移参数(第3个参数)选择此子序列中的起始位置。由于偏移参数不能大于2 ^ 67,因此不可能单独使用偏移参数来使生成的数字重叠。

但是,如果我们选择的话,我们会提供skipahead_sequence function,以便我们执行此操作。请考虑对您的代码和示例运行进行以下修改:

$ cat t557.cu
#include <stdio.h>
#include <curand.h>
#include <curand_kernel.h>

#define BLOCKSIZE 256

/**********/
/* iDivUp */
/**********/
int iDivUp(int a, int b){ return ((a % b) != 0) ? (a / b + 1) : (a / b); }

/***********************/
/* CUDA ERROR CHECKING */
/***********************/
#define gpuErrchk(ans) { gpuAssert((ans), __FILE__, __LINE__); }
inline void gpuAssert(cudaError_t code, char *file, int line, bool abort=true)
{
    if (code != cudaSuccess)
    {
        fprintf(stderr,"GPUassert: %s %s %d\n", cudaGetErrorString(code), file, line);
        if (abort) exit(code);
    }
}

/********************************************************/
/* KERNEL FUNCTION FOR TESTING RANDOM NUMBER GENERATION */
/********************************************************/
__global__ void testrand1(unsigned long seed, float *a, int N){
    int idx = threadIdx.x + blockIdx.x * blockDim.x;
    curandState state;
    if (idx < N) {
        curand_init(seed, idx, 0, &state);
        a[(idx*2)] = curand_uniform(&state);
        if(idx%2)
          skipahead_sequence(1, &state);
        a[(idx*2)+1] = curand_uniform(&state);

    }
}

/********/
/* MAIN */
/********/
int main() {

    const int N = 10;

    float *h_a  = (float*)malloc(2*N*sizeof(float));
    float *d_a; gpuErrchk(cudaMalloc((void**)&d_a, 2*N*sizeof(float)));

    testrand1<<<iDivUp(N, BLOCKSIZE), BLOCKSIZE>>>(1234, d_a, N);
    gpuErrchk(cudaPeekAtLastError());
    gpuErrchk(cudaDeviceSynchronize());

    gpuErrchk(cudaMemcpy(h_a, d_a, 2*N*sizeof(float), cudaMemcpyDeviceToHost));

    for (int i=0; i<2*N; i++) printf("%i %f\n", i, h_a[i]);

}
$ nvcc -arch=sm_20 -o t557 t557.cu
$ ./t557
0 0.145468
1 0.434899
2 0.820181
3 0.811845
4 0.550399
5 0.811845
6 0.294830
7 0.557235
8 0.914733
9 0.557235
10 0.868979
11 0.206681
12 0.321921
13 0.206681
14 0.782857
15 0.539587
16 0.011302
17 0.539587
18 0.285450
19 0.739071
$

内核现在导致每个线程在它们的序列中生成2个数字,但是在生成2个数字之间,备用线程将skipahead_sequence。由于我选择1作为skipahead_sequence的第一个参数,因此函数调用的效果就好像我已经要求1 * 2 ^ 67个数字,就好像我已经调用curand_uniform一样2 ^ 67次。这意味着线程对将生成它们的第二个数字相同,这正是我们在输出中的索引3和5,7和9,11和13等处看到的。

这是一些ASCII艺术:

主序列(长度2 ^ 190)(由seed参数确定):

|0     ...   2^67-1 2^67 ...     2^68-1 2^68 ... ... ... 2^190|0 ... 
                                                              (main seq repeats)

序列(每个长度为2 ^ 67)(由sequence参数确定):

|seq0  ...   2^67-1|seq1   ...   2^68-1|seq2   ... 
                           ^
                   |offset |                     (determined by offset parameter)
                           |
                           RNG begins here for given seed, sequence(=seq1), offset