2. 主机API概述

要使用主机API,用户代码应该包含库的头文件mcrand.h,并且动态链接到mcRAND库。该库使用MXMACA 运行时,因此用户代码也必须使用该运行时。

随机数是由生成器产生的。在mcRAND 中的生成器封装了生成伪随机数或准随机数序列所需的所有内部状态。通常的操作顺序如下:

1 使用 mcrandCreateGenerator() 创建所需类型的新生成器(参见 2.1 生成器类型 )。

2 设置生成器选项(参见 2.2 生成器选项 );例如,使用 mcrandSetPseudoRandomGeneratorSeed() 设置种子。

3 使用 mcMalloc() 在设备上分配内存。

4 使用 mcrandGenerate() 或其他生成函数生成随机数。

5 使用生成的结果。

6 如果需要,可以通过多次调用 mcrandGenerate() 生成更多的随机数。

7 使用 mcrandDestroyGenerator() 进行清理。

为了在主机CPU上生成随机数,在上述步骤1中调用 mcrandCreateGeneratorHost() 函数,并在步骤3中分配一个主机内存缓冲区以接收结果。 除此之外,无论是在设备上还是在主机CPU上生成随机数,所有其他调用的工作原理都是一样的。

同时创建多个生成器是合法的。每个生成器封装了一个独立的状态,并且与所有其他生成器都是独立的。 每个生成器产生的数字序列是确定的。在相同的设置参数下,程序的每次运行都会生成相同的序列。 在设备上生成的随机数将导致在主机CPU上生成相同的序列。

请注意,上述步骤4中的 mcrandGenerate() 函数会启动一个内核并异步返回。 如果您在不同的流中启动另一个内核,并且该内核需要使用 mcrandGenerate() 的结果,您必须调用 mcThreadSynchronize() 或使用流管理/事件管理例程,以确保随机生成内核在新内核启动前已执行完毕。

请注意,将主机内存指针传递给在设备上运行的生成器是无效的,同样地,将设备内存指针传递给在CPU上运行的生成器也是不合法的。在这些情况下的行为是未被定义的。

2.1. 生成器类型

随机数生成器是通过将类型传递给 mcrandCreateGenerator() 函数来创建的。

mcRAND中有九种类型的随机数生成器,总共分为两类。

MCRAND_RNG_PSEUDO_XORWOW, MCRAND_RNG_PSEUDO_MRG32K3A, MCRAND_RNG_PSEUDO_MTGP32, MCRAND_RNG_PSEUDO_PHILOX4_32_10MCRAND_RNG_PSEUDO_MT19937 是伪随机数生成器。

MCRAND_RNG_PSEUDO_XORWOW 是使用XORWOW算法实现的,它是xor-shift伪随机数生成器系列的成员之一。

MCRAND_RNG_PSEUDO_MRG32K3A 是Combined Multiple Recursive伪随机数生成器系列的成员之一。

MCRAND_RNG_PSEUDO_MT19937MCRAND_RNG_PSEUDO_MTGP32 是Mersenne Twister伪随机数生成器系列的成员。

MCRAND_RNG_PSEUDO_MTGP32 具有专为在GPU上运行而定制的参数。

MCRAND_RNG_PSEUDO_MT19937 具有与CPU版本相同的参数,但顺序不同。

MCRAND_RNG_PSEUDO_MT19937 仅支持HOST API。

MCRAND_RNG_PHILOX4_32_10 是Philox系列的成员之一,它是D E Shaw Research在SC11会议上介绍的三种基于计数器的非密码学随机数生成器之一。

基本的Sobol准随机数生成器有4个变种。所有的变种都可以生成多达20,000维的序列。

MCRAND_RNG_QUASI_SOBOL32, MCRAND_RNG_QUASI_SCRAMBLED_SOBOL32, MCRAND_RNG_QUASI_SOBOL64, 和 MCRAND_RNG_QUASI_SCRAMBLED_SOBOL64 是准随机数生成器类型.

MCRAND_RNG_QUASI_SOBOL32 是一个32位序列的Sobol生成器。

MCRAND_RNG_QUASI_SCRAMBLED_SOBOL32 是一个32位序列的Scrambled Sobol生成器。

MCRAND_RNG_QUASI_SOBOL64 是一个64位序列的Sobol生成器。

MCRAND_RNG_QUASI_SCRAMBLED_SOBOL64 是一个64位序列的Scrambled Sobol生成器。

2.2. 生成器选项

创建后,可使用seed、offset和order等常规选项定义随机数生成器。

2.2.1. Seed

seed参数是一个64位整数,用于初始化伪随机数生成器的起始状态。相同的seed始终会产生相同的结果序列。

