From e06fd8c3ca9a8d9b2197895dfc256209e022f8c7 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 | 14 +- 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 | 403 ++++++++++++++++++ dpnp/random/_generator.py | 3 +- dpnp/tests/AGENTS.md | 2 +- dpnp/tests/test_random_generator.py | 322 +++++++++++++- dpnp/tests/test_sycl_queue.py | 5 +- dpnp/tests/test_usm_type.py | 5 +- 16 files changed, 1082 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a8c8e024bc0..d7df4c2ab55f 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 b8f0ec87e530..64e2bb159d18 100644 --- a/doc/known_words.txt +++ b/doc/known_words.txt @@ -42,9 +42,11 @@ Hanning histogrammed Hypergeometric kwargs +Kelton iaxis iinfo Infs +L'Ecuyer intp ints iterable @@ -86,15 +88,19 @@ prepended prepending representable resampling +RngStreams runtimes scikit se signbit signum +Simard sinc subarray subarrays subclasses +substream +substreams subtype SyclDevice SyclQueue diff --git a/doc/reference/random.rst b/doc/reference/random.rst index 0a7f4a300f39..810a5ff7f5bd 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. @@ -31,8 +33,10 @@ Differences from NumPy ---------------------- * The same seed gives the same stream on every device, but for the discrete - distributions noted below. - The streams differ from NumPy's. + distributions noted below. 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`. * The floating-point distributions take a ``dtype`` keyword, as in CuPy, which defaults to the default floating-point type of the device. All methods take a ``usm_type`` keyword. @@ -144,6 +148,8 @@ Bit generators BitGenerator Philox4x3210 + PCG64DXSM + MRG32k3a SeedSequence Legacy random generation diff --git a/dpnp/AGENTS.md b/dpnp/AGENTS.md index f87cde807604..8d2a8d977af2 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 abdd80042eb1..4c4396807480 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