CURAND - 设备API和种子

时间:2013-11-02 01:00:10

标签: c cuda

上下文:我目前正在学习如何正确使用CUDA,特别是如何使用CURAND生成随机数。我了解到here在我需要的时候直接生成随机数可能是明智的,在我的代码中执行核心计算的内核中。

documentation之后,我决定尝试一下,尝试编写一段简单的代码,以后我可以根据自己的需要进行调整。

由于块中256个并发线程的限制(仅200个预生成的参数集),我排除了MTGP32。此外,我不想使用双打,所以我决定坚持使用默认生成器(XORWOW)。

问题:我很难理解为什么我的代码中的相同种子值为每个大于128的块的线程生成不同的数字序列(当blockSize< 129时,所有内容像我期望的那样跑。正如罗伯特在评论中所做的那样,在做了CUDA error checking之后,硬件限制发挥了作用。此外,在编译时不使用“-G -g”标志会将“阈值故障”从128增加到384。

问题:究竟是什么造成的? Robert worte在评论中说“它可能是每个线程问题的寄存器”。这是什么意思?有没有一种简单的方法来查看硬件规格并说出这个限制的位置?我可以解决这个问题而不必为每个线程生成更多的随机数吗?

似乎已经讨论过相关问题here,但我不认为它适用于我的情况。

我的代码(见下文)主要受these examples启发。

代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <cuda.h>
    #include <curand_kernel.h>

    #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);
        }
    }

    __global__ void setup_kernel(curandState *state, int seed, int n){

        int id = threadIdx.x + blockIdx.x*blockDim.x;

        if(id<n){
            curand_init(seed, id, 0, &state[id]);
        }
    }

    __global__ void generate_uniform_kernel(curandState *state, float *result, int n){

        int id = threadIdx.x + blockIdx.x*blockDim.x;
        float x;

        if(id<n){
            curandState localState = state[id];
            x = curand_uniform(&localState);
            state[id] = localState;
            result[id] = x; 
        }
    }

    int main(int argc, char *argv[]){

        curandState *devStates;
        float *devResults, *hostResults;

        int n = atoi(argv[1]);
        int s = atoi(argv[2]);
        int blockSize = atoi(argv[3]);

        int nBlocks = n/blockSize + (n%blockSize == 0?0:1);

        printf("\nn: %d, blockSize: %d, nBlocks: %d, seed: %d\n", n, blockSize, nBlocks, s);

        hostResults = (float *)calloc(n, sizeof(float));
        cudaMalloc((void **)&devResults, n*sizeof(float));

        cudaMalloc((void **)&devStates, n*sizeof(curandState));
        setup_kernel<<<nBlocks, blockSize>>>(devStates, s, n);
        gpuErrchk( cudaPeekAtLastError() );
        gpuErrchk( cudaDeviceSynchronize() );

        generate_uniform_kernel<<<nBlocks, blockSize>>>(devStates, devResults, n);
        gpuErrchk( cudaPeekAtLastError() );
        gpuErrchk( cudaDeviceSynchronize() );

        cudaMemcpy(hostResults, devResults, n*sizeof(float), cudaMemcpyDeviceToHost);

        for(int i=0; i<n; i++) {
            printf("\n%10.13f", hostResults[i]);
        }

        cudaFree(devStates);
        cudaFree(devResults);
        free(hostResults);

        return 0;
    }

