我在理解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}}参数的使用情况?
答案 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