在openCL中维护多个设备中的RNG状态的最佳方法

时间:2014-10-11 00:53:41

标签: random opencl gpu prng mersenne-twister

所以我试图将这个自定义RNG库用于openCL: http://cas.ee.ic.ac.uk/people/dt10/research/rngs-gpu-mwc64x.html

库定义了一个状态结构:

//! Represents the state of a particular generator
typedef struct{ uint x; uint c; } mwc64x_state_t;

为了生成随机uint,您将状态传递给以下函数:

uint MWC64X_NextUint(mwc64x_state_t *s)

更新状态,这样当你再次将它传递给函数时,下一个"随机"将生成序列中的数字。

对于我正在创建的项目,我需要能够生成随机数,不仅可以在不同的工作组/项目中,而且还可以同时在多个设备上生成随机数,而且我无法确定设计此方法的最佳方法。我应该为每个设备/命令队列创建1个mwc64x_state_t对象,并将该状态作为全局变量传递?或者是否可以一次为所有设备创建1个状态对象? 或者我甚至不将其作为全局变量传递并在每个内核函数中本地声明一个新状态?

该库还附带此功能:

void MWC64X_SeedStreams(mwc64x_state_t *s, ulong baseOffset, ulong perStreamOffset)

据说应该将RNG分成多个"流"但是在我的内核中包含它会让它难以置信慢。例如,如果我做一些非常简单的事情,如下所示:

__kernel void myKernel()
{
    mwc64x_state_t rng;
    MWC64X_SeedStreams(&rng, 0, 10000);
}

然后内核调用变慢约40倍。

该库提供了一些源代码作为示例用法,但示例代码有点受限,似乎没有用。

因此,如果有人在openCL中熟悉RNG,或者您在我非常感谢您的建议之前使用过这个特定的图书馆。

1 个答案:

答案 0 :(得分:3)

MWC64X_SeedStreams函数确实相对较慢,至少相比之下 到MWC64X_NextUint调用,但尝试的大多数并行RNG都是如此 将大型全局流拆分为可用于的许多子流 平行。假设您将多次调用NextUint 在内核中(例如一百个或更多),但SeedStreams只在顶部。

这是随附的EstimatePi示例的注释版本 使用库(mwc64x / test / estimate_pi.cpp和mwc64x / test / test_mwc64x.cl)。

__kernel void EstimatePi(ulong n, ulong baseOffset, __global ulong *acc)
{
    // One RNG state per work-item
    mwc64x_state_t rng;

    // This calculates the number of samples that each work-item uses
    ulong samplesPerStream=n/get_global_size(0);

    // And then skip each work-item to their part of the stream, which
    // will from stream offset:
    //   baseOffset+2*samplesPerStream*get_global_id(0)
    // up to (but not including):
    //   baseOffset+2*samplesPerStream*(get_global_id(0)+1)
    //
    MWC64X_SeedStreams(&rng, baseOffset, 2*samplesPerStream);


    // Now use the numbers
    uint count=0;
    for(uint i=0;i<samplesPerStream;i++){
        ulong x=MWC64X_NextUint(&rng);
        ulong y=MWC64X_NextUint(&rng);
        ulong x2=x*x;
        ulong y2=y*y;
        if(x2+y2 >= x2)
            count++;
    }
    acc[get_global_id(0)] = count;
}

所以意图是n应该很大并且随着数字的增长而增长 工作项的增长,使samplesPerStream保持不变 一百或更多。

如果你想在多个设备上使用多个内核,那么你 需要为流拆分添加另一级别的层次结构, 例如,如果你有:

  • K:设备数量(可能在并行机器上)
  • W:每台设备的工作项数
  • C:每个工作项的NextUint调用次数

你最终得到N = K W C对NextUint的总呼叫 工作项。如果您的设备被识别为k = 0 ..(K-1), 然后在每个内核中你会做:

MWC64X_SeedStreams(&rng, W*C*k, C);

然后流中的索引将是:

[0             .. N ) : Parts of stream used across all devices
[k*(W*C)       .. (k+1)*(W*C) )    : Used within device k
[k*(W*C)+(i*C) .. (k*W*C)+(i+1)*C ) : Used by work-item i in device k.

如果每个内核使用少于C个样本,则可以 如有必要,估计过高。

(我是图书馆的作者)。