From c5182f4050f83d79884182169a0d21769881b379 Mon Sep 17 00:00:00 2001 From: Nikita Grigorian Date: Wed, 4 Nov 2026 11:46:56 -0800 Subject: [PATCH] Add the PCG64DXSM and MRG32k3a bit generators to dpnp.random PCG64DXSM is a custom engine bit-identical with NumPy's for raw values, random, uniform and bytes, drawn in a sequential layout for the samplers taking a fixed number of draws. MRG32k3a uses oneMKL's device engine, with work-item w on substream w. sycl::mul_hi is replaced with a limb-based umul_hi, as DPC++ 2026.2 miscompiles it on Intel GPUs. --- CHANGELOG.md | 2 + doc/known_words.txt | 6 + doc/reference/random.rst | 21 +- dpnp/AGENTS.md | 2 +- dpnp/libtensor/extensions/rng/common.hpp | 35 +- .../extensions/rng/kernels/engines.hpp | 236 +++++++++- .../extensions/rng/kernels/sample.hpp | 45 +- .../extensions/rng/kernels/samplers.hpp | 11 +- dpnp/libtensor/extensions/rng/rng_py.cpp | 8 +- dpnp/random/__init__.py | 10 +- dpnp/random/_bit_generator.py | 407 ++++++++++++++++++ dpnp/random/_generator.py | 8 +- dpnp/tests/AGENTS.md | 2 +- dpnp/tests/test_random_generator.py | 346 ++++++++++++++- dpnp/tests/test_sycl_queue.py | 5 +- dpnp/tests/test_usm_type.py | 5 +- .../cupy/random_tests/test_bit_generator.py | 1 - 17 files changed, 1118 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bcaf298232c..6bc0ed02c78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * Added the continuous distributions of `numpy.random.Generator`, such as `gamma`, `beta` and `vonmises`, to `dpnp.random.Generator` [#3158](https://github.com/IntelPython/dpnp/pull/3158) * Added `integers` and the discrete distributions of `numpy.random.Generator`, such as `poisson`, `binomial` and `hypergeometric`, to `dpnp.random.Generator`, along with `noncentral_chisquare` and `noncentral_f` [#3159](https://github.com/IntelPython/dpnp/pull/3159) * Added the multivariate distributions, `permutation`, `shuffle`, `permuted`, `choice` and `bytes` to `dpnp.random.Generator` [#3160](https://github.com/IntelPython/dpnp/pull/3160) +* Added the `dpnp.random.PCG64DXSM` bit generator, which matches `numpy.random.PCG64DXSM` for raw values, `random`, `uniform` and `bytes` [#3161](https://github.com/IntelPython/dpnp/pull/3161) +* Added the `dpnp.random.MRG32k3a` bit generator [#3161](https://github.com/IntelPython/dpnp/pull/3161) ### Changed diff --git a/doc/known_words.txt b/doc/known_words.txt index 4d81cd5b853..d537c6ef46b 100644 --- a/doc/known_words.txt +++ b/doc/known_words.txt @@ -43,9 +43,11 @@ Hanning histogrammed Hypergeometric kwargs +Kelton iaxis iinfo Infs +L'Ecuyer intp ints iterable @@ -89,17 +91,21 @@ prepending reentrant representable resampling +RngStreams runtimes scikit se SeedSequence signbit signum +Simard sinc strided subarray subarrays subclasses +substream +substreams subtype SyclDevice SyclQueue diff --git a/doc/reference/random.rst b/doc/reference/random.rst index 93da08ce480..a937481ded1 100644 --- a/doc/reference/random.rst +++ b/doc/reference/random.rst @@ -5,8 +5,10 @@ Random sampling (:mod:`dpnp.random`) .. hint:: `NumPy API Reference: Random sampling `_ -DPNP draws random numbers on the SYCL device, with the counter-based -Philox4x32x10 generator of the oneMKL device RNG API. It provides the +DPNP draws random numbers on the SYCL device, by default with the +counter-based Philox4x32x10 generator of the oneMKL device RNG API, or with +NumPy's PCG64DXSM generator or the MRG32k3a generator of the oneMKL device +RNG API. It provides the :class:`Generator` API of NumPy, recommended for new code. The legacy :class:`RandomState` API and the module-level functions still use the host-side oneMKL RNG. @@ -28,7 +30,10 @@ A generator draws all its samples on one SYCL queue, chosen with the Differences from NumPy ---------------------- -* The streams differ from NumPy's. For a given seed, output size and data +* The streams differ from NumPy's, except that :class:`PCG64DXSM` gives + NumPy's raw values, :meth:`Generator.random`, :meth:`Generator.uniform` + and :meth:`Generator.bytes`, and can exchange states with + :class:`numpy.random.PCG64DXSM`. For a given seed, output size and data type, the raw bits and the uniform floats are the same on every device. The other distributions use transcendental functions, which may round differently on another device: their samples agree to a few ULP, or @@ -37,7 +42,8 @@ Differences from NumPy precision, as noted below. * The values depend on the output size: a draw is not a prefix of a larger one, and two draws of ``n`` values differ from one draw of - ``2 * n``. Code splitting its draws into chunks gets other values. + ``2 * n``, but for the methods :class:`PCG64DXSM` draws in the order of + its stream. Code splitting its draws into chunks gets other values. * The floating-point distributions take a ``dtype`` keyword, as in CuPy, which defaults to the default floating-point type of the device, so on a device without double precision the same seed gives other values by @@ -66,8 +72,9 @@ Differences from NumPy * ``hypergeometric`` requires ``ngood`` and ``nbad`` below ``10**9``. * :meth:`Generator.shuffle` only accepts a :class:`dpnp.ndarray`, as in CuPy. -* Generators and bit generators cannot be pickled or copied, and the bit - generators have no ``advance`` or ``jumped`` method. +* Generators and bit generators cannot be pickled or copied, and + :class:`Philox4x3210` and :class:`MRG32k3a` have no ``advance`` or + ``jumped`` method. Random Generator ---------------- @@ -168,6 +175,8 @@ Bit generators BitGenerator.spawn BitGenerator.state Philox4x3210 + PCG64DXSM + MRG32k3a SeedSequence Legacy random generation diff --git a/dpnp/AGENTS.md b/dpnp/AGENTS.md index 6009def4f08..3f545cb7ef4 100644 --- a/dpnp/AGENTS.md +++ b/dpnp/AGENTS.md @@ -6,7 +6,7 @@ - `dpnp_algo/dpnp_elementwise_common.py`: ufunc wrapper classes (`DPNPUnaryFunc`, `DPNPBinaryFunc`, two-output and specialised variants) - `dpnp_utils/`: shared helpers (`get_usm_allocations`, `map_dtype_to_device`, reduction wrappers) - `linalg/`, `fft/`, `random/`, `scipy/`, `lib/`: subpackages, each exporting through its own `__init__.py` -- `random/`: `_bit_generator.py` (`BitGenerator`, `Philox4x3210`; `SeedSequence` is NumPy's) and `_generator.py` (`Generator`, which implements every distribution). The kernels are in `dpnp.libtensor._rng_impl`. +- `random/`: `_bit_generator.py` (`BitGenerator`, `Philox4x3210`, `PCG64DXSM`, `MRG32k3a`; `SeedSequence` is NumPy's) and `_generator.py` (`Generator`, which implements every distribution). The kernels are in `dpnp.libtensor._rng_impl`. - Legacy, don't extend: the Cython `dpnp_algo` path and `dpnp_backend_c` (still used by `dpnp.partition` and `dpnp.random`), and the NumPy fallback `call_origin`. New kernels go into `dpnp/libtensor` (see `dpnp/libtensor/AGENTS.md`). ## Adding a Public Function diff --git a/dpnp/libtensor/extensions/rng/common.hpp b/dpnp/libtensor/extensions/rng/common.hpp index abdd80042eb..4c439680748 100644 --- a/dpnp/libtensor/extensions/rng/common.hpp +++ b/dpnp/libtensor/extensions/rng/common.hpp @@ -128,7 +128,7 @@ sycl::event submit_sample(sycl::queue &exec_q, const std::size_t nelems, const std::vector &depends) { - const std::size_t n_wi = rng_ns::num_work_items(nelems); + const std::size_t n_wi = rng_ns::num_work_items(nelems, L); return exec_q.submit([&](sycl::handler &cgh) { cgh.depends_on(depends); @@ -182,7 +182,12 @@ sycl::event sample_impl(sycl::queue &exec_q, T *res = reinterpret_cast(result); - if constexpr (rng_ns::can_draw_blocked) { + if constexpr (rng_ns::can_draw_sequential) { + return detail::submit_sample( + exec_q, state, sampler, res, nelems, depends); + } + else if constexpr (rng_ns::can_draw_blocked) { // the blocked layout gives the same values, faster on GPUs but // slower on CPUs if (exec_q.get_device().is_gpu()) { @@ -242,7 +247,7 @@ void init_sample_dispatch_table(sample_dispatch_table_t &table) { using ext::common::init_dispatch_vector; - static_assert(rng_ns::num_engines == 1, + static_assert(rng_ns::num_engines == 3, "Every engine must have a row in the dispatch table"); constexpr int philox = static_cast(rng_ns::engine_id::philox4x32x10); @@ -250,18 +255,40 @@ void init_sample_dispatch_table(sample_dispatch_table_t &table) SampleFactoryFor::template type>( table[philox]); + + constexpr int pcg = static_cast(rng_ns::engine_id::pcg64dxsm); + init_dispatch_vector::template type>( + table[pcg]); + + constexpr int mrg = static_cast(rng_ns::engine_id::mrg32k3a); + init_dispatch_vector::template type>( + table[mrg]); } template