CUDA 的随机数算法 API

yizhihongxing

参考自 Nvidia cuRand 官方 API 文档

一、具体使用场景

如下是是在 dropout 优化中手写的 uniform_random 的 Kernel:

#include <cuda_runtime.h>
#include <curand_kernel.h>

__device__ inline float cinn_nvgpu_uniform_random_fp32(int seed){
  curandStatePhilox4_32_10_t state;
  int idx = threadIdx.x + blockIdx.x * blockDim.x;
  curand_init(seed, idx, 1, &state);
  return curand_uniform(&state);
}

二、API 解析

我们首先来看 curand_init 函数的签名和语义:

__device__ void
curand_init(unsigned long long seed,
            unsigned long long subsequence,
            unsigned long long offset,
            curandStatePhilox4_32_10_t *state)

给定相同的seed、sequence、offset 参数下,curand_init 会保证产生相同的其实状态 state。另外此函数会在调用 2^67 ⋅ sequence + offset次 cu_rand API 之后「重置」为起始状态。关于 sequence 和 offset 的如何生效的机制,参考 StackOverflow

注意:在 seed 相同、sequence 不同时,一般不会产生有统计学上关联的结果。原文:Sequences generated with the same seed and different sequence numbers will not have statistically correlated values.

另外在CUDA 并行产生随机数实践上,也有一些经验上的建议:

  • 若保证高质量的伪随机数,建议使用不同的 seed
  • 若是并行在一个「实验」里,建议指定不同的sequence参数,且最好「单调递增」
  • 若果线程里的config都是一样,即 state 一样,则可以把「随机状态量」放到 global memory里,以减少 setup 开销

参考原文:For the highest quality parallel pseudorandom number generation, each experiment should be assigned a unique seed. Within an experiment, each thread of computation should be assigned a unique sequence number. If an experiment spans multiple kernel launches, it is recommended that threads between kernel launches be given the same seed, and sequence numbers be assigned in a monotonically increasing way. If the same configuration of threads is launched, random state can be preserved in global memory between launches to avoid state setup time.

然后我们看下Nvidia 主要提供了哪些常用的随机数生成API:

__device__ float
curand_uniform (curandState_t *state);   // <----  It may return from 0.0 to 1.0, where 1.0 is included and 0.0 is excluded.

__device__ float
curand_normal (curandState_t *state);    // <-----  returns a single normally distributed float with mean 0.0 and standard deviation 1.0.

__device__ float
curand_log_normal (curandState_t *state, float mean, float stddev); // <----- returns a single log-normally distributed float based on a normal distribution with the given mean and standard deviation.

// 如下是上述 3 个API 的 double 版本
__device__ double
curand_uniform_double (curandState_t *state);

__device__ double
curand_normal_double (curandState_t *state);

__device__ double
curand_log_normal_double (curandState_t *state, double mean, double stddev);

上面的 device API 在每次调用时,只会生成一个 float/double 的随机数。Nvidia 同样提供了一次可以生成 2个或4个 device API:

__device__ uint4
curand4 (curandStatePhilox4_32_10_t *state);

__device__ float4
curand_uniform4 (curandStatePhilox4_32_10_t *state);

__device__ float4
curand_normal4 (curandStatePhilox4_32_10_t *state);

__device__ float4
curand_log_normal4 (curandStatePhilox4_32_10_t *state, float mean, float stddev);

从上面的函数接口以及 Nvidia 的文档来看,在初始化某种类型的 state 状态量后,每次调用类似 curand() 的 API 后,state 都会自动进行 offset 偏移。

因此,Nvidia 官网上也提供了单独对 state 进行位移的 API,其效果等价于调用多次无返回值的 curand() API,且性能更好:

__device__ void
skipahead(unsigned long long n, curandState_t *state); // <----- == calls n*curand()

__device__ void
skipahead_sequence(unsigned long long n, curandState_t *state); // <----- == calls n*2^67 curand()

三、性能分析

Nvidia 的官网明确指出了存在的性能问题,给开发者实现高性能 Kernel 提供了充分的经验指导:

  • curand_init()要比curand()和curand_uniform()慢!
  • curand_init()在 offset 比较大时性能也会比小 offset 差!
  • save/load操作 state 比每次重复创建起始 state 性能要快很多 !

原文如下:Calls to curand_init() are slower than calls to curand() or curand_uniform(). Large offsets to curand_init() take more time than smaller offsets. It is much faster to save and restore random generator state than to recalculate the starting state repeatedly.

对于上述第三点,Nvidia 建议可以将 state 存放到 global memory 中,如下是一个样例代码:

__global__ void example(curandState *global_state)
{
    curandState local_state;
    local_state = global_state[threadIdx.x];
    for(int i = 0; i < 10000; i++) {
        unsigned int x = curand(&local_state);
        ...
    }
    global_state[threadIdx.x] = local_state;
}

从另一个维度来讲,相对于A产生随机数操作的API,初始化 state 会占用更多的「寄存器」和 local memory 资源。因此 nvidia 建议将 curand_initcurand() API 拆分放到不同的 Kernel 中,可以获得最大的性能收益;

原文:Initialization of the random generator state generally requires more registers and local memory than random number generation. It may be beneficial to separate calls to curand_init() and curand() into separate kernels for maximum performance.
State setup can be an expensive operation. One way to speed up the setup is to use different seeds for each thread and a constant sequence number of 0. This can be especially helpful if many generators need to be created. While faster to set up, this method provides less guarantees about the mathematical properties of the generated sequences. If there happens to be a bad interaction between the hash function that initializes the generator state from the seed and the periodicity of the generators, there might be threads with highly correlated outputs for some seed values. We do not know of any problem values; if they do exist they are likely to be rare.

