Class: Ignis::Random::Generator

Inherits:
Object
  • Object
show all
Defined in:
lib/nvruby/random/generator.rb

Overview

GPU random number generator using cuRAND

Constant Summary collapse

GENERATOR_TYPES =

Available generator types

{
  default: CuRANDBindings::CURAND_RNG_PSEUDO_DEFAULT,
  xorwow: CuRANDBindings::CURAND_RNG_PSEUDO_XORWOW,
  mrg32k3a: CuRANDBindings::CURAND_RNG_PSEUDO_MRG32K3A,
  mtgp32: CuRANDBindings::CURAND_RNG_PSEUDO_MTGP32,
  mt19937: CuRANDBindings::CURAND_RNG_PSEUDO_MT19937,
  philox: CuRANDBindings::CURAND_RNG_PSEUDO_PHILOX4_32_10,
  sobol32: CuRANDBindings::CURAND_RNG_QUASI_SOBOL32,
  scrambled_sobol32: CuRANDBindings::CURAND_RNG_QUASI_SCRAMBLED_SOBOL32,
  sobol64: CuRANDBindings::CURAND_RNG_QUASI_SOBOL64,
  scrambled_sobol64: CuRANDBindings::CURAND_RNG_QUASI_SCRAMBLED_SOBOL64
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(generator_type: :xorwow, seed: nil, device: nil) ⇒ Generator

Returns a new instance of Generator.

Parameters:

  • generator_type (Symbol) (defaults to: :xorwow)

    Type of generator

  • seed (Integer, nil) (defaults to: nil)

    Random seed

  • device (Integer, nil) (defaults to: nil)

    Device index



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/nvruby/random/generator.rb', line 35

def initialize(generator_type: :xorwow, seed: nil, device: nil)
  CuRANDBindings.ensure_loaded!

  @generator_type = generator_type
  @seed = seed
  @device_index = device || Ignis.configuration.default_device
  @destroyed = false

  @handle = create_generator
  set_seed(seed) if seed

  ObjectSpace.define_finalizer(self, self.class.release_finalizer(@handle))
end

Instance Attribute Details

#device_indexInteger (readonly)

Returns Device index.

Returns:

  • (Integer)

    Device index



30
31
32
# File 'lib/nvruby/random/generator.rb', line 30

def device_index
  @device_index
end

#generator_typeSymbol (readonly)

Returns Generator type.

Returns:

  • (Symbol)

    Generator type



24
25
26
# File 'lib/nvruby/random/generator.rb', line 24

def generator_type
  @generator_type
end

#seedInteger? (readonly)

Returns Seed value.

Returns:

  • (Integer, nil)

    Seed value



27
28
29
# File 'lib/nvruby/random/generator.rb', line 27

def seed
  @seed
end

Class Method Details

.release_finalizer(handle) ⇒ Proc

Create a finalizer for generator cleanup

Parameters:

  • handle (FFI::Pointer)

    Generator handle

Returns:

  • (Proc)


247
248
249
250
251
252
# File 'lib/nvruby/random/generator.rb', line 247

def release_finalizer(handle)
  proc do
    CuRANDBindings.ensure_loaded!
    CuRANDBindings.curandDestroyGenerator(handle)
  end
end

.versionInteger

Get cuRAND version

Returns:

  • (Integer)

    Version number



256
257
258
259
260
261
262
# File 'lib/nvruby/random/generator.rb', line 256

def version
  CuRANDBindings.ensure_loaded!
  version_ptr = FFI::MemoryPointer.new(:int)
  status = CuRANDBindings.curandGetVersion(version_ptr)
  CuRANDBindings.check_status!(status, "Get cuRAND version")
  version_ptr.read_int
end

Instance Method Details

#destroy!void

This method returns an undefined value.

Destroy the generator and free resources



228
229
230
231
232
233
234
235
# File 'lib/nvruby/random/generator.rb', line 228

def destroy!
  return if @destroyed

  CuRANDBindings.curandDestroyGenerator(@handle)
  @handle = nil
  @destroyed = true
  ObjectSpace.undefine_finalizer(self)
end

#destroyed?Boolean

Check if generator has been destroyed

Returns:

  • (Boolean)


222
223
224
# File 'lib/nvruby/random/generator.rb', line 222

def destroyed?
  @destroyed
end

#integers(shape) ⇒ NvArray

Generate raw 32-bit unsigned integers

Parameters:

  • shape (Array<Integer>)

    Shape of output array

Returns:

  • (NvArray)

    Array filled with random uint32 values

Raises:

  • (InvalidOperationError)


202
203
204
205
206
207
208
209
210
211
212
# File 'lib/nvruby/random/generator.rb', line 202

def integers(shape)
  raise InvalidOperationError, "Generator has been destroyed" if @destroyed

  output = NvArray.new(shape: shape, dtype: :uint32, device: @device_index)
  output.to_device

  status = CuRANDBindings.curandGenerate(@handle, output.device_ffi_ptr, output.size)
  CuRANDBindings.check_status!(status, "Generate integers")

  output
end

#log_normal(shape, mean: 0.0, std: 1.0, dtype: :float32) ⇒ NvArray

Generate log-normal random numbers

Parameters:

  • shape (Array<Integer>)

    Shape of output array

  • mean (Float) (defaults to: 0.0)

    Mean of the underlying normal distribution

  • std (Float) (defaults to: 1.0)

    Standard deviation of the underlying normal distribution

  • dtype (Symbol) (defaults to: :float32)

    Data type (:float32 or :float64)

Returns:

  • (NvArray)

    Array filled with log-normal random values

Raises:

  • (InvalidOperationError)


156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/nvruby/random/generator.rb', line 156

def log_normal(shape, mean: 0.0, std: 1.0, dtype: :float32)
  raise InvalidOperationError, "Generator has been destroyed" if @destroyed
  raise UnsupportedDTypeError.new(dtype, operation: "log_normal") unless %i[float32 float64].include?(dtype)

  size = Array(shape).reduce(1, :*)
  padded_size = size.even? ? size : size + 1

  temp_output = CUDA::Memory.new(padded_size * DType.byte_size(dtype), device: @device_index)

  status = if dtype == :float32
             CuRANDBindings.curandGenerateLogNormal(@handle, temp_output.ffi_ptr, padded_size, mean, std)
           else
             CuRANDBindings.curandGenerateLogNormalDouble(@handle, temp_output.ffi_ptr, padded_size, mean, std)
           end

  CuRANDBindings.check_status!(status, "Generate log-normal")

  output = NvArray.new(shape: shape, dtype: dtype, device: @device_index)
  output.to_device
  output.device_memory.copy_from_device(temp_output, count: output.nbytes)

  temp_output.free!

  output
end

#normal(shape, mean: 0.0, std: 1.0, dtype: :float32) ⇒ NvArray

Generate normal (Gaussian) random numbers

Parameters:

  • shape (Array<Integer>)

    Shape of output array

  • mean (Float) (defaults to: 0.0)

    Mean value

  • std (Float) (defaults to: 1.0)

    Standard deviation

  • dtype (Symbol) (defaults to: :float32)

    Data type (:float32 or :float64)

Returns:

  • (NvArray)

    Array filled with normal random values

Raises:

  • (InvalidOperationError)


122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/nvruby/random/generator.rb', line 122

def normal(shape, mean: 0.0, std: 1.0, dtype: :float32)
  raise InvalidOperationError, "Generator has been destroyed" if @destroyed
  raise UnsupportedDTypeError.new(dtype, operation: "normal") unless %i[float32 float64].include?(dtype)

  # cuRAND requires even number of elements for normal distribution
  size = Array(shape).reduce(1, :*)
  padded_size = size.even? ? size : size + 1

  temp_output = CUDA::Memory.new(padded_size * DType.byte_size(dtype), device: @device_index)

  status = if dtype == :float32
             CuRANDBindings.curandGenerateNormal(@handle, temp_output.ffi_ptr, padded_size, mean, std)
           else
             CuRANDBindings.curandGenerateNormalDouble(@handle, temp_output.ffi_ptr, padded_size, mean, std)
           end

  CuRANDBindings.check_status!(status, "Generate normal")

  # Create output array and copy
  output = NvArray.new(shape: shape, dtype: dtype, device: @device_index)
  output.to_device
  output.device_memory.copy_from_device(temp_output, count: output.nbytes)

  temp_output.free!

  output
end

#poisson(shape, lambda_param:) ⇒ NvArray

Generate Poisson-distributed random numbers

Parameters:

  • shape (Array<Integer>)

    Shape of output array

  • lambda_param (Float)

    Lambda parameter (mean and variance)

Returns:

  • (NvArray)

    Array filled with Poisson random values (uint32)

Raises:

  • (InvalidOperationError)


186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/nvruby/random/generator.rb', line 186

def poisson(shape, lambda_param:)
  raise InvalidOperationError, "Generator has been destroyed" if @destroyed
  raise ArgumentError, "Lambda must be positive" unless lambda_param.positive?

  output = NvArray.new(shape: shape, dtype: :uint32, device: @device_index)
  output.to_device

  status = CuRANDBindings.curandGeneratePoisson(@handle, output.device_ffi_ptr, output.size, lambda_param)
  CuRANDBindings.check_status!(status, "Generate Poisson")

  output
end

#quasi_random?Boolean

Check if quasi-random generator

Returns:

  • (Boolean)


216
217
218
# File 'lib/nvruby/random/generator.rb', line 216

def quasi_random?
  %i[sobol32 scrambled_sobol32 sobol64 scrambled_sobol64].include?(@generator_type)
end

#set_offset(offset) ⇒ self

Set generator offset

Parameters:

  • offset (Integer)

    Offset value

Returns:

  • (self)

Raises:

  • (InvalidOperationError)


66
67
68
69
70
71
72
73
# File 'lib/nvruby/random/generator.rb', line 66

def set_offset(offset)
  raise InvalidOperationError, "Generator has been destroyed" if @destroyed

  status = CuRANDBindings.curandSetGeneratorOffset(@handle, offset)
  CuRANDBindings.check_status!(status, "Set generator offset")

  self
end

#set_seed(seed) ⇒ self

Set the random seed

Parameters:

  • seed (Integer)

    Seed value

Returns:

  • (self)

Raises:

  • (InvalidOperationError)


52
53
54
55
56
57
58
59
60
61
# File 'lib/nvruby/random/generator.rb', line 52

def set_seed(seed)
  raise InvalidOperationError, "Generator has been destroyed" if @destroyed
  raise ArgumentError, "Cannot set seed for quasi-random generator" if quasi_random?

  @seed = seed
  status = CuRANDBindings.curandSetPseudoRandomGeneratorSeed(@handle, seed)
  CuRANDBindings.check_status!(status, "Set generator seed")

  self
end

#set_stream(stream) ⇒ self

Set CUDA stream

Parameters:

  • stream (CUDA::Stream)

    Stream to use

Returns:

  • (self)

Raises:

  • (InvalidOperationError)


78
79
80
81
82
83
84
85
# File 'lib/nvruby/random/generator.rb', line 78

def set_stream(stream)
  raise InvalidOperationError, "Generator has been destroyed" if @destroyed

  status = CuRANDBindings.curandSetStream(@handle, stream.handle)
  CuRANDBindings.check_status!(status, "Set generator stream")

  self
end

#to_sString

Returns:

  • (String)


238
239
240
241
# File 'lib/nvruby/random/generator.rb', line 238

def to_s
  status = @destroyed ? "destroyed" : "active"
  "Generator(type=#{@generator_type}, seed=#{@seed || 'auto'}, #{status})"
end

#uniform(shape, low: 0.0, high: 1.0, dtype: :float32) ⇒ NvArray

Generate uniform random numbers in [0, 1)

Parameters:

  • shape (Array<Integer>)

    Shape of output array

  • low (Float) (defaults to: 0.0)

    Lower bound (inclusive)

  • high (Float) (defaults to: 1.0)

    Upper bound (exclusive)

  • dtype (Symbol) (defaults to: :float32)

    Data type (:float32 or :float64)

Returns:

  • (NvArray)

    Array filled with uniform random values

Raises:

  • (InvalidOperationError)


93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/nvruby/random/generator.rb', line 93

def uniform(shape, low: 0.0, high: 1.0, dtype: :float32)
  raise InvalidOperationError, "Generator has been destroyed" if @destroyed
  raise UnsupportedDTypeError.new(dtype, operation: "uniform") unless %i[float32 float64].include?(dtype)

  output = NvArray.new(shape: shape, dtype: dtype, device: @device_index)
  output.to_device

  count = output.size

  status = if dtype == :float32
             CuRANDBindings.curandGenerateUniform(@handle, output.device_ffi_ptr, count)
           else
             CuRANDBindings.curandGenerateUniformDouble(@handle, output.device_ffi_ptr, count)
           end

  CuRANDBindings.check_status!(status, "Generate uniform")

  # Scale to [low, high) if needed
  scale_uniform(output, low, high) unless low.zero? && high == 1.0

  output
end