使用Random123的OpenCL随机数

时间:2012-06-29 19:44:05

标签: random opencl

我一直在查看此lib Random123及其相关引用:

  

一位神秘男子来到我的展位,询问我对使用OpenCL生成随机数的了解。我告诉他关于Mersenne Twister的实施,但他没有留下深刻的印象。他告诉我一篇新的技术论文,它解释了如何通过组合整数计数器和分组密码在GPU上生成随机数。在虔诚的语调中,他说基于反基础的随机数发生器(CBRNG)产生的数字具有比MT更大的统计随机性,并且速度更快。

我能够使用这个内核运行一个演示:

__kernel void counthits(unsigned n, __global uint2 *hitsp) {
    unsigned tid = get_global_id(0);
    unsigned hits = 0, tries = 0;
    threefry4x32_key_t k = {{tid, 0xdecafbad, 0xfacebead, 0x12345678}};
    threefry4x32_ctr_t c = {{0, 0xf00dcafe, 0xdeadbeef, 0xbeeff00d}};
    while (tries < n) {
        union {
            threefry4x32_ctr_t c;
            int4 i;
        } u;
        c.v[0]++;
        u.c = threefry4x32(c, k);
        long x1 = u.i.x, y1 = u.i.y;
        long x2 = u.i.z, y2 = u.i.w;
        if ((x1*x1 + y1*y1) < (1L<<62)) {
            hits++;
        }
        tries++;
        if ((x2*x2 + y2*y2) < (1L<<62)) {
            hits++;
        }
        tries++;
    }
    hitsp[tid].x = hits;
    hitsp[tid].y = tries;
}

我现在的问题是,每次运行时这不会生成相同的随机数,随机数是基于全局ID吗?我怎样才能每次生成新的随机数。可以提供种子作为内核的参数,然后以某种方式使用它吗?

任何使用过此lib的人都可以对我的使用有更深入的了解吗?

3 个答案:

答案 0 :(得分:4)

是。示例代码每次调用时都会生成相同的随机数序列。

要获得不同的随机数流,只需以不同方式初始化任何值k [1..3]和/或c [1..3]。您可以从命令行参数,环境变量,时间,已保存状态,/ dev / urandom或任何其他源初始化它们。请注意:

a)如果你在两个不同的运行中以完全相同的方式初始化它们,那么这两个运行将获得相同的随机数流

b)如果你在两个不同的运行中以不同方式初始化它们,那么这两个运行将获得不同的随机数流。

有时你想要财产a)。有时你想要财产b)。花点时间考虑一下你想要的东西,并确保你正在做你想做的事。

更一般地说,库中的函数(例如threefry4x32)具有无状态。如果更改输入中的任何位(即c或k的任何元素中的任何位),您将获得完全不同的随机,统计独立,均匀分布的输出。

P.S。我是该图书馆的作者之一,并撰写了文章“平行数:简单如1,2,3”: http://dl.acm.org/citation.cfm?id=2063405

如果您不是ACM数字图书馆的订阅者,则上面的链接可能会打到付费墙。或者,您可以按照此页面上的链接免费获取论文:

http://www.thesalmons.org/john/random123/index.html

答案 1 :(得分:2)

我本身无法帮助您使用库,但我可以告诉您,在OpenCL中生成随机数的最常用方法是在内核调用之间保存一些状态。

随机数生成器通常使用一种状态,从该状态生成新状态和随机数。实际上,这并不复杂:你只需传递一个保持状态的额外数组。在我的代码中,我实现了如下随机数:

uint rand_uint(uint2* rvec) {  //Adapted from http://cas.ee.ic.ac.uk/people/dt10/research/rngs-gpu-mwc64x.html
    #define A 4294883355U
    uint x=rvec->x, c=rvec->y; //Unpack the state
    uint res = x ^ c;          //Calculate the result
    uint hi = mul_hi(x,A);     //Step the RNG
    x = x*A + c;
    c = hi + (x<c);
    *rvec = (uint2)(x,c);      //Pack the state back up
    return res;                //Return the next result
    #undef A
}
inline float rand_float(uint2* rvec) {
    return (float)(rand_uint(rvec)) / (float)(0xFFFFFFFF);
}
__kernel void my_kernel(/*more arguments*/ __global uint2* randoms) {
    int index = get_global_id(0);
    uint2 rvec = randoms[index];

    //Call rand_uint or rand_float a number of times with "rvec" as argument.
    //These calls update "rvec" with new state, and return a random number

    randoms[index] = rvec;
}

。 。 。然后,你要做的就是传递一个额外的数组,将RNG的状态保持为随机状态。实际上,您需要为每个工作项以不同方式为此数组设置种子。

答案 2 :(得分:1)

0xdecafbad0xfacebead0x123456780xf00dcafe0xdeadbeef0xbeeff00d只是任意选择的数字,它们并不特殊。任何其他数字(甚至0)都可以在他们的位置使用 - 我将在示例代码中添加注释。

您可以使用传入的变量替换其中任何一个;避免输出随机“流”中不希望的重复的唯一要求是避免重复(c,k)输入元组。示例代码使用线程id和循环索引来确保唯一性,但您可以轻松添加更多变量以确保唯一性 - 例如计算主机代码中的内核调用并传入该计数器,用它代替k或c中的一个元素。

顺便说一句,尽管名称为'基于计数器的随机数生成器',但并不要求输入(c,k)为'计数器',只是计数器恰好是确保输入的最方便的习惯用语。不要重复。