Nvidia 提供的 API 样例代码如下:

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

#include <cuda_runtime.h>
#include <curand_kernel.h>

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

__global__ void setup_kernel(curandState *state)
{
    int id = threadIdx.x + blockIdx.x * blockDim.x;
    /* Each thread gets same seed, a different sequence
       number, no offset */
    curand_init(1234, id, 0, &state[id]);
}

__global__ void generate_uniform_kernel(curandStatePhilox4_32_10_t *state,
                                int n,
                                unsigned int *result)
{
    int id = threadIdx.x + blockIdx.x * blockDim.x;
    unsigned int count = 0;
    float x;
    /* Copy state to local memory for efficiency */
    curandStatePhilox4_32_10_t localState = state[id];
    /* Generate pseudo-random uniforms */
    for(int i = 0; i < n; i++) {
        x = curand_uniform(&localState);
        /* Check if > .5 */
        if(x > .5) {
            count++;
        }
    }
    /* Copy state back to global memory */
    state[id] = localState;
    /* Store results */
    result[id] += count;
}

原文链接:https://www.cnblogs.com/CocoML/p/17376020.html

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:CUDA 的随机数算法 API - Python技术站

(0)
上一篇 2023年5月6日
下一篇 2023年5月6日

相关文章

  • 汇总|基于激光雷达的3D目标检测开源项目&数据集

    作者:蒋天园 来源:公众号@3D视觉工坊 链接:汇总|基于激光雷达的3D目标检测开源项目&数据集 前言 这一片文章主要介绍目前3D目标检测的一些比较重要的数据集合在github上比较好用的3D目标检测项目。包含了最火最热的KITTI到当前研究前沿的多模态,时序融合等的新数据集。分类方法如下,首先按照场景可以将数据集划分为室内和室外数据集。然后分别介绍…

    2023年4月8日
    00
  • 基于Sklearn机器学习代码实战

    本文主要跟随Datawhale的学习路线以及内容教程,详细介绍了机器学期常见的多个基础算法的基于sklearn的实现过程,内容丰富。 LinearRegression 线性回归入门 数据生成 为了直观地看到算法的思路,我们先生成一些二维数据来直观展现 import numpy as np import matplotlib.pyplot as plt def…

    机器学习 2023年4月11日
    00
  • [svc]caffe安装笔记-显卡购买

    caffe,这是是数据组需要做一些大数据模型的训练(深度学习), 要求 服务器+显卡(运算卡), 刚开始老板让买的牌子是泰坦的(这是2年前的事情了). 后来买不到这个牌子的,(jd,tb)看过丽台的,看过gtx系列的哪个型号来着, 也不合适,后来买的特斯拉显卡 [查了下一些知名的显卡牌子](https://www.zhihu.com/question/421…

    Caffe 2023年4月7日
    00
  • Keras Conv1d 参数及输入输出详解

    Conv1d(in_channels,out_channels,kernel_size,stride=1,padding=0,dilation=1,groups=1,bias=True) filters:卷积核的数目(即输出的维度) kernel_size:整数或由单个整数构成的list/tuple,卷积核的空域或时域窗长度 strides:整数或由单个整数…

    Keras 2023年4月8日
    00
  • pytorch下的lib库 源码阅读笔记(2)

    2017年11月22日00:25:54 对lib下面的TH的大致结构基本上理解了,我阅读pytorch底层代码的目的是为了知道 python层面那个_C模块是个什么东西,底层完全黑箱的话对于理解pytorch的优缺点太欠缺了。 看到 TH 的 Tensor 结构体定义中offset等变量时不甚理解,然后搜到个大牛的博客,下面是第一篇: 从零开始山寨Caffe…

    PyTorch 2023年4月8日
    00
  • tensorflow实现验证码识别案例

    1、知识点 “”” 验证码分析: 对图片进行分析: 1、分割识别 2、整体识别 输出:[3,5,7] –>softmax转为概率[0.04,0.16,0.8] —> 交叉熵计算损失值 (目标值和预测值的对数) tf.argmax(预测值,2)验证码样例:[NAZP] [XCVB] [WEFW] ,都是字母的 “”” 2、将数据写入TFRec…

    tensorflow 2023年4月8日
    00
  • 关于auto-keras训练cnn模型

    # 我在训练自己的人脸分类模型的时候发现图片的维度不能太高,经过很多次测试过后觉得一般人脸图片分为28*28大小训练的效果比较好。建议在使用其训练自己的物体识别模型的时候,尽量把图片压缩到28*28# coding:utf-8 import time import matplotlib.pyplot as plt from autokeras import …

    2023年4月6日
    00
  • [机器学习]-Adaboost提升算法从原理到实践

    转发 from  http://www.cnblogs.com/NextNight/p/6227526.html 1.基本思想: 综合某些专家的判断,往往要比一个专家单独的判断要好。在”强可学习”和”弱可学习”的概念上来说就是我们通过对多个弱可学习的算法进行”组合提升或者说是强化”得到一个性能赶超强可学习算法的算法。如何地这些弱算法进行提升是关键!AdaBo…

    机器学习 2023年4月13日
    00
合作推广
合作推广
分享本页
返回顶部