diff --git a/docs/Changelog.md b/docs/Changelog.md
index 198aa0b73db..08c6c94a7dc 100644
--- a/docs/Changelog.md
+++ b/docs/Changelog.md
@@ -33093,6 +33093,87 @@ This version of the operator has been available since version 28 of the default
Constrain input and output types to float tensors.
+### **RandomUniform-28**
+
+ Generate a tensor with random values drawn from a uniform distribution. The shape
+ of the tensor is specified by the `shape` argument and the range by `low` and `high`.
+
+ The data type is specified by the 'dtype' argument. The 'dtype' argument must
+ be one of the data types specified in the 'DataType' enum field in the
+ TensorProto message.
+
+ The `generator` attribute selects the pseudo-random number generator algorithm.
+ With the default value "unspecified", the choice of generator is left to the
+ implementation and no determinism guarantee is given: results may differ across
+ implementations and even across runs of the same implementation, even when
+ `seed` is specified. An implementation may produce reproducible results in this
+ mode (for example for a fixed `seed`), but it is not required to. Setting
+ `generator` to "mersenne_twister" fully specifies the generated values: given
+ the same `seed`, every conforming implementation must produce bit-identical
+ results, which makes the operator deterministic and testable. More algorithms
+ may be added in future opset versions.
+
+ When `generator` is "mersenne_twister", the `seed` attribute must be specified
+ and the output is computed as follows:
+ 1. Initialize a standard 32-bit Mersenne Twister (MT19937) state using the
+ `init_genrand` seeding routine from the reference implementation of Matsumoto
+ and Nishimura (the seeding also used by C++ `std::mt19937`), with the seed
+ value obtained by truncating `seed` toward zero and converting it to an
+ unsigned 32-bit integer (modulo 2^32).
+ 2. For each output element, in row-major order, draw a value `r` in the
+ interval [0, 1) whose resolution matches the precision of `dtype`. Let `p`
+ be the number of significand bits of `dtype`, including the implicit bit
+ (8 for bfloat16, 11 for float16, 24 for float, 53 for double):
+ - If `dtype` is double, draw two consecutive 32-bit outputs `a` and `b` and
+ form `r = (floor(a / 2^5) * 2^26 + floor(b / 2^6)) / 2^53` (the
+ `genrand_res53` method).
+ - Otherwise, draw one 32-bit output `a` and form
+ `r = floor(a / 2^(32-p)) / 2^p`, which is exactly representable in
+ `dtype`.
+ 3. The element value is `low + r * (high - low)`, where `low` and `high` are
+ first converted to `dtype` and the subtraction, multiplication, and
+ addition are performed in `dtype` with IEEE 754 round-to-nearest-even
+ semantics. Note that due to this rounding, the result may equal `high` for
+ low-precision types.
+
+#### Version
+
+This version of the operator has been available since version 28 of the default ONNX operator set.
+
+#### Attributes
+
+
+- dtype : int (default is 1)
+- The data type for the elements of the output tensor. If not specified, default is TensorProto::FLOAT.
+- generator : string (default is unspecified)
+- (Optional) The pseudo-random number generator algorithm. "unspecified" leaves the choice of generator to the implementation and provides no determinism guarantee: results may differ across implementations and even across runs of the same implementation, even when `seed` is specified (an implementation may produce reproducible results, but is not required to). "mersenne_twister" selects the fully specified MT19937 algorithm described in the operator documentation, making the output deterministic for a given `seed`. More algorithms may be added in future opset versions.
+- high : float (default is 1.0)
+- Upper boundary of the output values.
+- low : float (default is 0.0)
+- Lower boundary of the output values.
+- seed : float
+- (Optional) Seed to the random generator, if not specified we will auto generate one. Must be specified when `generator` is "mersenne_twister".
+- shape : list of ints (required)
+- The shape of the output tensor.
+
+
+#### Inputs
+
+
+#### Outputs
+
+
+- output : T
+- Output tensor of random values drawn from uniform distribution
+
+
+#### Type Constraints
+
+
+- T : tensor(bfloat16), tensor(float16), tensor(float), tensor(double)
+- Constrain output types to float tensors.
+
+
# ai.onnx.preview
## Version 1 of the 'ai.onnx.preview' operator set
### **ai.onnx.preview.FlexAttention-1**
diff --git a/docs/Operators.md b/docs/Operators.md
index d7aa4ec05bc..a8654086c5e 100644
--- a/docs/Operators.md
+++ b/docs/Operators.md
@@ -113,7 +113,7 @@ For an operator input/output's differentiability, it can be differentiable,
|RNN|22, 14, 7, 1|
|RandomNormal|22, 1|
|RandomNormalLike|22, 1|
-|RandomUniform|22, 1|
+|RandomUniform|28, 22, 1|
|RandomUniformLike|22, 1|
|Reciprocal|13, 6, 1|
|ReduceMax|20, 18, 13, 12, 11, 1|
@@ -27515,23 +27515,59 @@ Other versions of this operator: 1
be one of the data types specified in the 'DataType' enum field in the
TensorProto message.
+ The `generator` attribute selects the pseudo-random number generator algorithm.
+ With the default value "unspecified", the choice of generator is left to the
+ implementation and no determinism guarantee is given: results may differ across
+ implementations and even across runs of the same implementation, even when
+ `seed` is specified. An implementation may produce reproducible results in this
+ mode (for example for a fixed `seed`), but it is not required to. Setting
+ `generator` to "mersenne_twister" fully specifies the generated values: given
+ the same `seed`, every conforming implementation must produce bit-identical
+ results, which makes the operator deterministic and testable. More algorithms
+ may be added in future opset versions.
+
+ When `generator` is "mersenne_twister", the `seed` attribute must be specified
+ and the output is computed as follows:
+ 1. Initialize a standard 32-bit Mersenne Twister (MT19937) state using the
+ `init_genrand` seeding routine from the reference implementation of Matsumoto
+ and Nishimura (the seeding also used by C++ `std::mt19937`), with the seed
+ value obtained by truncating `seed` toward zero and converting it to an
+ unsigned 32-bit integer (modulo 2^32).
+ 2. For each output element, in row-major order, draw a value `r` in the
+ interval [0, 1) whose resolution matches the precision of `dtype`. Let `p`
+ be the number of significand bits of `dtype`, including the implicit bit
+ (8 for bfloat16, 11 for float16, 24 for float, 53 for double):
+ - If `dtype` is double, draw two consecutive 32-bit outputs `a` and `b` and
+ form `r = (floor(a / 2^5) * 2^26 + floor(b / 2^6)) / 2^53` (the
+ `genrand_res53` method).
+ - Otherwise, draw one 32-bit output `a` and form
+ `r = floor(a / 2^(32-p)) / 2^p`, which is exactly representable in
+ `dtype`.
+ 3. The element value is `low + r * (high - low)`, where `low` and `high` are
+ first converted to `dtype` and the subtraction, multiplication, and
+ addition are performed in `dtype` with IEEE 754 round-to-nearest-even
+ semantics. Note that due to this rounding, the result may equal `high` for
+ low-precision types.
+
#### Version
-This version of the operator has been available since version 22 of the default ONNX operator set.
+This version of the operator has been available since version 28 of the default ONNX operator set.
-Other versions of this operator: 1
+Other versions of this operator: 1, 22
#### Attributes
- dtype : int (default is 1)
- The data type for the elements of the output tensor. If not specified, default is TensorProto::FLOAT.
+- generator : string (default is unspecified)
+- (Optional) The pseudo-random number generator algorithm. "unspecified" leaves the choice of generator to the implementation and provides no determinism guarantee: results may differ across implementations and even across runs of the same implementation, even when `seed` is specified (an implementation may produce reproducible results, but is not required to). "mersenne_twister" selects the fully specified MT19937 algorithm described in the operator documentation, making the output deterministic for a given `seed`. More algorithms may be added in future opset versions.
- high : float (default is 1.0)
- Upper boundary of the output values.
- low : float (default is 0.0)
- Lower boundary of the output values.
- seed : float
-- (Optional) Seed to the random generator, if not specified we will auto generate one.
+- (Optional) Seed to the random generator, if not specified we will auto generate one. Must be specified when `generator` is "mersenne_twister".
- shape : list of ints (required)
- The shape of the output tensor.
@@ -27554,6 +27590,112 @@ Other versions of this operator: 1
+#### Examples
+
+
+randomuniform_mersenne_twister
+
+```python
+node = onnx.helper.make_node(
+ "RandomUniform",
+ inputs=[],
+ outputs=["y"],
+ shape=[3, 4],
+ seed=42.0,
+ generator="mersenne_twister",
+)
+
+y = mersenne_twister_uniform(42, (3, 4), np.float32)
+expect(
+ node,
+ inputs=[],
+ outputs=[y],
+ name="test_randomuniform_mersenne_twister",
+)
+```
+
+
+
+
+
+randomuniform_mersenne_twister_double
+
+```python
+node = onnx.helper.make_node(
+ "RandomUniform",
+ inputs=[],
+ outputs=["y"],
+ dtype=onnx.TensorProto.DOUBLE,
+ shape=[2, 4],
+ seed=123.0,
+ generator="mersenne_twister",
+)
+
+y = mersenne_twister_uniform(123, (2, 4), np.float64)
+expect(
+ node,
+ inputs=[],
+ outputs=[y],
+ name="test_randomuniform_mersenne_twister_double",
+)
+```
+
+
+
+
+
+randomuniform_mersenne_twister_float16
+
+```python
+node = onnx.helper.make_node(
+ "RandomUniform",
+ inputs=[],
+ outputs=["y"],
+ dtype=onnx.TensorProto.FLOAT16,
+ shape=[10],
+ seed=7.0,
+ generator="mersenne_twister",
+)
+
+y = mersenne_twister_uniform(7, (10,), np.float16)
+expect(
+ node,
+ inputs=[],
+ outputs=[y],
+ name="test_randomuniform_mersenne_twister_float16",
+)
+```
+
+
+
+
+
+randomuniform_mersenne_twister_low_high
+
+```python
+node = onnx.helper.make_node(
+ "RandomUniform",
+ inputs=[],
+ outputs=["y"],
+ low=5.0,
+ high=10.0,
+ shape=[2, 3],
+ seed=0.0,
+ generator="mersenne_twister",
+)
+
+y = mersenne_twister_uniform(0, (2, 3), np.float32, low=5.0, high=10.0)
+expect(
+ node,
+ inputs=[],
+ outputs=[y],
+ name="test_randomuniform_mersenne_twister_low_high",
+)
+```
+
+
+
+
### **RandomUniformLike**
Generate a tensor with random values drawn from a uniform distribution.
diff --git a/docs/TestCoverage.md b/docs/TestCoverage.md
index 3073d6dea79..f1b3b8e87df 100644
--- a/docs/TestCoverage.md
+++ b/docs/TestCoverage.md
@@ -6,7 +6,7 @@
* [Overall Test Coverage](#overall-test-coverage)
# Node Test Coverage
## Summary
-Node tests have covered 189/201 (94.03%, 5 generators excluded) common operators.
+Node tests have covered 190/202 (94.06%, 4 generators excluded) common operators.
Node tests have covered 1/1 (100.00%, 0 generators excluded) experimental operators.
@@ -19846,6 +19846,106 @@ expect(
+### RandomUniform
+There are 4 test cases, listed as following:
+
+randomuniform_mersenne_twister
+
+```python
+node = onnx.helper.make_node(
+ "RandomUniform",
+ inputs=[],
+ outputs=["y"],
+ shape=[3, 4],
+ seed=42.0,
+ generator="mersenne_twister",
+)
+
+y = mersenne_twister_uniform(42, (3, 4), np.float32)
+expect(
+ node,
+ inputs=[],
+ outputs=[y],
+ name="test_randomuniform_mersenne_twister",
+)
+```
+
+
+
+randomuniform_mersenne_twister_double
+
+```python
+node = onnx.helper.make_node(
+ "RandomUniform",
+ inputs=[],
+ outputs=["y"],
+ dtype=onnx.TensorProto.DOUBLE,
+ shape=[2, 4],
+ seed=123.0,
+ generator="mersenne_twister",
+)
+
+y = mersenne_twister_uniform(123, (2, 4), np.float64)
+expect(
+ node,
+ inputs=[],
+ outputs=[y],
+ name="test_randomuniform_mersenne_twister_double",
+)
+```
+
+
+
+randomuniform_mersenne_twister_float16
+
+```python
+node = onnx.helper.make_node(
+ "RandomUniform",
+ inputs=[],
+ outputs=["y"],
+ dtype=onnx.TensorProto.FLOAT16,
+ shape=[10],
+ seed=7.0,
+ generator="mersenne_twister",
+)
+
+y = mersenne_twister_uniform(7, (10,), np.float16)
+expect(
+ node,
+ inputs=[],
+ outputs=[y],
+ name="test_randomuniform_mersenne_twister_float16",
+)
+```
+
+
+
+randomuniform_mersenne_twister_low_high
+
+```python
+node = onnx.helper.make_node(
+ "RandomUniform",
+ inputs=[],
+ outputs=["y"],
+ low=5.0,
+ high=10.0,
+ shape=[2, 3],
+ seed=0.0,
+ generator="mersenne_twister",
+)
+
+y = mersenne_twister_uniform(0, (2, 3), np.float32, low=5.0, high=10.0)
+expect(
+ node,
+ inputs=[],
+ outputs=[y],
+ name="test_randomuniform_mersenne_twister_low_high",
+)
+```
+
+
+
+
### Range
There are 4 test cases, listed as following:
@@ -30721,9 +30821,6 @@ expect(node, inputs=[x, y], outputs=[z], name="test_xor_bcast4v4d")
### RandomNormalLike (random generator operator)
-### RandomUniform (random generator operator)
-
-
### RandomUniformLike (random generator operator)
diff --git a/onnx/backend/test/case/node/randomuniform.py b/onnx/backend/test/case/node/randomuniform.py
new file mode 100644
index 00000000000..20fed5bda9f
--- /dev/null
+++ b/onnx/backend/test/case/node/randomuniform.py
@@ -0,0 +1,140 @@
+# Copyright (c) ONNX Project Contributors
+#
+# SPDX-License-Identifier: Apache-2.0
+from __future__ import annotations
+
+import numpy as np
+
+import onnx
+from onnx.backend.test.case.base import Base
+from onnx.backend.test.case.node import expect
+
+
+def mersenne_twister_uniform(seed, shape, dtype, low=0.0, high=1.0):
+ """Independent implementation of RandomUniform with generator="mersenne_twister".
+
+ Follows the operator specification: MT19937 seeded with ``init_genrand``,
+ per-element values in [0, 1) with a resolution matching the precision of
+ `dtype` (two 32-bit outputs via ``genrand_res53`` for double, one 32-bit
+ output otherwise), and ``low + r * (high - low)`` evaluated in `dtype`.
+ Kept separate from onnx.reference so the generated test data cross-checks
+ the reference implementation.
+ """
+ n, m = 624, 397
+ mt = [0] * n
+ mt[0] = int(seed) & 0xFFFFFFFF
+ for i in range(1, n):
+ mt[i] = (1812433253 * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i) & 0xFFFFFFFF
+ index = n
+
+ def next_uint32():
+ nonlocal index
+ if index >= n:
+ for i in range(n):
+ y = (mt[i] & 0x80000000) | (mt[(i + 1) % n] & 0x7FFFFFFF)
+ mt[i] = mt[(i + m) % n] ^ (y >> 1) ^ (0x9908B0DF if y & 1 else 0)
+ index = 0
+ y = mt[index]
+ index += 1
+ y ^= y >> 11
+ y ^= (y << 7) & 0x9D2C5680
+ y ^= (y << 15) & 0xEFC60000
+ y ^= y >> 18
+ return y & 0xFFFFFFFF
+
+ num = int(np.prod(shape))
+ if np.dtype(dtype) == np.float64:
+ r = [
+ ((next_uint32() >> 5) * 67108864.0 + (next_uint32() >> 6))
+ / 9007199254740992.0
+ for _ in range(num)
+ ]
+ else:
+ p = np.finfo(dtype).nmant + 1
+ r = [(next_uint32() >> (32 - p)) / (1 << p) for _ in range(num)]
+ r = np.array(r, dtype=np.float64).reshape(shape).astype(dtype)
+ low = np.asarray(low, dtype=dtype)
+ high = np.asarray(high, dtype=dtype)
+ return r * (high - low) + low
+
+
+class RandomUniform(Base):
+ @staticmethod
+ def export_randomuniform_mersenne_twister() -> None:
+ node = onnx.helper.make_node(
+ "RandomUniform",
+ inputs=[],
+ outputs=["y"],
+ shape=[3, 4],
+ seed=42.0,
+ generator="mersenne_twister",
+ )
+
+ y = mersenne_twister_uniform(42, (3, 4), np.float32)
+ expect(
+ node,
+ inputs=[],
+ outputs=[y],
+ name="test_randomuniform_mersenne_twister",
+ )
+
+ @staticmethod
+ def export_randomuniform_mersenne_twister_low_high() -> None:
+ node = onnx.helper.make_node(
+ "RandomUniform",
+ inputs=[],
+ outputs=["y"],
+ low=5.0,
+ high=10.0,
+ shape=[2, 3],
+ seed=0.0,
+ generator="mersenne_twister",
+ )
+
+ y = mersenne_twister_uniform(0, (2, 3), np.float32, low=5.0, high=10.0)
+ expect(
+ node,
+ inputs=[],
+ outputs=[y],
+ name="test_randomuniform_mersenne_twister_low_high",
+ )
+
+ @staticmethod
+ def export_randomuniform_mersenne_twister_double() -> None:
+ node = onnx.helper.make_node(
+ "RandomUniform",
+ inputs=[],
+ outputs=["y"],
+ dtype=onnx.TensorProto.DOUBLE,
+ shape=[2, 4],
+ seed=123.0,
+ generator="mersenne_twister",
+ )
+
+ y = mersenne_twister_uniform(123, (2, 4), np.float64)
+ expect(
+ node,
+ inputs=[],
+ outputs=[y],
+ name="test_randomuniform_mersenne_twister_double",
+ )
+
+ @staticmethod
+ def export_randomuniform_mersenne_twister_float16() -> None:
+ node = onnx.helper.make_node(
+ "RandomUniform",
+ inputs=[],
+ outputs=["y"],
+ dtype=onnx.TensorProto.FLOAT16,
+ shape=[10],
+ seed=7.0,
+ generator="mersenne_twister",
+ )
+
+ y = mersenne_twister_uniform(7, (10,), np.float16)
+ expect(
+ node,
+ inputs=[],
+ outputs=[y],
+ name="test_randomuniform_mersenne_twister_float16",
+ )
diff --git a/onnx/backend/test/data/node/test_randomuniform_mersenne_twister/model.onnx b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister/model.onnx
new file mode 100644
index 00000000000..345260208df
Binary files /dev/null and b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister/model.onnx differ
diff --git a/onnx/backend/test/data/node/test_randomuniform_mersenne_twister/test_data_set_0/output_0.pb b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister/test_data_set_0/output_0.pb
new file mode 100644
index 00000000000..eb853968023
--- /dev/null
+++ b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister/test_data_set_0/output_0.pb
@@ -0,0 +1 @@
+ByJ0¸Ã¿>=êK?bs?TÖ;>ôc;?Ô™G?®A?,Ë?XÃ>0Dä>½> ¿Ì=
\ No newline at end of file
diff --git a/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_double/model.onnx b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_double/model.onnx
new file mode 100644
index 00000000000..59d43b2ffdf
Binary files /dev/null and b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_double/model.onnx differ
diff --git a/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_double/test_data_set_0/output_0.pb b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_double/test_data_set_0/output_0.pb
new file mode 100644
index 00000000000..907ad3fd924
--- /dev/null
+++ b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_double/test_data_set_0/output_0.pb
@@ -0,0 +1 @@
+ByJ@õÚ¾yIæ?*‚m[PÒ?DÝ
ëw Í?ðhàÞ^¤á?áçöÉãç?0=-Û?[ɤ™kbï?a›Q êå?
\ No newline at end of file
diff --git a/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_float16/model.onnx b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_float16/model.onnx
new file mode 100644
index 00000000000..7f0ad00d4bd
Binary files /dev/null and b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_float16/model.onnx differ
diff --git a/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_float16/test_data_set_0/output_0.pb b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_float16/test_data_set_0/output_0.pb
new file mode 100644
index 00000000000..78f984daf3b
--- /dev/null
+++ b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_float16/test_data_set_0/output_0.pb
@@ -0,0 +1,3 @@
+
+
+ByJà,D3=:57Ó;É9J7Ò;ì4
\ No newline at end of file
diff --git a/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_low_high/model.onnx b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_low_high/model.onnx
new file mode 100644
index 00000000000..9c85669d7cf
Binary files /dev/null and b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_low_high/model.onnx differ
diff --git a/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_low_high/test_data_set_0/output_0.pb b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_low_high/test_data_set_0/output_0.pb
new file mode 100644
index 00000000000..cea18a45c97
Binary files /dev/null and b/onnx/backend/test/data/node/test_randomuniform_mersenne_twister_low_high/test_data_set_0/output_0.pb differ
diff --git a/onnx/defs/doc_strings.cc b/onnx/defs/doc_strings.cc
index abab3015f08..b07b0bb46b6 100644
--- a/onnx/defs/doc_strings.cc
+++ b/onnx/defs/doc_strings.cc
@@ -154,6 +154,49 @@ be one of the data types specified in the 'DataType' enum field in the
TensorProto message.
)DOC";
+const char kDoc_RandomUniform_ver28[] = R"DOC(
+Generate a tensor with random values drawn from a uniform distribution. The shape
+of the tensor is specified by the `shape` argument and the range by `low` and `high`.
+
+The data type is specified by the 'dtype' argument. The 'dtype' argument must
+be one of the data types specified in the 'DataType' enum field in the
+TensorProto message.
+
+The `generator` attribute selects the pseudo-random number generator algorithm.
+With the default value "unspecified", the choice of generator is left to the
+implementation and no determinism guarantee is given: results may differ across
+implementations and even across runs of the same implementation, even when
+`seed` is specified. An implementation may produce reproducible results in this
+mode (for example for a fixed `seed`), but it is not required to. Setting
+`generator` to "mersenne_twister" fully specifies the generated values: given
+the same `seed`, every conforming implementation must produce bit-identical
+results, which makes the operator deterministic and testable. More algorithms
+may be added in future opset versions.
+
+When `generator` is "mersenne_twister", the `seed` attribute must be specified
+and the output is computed as follows:
+1. Initialize a standard 32-bit Mersenne Twister (MT19937) state using the
+ `init_genrand` seeding routine from the reference implementation of Matsumoto
+ and Nishimura (the seeding also used by C++ `std::mt19937`), with the seed
+ value obtained by truncating `seed` toward zero and converting it to an
+ unsigned 32-bit integer (modulo 2^32).
+2. For each output element, in row-major order, draw a value `r` in the
+ interval [0, 1) whose resolution matches the precision of `dtype`. Let `p`
+ be the number of significand bits of `dtype`, including the implicit bit
+ (8 for bfloat16, 11 for float16, 24 for float, 53 for double):
+ - If `dtype` is double, draw two consecutive 32-bit outputs `a` and `b` and
+ form `r = (floor(a / 2^5) * 2^26 + floor(b / 2^6)) / 2^53` (the
+ `genrand_res53` method).
+ - Otherwise, draw one 32-bit output `a` and form
+ `r = floor(a / 2^(32-p)) / 2^p`, which is exactly representable in
+ `dtype`.
+3. The element value is `low + r * (high - low)`, where `low` and `high` are
+ first converted to `dtype` and the subtraction, multiplication, and
+ addition are performed in `dtype` with IEEE 754 round-to-nearest-even
+ semantics. Note that due to this rounding, the result may equal `high` for
+ low-precision types.
+)DOC";
+
const char kDoc_DequantizeLinear_ver24[] = R"DOC(
The linear dequantization operator. It consumes a quantized tensor, a scale, and a zero point to compute the
full-precision tensor. The dequantization formula is `y = (x - x_zero_point) * x_scale`. `x_scale` and `x_zero_point`
@@ -1318,6 +1361,7 @@ const char kDoc_Squeeze_ver24[] = "";
const char kDoc_MaxUnpool_ver11[] = "";
const char kDoc_Size_ver24[] = "";
const char kDoc_RandomUniform_ver1[] = "";
+const char kDoc_RandomUniform_ver28[] = "";
const char kDoc_Range_ver11[] = "";
const char kDoc_Range_ver27[] = "";
const char kDoc_DequantizeLinear_ver24[] = "";
diff --git a/onnx/defs/doc_strings.h b/onnx/defs/doc_strings.h
index 5ebb7875924..ab3d5d7cc87 100644
--- a/onnx/defs/doc_strings.h
+++ b/onnx/defs/doc_strings.h
@@ -54,6 +54,7 @@ extern const char kDoc_PRelu_ver7[];
extern const char kDoc_RandomNormal_ver1[];
extern const char kDoc_RandomNormalLike_ver1[];
extern const char kDoc_RandomUniform_ver1[];
+extern const char kDoc_RandomUniform_ver28[];
extern const char kDoc_Range_ver11[];
extern const char kDoc_Range_ver27[];
extern const char kDoc_RandomUniformLike_ver1[];
diff --git a/onnx/defs/generator/defs.cc b/onnx/defs/generator/defs.cc
index 4272b7b8f8c..657f5a90190 100644
--- a/onnx/defs/generator/defs.cc
+++ b/onnx/defs/generator/defs.cc
@@ -150,16 +150,28 @@ ONNX_OPERATOR_SET_SCHEMA(
ONNX_OPERATOR_SET_SCHEMA(
RandomUniform,
- 22,
+ 28,
OpSchema()
- .SetDoc(kDoc_RandomUniform_ver1)
+ .SetDoc(kDoc_RandomUniform_ver28)
.Attr("low", "Lower boundary of the output values.", AttributeProto::FLOAT, 0.0f)
.Attr("high", "Upper boundary of the output values.", AttributeProto::FLOAT, 1.0f)
.Attr(
"seed",
- "(Optional) Seed to the random generator, if not specified we will auto generate one.",
+ "(Optional) Seed to the random generator, if not specified we will auto generate one. "
+ "Must be specified when `generator` is \"mersenne_twister\".",
AttributeProto::FLOAT,
OPTIONAL_VALUE)
+ .Attr(
+ "generator",
+ "(Optional) The pseudo-random number generator algorithm. \"unspecified\" leaves the choice of "
+ "generator to the implementation and provides no determinism guarantee: results may differ "
+ "across implementations and even across runs of the same implementation, even when `seed` is "
+ "specified (an implementation may produce reproducible results, but is not required to). "
+ "\"mersenne_twister\" selects the fully specified MT19937 algorithm described in the operator "
+ "documentation, making the output deterministic for a given `seed`. More algorithms may be "
+ "added in future opset versions.",
+ AttributeProto::STRING,
+ std::string("unspecified"))
.Attr(
"dtype",
"The data type for the elements of the output tensor. If not specified, default is TensorProto::FLOAT.",
@@ -170,6 +182,17 @@ ONNX_OPERATOR_SET_SCHEMA(
.TypeConstraint("T", OpSchema::all_float_types_ir4(), "Constrain output types to float tensors.")
.SetNodeDeterminism(OpSchema::NodeDeterminism::NonDeterministic)
.TypeAndShapeInferenceFunction([](InferenceContext& ctx) {
+ const auto* generator_attr = ctx.getAttribute("generator");
+ if (generator_attr != nullptr) {
+ const std::string& generator = generator_attr->s();
+ if (generator != "unspecified" && generator != "mersenne_twister") {
+ fail_shape_inference(
+ "Attribute 'generator' must be one of 'unspecified' or 'mersenne_twister', got '", generator, "'.");
+ }
+ if (generator != "unspecified" && ctx.getAttribute("seed") == nullptr) {
+ fail_shape_inference("Attribute 'seed' must be specified when 'generator' is '", generator, "'.");
+ }
+ }
propagateElemTypeFromAttributeToOutput(ctx, "dtype", 0, TensorProto::FLOAT);
propagateShapeFromAttributeToOutput(ctx, "shape", 0);
}));
diff --git a/onnx/defs/generator/old.cc b/onnx/defs/generator/old.cc
index 0d2c356425f..6e50f0d64e2 100644
--- a/onnx/defs/generator/old.cc
+++ b/onnx/defs/generator/old.cc
@@ -229,6 +229,32 @@ ONNX_OPERATOR_SET_SCHEMA(
propagateShapeFromAttributeToOutput(ctx, "shape", 0);
}));
+ONNX_OPERATOR_SET_SCHEMA(
+ RandomUniform,
+ 22,
+ OpSchema()
+ .SetDoc(kDoc_RandomUniform_ver1)
+ .Attr("low", "Lower boundary of the output values.", AttributeProto::FLOAT, 0.0f)
+ .Attr("high", "Upper boundary of the output values.", AttributeProto::FLOAT, 1.0f)
+ .Attr(
+ "seed",
+ "(Optional) Seed to the random generator, if not specified we will auto generate one.",
+ AttributeProto::FLOAT,
+ OPTIONAL_VALUE)
+ .Attr(
+ "dtype",
+ "The data type for the elements of the output tensor. If not specified, default is TensorProto::FLOAT.",
+ AttributeProto::INT,
+ static_cast(TensorProto::FLOAT))
+ .Attr("shape", "The shape of the output tensor.", AttributeProto::INTS)
+ .Output(0, "output", "Output tensor of random values drawn from uniform distribution", "T")
+ .TypeConstraint("T", OpSchema::all_float_types_ir4(), "Constrain output types to float tensors.")
+ .SetNodeDeterminism(OpSchema::NodeDeterminism::NonDeterministic)
+ .TypeAndShapeInferenceFunction([](InferenceContext& ctx) {
+ propagateElemTypeFromAttributeToOutput(ctx, "dtype", 0, TensorProto::FLOAT);
+ propagateShapeFromAttributeToOutput(ctx, "shape", 0);
+ }));
+
ONNX_OPERATOR_SET_SCHEMA(
RandomUniform,
1,
diff --git a/onnx/defs/operator_sets.h b/onnx/defs/operator_sets.h
index de677584826..d429656b408 100644
--- a/onnx/defs/operator_sets.h
+++ b/onnx/defs/operator_sets.h
@@ -1471,12 +1471,14 @@ class OpSet_Onnx_ver27 {
// Forward declarations for ai.onnx version 28
class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 28, Celu);
+class ONNX_OPERATOR_SET_SCHEMA_CLASS_NAME(Onnx, 28, RandomUniform);
// Iterate over schema from ai.onnx version 28
class OpSet_Onnx_ver28 {
public:
static void ForEachSchema(const std::function& fn) {
fn(GetOpSchema());
+ fn(GetOpSchema());
}
};
diff --git a/onnx/reference/ops/_op_common_random.py b/onnx/reference/ops/_op_common_random.py
index c7b011e2929..c565887e1c5 100644
--- a/onnx/reference/ops/_op_common_random.py
+++ b/onnx/reference/ops/_op_common_random.py
@@ -9,6 +9,77 @@
from onnx.reference.op_run import OpRun
+class _MT19937:
+ """Standard 32-bit Mersenne Twister (MT19937).
+
+ Implements the ``init_genrand`` seeding routine and the ``genrand_res53``
+ double generation method from the reference implementation of Matsumoto
+ and Nishimura (mt19937ar.c). The 32-bit output stream matches C++
+ ``std::mt19937`` seeded with the same value. This is the algorithm
+ selected by the ``generator="mersenne_twister"`` attribute of the random
+ operators, which fully specifies their output for a given seed.
+ """
+
+ _N = 624
+ _M = 397
+ _MATRIX_A = 0x9908B0DF
+ _UPPER_MASK = 0x80000000
+ _LOWER_MASK = 0x7FFFFFFF
+
+ def __init__(self, seed: int):
+ mt = [0] * self._N
+ mt[0] = seed & 0xFFFFFFFF
+ for i in range(1, self._N):
+ mt[i] = (1812433253 * (mt[i - 1] ^ (mt[i - 1] >> 30)) + i) & 0xFFFFFFFF
+ self._mt = mt
+ self._index = self._N
+
+ def _twist(self) -> None:
+ mt = self._mt
+ for i in range(self._N):
+ y = (mt[i] & self._UPPER_MASK) | (mt[(i + 1) % self._N] & self._LOWER_MASK)
+ mt[i] = (
+ mt[(i + self._M) % self._N]
+ ^ (y >> 1)
+ ^ (self._MATRIX_A if y & 1 else 0)
+ )
+ self._index = 0
+
+ def next_uint32(self) -> int:
+ if self._index >= self._N:
+ self._twist()
+ y = self._mt[self._index]
+ self._index += 1
+ y ^= y >> 11
+ y ^= (y << 7) & 0x9D2C5680
+ y ^= (y << 15) & 0xEFC60000
+ y ^= y >> 18
+ return y & 0xFFFFFFFF
+
+ def random_res53(self, num: int) -> np.ndarray:
+ """Draw `num` doubles in [0, 1) with 53-bit resolution (genrand_res53)."""
+ res = np.empty(num, dtype=np.float64)
+ for k in range(num):
+ a = self.next_uint32() >> 5
+ b = self.next_uint32() >> 6
+ res[k] = (a * 67108864.0 + b) * (1.0 / 9007199254740992.0)
+ return res
+
+ def random_res(self, num: int, precision: int) -> np.ndarray:
+ """Draw `num` values in [0, 1) with `precision` significand bits.
+
+ Each value uses one 32-bit output: ``(next_uint32() >> (32 - p)) / 2^p``.
+ The results are exactly representable in any binary float type with at
+ least `precision` significand bits.
+ """
+ res = np.empty(num, dtype=np.float64)
+ shift = 32 - precision
+ scale = 1.0 / (1 << precision)
+ for k in range(num):
+ res[k] = (self.next_uint32() >> shift) * scale
+ return res
+
+
class _CommonRandom(OpRun):
def __init__(self, onnx_node, run_params):
OpRun.__init__(self, onnx_node, run_params)
@@ -53,3 +124,33 @@ def _get_state(seed):
else:
state = np.random.RandomState(seed=int(seed))
return state
+
+ @staticmethod
+ def _deterministic_uniform(generator, seed, shape, dtype):
+ """Draw uniform values in [0, 1) with the fully specified generator.
+
+ Unlike the "unspecified" generator, the result is bit-identical across
+ implementations for a given seed (see the operator specification).
+ The resolution of the values matches the precision of `dtype`: double
+ uses the two-word genrand_res53 method, all other float types use one
+ 32-bit output per element, keeping every value exactly representable
+ in `dtype`.
+ """
+ if generator != "mersenne_twister":
+ raise ValueError(
+ f"Unsupported value {generator!r} for attribute 'generator' "
+ f"(expected 'unspecified' or 'mersenne_twister')."
+ )
+ if seed is None or np.isnan(seed):
+ raise ValueError(
+ "Attribute 'seed' must be specified when 'generator' is "
+ "'mersenne_twister'."
+ )
+ state = _MT19937(int(seed) & 0xFFFFFFFF)
+ num = int(np.prod(shape))
+ if np.dtype(dtype) == np.float64:
+ res = state.random_res53(num)
+ else:
+ precision = np.finfo(dtype).nmant + 1
+ res = state.random_res(num, precision)
+ return res.reshape(shape).astype(dtype)
diff --git a/onnx/reference/ops/op_random_uniform.py b/onnx/reference/ops/op_random_uniform.py
index be6a74b3ac2..b5f9c170924 100644
--- a/onnx/reference/ops/op_random_uniform.py
+++ b/onnx/reference/ops/op_random_uniform.py
@@ -3,12 +3,22 @@
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
+import numpy as np
+
from onnx.reference.ops._op_common_random import _CommonRandom
class RandomUniform(_CommonRandom):
- def _run(self, dtype=None, high=None, low=None, seed=None, shape=None):
+ def _run(
+ self, dtype=None, generator=None, high=None, low=None, seed=None, shape=None
+ ):
dtype = self._dtype(dtype=dtype)
+ if generator not in (None, "unspecified"):
+ res = self._deterministic_uniform(generator, seed, shape, dtype)
+ # low + r * (high - low), evaluated in the target data type
+ low_t = np.asarray(low, dtype=dtype)
+ high_t = np.asarray(high, dtype=dtype)
+ return (res * (high_t - low_t) + low_t,)
state = self._get_state(seed)
res = state.rand(*shape).astype(dtype)
res *= high - low
diff --git a/onnx/test/reference_evaluator_test.py b/onnx/test/reference_evaluator_test.py
index c2cefbca4c9..4491dd68fbd 100644
--- a/onnx/test/reference_evaluator_test.py
+++ b/onnx/test/reference_evaluator_test.py
@@ -1477,6 +1477,84 @@ def test_onnxt_runtime_random_uniform(self):
self.assertGreater(got.min(), 0)
self.assertLess(got.max(), 1)
+ def test_onnxt_runtime_random_uniform_mersenne_twister(self):
+ Y = make_tensor_value_info("Y", TensorProto.FLOAT, [None])
+ node1 = make_node(
+ "RandomUniform",
+ [],
+ ["Y"],
+ seed=42.0,
+ shape=[2, 3],
+ generator="mersenne_twister",
+ )
+ graph = make_graph([node1], "g", [], [Y])
+ onnx_model = make_model(graph)
+ check_model(onnx_model)
+ sess = ReferenceEvaluator(onnx_model)
+ got = sess.run(None, {})[0]
+ # For float32, each element uses one 32-bit output of MT19937 seeded
+ # with init_genrand(42): r = (a >> 8) / 2^24, as produced by C++
+ # std::mt19937.
+ expected = np.array(
+ [
+ [0.374540091, 0.796542943, 0.95071429],
+ [0.183434784, 0.731993914, 0.779690981],
+ ],
+ dtype=np.float32,
+ )
+ assert_allclose(got, expected, rtol=0, atol=0)
+ self.assertEqual(got.dtype, np.float32)
+ # A second run must produce bit-identical values.
+ assert_allclose(sess.run(None, {})[0], expected, rtol=0, atol=0)
+
+ def test_onnxt_runtime_random_uniform_mersenne_twister_low_high(self):
+ Y = make_tensor_value_info("Y", TensorProto.DOUBLE, [None])
+ node1 = make_node(
+ "RandomUniform",
+ [],
+ ["Y"],
+ seed=42.0,
+ low=5.0,
+ high=10.0,
+ dtype=TensorProto.DOUBLE,
+ shape=[3],
+ generator="mersenne_twister",
+ )
+ graph = make_graph([node1], "g", [], [Y])
+ onnx_model = make_model(graph)
+ check_model(onnx_model)
+ sess = ReferenceEvaluator(onnx_model)
+ got = sess.run(None, {})[0]
+ expected = 5.0 + np.array(
+ [0.3745401188473625, 0.9507143064099162, 0.7319939418114051],
+ dtype=np.float64,
+ ) * (10.0 - 5.0)
+ assert_allclose(got, expected, rtol=0, atol=0)
+ self.assertEqual(got.dtype, np.float64)
+
+ def test_onnxt_runtime_random_uniform_mersenne_twister_no_seed_raises(self):
+ Y = make_tensor_value_info("Y", TensorProto.FLOAT, [None])
+ node1 = make_node(
+ "RandomUniform", [], ["Y"], shape=[2, 3], generator="mersenne_twister"
+ )
+ graph = make_graph([node1], "g", [], [Y])
+ onnx_model = make_model(graph)
+ sess = ReferenceEvaluator(onnx_model)
+ with self.assertRaises(ValueError):
+ sess.run(None, {})
+
+ def test_mt19937_canonical_test_vector(self):
+ # The 10000th output of MT19937 seeded with init_genrand(5489) is
+ # 4123659995 (Matsumoto & Nishimura; also std::mt19937 in C++11).
+ from onnx.reference.ops._op_common_random import ( # noqa: PLC0415
+ _MT19937,
+ )
+
+ gen = _MT19937(5489)
+ for _ in range(9999):
+ gen.next_uint32()
+ self.assertEqual(gen.next_uint32(), 4123659995)
+
def test_onnxt_runtime_random_uniform_like(self):
X = make_tensor_value_info("X", TensorProto.FLOAT, [None])
Y = make_tensor_value_info("Y", TensorProto.FLOAT, [None])
diff --git a/onnx/test/schema_test.py b/onnx/test/schema_test.py
index f135f56fa9b..62a593476f3 100644
--- a/onnx/test/schema_test.py
+++ b/onnx/test/schema_test.py
@@ -78,6 +78,18 @@ def allowed(schema):
self.assertTrue(celu28.has_function)
self.assertEqual(allowed(defs.get_schema("Celu", 12)), {"tensor(float)"})
+ def test_randomuniform_generator_attribute(self) -> None:
+ schema28 = defs.get_schema("RandomUniform", 28)
+ self.assertIn("generator", schema28.attributes)
+ generator = schema28.attributes["generator"]
+ self.assertEqual(generator.type, defs.OpSchema.AttrType.STRING)
+ self.assertEqual(generator.default_value.s, b"unspecified")
+ self.assertFalse(generator.required)
+ # The operator stays non-deterministic at the schema level: with the
+ # "unspecified" generator the output is still implementation-defined.
+ self.assertTrue(schema28.non_deterministic)
+ self.assertNotIn("generator", defs.get_schema("RandomUniform", 22).attributes)
+
def test_range_supported_types(self) -> None:
"""Test Range operator supports all expected numeric types."""
range_schema = defs.get_schema("Range")
diff --git a/onnx/test/shape_inference_test.py b/onnx/test/shape_inference_test.py
index a2a500d8425..9194c831536 100644
--- a/onnx/test/shape_inference_test.py
+++ b/onnx/test/shape_inference_test.py
@@ -4404,6 +4404,59 @@ def test_random_normal(self) -> None:
graph, [make_tensor_value_info("out", TensorProto.DOUBLE, (3, 4, 5))]
)
+ def test_random_uniform_mersenne_twister(self) -> None:
+ graph = self._make_graph(
+ [],
+ [
+ make_node(
+ "RandomUniform",
+ [],
+ ["out"],
+ dtype=TensorProto.DOUBLE,
+ shape=(3, 4),
+ seed=42.0,
+ generator="mersenne_twister",
+ )
+ ],
+ [],
+ )
+ self._assert_inferred(
+ graph, [make_tensor_value_info("out", TensorProto.DOUBLE, (3, 4))]
+ )
+
+ def test_random_uniform_unknown_generator_fails(self) -> None:
+ graph = self._make_graph(
+ [],
+ [
+ make_node(
+ "RandomUniform",
+ [],
+ ["out"],
+ shape=(3, 4),
+ seed=0.0,
+ generator="xorshift",
+ )
+ ],
+ [],
+ )
+ self.assertRaises(onnx.shape_inference.InferenceError, self._inferred, graph)
+
+ def test_random_uniform_mersenne_twister_without_seed_fails(self) -> None:
+ graph = self._make_graph(
+ [],
+ [
+ make_node(
+ "RandomUniform",
+ [],
+ ["out"],
+ shape=(3, 4),
+ generator="mersenne_twister",
+ )
+ ],
+ [],
+ )
+ self.assertRaises(onnx.shape_inference.InferenceError, self._inferred, graph)
+
def test_random_normal_like(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (2, 3, 4))],
diff --git a/onnx/test/version_converter_test.py b/onnx/test/version_converter_test.py
index 2d667a91aa2..2807c5913b4 100644
--- a/onnx/test/version_converter_test.py
+++ b/onnx/test/version_converter_test.py
@@ -2900,3 +2900,36 @@ def test_celu_float_27_28_and_28_27(self) -> None:
)
def test_celu_28_27_unsupported_type_fails(self, _: str, dtype: int) -> None:
self.assertRaises(RuntimeError, lambda: self._celu_converted(dtype, 28, 27))
+
+ def _randomuniform_converted(self, src: int, dst: int, **attrs) -> ModelProto:
+ node = helper.make_node("RandomUniform", [], ["Y"], shape=[2, 3], **attrs)
+ graph = helper.make_graph(
+ [node],
+ "randomuniform",
+ [],
+ [helper.make_tensor_value_info("Y", TensorProto.FLOAT, [2, 3])],
+ )
+ return self._converted(graph, helper.make_operatorsetid("", src), dst)
+
+ # RandomUniform 27 -> 28: CompatibleAdapter (generator attribute has a default)
+ def test_randomuniform_27_28(self) -> None:
+ converted = self._randomuniform_converted(27, 28, seed=0.0)
+ assert converted.opset_import[0].version == 28
+
+ # RandomUniform 28 -> 27: generator="unspecified" matches the old
+ # implementation-defined behavior, so the attribute is dropped on downgrade
+ def test_randomuniform_28_27_unspecified_generator_removed(self) -> None:
+ converted = self._randomuniform_converted(28, 27, generator="unspecified")
+ assert converted.opset_import[0].version == 27
+ node = next(n for n in converted.graph.node if n.op_type == "RandomUniform")
+ assert not any(a.name == "generator" for a in node.attribute)
+
+ # RandomUniform 28 -> 27: a deterministic generator cannot be expressed in
+ # older opsets and must be rejected
+ def test_randomuniform_28_27_mersenne_twister_fails(self) -> None:
+ self.assertRaises(
+ RuntimeError,
+ lambda: self._randomuniform_converted(
+ 28, 27, generator="mersenne_twister", seed=42.0
+ ),
+ )
diff --git a/onnx/version_converter/adapters/CMakeLists.txt b/onnx/version_converter/adapters/CMakeLists.txt
index d4afa967b36..2be3e2c7035 100644
--- a/onnx/version_converter/adapters/CMakeLists.txt
+++ b/onnx/version_converter/adapters/CMakeLists.txt
@@ -24,6 +24,7 @@ target_sources(onnx PRIVATE
no_previous_version.h
pad_10_11.h
q_dq_21_20.h
+ random_uniform_28_27.h
remove_consumed_inputs.h
reshape_4_5.h
reshape_5_4.h
diff --git a/onnx/version_converter/adapters/random_uniform_28_27.h b/onnx/version_converter/adapters/random_uniform_28_27.h
new file mode 100644
index 00000000000..4957c652fc3
--- /dev/null
+++ b/onnx/version_converter/adapters/random_uniform_28_27.h
@@ -0,0 +1,42 @@
+// Copyright (c) ONNX Project Contributors
+//
+// SPDX-License-Identifier: Apache-2.0
+
+// Adapter for RandomUniform in default domain from version 28 to 27
+
+#pragma once
+
+#include
+#include
+
+#include "onnx/version_converter/adapters/adapter.h"
+
+namespace ONNX_NAMESPACE {
+namespace version_conversion {
+
+class RandomUniform_28_27 final : public Adapter {
+ public:
+ RandomUniform_28_27() : Adapter("RandomUniform", OpSetID(28), OpSetID(27)) {}
+
+ Node* adapt(std::shared_ptr /*graph*/, Node* node) const override {
+ const Symbol generator("generator");
+ if (node->hasAttribute(generator)) {
+ // "unspecified" matches the implementation-defined behavior of
+ // RandomUniform v22, so the attribute can simply be dropped. Any other
+ // generator selects fully specified deterministic output, which older
+ // versions cannot express.
+ ONNX_ASSERTM(
+ node->s(generator) == "unspecified",
+ "Attribute 'generator' of operator '",
+ name(),
+ "' must be 'unspecified' in Opset Version ",
+ static_cast(target_version().version()),
+ ".");
+ node->removeAttribute(generator);
+ }
+ return node;
+ }
+};
+
+} // namespace version_conversion
+} // namespace ONNX_NAMESPACE
diff --git a/onnx/version_converter/convert.h b/onnx/version_converter/convert.h
index 6ad1e7d1d6c..f0ef91996e2 100644
--- a/onnx/version_converter/convert.h
+++ b/onnx/version_converter/convert.h
@@ -38,6 +38,7 @@
#include "onnx/version_converter/adapters/no_previous_version.h"
#include "onnx/version_converter/adapters/pad_10_11.h"
#include "onnx/version_converter/adapters/q_dq_21_20.h"
+#include "onnx/version_converter/adapters/random_uniform_28_27.h"
#include "onnx/version_converter/adapters/range_27_26.h"
#include "onnx/version_converter/adapters/reshape_4_5.h"
#include "onnx/version_converter/adapters/reshape_5_4.h"
@@ -981,12 +982,15 @@ class DefaultVersionConverter : public BaseVersionConverter {
/******** 27 -> 28 ********/
registerAdapter(std::make_unique("Celu", OpSetID(27), OpSetID(28)));
+ registerAdapter(std::make_unique("RandomUniform", OpSetID(27), OpSetID(28)));
/******** 28 -> 27 ********/
// Celu v28 widened T to all_float_types_ir4(); Celu v12 (opset 27) supports only FLOAT.
const std::vector celu_28_unallowed_types = {
TensorProto_DataType_FLOAT16, TensorProto_DataType_BFLOAT16, TensorProto_DataType_DOUBLE};
registerAdapter(std::make_unique("Celu", OpSetID(28), OpSetID(27), celu_28_unallowed_types));
+ // RandomUniform v28 added the generator attribute; only generator="unspecified" can be downgraded.
+ registerAdapter(std::make_unique());
}
ModelProto convert_version(const ModelProto& mp_in, const OpSetID& initial_version, const OpSetID& target_version)