import numpy as np import pycuda.compiler import pycuda.driver as drv import pycuda.gpuarray as array from pytools import memoize_method # {{{ MD5-based random number generation md5_code = """ /* ********************************************************************** ** Copyright (C) 1990, RSA Data Security, Inc. All rights reserved. ** ** ** ** License to copy and use this software is granted provided that ** ** it is identified as the "RSA Data Security, Inc. MD5 Message ** ** Digest Algorithm" in all material mentioning or referencing this ** ** software or this function. ** ** ** ** License is also granted to make and use derivative works ** ** provided that such works are identified as "derived from the RSA ** ** Data Security, Inc. MD5 Message Digest Algorithm" in all ** ** material mentioning or referencing the derived work. ** ** ** ** RSA Data Security, Inc. makes no representations concerning ** ** either the merchantability of this software or the suitability ** ** of this software for any particular purpose. It is provided "as ** ** is" without express or implied warranty of any kind. ** ** ** ** These notices must be retained in any copies of any part of this ** ** documentation and/or software. ** ********************************************************************** */ /* F, G and H are basic MD5 functions: selection, majority, parity */ #define F(x, y, z) (((x) & (y)) | ((~x) & (z))) #define G(x, y, z) (((x) & (z)) | ((y) & (~z))) #define H(x, y, z) ((x) ^ (y) ^ (z)) #define I(x, y, z) ((y) ^ ((x) | (~z))) /* ROTATE_LEFT rotates x left n bits */ #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32-(n)))) /* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4 */ /* Rotation is separate from addition to prevent recomputation */ #define FF(a, b, c, d, x, s, ac) \ {(a) += F ((b), (c), (d)) + (x) + (ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } #define GG(a, b, c, d, x, s, ac) \ {(a) += G ((b), (c), (d)) + (x) + (ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } #define HH(a, b, c, d, x, s, ac) \ {(a) += H ((b), (c), (d)) + (x) + (ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } #define II(a, b, c, d, x, s, ac) \ {(a) += I ((b), (c), (d)) + (x) + (ac); \ (a) = ROTATE_LEFT ((a), (s)); \ (a) += (b); \ } #define X0 threadIdx.x #define X1 threadIdx.y #define X2 threadIdx.z #define X3 blockIdx.x #define X4 blockIdx.y #define X5 blockIdx.z #define X6 seed #define X7 i #define X8 n #define X9 blockDim.x #define X10 blockDim.y #define X11 blockDim.z #define X12 gridDim.x #define X13 gridDim.y #define X14 gridDim.z #define X15 0 unsigned int a = 0x67452301; unsigned int b = 0xefcdab89; unsigned int c = 0x98badcfe; unsigned int d = 0x10325476; /* Round 1 */ #define S11 7 #define S12 12 #define S13 17 #define S14 22 FF ( a, b, c, d, X0 , S11, 3614090360); /* 1 */ FF ( d, a, b, c, X1 , S12, 3905402710); /* 2 */ FF ( c, d, a, b, X2 , S13, 606105819); /* 3 */ FF ( b, c, d, a, X3 , S14, 3250441966); /* 4 */ FF ( a, b, c, d, X4 , S11, 4118548399); /* 5 */ FF ( d, a, b, c, X5 , S12, 1200080426); /* 6 */ FF ( c, d, a, b, X6 , S13, 2821735955); /* 7 */ FF ( b, c, d, a, X7 , S14, 4249261313); /* 8 */ FF ( a, b, c, d, X8 , S11, 1770035416); /* 9 */ FF ( d, a, b, c, X9 , S12, 2336552879); /* 10 */ FF ( c, d, a, b, X10, S13, 4294925233); /* 11 */ FF ( b, c, d, a, X11, S14, 2304563134); /* 12 */ FF ( a, b, c, d, X12, S11, 1804603682); /* 13 */ FF ( d, a, b, c, X13, S12, 4254626195); /* 14 */ FF ( c, d, a, b, X14, S13, 2792965006); /* 15 */ FF ( b, c, d, a, X15, S14, 1236535329); /* 16 */ /* Round 2 */ #define S21 5 #define S22 9 #define S23 14 #define S24 20 GG ( a, b, c, d, X1 , S21, 4129170786); /* 17 */ GG ( d, a, b, c, X6 , S22, 3225465664); /* 18 */ GG ( c, d, a, b, X11, S23, 643717713); /* 19 */ GG ( b, c, d, a, X0 , S24, 3921069994); /* 20 */ GG ( a, b, c, d, X5 , S21, 3593408605); /* 21 */ GG ( d, a, b, c, X10, S22, 38016083); /* 22 */ GG ( c, d, a, b, X15, S23, 3634488961); /* 23 */ GG ( b, c, d, a, X4 , S24, 3889429448); /* 24 */ GG ( a, b, c, d, X9 , S21, 568446438); /* 25 */ GG ( d, a, b, c, X14, S22, 3275163606); /* 26 */ GG ( c, d, a, b, X3 , S23, 4107603335); /* 27 */ GG ( b, c, d, a, X8 , S24, 1163531501); /* 28 */ GG ( a, b, c, d, X13, S21, 2850285829); /* 29 */ GG ( d, a, b, c, X2 , S22, 4243563512); /* 30 */ GG ( c, d, a, b, X7 , S23, 1735328473); /* 31 */ GG ( b, c, d, a, X12, S24, 2368359562); /* 32 */ /* Round 3 */ #define S31 4 #define S32 11 #define S33 16 #define S34 23 HH ( a, b, c, d, X5 , S31, 4294588738); /* 33 */ HH ( d, a, b, c, X8 , S32, 2272392833); /* 34 */ HH ( c, d, a, b, X11, S33, 1839030562); /* 35 */ HH ( b, c, d, a, X14, S34, 4259657740); /* 36 */ HH ( a, b, c, d, X1 , S31, 2763975236); /* 37 */ HH ( d, a, b, c, X4 , S32, 1272893353); /* 38 */ HH ( c, d, a, b, X7 , S33, 4139469664); /* 39 */ HH ( b, c, d, a, X10, S34, 3200236656); /* 40 */ HH ( a, b, c, d, X13, S31, 681279174); /* 41 */ HH ( d, a, b, c, X0 , S32, 3936430074); /* 42 */ HH ( c, d, a, b, X3 , S33, 3572445317); /* 43 */ HH ( b, c, d, a, X6 , S34, 76029189); /* 44 */ HH ( a, b, c, d, X9 , S31, 3654602809); /* 45 */ HH ( d, a, b, c, X12, S32, 3873151461); /* 46 */ HH ( c, d, a, b, X15, S33, 530742520); /* 47 */ HH ( b, c, d, a, X2 , S34, 3299628645); /* 48 */ /* Round 4 */ #define S41 6 #define S42 10 #define S43 15 #define S44 21 II ( a, b, c, d, X0 , S41, 4096336452); /* 49 */ II ( d, a, b, c, X7 , S42, 1126891415); /* 50 */ II ( c, d, a, b, X14, S43, 2878612391); /* 51 */ II ( b, c, d, a, X5 , S44, 4237533241); /* 52 */ II ( a, b, c, d, X12, S41, 1700485571); /* 53 */ II ( d, a, b, c, X3 , S42, 2399980690); /* 54 */ II ( c, d, a, b, X10, S43, 4293915773); /* 55 */ II ( b, c, d, a, X1 , S44, 2240044497); /* 56 */ II ( a, b, c, d, X8 , S41, 1873313359); /* 57 */ II ( d, a, b, c, X15, S42, 4264355552); /* 58 */ II ( c, d, a, b, X6 , S43, 2734768916); /* 59 */ II ( b, c, d, a, X13, S44, 1309151649); /* 60 */ II ( a, b, c, d, X4 , S41, 4149444226); /* 61 */ II ( d, a, b, c, X11, S42, 3174756917); /* 62 */ II ( c, d, a, b, X2 , S43, 718787259); /* 63 */ II ( b, c, d, a, X9 , S44, 3951481745); /* 64 */ a += 0x67452301; b += 0xefcdab89; c += 0x98badcfe; d += 0x10325476; """ def rand(shape, dtype=np.float32, stream=None): from pycuda.gpuarray import GPUArray from pycuda.elementwise import get_elwise_kernel result = GPUArray(shape, dtype) if dtype == np.float32: func = get_elwise_kernel( "float *dest, unsigned int seed", md5_code + """ #define POW_2_M32 (1/4294967296.0f) dest[i] = a*POW_2_M32; if ((i += total_threads) < n) dest[i] = b*POW_2_M32; if ((i += total_threads) < n) dest[i] = c*POW_2_M32; if ((i += total_threads) < n) dest[i] = d*POW_2_M32; """, "md5_rng_float", ) elif dtype == np.float64: func = get_elwise_kernel( "double *dest, unsigned int seed", md5_code + """ #define POW_2_M32 (1/4294967296.0) #define POW_2_M64 (1/18446744073709551616.) dest[i] = a*POW_2_M32 + b*POW_2_M64; if ((i += total_threads) < n) { dest[i] = c*POW_2_M32 + d*POW_2_M64; } """, "md5_rng_float", ) elif dtype in [np.int32, np.uint32]: func = get_elwise_kernel( "unsigned int *dest, unsigned int seed", md5_code + """ dest[i] = a; if ((i += total_threads) < n) dest[i] = b; if ((i += total_threads) < n) dest[i] = c; if ((i += total_threads) < n) dest[i] = d; """, "md5_rng_int", ) else: raise NotImplementedError func.prepared_async_call( result._grid, result._block, stream, result.gpudata, np.random.randint(2 ** 31 - 1), result.size, ) return result # }}} # {{{ CURAND wrapper try: import pycuda._driver as _curand # used to be separate module except ImportError: def get_curand_version(): return None else: get_curand_version = _curand.get_curand_version if get_curand_version() >= (3, 2, 0): direction_vector_set = _curand.direction_vector_set _get_direction_vectors = _curand._get_direction_vectors if get_curand_version() >= (4, 0, 0): _get_scramble_constants32 = _curand._get_scramble_constants32 _get_scramble_constants64 = _curand._get_scramble_constants64 # {{{ Base class gen_template = """ __global__ void %(name)s(%(state_type)s *s, %(out_type)s *d, const size_t n) { const size_t tidx = blockIdx.x*blockDim.x+threadIdx.x; const size_t delta = blockDim.x*gridDim.x; for (size_t idx = tidx; idx < n; idx += delta) d[idx] = curand%(suffix)s(&s[tidx]); } """ gen_log_template = """ __global__ void %(name)s(%(state_type)s *s, %(out_type)s *d, %(in_type)s mean, %(in_type)s stddev, const size_t n) { const size_t tidx = blockIdx.x*blockDim.x+threadIdx.x; const size_t delta = blockDim.x*gridDim.x; for (size_t idx = tidx; idx < n; idx += delta) d[idx] = curand_log%(suffix)s(&s[tidx], mean, stddev); } """ gen_poisson_template = """ __global__ void %(name)s(%(state_type)s *s, %(out_type)s *d, double lambda, const size_t n) { const size_t tidx = blockIdx.x*blockDim.x+threadIdx.x; const size_t delta = blockDim.x*gridDim.x; for (size_t idx = tidx; idx < n; idx += delta) d[idx] = curand_poisson%(suffix)s(&s[tidx], lambda); } """ gen_poisson_inplace_template = """ __global__ void %(name)s(%(state_type)s *s, %(inout_type)s *d, const size_t n) { const size_t tidx = blockIdx.x*blockDim.x+threadIdx.x; const size_t delta = blockDim.x*gridDim.x; for (size_t idx = tidx; idx < n; idx += delta) d[idx] = (%(inout_type)s)(curand_poisson%(suffix)s(&s[tidx], double(d[idx]))); } """ random_source = """ // Uses C++ features (templates); do not surround with extern C #include extern "C" { %(generators)s } """ random_skip_ahead32_source = """ extern "C" { __global__ void skip_ahead(%(state_type)s *s, const size_t n, const unsigned int skip) { const size_t idx = blockIdx.x*blockDim.x+threadIdx.x; if (idx < n) skipahead(skip, &s[idx]); } __global__ void skip_ahead_array(%(state_type)s *s, const size_t n, const unsigned int *skip) { const size_t idx = blockIdx.x*blockDim.x+threadIdx.x; if (idx < n) skipahead(skip[idx], &s[idx]); } } """ random_skip_ahead64_source = """ extern "C" { __global__ void skip_ahead(%(state_type)s *s, const size_t n, const unsigned long long skip) { const size_t idx = blockIdx.x*blockDim.x+threadIdx.x; if (idx < n) skipahead(skip, &s[idx]); } __global__ void skip_ahead_array(%(state_type)s *s, const size_t n, const unsigned long long *skip) { const size_t idx = blockIdx.x*blockDim.x+threadIdx.x; if (idx < n) skipahead(skip[idx], &s[idx]); } } """ class _RandomNumberGeneratorBase: """ Class surrounding CURAND kernels from CUDA 3.2. It allows for generating random numbers with uniform and normal probability function of various types. """ gen_info = [ ("uniform_int", "unsigned int", ""), ("uniform_long", "unsigned long long", ""), ("uniform_float", "float", "_uniform"), ("uniform_double", "double", "_uniform_double"), ("normal_float", "float", "_normal"), ("normal_double", "double", "_normal_double"), ("normal_float2", "float2", "_normal2"), ("normal_double2", "double2", "_normal2_double"), ] gen_log_info = [ ("normal_log_float", "float", "float", "_normal"), ("normal_log_double", "double", "double", "_normal_double"), ("normal_log_float2", "float", "float2", "_normal2"), ("normal_log_double2", "double", "double2", "_normal2_double"), ] gen_poisson_info = [ ("poisson_int", "unsigned int", ""), ] gen_poisson_inplace_info = [ ("poisson_inplace_float", "float", ""), ("poisson_inplace_double", "double", ""), ("poisson_inplace_int", "unsigned int", ""), ] def __init__( self, state_type, vector_type, generator_bits, additional_source, scramble_type=None, ): if get_curand_version() < (3, 2, 0): raise OSError("Need at least CUDA 3.2") dev = drv.Context.get_device() self.block_count = dev.get_attribute( pycuda.driver.device_attribute.MULTIPROCESSOR_COUNT ) from pycuda.characterize import has_double_support def do_generate(out_type): result = True if "double" in out_type: result = result and has_double_support() if "2" in out_type: result = result and self.has_box_muller return result my_generators = [ (name, out_type, suffix) for name, out_type, suffix in self.gen_info if do_generate(out_type) ] if get_curand_version() >= (4, 0, 0): my_log_generators = [ (name, in_type, out_type, suffix) for name, in_type, out_type, suffix in self.gen_log_info if do_generate(out_type) ] if get_curand_version() >= (5, 0, 0): my_poisson_generators = [ (name, out_type, suffix) for name, out_type, suffix in self.gen_poisson_info if do_generate(out_type) ] my_poisson_inplace_generators = [ (name, inout_type, suffix) for name, inout_type, suffix in self.gen_poisson_inplace_info if do_generate(inout_type) ] generator_sources = [ gen_template % { "name": name, "out_type": out_type, "suffix": suffix, "state_type": state_type, } for name, out_type, suffix in my_generators ] if get_curand_version() >= (4, 0, 0): generator_sources.extend( [ gen_log_template % { "name": name, "in_type": in_type, "out_type": out_type, "suffix": suffix, "state_type": state_type, } for name, in_type, out_type, suffix in my_log_generators ] ) if get_curand_version() >= (5, 0, 0): generator_sources.extend( [ gen_poisson_template % { "name": name, "out_type": out_type, "suffix": suffix, "state_type": state_type, } for name, out_type, suffix in my_poisson_generators ] ) generator_sources.extend( [ gen_poisson_inplace_template % { "name": name, "inout_type": inout_type, "suffix": suffix, "state_type": state_type, } for name, inout_type, suffix in my_poisson_inplace_generators ] ) source = (random_source + additional_source) % { "state_type": state_type, "vector_type": vector_type, "scramble_type": scramble_type, "generators": "\n".join(generator_sources), } # store in instance to let subclass constructors get to it. self.module = module = pycuda.compiler.SourceModule(source, no_extern_c=True) self.generators = {} for name, out_type, suffix in my_generators: gen_func = module.get_function(name) gen_func.prepare("PPn") self.generators[name] = gen_func if get_curand_version() >= (4, 0, 0): for name, in_type, out_type, suffix in my_log_generators: gen_func = module.get_function(name) if in_type == "float": gen_func.prepare("PPffn") if in_type == "double": gen_func.prepare("PPddn") self.generators[name] = gen_func if get_curand_version() >= (5, 0, 0): for name, out_type, suffix in my_poisson_generators: gen_func = module.get_function(name) gen_func.prepare("PPdn") self.generators[name] = gen_func for name, inout_type, suffix in my_poisson_inplace_generators: gen_func = module.get_function(name) gen_func.prepare("PPn") self.generators[name] = gen_func self.generator_bits = generator_bits self._prepare_skipahead() self.state_type = state_type self._state = None def _prepare_skipahead(self): self.skip_ahead = self.module.get_function("skip_ahead") if self.generator_bits == 32: self.skip_ahead.prepare("PnI") if self.generator_bits == 64: self.skip_ahead.prepare("PnQ") self.skip_ahead_array = self.module.get_function("skip_ahead_array") self.skip_ahead_array.prepare("PnP") def _kernels(self): return list(self.generators.values()) + [ self.skip_ahead, self.skip_ahead_array, ] @property @memoize_method def generators_per_block(self): return min(kernel.max_threads_per_block for kernel in self._kernels()) @property def state(self): if self._state is None: from pycuda.characterize import sizeof data_type_size = sizeof(self.state_type, "#include ") self._state = drv.mem_alloc( self.block_count * self.generators_per_block * data_type_size ) return self._state def fill_uniform(self, data, stream=None): if data.dtype == np.float32: func = self.generators["uniform_float"] elif data.dtype == np.float64: func = self.generators["uniform_double"] elif data.dtype in [np.int32, np.uint32]: func = self.generators["uniform_int"] elif data.dtype in [np.int64, np.uint64] and self.generator_bits >= 64: func = self.generators["uniform_long"] else: raise NotImplementedError func.prepared_async_call( (self.block_count, 1), (self.generators_per_block, 1, 1), stream, self.state, data.gpudata, data.size, ) def fill_normal(self, data, stream=None): if data.dtype == np.float32: func_name = "normal_float" elif data.dtype == np.float64: func_name = "normal_double" else: raise NotImplementedError data_size = data.size if self.has_box_muller and data_size % 2 == 0: func_name += "2" data_size //= 2 func = self.generators[func_name] func.prepared_async_call( (self.block_count, 1), (self.generators_per_block, 1, 1), stream, self.state, data.gpudata, int(data_size), ) def gen_uniform(self, shape, dtype, stream=None): result = array.empty(shape, dtype) self.fill_uniform(result, stream) return result def gen_normal(self, shape, dtype, stream=None): result = array.empty(shape, dtype) self.fill_normal(result, stream) return result if get_curand_version() >= (4, 0, 0): def fill_log_normal(self, data, mean, stddev, stream=None): if data.dtype == np.float32: func_name = "normal_log_float" elif data.dtype == np.float64: func_name = "normal_log_double" else: raise NotImplementedError data_size = data.size if self.has_box_muller and data_size % 2 == 0: func_name += "2" data_size //= 2 func = self.generators[func_name] func.prepared_async_call( (self.block_count, 1), (self.generators_per_block, 1, 1), stream, self.state, data.gpudata, mean, stddev, int(data_size), ) def gen_log_normal(self, shape, dtype, mean, stddev, stream=None): result = array.empty(shape, dtype) self.fill_log_normal(result, mean, stddev, stream) return result if get_curand_version() >= (5, 0, 0): def fill_poisson(self, data, lambda_value=None, stream=None): if lambda_value is None: if data.dtype == np.float32: func_name = "poisson_inplace_float" elif data.dtype == np.float64: func_name = "poisson_inplace_double" elif data.dtype == np.uint32: func_name = "poisson_inplace_int" else: raise NotImplementedError else: if data.dtype == np.uint32: func_name = "poisson_int" else: raise NotImplementedError func = self.generators[func_name] if lambda_value is None: func.prepared_async_call( (self.block_count, 1), (self.generators_per_block, 1, 1), stream, self.state, data.gpudata, data.size, ) else: func.prepared_async_call( (self.block_count, 1), (self.generators_per_block, 1, 1), stream, self.state, data.gpudata, lambda_value, data.size, ) def gen_poisson(self, shape, dtype, lambda_value, stream=None): result = array.empty(shape, dtype) self.fill_poisson(result, lambda_value, stream) return result def call_skip_ahead(self, i, stream=None): self.skip_ahead.prepared_async_call( (self.block_count, 1), (self.generators_per_block, 1, 1), stream, self.state, self.generators_per_block, i, ) def call_skip_ahead_array(self, i, stream=None): self.skip_ahead_array.prepared_async_call( (self.block_count, 1), (self.generators_per_block, 1, 1), stream, self.state, self.generators_per_block, i.gpudata, ) # }}} # {{{ XORWOW RNG class _PseudoRandomNumberGeneratorBase(_RandomNumberGeneratorBase): def __init__( self, seed_getter, offset, state_type, vector_type, generator_bits, additional_source, scramble_type=None, ): super().__init__( state_type, vector_type, generator_bits, additional_source ) generator_count = self.generators_per_block * self.block_count if seed_getter is None: seed = array.to_gpu( np.asarray( np.random.randint(0, (1 << 31) - 1, generator_count), dtype=np.int32 ) ) else: seed = seed_getter(generator_count) if not ( isinstance(seed, pycuda.gpuarray.GPUArray) and seed.dtype == np.int32 and seed.size == generator_count ): raise TypeError("seed must be GPUArray of integers of right length") p = self.module.get_function("prepare") p.prepare("PnPn") from pycuda.characterize import has_stack has_stack = has_stack() if has_stack: prev_stack_size = drv.Context.get_limit(drv.limit.STACK_SIZE) try: if has_stack: drv.Context.set_limit(drv.limit.STACK_SIZE, 1 << 14) # 16k try: p.prepared_call( (self.block_count, 1), (self.generators_per_block, 1, 1), self.state, generator_count, seed.gpudata, offset, ) except drv.LaunchError: raise ValueError("Initialisation failed. Decrease number of threads.") finally: if has_stack: drv.Context.set_limit(drv.limit.STACK_SIZE, prev_stack_size) def _prepare_skipahead(self): self.skip_ahead = self.module.get_function("skip_ahead") self.skip_ahead.prepare("PnQ") self.skip_ahead_array = self.module.get_function("skip_ahead_array") self.skip_ahead_array.prepare("PnP") self.skip_ahead_sequence = self.module.get_function("skip_ahead_sequence") self.skip_ahead_sequence.prepare("PnQ") self.skip_ahead_sequence_array = self.module.get_function( "skip_ahead_sequence_array" ) self.skip_ahead_sequence_array.prepare("PnP") def call_skip_ahead_sequence(self, i, stream=None): self.skip_ahead_sequence.prepared_async_call( (self.block_count, 1), (self.generators_per_block, 1, 1), stream, self.state, self.generators_per_block * self.block_count, i, ) def call_skip_ahead_sequence_array(self, i, stream=None): self.skip_ahead_sequence_array.prepared_async_call( (self.block_count, 1), (self.generators_per_block, 1, 1), stream, self.state, self.generators_per_block * self.block_count, i.gpudata, ) def _kernels(self): return ( _RandomNumberGeneratorBase._kernels(self) + [self.module.get_function("prepare")] + [ self.module.get_function("skip_ahead_sequence"), self.module.get_function("skip_ahead_sequence_array"), ] ) def seed_getter_uniform(n): result = pycuda.gpuarray.empty([n], np.int32) import random value = random.randint(0, 2 ** 31 - 1) return result.fill(value) def seed_getter_unique(n): result = np.random.randint(0, 2 ** 31 - 1, n).astype(np.int32) return pycuda.gpuarray.to_gpu(result) xorwow_random_source = """ extern "C" { __global__ void prepare(%(state_type)s *s, const size_t n, %(vector_type)s *v, const size_t o) { const size_t id = blockIdx.x*blockDim.x+threadIdx.x; if (id < n) curand_init(v[id], id, o, &s[id]); } } """ xorwow_skip_ahead_sequence_source = """ extern "C" { __global__ void skip_ahead_sequence(%(state_type)s *s, const size_t n, const unsigned long long skip) { const size_t idx = blockIdx.x*blockDim.x+threadIdx.x; if (idx < n) skipahead_sequence(skip, &s[idx]); } __global__ void skip_ahead_sequence_array(%(state_type)s *s, const size_t n, const unsigned long long *skip) { const size_t idx = blockIdx.x*blockDim.x+threadIdx.x; if (idx < n) skipahead_sequence(skip[idx], &s[idx]); } } """ if get_curand_version() >= (3, 2, 0): class XORWOWRandomNumberGenerator(_PseudoRandomNumberGeneratorBase): has_box_muller = True def __init__(self, seed_getter=None, offset=0): """ :arg seed_getter: a function that, given an integer count, will yield an `int32` :class:`GPUArray` of seeds. """ super().__init__( seed_getter, offset, "curandStateXORWOW", "unsigned int", 32, xorwow_random_source + xorwow_skip_ahead_sequence_source + random_skip_ahead64_source, ) # }}} # {{{ MRG32k3a RNG mrg32k3a_random_source = """ extern "C" { __global__ void prepare(%(state_type)s *s, const size_t n, %(vector_type)s *v, const size_t o) { const size_t id = blockIdx.x*blockDim.x+threadIdx.x; if (id < n) curand_init(v[id], id, o, &s[id]); } } """ mrg32k3a_skip_ahead_sequence_source = """ extern "C" { __global__ void skip_ahead_sequence(%(state_type)s *s, const size_t n, const unsigned long long skip) { const size_t idx = blockIdx.x*blockDim.x+threadIdx.x; if (idx < n) skipahead_sequence(skip, &s[idx]); } __global__ void skip_ahead_sequence_array(%(state_type)s *s, const size_t n, const unsigned long long *skip) { const size_t idx = blockIdx.x*blockDim.x+threadIdx.x; if (idx < n) skipahead_sequence(skip[idx], &s[idx]); } __global__ void skip_ahead_subsequence(%(state_type)s *s, const size_t n, const unsigned long long skip) { const size_t idx = blockIdx.x*blockDim.x+threadIdx.x; if (idx < n) skipahead_subsequence(skip, &s[idx]); } __global__ void skip_ahead_subsequence_array(%(state_type)s *s, const size_t n, const unsigned long long *skip) { const size_t idx = blockIdx.x*blockDim.x+threadIdx.x; if (idx < n) skipahead_subsequence(skip[idx], &s[idx]); } } """ if get_curand_version() >= (4, 1, 0): class MRG32k3aRandomNumberGenerator(_PseudoRandomNumberGeneratorBase): has_box_muller = True def __init__(self, seed_getter=None, offset=0): """ :arg seed_getter: a function that, given an integer count, will yield an `int32` :class:`GPUArray` of seeds. """ super().__init__( seed_getter, offset, "curandStateMRG32k3a", "unsigned int", 32, mrg32k3a_random_source + mrg32k3a_skip_ahead_sequence_source + random_skip_ahead64_source, ) def _prepare_skipahead(self): super()._prepare_skipahead() self.skip_ahead_subsequence = self.module.get_function( "skip_ahead_subsequence" ) self.skip_ahead_subsequence.prepare("PnQ") self.skip_ahead_subsequence_array = self.module.get_function( "skip_ahead_subsequence_array" ) self.skip_ahead_subsequence_array.prepare("PnP") def call_skip_ahead_subsequence(self, i, stream=None): self.skip_ahead_subsequence.prepared_async_call( (self.block_count, 1), (self.generators_per_block, 1, 1), stream, self.state, self.generators_per_block * self.block_count, i, ) def call_skip_ahead_subsequence_array(self, i, stream=None): self.skip_ahead_subsequence_array.prepared_async_call( (self.block_count, 1), (self.generators_per_block, 1, 1), stream, self.state, self.generators_per_block * self.block_count, i.gpudata, ) def _kernels(self): return _PseudoRandomNumberGeneratorBase._kernels(self) + [ self.module.get_function("skip_ahead_subsequence"), self.module.get_function("skip_ahead_subsequence_array"), ] # }}} # {{{ Sobol RNG def generate_direction_vectors(count, direction=None): if get_curand_version() >= (4, 0, 0): if ( direction == direction_vector_set.VECTOR_64 or direction == direction_vector_set.SCRAMBLED_VECTOR_64 ): result = np.empty((count, 64), dtype=np.uint64) else: result = np.empty((count, 32), dtype=np.uint32) else: result = np.empty((count, 32), dtype=np.uint32) _get_direction_vectors(direction, result, count) return pycuda.gpuarray.to_gpu(result) if get_curand_version() >= (4, 0, 0): def generate_scramble_constants32(count): result = np.empty((count,), dtype=np.uint32) _get_scramble_constants32(result, count) return pycuda.gpuarray.to_gpu(result) def generate_scramble_constants64(count): result = np.empty((count,), dtype=np.uint64) _get_scramble_constants64(result, count) return pycuda.gpuarray.to_gpu(result) sobol_random_source = """ extern "C" { __global__ void prepare(%(state_type)s *s, const size_t n, %(vector_type)s *v, const size_t o) { const size_t id = blockIdx.x*blockDim.x+threadIdx.x; if (id < n) curand_init(v[id], o, &s[id]); } } """ class _SobolRandomNumberGeneratorBase(_RandomNumberGeneratorBase): """ Class surrounding CURAND kernels from CUDA 3.2. It allows for generating quasi-random numbers with uniform and normal probability function of type int, float, and double. """ has_box_muller = False def __init__( self, dir_vector, dir_vector_dtype, dir_vector_size, dir_vector_set, offset, state_type, vector_type, generator_bits, sobol_random_source, ): super().__init__( state_type, vector_type, generator_bits, sobol_random_source ) if dir_vector is None: dir_vector = generate_direction_vectors( self.block_count * self.generators_per_block, dir_vector_set ) if not ( isinstance(dir_vector, pycuda.gpuarray.GPUArray) and dir_vector.dtype == dir_vector_dtype and dir_vector.shape == (self.block_count * self.generators_per_block, dir_vector_size) ): raise TypeError("seed must be GPUArray of integers of right length") p = self.module.get_function("prepare") p.prepare("PnPn") from pycuda.characterize import has_stack has_stack = has_stack() if has_stack: prev_stack_size = drv.Context.get_limit(drv.limit.STACK_SIZE) try: if has_stack: drv.Context.set_limit(drv.limit.STACK_SIZE, 1 << 14) # 16k try: p.prepared_call( (self.block_count, 1), (self.generators_per_block, 1, 1), self.state, self.block_count * self.generators_per_block, dir_vector.gpudata, offset, ) except drv.LaunchError: raise ValueError("Initialisation failed. Decrease number of threads.") finally: if has_stack: drv.Context.set_limit(drv.limit.STACK_SIZE, prev_stack_size) def _kernels(self): return _RandomNumberGeneratorBase._kernels(self) + [ self.module.get_function("prepare") ] scrambledsobol_random_source = """ extern "C" { __global__ void prepare( %(state_type)s *s, const size_t n, %(vector_type)s *v, %(scramble_type)s *scramble, const size_t o) { const size_t id = blockIdx.x*blockDim.x+threadIdx.x; if (id < n) curand_init(v[id], scramble[id], o, &s[id]); } } """ class _ScrambledSobolRandomNumberGeneratorBase(_RandomNumberGeneratorBase): """ Class surrounding CURAND kernels from CUDA 4.0. It allows for generating quasi-random numbers with uniform and normal probability function of type int, float, and double. """ has_box_muller = False def __init__( self, dir_vector, dir_vector_dtype, dir_vector_size, dir_vector_set, scramble_vector, scramble_vector_function, offset, state_type, vector_type, generator_bits, scramble_type, sobol_random_source, ): super().__init__( state_type, vector_type, generator_bits, sobol_random_source, scramble_type ) if dir_vector is None: dir_vector = generate_direction_vectors( self.block_count * self.generators_per_block, dir_vector_set ) if scramble_vector is None: scramble_vector = scramble_vector_function( self.block_count * self.generators_per_block ) if not ( isinstance(dir_vector, pycuda.gpuarray.GPUArray) and dir_vector.dtype == dir_vector_dtype and dir_vector.shape == (self.block_count * self.generators_per_block, dir_vector_size) ): raise TypeError("seed must be GPUArray of integers of right length") if not ( isinstance(scramble_vector, pycuda.gpuarray.GPUArray) and scramble_vector.dtype == dir_vector_dtype and scramble_vector.shape == (self.block_count * self.generators_per_block,) ): raise TypeError("scramble must be GPUArray of integers of right length") p = self.module.get_function("prepare") p.prepare("PnPPn") from pycuda.characterize import has_stack has_stack = has_stack() if has_stack: prev_stack_size = drv.Context.get_limit(drv.limit.STACK_SIZE) try: if has_stack: drv.Context.set_limit(drv.limit.STACK_SIZE, 1 << 14) # 16k try: p.prepared_call( (self.block_count, 1), (self.generators_per_block, 1, 1), self.state, self.block_count * self.generators_per_block, dir_vector.gpudata, scramble_vector.gpudata, offset, ) except drv.LaunchError: raise ValueError("Initialisation failed. Decrease number of threads.") finally: if has_stack: drv.Context.set_limit(drv.limit.STACK_SIZE, prev_stack_size) def _kernels(self): return _RandomNumberGeneratorBase._kernels(self) + [ self.module.get_function("prepare") ] if get_curand_version() >= (3, 2, 0): class Sobol32RandomNumberGenerator(_SobolRandomNumberGeneratorBase): """ Class surrounding CURAND kernels from CUDA 3.2. It allows for generating quasi-random numbers with uniform and normal probability function of type int, float, and double. """ def __init__(self, dir_vector=None, offset=0): super().__init__( dir_vector, np.uint32, 32, direction_vector_set.VECTOR_32, offset, "curandStateSobol32", "curandDirectionVectors32_t", 32, sobol_random_source + random_skip_ahead32_source, ) if get_curand_version() >= (4, 0, 0): class ScrambledSobol32RandomNumberGenerator( _ScrambledSobolRandomNumberGeneratorBase ): """ Class surrounding CURAND kernels from CUDA 4.0. It allows for generating quasi-random numbers with uniform and normal probability function of type int, float, and double. """ def __init__(self, dir_vector=None, scramble_vector=None, offset=0): super().__init__( dir_vector, np.uint32, 32, direction_vector_set.SCRAMBLED_VECTOR_32, scramble_vector, generate_scramble_constants32, offset, "curandStateScrambledSobol32", "curandDirectionVectors32_t", 32, "unsigned int", scrambledsobol_random_source + random_skip_ahead32_source, ) if get_curand_version() >= (4, 0, 0): class Sobol64RandomNumberGenerator(_SobolRandomNumberGeneratorBase): """ Class surrounding CURAND kernels from CUDA 4.0. It allows for generating quasi-random numbers with uniform and normal probability function of type int, float, and double. """ def __init__(self, dir_vector=None, offset=0): super().__init__( dir_vector, np.uint64, 64, direction_vector_set.VECTOR_64, offset, "curandStateSobol64", "curandDirectionVectors64_t", 64, sobol_random_source + random_skip_ahead64_source, ) if get_curand_version() >= (4, 0, 0): class ScrambledSobol64RandomNumberGenerator( _ScrambledSobolRandomNumberGeneratorBase ): """ Class surrounding CURAND kernels from CUDA 4.0. It allows for generating quasi-random numbers with uniform and normal probability function of type int, float, and double. """ def __init__(self, dir_vector=None, scramble_vector=None, offset=0): super().__init__( dir_vector, np.uint64, 64, direction_vector_set.SCRAMBLED_VECTOR_64, scramble_vector, generate_scramble_constants64, offset, "curandStateScrambledSobol64", "curandDirectionVectors64_t", 64, "unsigned long long", scrambledsobol_random_source + random_skip_ahead64_source, ) # }}} # }}} # vim: foldmethod=marker