2.2.2. Offset

offset参数用于在序列中跳过一定数量的随机数。如果offset=100,那么生成的第一个随机数将是序列中的第100个。这使得同一个程序的多次运行可以继续从同一个序列中生成结果,而不会重叠。请注意,MCRAND_RNG_PSEUDO_MTGP32和MCRAND_RNG_PSEUDO_MT19937生成器不支持跳过功能。

2.2.3. Order

order参数用于选择结果在全局内存中的排序方式。它还直接影响mcRAND生成函数的性能。

对于伪随机序列,有五种排序选择: MCRAND_ORDERING_PSEUDO_DEFAULTMCRAND_ORDERING_PSEUDO_LEGACYMCRAND_ORDERING_PSEUDO_BESTMCRAND_ORDERING_PSEUDO_SEEDEDMCRAND_ORDERING_PSEUDO_DYNAMIC

对于准随机数,有一种排序选择: MCRAND_ORDERING_QUASI_DEFAULT

伪随机数生成器的默认排序方式是 MCRAND_ORDERING_PSEUDO_DEFAULT ,而准随机数生成器的默认排序方式是 MCRAND_ORDERING_QUASI_DEFAULT

两种伪随机排序方式 MCRAND_ORDERING_PSEUDO_DEFAULTMCRAND_ORDERING_PSEUDO_BEST 对于所有伪随机生成器产生相同的输出排序,除了MT19937,MT19937中 MCRAND_ORDERING_PSEUDO_DEFAULTMCRAND_ORDERING_PSEUDO_LEGACY 相同。

对于MT19937, MCRAND_ORDERING_PSEUDO_BEST 在不同型号的GPU上可能会生成不同的输出,并且不能与使用mcrandCreateGeneratorHost()创建的主机生成器一起使用。

mcRAND的未来版本可能会改变与 MCRAND_ORDERING_PSEUDO_BEST 相关的排序方式,以提高性能或结果的质量。

使用 MCRAND_ORDERING_PSEUDO_BEST 获得的排序方式始终是确定的,并且每次运行程序时都是相同的。

使用 MCRAND_ORDERING_PSEUDO_LEGACY 获得的排序方式在所有mcRAND版本中保证保持不变。

MCRAND_ORDERING_PSEUDO_DYNAMIC 排序方式不能与使用mcrandCreateGeneratorHost()创建的主机生成器一起使用,目前仅支持以下伪随机生成器: MCRAND_RNG_PSEUDO_XORWOWMCRAND_RNG_PSEUDO_PHILOX4_32_10MCRAND_RNG_PSEUDO_MRG32K3AMCRAND_RNG_PSEUDO_MTGP32

当选择 MCRAND_ORDERING_PSEUDO_DYNAMIC 排序方式时,mcRAND会尽力最大化GPU利用率,以提供最佳性能。

使用 MCRAND_ORDERING_PSEUDO_DYNAMIC 获得的排序方式可能在不同的GPU上有所不同。不能保证在所有mcRAND版本中保持相同,并且不能保证在所有分布中保持相同。但是,能保证是确定的。

