add Random.fork(rng::Xoshiro)
to split rng
into a new instance
#58193
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
When new tasks are spawned, the task-local RNG of the current task is "split", or "forked", into a new RNG for the new task. This is achieved in a fast and sound way, involving a secondary RNG "hidden" in the 5th field of the
TaskLocalRNG
struct.Xoshiro
was made to mirrorTaskLocalRNG
and also has a 5th field, mostly unused. It was useful for example when you want to save the state ofTaskLocalRNG()
viacopy(TaskLocalRNG())
, which returns aXoshiro
.This PR re-implements in julia this forking mechanism from
jl_rng_split()
in "src/task.c", to allow forkingXoshiro
RNGs independently of task spawning. This is in particular useful for parallel (reproducible) computations, where tasks can be spawned with a forked (explicit) RNG from an initialXoshiro
object.There are alternatives, like "jumping", or for example seeding new RNGs objects from a master seed and a "worker id", like in
Xoshiro([master_seed, worker_id])
, but these techniques require some coordination. For example, it can be unsafe to locally create a new RNG via jumping, because you might have collision with another RNG created from the parent task also via jumping with the same number of steps.In contrast, "forking" allows to make local decisions about the shape of the computation without risks of collisions.
And it's simpler: you just write
forked_rng = Random.fork(src_rng)
.Alternative names could be
spawn
, which is used in Numpy, but that I find less descriptive thanfork
; orsplit
, perhaps what is mostly found in the literature, and also appears in the namejl_rng_split
; one problem being thatBase.split
means something else."Fork" seems appropriate as it's used in the name
Random.forkRand
for array-filling with SIMD, and in the long comment abovejl_rng_split
.