使用cuRAND在函数内生成随机数,无需预先分配

时间:2014-10-30 10:19:25

标签: random cuda

是否可以在设备功能中生成随机数而无需预先分配所有状态?我想在" realtime"中生成并使用它们。我需要将它们用于蒙特卡罗模拟最适合此目的的是什么?下面生成的数字是单精度,是否可以使它们具有双精度?

#include <iostream>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <curand_kernel.h>

__global__ void cudaRand(float *d_out, unsigned long seed)
{
    int i = blockDim.x * blockIdx.x + threadIdx.x;
    curandState state;
    curand_init(seed, i, 0, &state);
    d_out[i] = curand_uniform(&state);
}

int main(int argc, char** argv)
{
    size_t N = 1 << 4;
    float *v = new float[N];

    float *d_out;
    cudaMalloc((void**)&d_out, N * sizeof(float));

    // generate random numbers
    cudaRand << < 1, N >> > (d_out, time(NULL));

    cudaMemcpy(v, d_out, N * sizeof(float), cudaMemcpyDeviceToHost);

    for (size_t i = 0; i < N; i++)
    {
        printf("out: %f\n", v[i]);
    }

    cudaFree(d_out);
    delete[] v;

    return 0;
}

更新

#include <iostream>
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <curand_kernel.h>
#include <ctime>

__global__ void cudaRand(double *d_out)
{
    int i = blockDim.x * blockIdx.x + threadIdx.x;
    curandState state;
    curand_init((unsigned long long)clock() + i, 0, 0, &state);

    d_out[i] = curand_uniform_double(&state);
}

int main(int argc, char** argv)
{
    size_t N = 1 << 4;
    double *h_v = new double[N];

    double *d_out;
    cudaMalloc((void**)&d_out, N * sizeof(double));

    // generate random numbers
    cudaRand << < 1, N >> > (d_out);

    cudaMemcpy(h_v, d_out, N * sizeof(double), cudaMemcpyDeviceToHost);

    for (size_t i = 0; i < N; i++)
        printf("out: %f\n", h_v[i]);

    cudaFree(d_out);
    delete[] h_v;

    return 0;
}

1 个答案:

答案 0 :(得分:5)

我如何在__device__ / __global__函数中处理过去的类似情况:

int tId = threadIdx.x + (blockIdx.x * blockDim.x);
curandState state;
curand_init((unsigned long long)clock() + tId, 0, 0, &state);

double rand1 = curand_uniform_double(&state);
double rand2 = curand_uniform_double(&state);

所以只需使用curand_uniform_double来生成随机双打,并且我相信你不希望所有线程使用相同的种子,这就是我想要使用clock() + tId取而代之的。这样,在两个线程中的任何一个中具有相同rand1 / rand2的几率接近于零。

修改

但是,根据以下评论,建议的方法可能 或许导致偏见的结果:

  • JackOLantern向我指出了这部分文档:

      

    使用不同种子生成的序列通常没有统计学相关的值,但种子选择可能会给出统计相关的序列

  • 还有devtalk thread致力于如何提高curand_init的性能,其中提出的加速语音初始化的解决方案是:

      

    你可以做的一件事是为每个线程使用不同的种子,固定子序列为0,偏移量为0。

    但同一张海报后来说明:

      

    缺点是你在线程之间失去了一些很好的数学属性。在从种子初始化生成器状态的哈希函数与生成器的周期性之间可能存在不良交互。如果发生这种情况,您可能会获得两个线程,其中一些种子具有高度相关我不知道这样的任何问题,即使它们确实存在,也很可能很少

因此,无论您是想要更好的表现(如我所做)还是1000%无偏见的结果,基本取决于您。如果这是你想要的,那么JackOLantern提出的解决方案是正确的,即将curand初始化为:

curand_init((unsigned long long)clock(), tId, 0, &state)

0offset参数不使用subsequence值会降低性能。有关这些参数的详细信息,您可以查看this SO thread以及curand documentation

我看到JackOLantern在评论中说:

  

我想说从同一个内核调用curand_init和curand_uniform_double是不可推荐的,原因有两个......第二,curand_init初始化伪随机数生成器并设置它的所有参数,所以我&#39我担心你的做法有点慢。

我在几页的论文中处理了这个问题,尝试了各种方法在每个线程中获取不同的随机数,并在每个线程中创建curandState,这对我来说是最可行的解决方案。我需要在每个线程中生成~10个随机数,其中我尝试过:

  • 开发我自己的简单随机数生成器(线性同余生成器),其初始化基本上是免费的,但是,生成数字时性能受到很大影响,因此最终每个线程中curandState被证明是优越的,
  • 预先分配curandState并重新使用它们 - 这是内存繁重的,当我减少预分配状态的数量时,我必须对offset / subsequence参数使用非零值curand_uniform_double以消除导致生成数字时性能下降的偏见。

因此,经过彻底的分析后,我决定在每个帖子中调用curand_initcurand_uniform_double。唯一的问题是这些状态占用的注册表数量,所以我必须小心块大小不要超过每个块可用的最大注册数。

这就是我提供的解决方案,我终于能够测试它并且它在我的机器/ GPU上正常工作。我在上面的问题中运行 UPDATE 部分的代码,并在控制台中正确显示了16个不同的随机数。因此,我建议您在执行内核后正确执行错误检查,以查看内部出了什么问题。 this SO thread中详细介绍了此主题。