每种生成器类型的排序参数行为差异概述如下:

  • XORWOW伪随机生成器

    • MCRAND_ORDERING_PSEUDO_DEFAULT

      在当前版本中,MCRAND_ORDERING_PSEUDO_DEFAULT的输出顺序与MCRAND_ORDERING_PSEUDO_BEST相同。

    • MCRAND_ORDERING_PSEUDO_BEST

      在当前版本中,MCRAND_ORDERING_PSEUDO_BEST的输出顺序与MCRAND_ORDERING_PSEUDO_LEGACY相同。

    • MCRAND_ORDERING_PSEUDO_LEGACY

      在全局内存中,偏移量为 \(n\) 处的结果来自于原始XORWOW序列中的位置 \((n\operatorname{mod}4096) \cdot 2^{67} + \lfloor n/4096\rfloor\)

    • MCRAND_ORDERING_PSEUDO_DYNAMIC

      在不同的GPU上,MCRAND_ORDERING_PSEUDO_DYNAMIC的输出顺序可能不同。

    • MCRAND_ORDERING_PSEUDO_SEEDED

      在全局内存中,偏移为 \(n\) 处的结果来自于XORWOW序列中的位置 \(n/4096\rfloor\) ,该序列使用用户种子和数字 \(n\operatorname{mod}4096\) 的组合进行播种。换句话说,4096个线程中的每一个线程都使用不同的种子。这种种子设置方法减少了状态设置时间,但可能导致某些用户种子值的伪随机输出存在统计上的缺陷。

  • MRG32k3a伪随机生成器

    • MCRAND_ORDERING_PSEUDO_DEFAULT

      在当前版本中,MCRAND_ORDERING_PSEUDO_DEFAULT的输出顺序与MCRAND_ORDERING_PSEUDO_BEST相同。

    • MCRAND_ORDERING_PSEUDO_BEST

      在全局内存中,偏移量为 \(n\) 的结果来自原始MRG32k3a序列中的位置 \((n\operatorname{mod}81920) \cdot 2^{76} + \lfloor n/81920\rfloor\) 。(请注意,MRG32k3a的连续样本之间的步长与XORWOW不同)

    • MCRAND_ORDERING_PSEUDO_LEGACY

      在全局内存中,偏移量为 \(n\) 的结果来自原始MRG32k3a序列中的位置 \((n\operatorname{mod}4096) \cdot 2^{76} + \lfloor n/4096\rfloor\) 。(请注意,MRG32k3a的连续样本之间的步长与XORWOW不同)

    • MCRAND_ORDERING_PSEUDO_DYNAMIC

      MCRAND_ORDERING_PSEUDO_DYNAMIC的输出顺序在不同的GPU上可能会有所不同。

  • MTGP32伪随机生成器

    • MCRAND_ORDERING_PSEUDO_DEFAULT

      在当前版本中,MCRAND_ORDERING_PSEUDO_DEFAULT的输出顺序与MCRAND_ORDERING_PSEUDO_BEST相同。

    • MCRAND_ORDERING_PSEUDO_BEST

      MTGP32生成器实际上基于基本算法的不同的参数集生成192个不同的序列。设 \(S(p)\) 为参数集 \(p\) 对应的序列。在全局内存中,偏移量为 \(n\) 的结果来自序列 \(S(\lfloor n/256\rfloor\operatorname{mod}192)\) 中的位置 \(n\operatorname{mod}256\) 。换句话说,首先会生成来自 \(S(0)\) 的256个样本,然后是来自 \(S(1)\) 的256个样本,以此类推,直到 \(S(191)\) 。这个模式会重复,所以接下来的256个样本又是来自 \(S(0)\) ,然后是来自 \(S(1)\) ,以此类推。

    • MCRAND_ORDERING_PSEUDO_LEGACY

      MTGP32生成器实际上基于基本算法的不同参数集生成64个不同的序列。设 \(S(p)\) 为参数集 \(p\) 的序列。全局内存中偏移为 \(n\) 的结果来自于序列 \(S(\lfloor n/256\rfloor\operatorname{mod}64)\) 中的位置 \(n\operatorname{mod}256\) 。换句话说,从 \(S(0)\) 中取出256个样本,然后是从 \(S(1)\) 中取出256个样本,依此类推,直到 \(S(63)\) 。这个模式会重复,所以接下来的256个样本是从 \(S(0)\) 中取出的,然后是从 \(S(1)\) 中取出的,以此类推。

    • MCRAND_ORDERING_PSEUDO_DYNAMIC

      MCRAND_ORDERING_PSEUDO_DYNAMIC的输出顺序在不同的GPU上可能会有所不同。在这种顺序下,MTGP32可以使用不同于原始MTGP32实现的预计算参数。

  • MT19937伪随机生成器

    • MCRAND_ORDERING_PSEUDO_DEFAULT

      在当前版本中,MCRAND_ORDERING_PSEUDO_DEFAULT的输出顺序与MCRAND_ORDERING_PSEUDO_LEGACY相同。

    • MCRAND_ORDERING_PSEUDO_LEGACY

      排序主要基于标准的MT19937 CPU实现。输出是由8192个独立的生成器生成的。每个生成器生成原始序列的连续子序列。每个子序列的长度为 \(2^{1000}\) 。随机数是按照八个一组生成的,因此前8个元素来自第一个子序列,接下来的8个元素来自第二个子序列,依此类推。为了提高性能,结果的排列方式与原始排列方式不同。顺序与使用的硬件无关。

    • MCRAND_ORDERING_PSEUDO_BEST

      为了获得更好的性能,MCRAND_ORDERING_PSEUDO_BEST的输出顺序取决于组成您的GPU的SMs数量。随机数的生成方式与MCRAND_ORDERING_PSEUDO_LEGACY相同,但生成器的数量可能不同,以获得更好的性能。使用这种顺序生成种子的速度更快。MCRAND_ORDERING_PSEUDO_BEST仅支持GPU mcRAND随机数生成器,不能与mcrandCreateGeneratorHost()创建的主机生成器一起使用。

  • Philox_4x32_10伪随机生成器

    • MCRAND_ORDERING_PSEUDO_DEFAULT

      在当前版本中,MCRAND_ORDERING_PSEUDO_DEFAULT的输出顺序与MCRAND_ORDERING_PSEUDO_BEST相同。

    • MCRAND_ORDERING_PSEUDO_BEST

      在当前版本中,MCRAND_ORDERING_PSEUDO_BEST的输出顺序与MCRAND_ORDERING_PSEUDO_LEGACY相同。

    • MCRAND_ORDERING_PSEUDO_LEGACY

      Philox_4x32_10生成器中的每个线程根据基本算法的不同参数集生成不同的序列。在主机API中,有65536个不同的序列。每个序列的四个值后面跟着下一个序列的四个值。

    • MCRAND_ORDERING_PSEUDO_DYNAMIC

      在不同的GPU上,MCRAND_ORDERING_PSEUDO_DYNAMIC的输出顺序可能会有所不同。

  • 32位和64位的SOBOL和Scrambled SOBOL准随机生成器

    • MCRAND_ORDERING_QUASI_DEFAULT

      在生成 \(d\) 维度中的 \(n\) 个结果时,输出将由来自第一维度的 \(n/d\) 个结果组成,然后是来自第二维度的 \(n/d\) 个结果,依此类推,直到第 \(d\) 维度。只能生成维度大小的精确倍数。维度参数 \(d\) 可通过 mcrandSetQuasiRandomGeneratorDimensions() 进行设置,默认值为 1。

