slrand

package
v1.0.17 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 27, 2023 License: BSD-3-Clause Imports: 3 Imported by: 1

README

slrand

This package contains HLSL header files and matching Go code for various random number generation (RNG) functions. The gosl tool will automatically copy the slrand.hlsl self-contained file into the destination shaders directory if the Go code contains slrand. prefix. Here's how you include:

//gosl: hlsl mycode
// #include "slrand.hlsl"
//gosl: end mycode

slrand uses the Philox2x32 algorithm which is also available on CUDA on their cuRNG and in Tensorflow. A recent evaluation showed it to be the fastest GPU RNG, which also passes the standards for statistical quality (e.g., BigCrush). It is a counter based RNG, [CBRNG](https://en.wikipedia.org/wiki/Counter-based_random_number_generator_(CBRNG), where the random number is a direct function of the input state, with no other internal state. For a useful discussion of other alternatives, see reddit cpp thread. The code is based on the D.E. Shaw github implementation.

The key advantage of this algorithm is its stateless nature, where the result is a deterministic but highly nonlinear function of its two inputs:

    uint2 res = Philox2x32(inout uint2 counter, uint key);

where the HLSL uint2 type is 2 uint32 32-bit unsigned integers. For GPU usage, the key is always set to the unique element being processed (e.g., the index of the data structure being updated), ensuring that different numbers are generated for each such element, and the counter should be configured as a shared global value that is incremented after every RNG call. For example, if 4 RNG calls happen within a given set of GPU code, each thread starts with the same starting counter value, which is passed around as a local uint2 variable and incremented locally for each RNG. Then, after all threads have been performed, the shared starting counter is incremented using CounterAdd by 4.

The Float and Uint32 etc wrapper functions around Philox2x32 will automatically increment the counter var passed to it, using the CounterIncr() method that manages the two 32 bit numbers as if they are a full 64 bit uint.

The slrand.Counter struct provides a 16-byte aligned type for storing and incrementing the global counter. The Seed method initializes the starting counter value by setting the Hi uint32 value to given seed, which thus provides a random sequence length of over 4 billion numbers within the Lo uint32 counter -- use more widely spaced seed values for longer unique sequences.

gosl will automatically translate the Go versions of the slrand package functions into their HLSL equivalents.

See the axon and rand examples for how to use in combined Go / GPU code. In the axon example, the slrand.Counter is added to the Time context struct, and incremented after each cycle based on the number of random numbers generated for a single pass through the code, as determined by the parameter settings. The index of each neuron being processed is used as the key, which is consistent in CPU and GPU versions. Within each cycle, a local arg variable is incremented on each GPU processor as the computation unfolds, passed by reference after the top-level, so it updates as each RNG call is made within each pass.

Critically, these examples show that the CPU and GPU code produce identical random number sequences, which is otherwise quite difficult to achieve without this specific form of RNG.

Implementational details

Unfortunately, vulkan glslang does not support 64 bit integers, even though the shader language model has somehow been updated to support them: https://github.com/KhronosGroup/glslang/issues/2965 -- https://github.com/microsoft/DirectXShaderCompiler/issues/2067. This would also greatly speed up the impl: https://github.com/microsoft/DirectXShaderCompiler/issues/2821.

The result is that we have to use the slower version of the MulHiLo algorithm using only 32 bit uints.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BoolP

func BoolP(counter *sltype.Uint2, key uint32, p float32) bool

BoolP returns a bool true value with probability p

func CounterAdd

func CounterAdd(counter *sltype.Uint2, inc uint32)

CounterAdd adds the given increment to the counter

func CounterIncr

func CounterIncr(counter *sltype.Uint2)

CounterIncr increments the given counter as if it was a uint64 integer.

func Float

func Float(counter *sltype.Uint2, key uint32) float32

Float returns a uniformly-distributed 32 float in range (0,1) based on given counter and key. The counter is incremented by 1 (in a 64-bit equivalent manner) as a result of this call, ensuring that the next call will produce the next random number in the sequence. The key should be the unique index of the element being updated.

func Float11

func Float11(counter *sltype.Uint2, key uint32) float32

Float11 returns a uniformly-distributed 32 float in range [-1,1] based on given counter and key. The counter is incremented by 1 (in a 64-bit equivalent manner) as a result of this call, ensuring that the next call will produce the next random number in the sequence. The key should be the unique index of the element being updated.

func Float112

func Float112(counter *sltype.Uint2, key uint32) sltype.Float2

Float112 returns two uniformly-distributed 32 floats in range [-1,1] based on given counter and key. The counter is incremented by 1 (in a 64-bit equivalent manner) as a result of this call, ensuring that the next call will produce the next random number in the sequence. The key should be the unique index of the element being updated.

func Float2

func Float2(counter *sltype.Uint2, key uint32) sltype.Float2

Float2 returns two uniformly-distributed 32 floats in range (0,1) based on given counter and key. The counter is incremented by 1 (in a 64-bit equivalent manner) as a result of this call, ensuring that the next call will produce the next random number in the sequence. The key should be the unique index of the element being updated.

func MulHiLo64

func MulHiLo64(a, b uint32) (lo, hi uint32)

MulHiLo64 is the fast, simpler version when 64 bit uints become available

func NormFloat

func NormFloat(counter *sltype.Uint2, key uint32) float32

NormFloat returns a random 32 bit floating number distributed according to the normal, Gaussian distribution with zero mean and unit variance.

func NormFloat2

func NormFloat2(counter *sltype.Uint2, key uint32) sltype.Float2

NormFloat2 returns two random 32 bit floating numbers distributed according to the normal, Gaussian distribution with zero mean and unit variance. This is done very efficiently using the Box-Muller algorithm that consumes two random 32 bit uint32 values.

func Philox2x32

func Philox2x32(counter sltype.Uint2, key uint32) sltype.Uint2

Philox2x32 implements the stateless counter-based RNG algorithm returning a random number as 2 uint3232 32 bit values, given a counter and key input that determine the result.

func Philox2x32bumpkey

func Philox2x32bumpkey(key *uint32)

Philox2x32bumpkey does one round of updating of the key

func Philox2x32round

func Philox2x32round(counter *sltype.Uint2, key uint32)

Philox2x32round does one round of updating of the counter

func SincosPi

func SincosPi(x float32) (s, c float32)

func Uint2

func Uint2(counter *sltype.Uint2, key uint32) sltype.Uint2

Uint2 returns two uniformly-distributed 32 unsigned integers, based on given counter and key. The counter is incremented by 1 (in a 64-bit equivalent manner) as a result of this call, ensuring that the next call will produce the next random numberin the sequence. The key should be the unique index of the element being updated.

func Uint2ToFloat

func Uint2ToFloat(val sltype.Uint2) sltype.Float2

Uint2ToFloat converts two uint32 32 bit integers (Uint2) into two corresponding 32 bit float values (float2) in the (0,1) interval (i.e., exclusive of 1).

func Uint2ToFloat11

func Uint2ToFloat11(val sltype.Uint2) sltype.Float2

Uint2ToFloat11 converts two uint32 32 bit integers (Uint2) into two corresponding 32 bit float values (float2) in the (0,1) interval (i.e., exclusive of 1).

func Uint32

func Uint32(counter *sltype.Uint2, key uint32) uint32

Uint32 returns a uniformly-distributed 32 unsigned integer, based on given counter and key. The counter is incremented by 1 (in a 64-bit equivalent manner) as a result of this call, ensuring that the next call will produce the next random number in the sequence. The key should be the unique index of the element being updated.

func Uint32ToFloat

func Uint32ToFloat(val uint32) float32

Uint32ToFloat converts a uint32 32 bit integer into a 32 bit float in the (0,1) interval (i.e., exclusive of 0 and 1). This differs from the Go standard by excluding 0, which is handy for passing directly to Log function, and from the reference Philox code by excluding 1 which is in the Go standard and most other standard RNGs.

func Uint32ToFloat11

func Uint32ToFloat11(val uint32) float32

Uint32ToFloat11 converts a uint32 32 bit integer into a 32 bit float in the [1,1] interval (inclusive of -1 and 1, never identically == 0)

func Uintn

func Uintn(counter *sltype.Uint2, key uint32, n uint32) uint32

Uintn returns a uint32 in the range [0,n)

Types

type Counter

type Counter struct {

	// lower 32 bits of counter, incremented first
	Lo uint32 `desc:"lower 32 bits of counter, incremented first"`

	// higher 32 bits of counter, incremented only when Lo turns over
	Hi uint32 `desc:"higher 32 bits of counter, incremented only when Lo turns over"`

	// last seed value set by Seed method, restored by Reset()
	HiSeed uint32 `desc:"last seed value set by Seed method, restored by Reset()"`
	// contains filtered or unexported fields
}

Counter is used for storing the random counter using aligned 16 byte storage, with convenience methods for typical use cases. It retains a copy of the last Seed value, which is applied to the Hi uint32 value.

func (*Counter) Add

func (ct *Counter) Add(inc uint32) sltype.Uint2

Add increments the counter by given amount. Call this after thread completion with number of random numbers generated per thread.

func (*Counter) Reset

func (ct *Counter) Reset()

Reset resets counter to last set Seed state

func (*Counter) Seed added in v1.0.3

func (ct *Counter) Seed(seed uint32)

Seed sets the Hi uint32 value from given seed, saving it in HiSeed field. Each increment in seed generates a unique sequence of over 4 billion numbers, so it is reasonable to just use incremental values there, but more widely spaced numbers will result in longer unique sequences. Resets Lo to 0. This same seed will be restored during Reset

func (*Counter) Set

func (ct *Counter) Set(c sltype.Uint2)

Set sets the counter from a Uint2

func (*Counter) Uint2

func (ct *Counter) Uint2() sltype.Uint2

Uint2 returns counter as a Uint2

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL