How to set seed for Random.TaskLocalRNG()?

I see the following:

using MLJ
Tree = @load DecisionTreeClassifier pkg=DecisionTree

julia> tree = Tree()
DecisionTreeClassifier(
  max_depth = -1,
  min_samples_leaf = 1,
  min_samples_split = 2,
  min_purity_increase = 0.0,
  n_subfeatures = 0,
  post_prune = false,
  merge_purity_threshold = 1.0,
  display_depth = 5,
  feature_importance = :impurity,
  rng = Random.TaskLocalRNG())

not much success after reading docs about Random.TaskLocalRNG. How could I set a fixed seed for it? Should I use

Random.seed!(123)

or

Random.seed!(Random.TaskLocalRNG(), 123)

?

thanks

I usually use Random.Xoshiro(seed) for this purpose. Does Tree(rng=Random.Xoshiro(42)) work?

from the docs, seems like TaskLocalRNG has some distinct advantages like being reproducible in multithread environment. That’s why it’s chosen as the default.

According to ?Random.seed!:

After the call to seed!, rng is equivalent to a newly
created object initialized with the same seed.

So even though there’s ā€œno method matching Random.TaskLocalRNG(::Int64)ā€ (which I find bizarre), you can indeed use Random.seed!:

julia> rng = Random.seed!(Random.TaskLocalRNG(), 123)
Random.TaskLocalRNG()

julia> randn(rng)
-0.6457306721039767

julia> rng = Random.seed!(Random.TaskLocalRNG(), 123);

julia> randn(rng)
-0.6457306721039767

julia> let
        rng = Random.TaskLocalRNG()
        rng_new = Random.seed!(rng)
        rng === rng_new
       end
true

From the same docstring:

If rng is not specified, it defaults to seeding the state of the shared
task-local generator.

Random.seed!(123) seeds some default, existing RNG returned by Random.default_rng, while Random.seed!(Random.TaskLocalRNG(), 123) creates a new one.

1 Like

That’s not right. There is only one task local RNG per task, so Random.seed!(Random.TaskLocalRNG(), 123) does not create a new RNG. It seeds the existing one. That’s also why there isn’t a Random.TaskLocalRNG(::Int64) method - you can’t make a new task local RNG.

seed!(x::Int) is equivalent to seed!(Random.TaskLocalRNG(), x::Int), except that the former also stores the used seed, such that it can be ā€˜reset’ later by functionality such as @testset.

The TaskLocalRNG is a zero-sized struct that refers to four of the five rngState fields in the existing task:

julia> dump(Task)
mutable struct Task <: Any
 [... more fields ...]
  rngState0::UInt64
  rngState1::UInt64
  rngState2::UInt64
  rngState3::UInt64
  rngState4::UInt64
 [... more fields ...]
2 Likes

thanks for the help.

after some @enter, I think the correct seeding should be Random.seed!(Random.TaskLocalRNG(), 123), but not Random.seed!(123).
I got this conclusion by:

julia> @enter Random.seed!(123)
 427  function seed!(seed=nothing)
>428      seed!(default_rng(), seed)

which is seeding default_rng().

now

julia> @enter Random.seed!(Random.TaskLocalRNG(), 123)
>259  seed!(rng::Union{TaskLocalRNG, Xoshiro}, seed) =
 260      initstate!(rng, reinterpret(UInt64, hash_seed(seed)))
...
 236  @inline function initstate!(x::Union{TaskLocalRNG, Xoshiro}, state)
>237      length(state) == 4 && eltype(state) == UInt64 ||
 238          throw(ArgumentError("initstate! expects a list of 4 `UInt64` values"))
 239      s0, s1, s2, s3 = state
 240      setstate!(x, (s0, s1, s2, s3, 1s0 + 3s1 + 5s2 + 7s3))
...
 213  @inline function setstate!(x::TaskLocalRNG, (s0, s1, s2, s3, s4))
>214      t = current_task()
 215      t.rngState0 = s0
 216      t.rngState1 = s1
 217      t.rngState2 = s2
 218      t.rngState3 = s3

is actually seeding TaskLocalRNG.

am I correct?

The default RNG is the task local RNg (although that may change in the future)

1 Like

indeed…

julia> Random.default_rng()
Random.TaskLocalRNG()

TIL. Apparently Random.TaskLocalRNG() always returns the same object:

julia> let
        r1 = Random.TaskLocalRNG()
        r2 = Random.TaskLocalRNG()
        r1 === r2
       end
true

So the object returned by Random.TaskLocalRNG() is always the same. That’s the first time I see a constructor that doesn’t return a new object…

As a side note, why not make Random.TaskLocalRNG(seed::Int) = Random.seed!(Random.TaskLocalRNG(), seed)? That would make the API equivalent to, say, Xoshiro and MersenneTwister.

1 Like

There are lots of constructors returning the same object! Consider:

julia> Nothing() === Nothing()
true

julia> Missing() === Missing()
true

julia> EOFError() === EOFError()
true

julia> Base.HasLength() === Base.HasLength()
true

This happens whenever a type has no runtime information (e.g. it’s just an immutable struct with no fields)

2 Likes