我编译了两个二进制文件,一个使用“-G -g”调试标志而另一个没有。我分别将它们命名为 rng_gen_d rng_gen

     $ nvcc -lcuda -lcurand -O3 -G -g --ptxas-options=-v rng_gen.cu -o rng_gen_d
    ptxas /tmp/tmpxft_00002257_00000000-5_rng_gen.ptx, line 2143; warning : Double is not supported. Demoting to float
    ptxas info    : 77696 bytes gmem, 72 bytes cmem[0], 32 bytes cmem[14]
    ptxas info    : Compiling entry function '_Z12setup_kernelP17curandStateXORWOWii' for 'sm_10'
    ptxas info    : Used 43 registers, 32 bytes smem, 72 bytes cmem[1], 6480 bytes lmem
    ptxas info    : Compiling entry function '_Z23generate_uniform_kernelP17curandStateXORWOWPfi' for 'sm_10'
    ptxas info    : Used 10 registers, 36 bytes smem, 40 bytes cmem[1], 48 bytes lmem

     $ nvcc -lcuda -lcurand -O3 --ptxas-options=-v rng_gen.cu -o rng_gen
    ptxas /tmp/tmpxft_00002b73_00000000-5_rng_gen.ptx, line 533; warning : Double is not supported. Demoting to float
    ptxas info    : 77696 bytes gmem, 72 bytes cmem[0], 32 bytes cmem[14]
    ptxas info    : Compiling entry function '_Z12setup_kernelP17curandStateXORWOWii' for 'sm_10'
    ptxas info    : Used 20 registers, 32 bytes smem, 48 bytes cmem[1], 6440 bytes lmem
    ptxas info    : Compiling entry function '_Z23generate_uniform_kernelP17curandStateXORWOWPfi' for 'sm_10'
    ptxas info    : Used 19 registers, 36 bytes smem, 4 bytes cmem[1]

首先,编译时会出现一条奇怪的警告消息(见上文):

    ptxas /tmp/tmpxft_00002b31_00000000-5_rng_gen.ptx, line 2143; warning : Double is not supported. Demoting to float

某些调试显示导致此警告的行是:

    curandState localState = state[id];

没有宣布双打,所以我不确切知道如何解决这个问题(或者即使这需要解决)。

现在,我面临的(实际)问题的一个例子:

     $ ./rng_gen_d 5 314 127

    n: 5, blockSize: 127, nBlocks: 1, seed: 314

    0.9151657223701
    0.3925153017044
    0.7007563710213
    0.8806988000870
    0.5301177501678

     $ ./rng_gen_d 5 314 128

    n: 5, blockSize: 128, nBlocks: 1, seed: 314

    0.9151657223701
    0.3925153017044
    0.7007563710213
    0.8806988000870
    0.5301177501678

     $ ./rng_gen_d 5 314 129

    n: 5, blockSize: 129, nBlocks: 1, seed: 314
    GPUassert: too many resources requested for launch rng_gen.cu 54

第54行是setup_kernel()之后的gpuErrchk()。

使用其他二进制文件(编译时没有“-G -g”标志),“故障阈值”升至384:

     $ ./rng_gen 5 314 129

    n: 5, blockSize: 129, nBlocks: 1, seed: 314

    0.9151657223701
    0.3925153017044
    0.7007563710213
    0.8806988000870
    0.5301177501678

     $ ./rng_gen 5 314 384 

    n: 5, blockSize: 384, nBlocks: 1, seed: 314

    0.9151657223701
    0.3925153017044
    0.7007563710213
    0.8806988000870
    0.5301177501678

     $ ./rng_gen 5 314 385

    n: 5, blockSize: 385, nBlocks: 1, seed: 314
    GPUassert: too many resources requested for launch rng_gen.cu 54