2.3. 返回值

所有 mcRAND 主机库调用都具有 mcrandStatus_t 的返回值。如果调用成功且没有错误,则返回 MCRAND_STATUS_SUCCESS。如果发生错误,则根据错误返回其他值。由于 MXMACA 允许内核与 CPU 代码异步执行,因此在调用库函数时可能会检测到非 mcRAND 内核中的错误。在这种情况下,将返回 MCRAND_STATUS_PREEXISTING_ERROR。

2.4. 生成函数

mcrandStatus_t
mcrandGenerate(
   mcrandGenerator_t generator,
   unsigned int *outputPtr, size_t num)

mcrandStatus_t
mcrandGenerateLongLong(
   mcrandGenerator_t generator,
   unsigned long long *outputPtr, size_t num)

mcrandGenerate() 函数用于为 XORWOW、MRG32k3a、MTGP32、MT19937、Philox_4x32_10 和 SOBOL32 生成器生成伪随机或准随机的输出位。 每个输出元素是一个 32 位无符号整数,其中所有位都是随机的。 对于 SOBOL64 生成器,每个输出元素是一个 64 位无符号双长(long long)整数,其中所有位都是随机的。 mcrandGenerate() 对于 SOBOL64 生成器会返回错误。 使用 mcrandGenerateLongLong() 和 SOBOL64 生成器生成 64 位整数。

mcrandStatus_t
mcrandGenerateUniform(
    mcrandGenerator_t generator,
    float *outputPtr, size_t num)

mcrandGenerateUniform() 函数用于生成在 0.0 到 1.0 之间均匀分布的浮点数值,其中 0.0 被排除在外,而 1.0 被包含在内。

mcrandStatus_t
mcrandGenerateNormal(
    mcrandGenerator_t generator,
    float *outputPtr, size_t n,
    float mean, float stddev)

mcrandGenerateNormal() 函数用于生成具有给定均值和标准差的正态分布的浮点数值。

mcrandStatus_t
mcrandGenerateLogNormal(
    mcrandGenerator_t generator,
    float *outputPtr, size_t n,
    float mean, float stddev)

mcrandGenerateLogNormal() 函数用于基于具有给定均值和标准差的正态分布生成对数正态分布的浮点数值。

mcrandStatus_t
mcrandGeneratePoisson(
    mcrandGenerator_t generator,
    unsigned int *outputPtr, size_t n,
    double lambda)

mcrandGeneratePoisson()函数用于根据给定的lambda值生成符合泊松分布的整数值。

mcrandStatus_t
mcrandGenerateUniformDouble(
    mcrandGenerator_t generator,
    double *outputPtr, size_t num)

mcrandGenerateUniformDouble()函数用于生成双精度的均匀分布随机数。

mcrandStatus_t
mcrandGenerateNormalDouble(
    mcrandGenerator_t generator,
    double *outputPtr, size_t n,
    double mean, double stddev)

mcrandGenerateNormalDouble()函数用于生成具有给定均值和标准差的双精度正态分布结果。

mcrandStatus_t
mcrandGenerateLogNormalDouble(
    mcrandGenerator_t generator,
    double *outputPtr, size_t n,
    double mean, double stddev)

