如何使用CUDA CURAND保存和恢复随机数生成器的状态?

时间:2015-04-13 21:08:22

标签: python c++ numpy cuda

我正在使用我们组织内部开发的大型CUDA矩阵库。我需要保存CUDA RNG的状态,以便对长时间运行的模拟进行快照,并能够在以后恢复它。这很简单,例如,python + numpy:

state = numpy.random.get_state()
# state is a tuple with 5 fields which can be pickled, etc.
...
numpy.random.set_state(state)

我似乎无法在CUDA主机api中找到相同的功能。您可以设置种子和偏移量,但无法检索它以进行保存。设备API似乎提供了这样的东西,但是这个库使用了主机api,改变它会很奇怪。

我正在考虑的黑客解决方案是跟踪RNG的呼叫数量(设置种子时重置),并简单地重复调用RNG功能。但是,我不确定函数参数是否必须相同,例如矩阵形状等,使其达到相同的状态。类似地,如果调用的数量等于用于初始化RNG的偏移参数,这也可以工作,即,如果我将RNG调用200次,我可以将偏移设置为200.但是,在python中,偏移量为每次调用时状态可以增加1以上,所以这也可能是错误的。

对如何解决这个问题的任何见解表示赞赏!

2 个答案:

答案 0 :(得分:1)

对于CURAND Host API,我相信curandSetGeneratorOffset()可能适用于此。

以下是curand主机API文档中的修改示例:

$ cat t721.cu
/*
 * This program uses the host CURAND API to generate 10
 * pseudorandom floats.  And then regenerate those same floats.
 */
#include <stdio.h>
#include <stdlib.h>
#include <cuda.h>
#include <curand.h>

#define CUDA_CALL(x) do { if((x)!=cudaSuccess) { \
    printf("Error at %s:%d\n",__FILE__,__LINE__);\
    return EXIT_FAILURE;}} while(0)
#define CURAND_CALL(x) do { if((x)!=CURAND_STATUS_SUCCESS) { \
    printf("Error at %s:%d\n",__FILE__,__LINE__);\
    return EXIT_FAILURE;}} while(0)

int main(int argc, char *argv[])
{
    size_t n = 10;
    size_t i;
    curandGenerator_t gen;
    float *devData, *hostData;

    /* Allocate n floats on host */
    hostData = (float *)calloc(n, sizeof(float));

    /* Allocate n floats on device */
    CUDA_CALL(cudaMalloc((void **)&devData, n*sizeof(float)));

    /* Create pseudo-random number generator */
    CURAND_CALL(curandCreateGenerator(&gen,
                CURAND_RNG_PSEUDO_DEFAULT));

    /* Set seed */
    CURAND_CALL(curandSetPseudoRandomGeneratorSeed(gen,
                1234ULL));
    // generator offset = 0
    /* Generate n floats on device */
    CURAND_CALL(curandGenerateUniform(gen, devData, n));
    // generator offset = n
    /* Generate n floats on device */
    CURAND_CALL(curandGenerateUniform(gen, devData, n));
    // generator offset = 2n
    /* Copy device memory to host */
    CUDA_CALL(cudaMemcpy(hostData, devData, n * sizeof(float),
        cudaMemcpyDeviceToHost));

    /* Show result */
    for(i = 0; i < n; i++) {
        printf("%1.4f ", hostData[i]);
    }
    printf("\n\n");

    CURAND_CALL(curandSetGeneratorOffset(gen, n));
    // generator offset = n
    CURAND_CALL(curandGenerateUniform(gen, devData, n));
    // generator offset = 2n
    /* Copy device memory to host */
    CUDA_CALL(cudaMemcpy(hostData, devData, n * sizeof(float),
        cudaMemcpyDeviceToHost));

    /* Show result */
    for(i = 0; i < n; i++) {
        printf("%1.4f ", hostData[i]);
    }
    printf("\n");



    /* Cleanup */
    CURAND_CALL(curandDestroyGenerator(gen));
    CUDA_CALL(cudaFree(devData));
    free(hostData);
    return EXIT_SUCCESS;
}
$ nvcc -o t721 t721.cu -lcurand
$ ./t721
0.7816 0.2338 0.6791 0.2824 0.6299 0.1212 0.4333 0.3831 0.5136 0.2987

0.7816 0.2338 0.6791 0.2824 0.6299 0.1212 0.4333 0.3831 0.5136 0.2987
$

因此,您需要跟踪生成的随机数的数量( RNG函数调用的数量),直到您执行检查点为止,并保存它。

重新启动时,以相同的方式初始化生成器:

    /* Create pseudo-random number generator */
    CURAND_CALL(curandCreateGenerator(&gen,
                CURAND_RNG_PSEUDO_DEFAULT));

    /* Set seed */
    CURAND_CALL(curandSetPseudoRandomGeneratorSeed(gen,
                1234ULL));

然后按先前生成的值(n)的数量前进:

    CURAND_CALL(curandSetGeneratorOffset(gen, n));

答案 1 :(得分:0)

因此,可以通过跟踪使用curandSetGeneratorOffset生成的32位值的数量来存储和恢复状态。该算法类似于:

template<typename T> RNG(T* X, size_T N /*number of values*/){
...

if (sizeof(T) == 1)
    offset += (N+4-1)/4;
else if (sizeof(T) == 2)
    offset += (N+2-1)/4;
else if (sizeof(T) == 4 || USING_GENERATE_UNIFORM_DOUBLE)
    offset += N;
else if (sizeof(T) == 8)
    offset += 2*N;
}

对于8位值,对于生成的N值,将偏移量提前N *下一个最高倍数4。对于16,通过N *前进到下一个倍数2.对于32提前N,并且提前64提前2 * N.

但是,如果你使用GenerateUniformDouble,你只需要提前N,而不是2 * N.我不确定为什么。

感谢您的帮助!