最后,如果这与我用于此初步测试的硬件有关(该项目稍后将在更强大的机器上启动),以下是我使用的卡的规格:

    ./deviceQuery Starting...

     CUDA Device Query (Runtime API) version (CUDART static linking)

    Detected 1 CUDA Capable device(s)

    Device 0: "Quadro NVS 160M"
      CUDA Driver Version / Runtime Version          5.5 / 5.5
      CUDA Capability Major/Minor version number:    1.1
      Total amount of global memory:                 256 MBytes (268107776 bytes)
      ( 1) Multiprocessors, (  8) CUDA Cores/MP:     8 CUDA Cores
      GPU Clock rate:                                1450 MHz (1.45 GHz)
      Memory Clock rate:                             702 Mhz
      Memory Bus Width:                              64-bit
      Maximum Texture Dimension Size (x,y,z)         1D=(8192), 2D=(65536, 32768), 3D=(2048, 2048, 2048)
      Maximum Layered 1D Texture Size, (num) layers  1D=(8192), 512 layers
      Maximum Layered 2D Texture Size, (num) layers  2D=(8192, 8192), 512 layers
      Total amount of constant memory:               65536 bytes
      Total amount of shared memory per block:       16384 bytes
      Total number of registers available per block: 8192
      Warp size:                                     32
      Maximum number of threads per multiprocessor:  768
      Maximum number of threads per block:           512
      Max dimension size of a thread block (x,y,z): (512, 512, 64)
      Max dimension size of a grid size    (x,y,z): (65535, 65535, 1)
      Maximum memory pitch:                          2147483647 bytes
      Texture alignment:                             256 bytes
      Concurrent copy and kernel execution:          No with 0 copy engine(s)
      Run time limit on kernels:                     Yes
      Integrated GPU sharing Host Memory:            No
      Support host page-locked memory mapping:       Yes
      Alignment requirement for Surfaces:            Yes
      Device has ECC support:                        Disabled
      Device supports Unified Addressing (UVA):      No
      Device PCI Bus ID / PCI location ID:           1 / 0
      Compute Mode:
         < Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >

    deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 5.5, CUDA Runtime Version = 5.5, NumDevs = 1, Device0 = Quadro NVS 160M
    Result = PASS

就是这样。任何有关此事的指导都将受到欢迎。谢谢!

修改

1)根据Robert的建议添加了正确的cuda error checking

2)删除了无用的cudaMemset行。

3)编译并运行没有“-G -g”标记的代码。

4)相应地更新了输出。

1 个答案:

答案 0 :(得分:2)

首先,当您遇到CUDA代码时出现问题时,最好选择正确的cuda error checking。它会消除一定程度的头部刮伤,可能会节省你一些时间,并且肯定会提高人们在像这样的网站上帮助你的能力。

现在您发现每个线程问题都有一个寄存器。生成代码时编译器将使用寄存器用于各种目的。每个线程都需要这些寄存器来运行它的线程代码。当您尝试启动内核时,必须满足的要求之一是每个线程所需的寄存器数乘以启动时请求的线程数必须小于每个块可用的寄存器总数。请注意,每个线程所需的寄存器数量可能必须四舍五入到某个粒度分配增量。另请注意,在32的 warps 中启动线程时,请求的线程数通常会向上舍入到下一个更高的32增量(如果不能被32整除),另请注意最大寄存器每个块因计算能力而异,并且可以通过您显示的deviceQuery样本检查此数量。另外,正如您所发现的,某些命令行开关(如-G)可能会影响nvcc如何使用寄存器。

要预先了解这些类型的资源问题,可以使用其他命令行开关编译代码:

nvcc -arch=sm_11 -Xptxas=-v -o mycode mycode.cu

-Xptxas=-v开关将通过ptxas汇编程序(将中间ptx代码转换为sass汇编代码,即机器代码)生成资源使用输出,包括每个线程所需的寄存器。请注意,在这种情况下,每个内核可以输出输出,因为每个内核可能都有自己的要求。您可以在documentation中获得有关nvcc编译器的更多信息。

作为一种粗略的解决方法,您可以在编译时指定一个开关,以将所有内核编译限制为最大寄存器使用数:

nvcc -arch=sm_11 -Xptxas=-v -maxrregcount=16 -o mycode mycode.cu

这将限制每个内核每个线程使用不超过16个寄存器。当乘以512(cc1.x设备的每个块的线程硬件限制)时,这将产生一个值8192,这是设备每个线程块的总寄存器的硬件限制。

然而,上述方法很粗糙,因为它对程序中的所有内核应用了相同的限制。如果你想为每个内核启动定制它(例如,如果程序中的不同内核启动了不同数量的线程),你可以使用启动边界方法,如here所述。