mcrandGenerateLogNormalDouble()函数基于具有给定均值和标准差的正态分布,生成双精度的对数正态分布结果。

对于准随机生成,返回的结果数量必须是生成器维度的倍数。

可以在同一个生成器上多次调用生成函数来生成连续的结果块。 对于伪随机生成器,多次调用生成函数的结果与单次调用大尺寸生成器的结果相同。 对于准随机生成器,由于内存中维度的排序,许多较短的调用将不会在内存中产生与一个较大的调用相同的结果;然而生成的 \(n\) 维向量将是相同的。

2.5. 主机API示例

/*
* 这个程序使用主机MCRAND API生成100个数值。
* 伪随机浮点数。
*/
#include <stdio.h>
#include <stdlib.h>
#include <mc_runtime.h>
#include <mcrand.h>

#define MC_CALL(x) do { if((x)!=mcSuccess) { \
        printf("Error at %s:%d\n",__FILE__,__LINE__);\
        return EXIT_FAILURE;}} while(0)
#define MCRAND_CALL(x) do { if((x)!=MCRAND_STATUS_SUCCESS) { \
        printf("Error at %s:%d\n",__FILE__,__LINE__);\
        return EXIT_FAILURE;}} while(0)

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

    /* 在主机上分配n个浮点数的内存空间。 */
    hostData = (float *)calloc(n, sizeof(float));

    /* 在设备上分配n个浮点数的内存空间。 */
    MC_CALL(mcMalloc((void **)&devData, n*sizeof(float)));

    /* 创建伪随机数生成器。 */
    MCRAND_CALL(mcrandCreateGenerator(&gen,
                MCRAND_RNG_PSEUDO_DEFAULT));

    /* 设置随机数种子 */
    MCRAND_CALL(mcrandSetPseudoRandomGeneratorSeed(gen,
                1234ULL));

    /* 在设备上生成n个浮点数。*/
    MCRAND_CALL(mcrandGenerateUniform(gen, devData, n));

    /* 将设备内存复制到主机。 */
    MC_CALL(mcMemcpy(hostData, devData, n * sizeof(float),
        mcMemcpyDeviceToHost));

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

    /* 清除*/
    MCRAND_CALL(mcrandDestroyGenerator(gen));
    MC_CALL(mcFree(devData));
    free(hostData);
    return EXIT_SUCCESS;
}

2.6. 静态库支持

在 Linux 上,mcRAND 库也提供了一个名为 libmcrand_static.a 的静态库供使用。

例如,在linux上,要使用mcRAND针对动态库编译一个小型应用程序,可以使用以下命令:

mxcc myMCrandApp.c
   -lmcrand -o myMCrandApp

如果要针对静态的mcRAND库进行编译,则必须使用以下命令:

mxcc myMCrandApp.c
   -lmcrand_static -o myMCrandApp

2.7. 性能说明

一般来说,通过生成尽可能大的随机数块,可以从mcRAND库中获得最佳性能。用更少的调用生成多个随机数,比多次调用只生成几个随机数更有效。默认的伪随机生成器XORWOW,在第一次调用时需要一些时间进行设置。后续的生成调用不需要进行这个设置。为了省去这个设置时间,请使用MCRAND_ORDERING_PSEUDO_SEEDED排序方式。

MTGP32 Mersenne Twister算法与线程计数和块计数密切相关。MTGP32的状态结构实际上包含给定序列的256个连续样本的状态,由特定的参数集确定。每64个块使用不同的参数集,每256个线程从状态中生成一个样本,并更新状态。因此,MTGP32的最有效使用方式是生成16384个样本的倍数。

MT19937算法的性能取决于单个调用过程中产生的样本数量。 在生成超过2GB的数据时可以达到峰值性能,但仅生成80MB的数据就可以达到峰值性能的80%。

Philox_4x32_10算法与线程计数和块计数密切相关。每个线程同时计算4个随机数,因此Philox_4x32_10的最有效使用方式是生成4倍线程数的样本数。

为了获得mcRAND主机API的最佳性能,鼓励用户使用MCRAND_ORDERING_PSEUDO_BEST或MCRAND_ORDERING_PSEUDO_DYNAMIC排序。

2.8. 线程安全性

不同的主机线程使用不同的生成器,生成器不是MT19937(MCRAND_RNG_PSEUDO_MT19937)且输出不重叠的情况下,mcRAND主机API就是线程安全的。

请注意,当与MT19937生成器(MCRAND_RNG_PSEUDO_MT19937)一起使用时,mcRAND主机API不是线程安全的。