How-to Guides¶
How to save and resume long computation¶
RandomState is pickleable. Pickling allows to save and restore
the internal state of the pseudo-random number generators.
import numpy as np
import mkl_random
import pickle
rs = mkl_random.RandomState(seed=777, brng="r250")
draw = rs.standard_normal(size=1357913)
# pickle random state
saved = pickle.dumps(rs)
# draw some numbers as if computation were to continue
post_draw = rs.gamma(5, 1, size=100)
# restore random state, and continue from
restored_rs = pickle.loads(saved)
resumed_draw = restored_rs.gamma(5, 1, size=100)
# sample from restored stated is the same as sample
# from the original one
assert np.array_equal(restored_rs, resumed_draw)
Stochastic computations in parallel with multiprocessing¶
When performing stochastic computations in parallel, care is due to ensure statistical independence of samples drawn in parallel.
Basic quasi-random number generators provide different means to accomplishing
this. Some support skipahead() method or leapfrog() method, while
others provide a fixed-size family of generators with nice property that generators
from such family, initialized equally, produce streams of randomness statistically
indistinguishable from independent.
- skipahead(nskips)¶
Advance the state of the generator using skip-ahead method, or raise
ValueErrorexception if not supported.The argument nskips must be a positive Python integer.
The method is supported for “philox4x32x10”, “mrg32k3a”, “mcg31m1”, “mcg59”, “wh”, “mt19937”, “sfmt19937”, and “ars5” basic random number generators.
Note
When using skipahead(), it is important to ensure that a parallel task does not consume more than
nskips states, otherwise streams of randomness begin to overlap and the assumption of statistical
independence breaks down.
- leapfrog(k, nstreams)¶
Initialize the state of the generator using leap-frog method, or raise
ValueErrorexception if not supported.The leap-frog method partitions state trajectory into
nstreaminterleaved non-overlapping sub-sequences, and argumentkidentifies the subsequence.The method is supported for “mcg31m1”, “mcg59”, and “wh” basic pseudo-random number generators.
Note
When using leapfrog() or skipahead() methods one must remember that parallel tasks partition
generators period and choose a generator with sufficiently long period to avoid cycling over the period
more than once, as doing so also breaks the assumption of statistical independence and may compromise
correctness of the simulation.
mkl_random also provides two families of basic pseudo-random number generators, “mt2203” and
“wh”, with property that members from particular family, initialized equally, produce streams of
randomness stasistically indistunguishable from independent. To use such families in parallel computation, assign
difference family generators to different parallel workers and sample those assigned generators in each parallel worker.
Please refer to “examples/” folder in the GitHub repo for more details.
Thread safety and free-threaded Python¶
mkl_random supports free-threaded (GIL-disabled) CPython.
Every MKLRandomState instance owns a lock guarding its underlying MKL stream,
so concurrent calls on a single instance cannot corrupt its state.
A few properties are worth keeping in mind when sampling from several threads:
Prefer one generator per thread. Sharing one instance across threads is safe – draws are serialized by the instance lock – but the order in which concurrent calls interleave is not reproducible from run to run. For reproducible parallel streams, give each thread its own generator and partition the streams as described in the parallel section above (
skipahead(),leapfrog(), or themt2203/whfamilies).The module-level functions share one global generator. Calls such as
mkl_random.normal(...)all delegate to a single hiddenMKLRandomState. They are thread-safe, but concurrent calls draw from the same stream with a non-reproducible interleaving. Instantiate your own generators when you need control over the streams.Patching NumPy is process-global.
patch_numpy_randomreplaces functions onnumpy.randomfor the whole process, so it affects every thread, not just the calling one. Prefer themkl_randomcontext manager for balanced patch / restore handling, but keep in mind that the patch itself is still process-global.In-place shuffles of a shared array are serialized per generator.
shufflemutates the array passed to it, under a lock of its own, so concurrent shuffles through one generator cannot drop or duplicate elements (their order is not reproducible). Shuffling one array through different generators is still a data race: each generator has its own lock. Give each thread its own array, or share a single generator.