Skip to content

Add RandomUniform-28 with deterministic generator attribute (MT19937) - #2

Open
strimo378 wants to merge 5 commits into
mainfrom
claude/operator-determinism-testing-81n6xm
Open

Add RandomUniform-28 with deterministic generator attribute (MT19937)#2
strimo378 wants to merge 5 commits into
mainfrom
claude/operator-determinism-testing-81n6xm

Conversation

@strimo378

@strimo378 strimo378 commented Jul 4, 2026

Copy link
Copy Markdown

Adds an optional generator attribute to RandomUniform (new opset-28 schema) that makes the operator optionally deterministic — and therefore testable. RandomUniform was previously the only generator op with no backend node tests at all, because its output could not be verified.

  • generator="unspecified" (default value): the PRNG remains implementation-defined; fully backward compatible. This mode gives 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 here, but is not required to.
  • generator="mersenne_twister": fully specified 32-bit MT19937 with init_genrand seeding (identical to C++ std::mt19937). Values are produced in the target data type — double arithmetic is only required when dtype is double:
    • dtype=double: two 32-bit outputs per element via the genrand_res53 method (r = (⌊a/2^5⌋·2^26 + ⌊b/2^6⌋) / 2^53).
    • bfloat16/float16/float: one 32-bit output per element, r = ⌊a / 2^(32-p)⌋ / 2^p with p significand bits (8/11/24), exactly representable in the target type.
    • The element value is low + r * (high - low), with low/high converted to dtype and all arithmetic performed in dtype under IEEE 754 round-to-nearest-even — bit-identical across conforming implementations for a given seed. seed is required in this mode (enforced by shape inference, as is rejection of unknown generator names).

The attribute value set is deliberately extensible: the same pattern is intended to be rolled out to the other non-deterministic operators later (RandomNormal, RandomUniformLike, RandomNormalLike, Bernoulli, Multinomial), and future opset versions can add further generator algorithms. The MT19937 implementation already lives in the shared _CommonRandom base of the reference runtime in preparation for that.

Changes

  • defs.cc: RandomUniform-28 with generator attribute and attribute validation in the inference function; old.cc: v22 schema preserved; operator_sets.h: registered under opset 28; new doc string describing the exact MT19937 algorithm and the (non-)guarantees of both modes
  • convert.h / adapters: 27→28 CompatibleAdapter; 28→27 custom adapter that drops generator="unspecified" and rejects deterministic generators (not expressible in older opsets)
  • Reference implementation: standalone _MT19937 class (init_genrand, genrand_res53, per-precision draws) in _op_common_random.py, wired into RandomUniform; verified against the canonical MT19937 test vector (10000th output for seed 5489 = 4123659995) and the std::mt19937 uint32 stream
  • Node tests: first-ever backend node tests for RandomUniform (4 cases: default range, low/high, double, float16) with exact expected outputs generated by an independent inline MT19937 implementation in the test case, verified bit-exactly by the reference backend runner
  • Unit tests: schema, shape inference (incl. error cases), version converter 27↔28, reference evaluator with hard-coded expected values derived from std::mt19937
  • Regenerated docs/Operators.md, docs/Changelog.md, docs/TestCoverage.md, and backend test data

Motivation and Context

The random-number operators are non-deterministic per spec: even with seed set, results differ across implementations, so conformance tests cannot verify their output. As a consequence, the random operators are effectively invisible to conformance tracking such as the ONNX Backend Scoreboard: there are no node tests whose results a backend could be checked against. With an opt-in, fully specified generator (Mersenne Twister for now, extensible later), the output becomes reproducible and bit-exactly verifiable, so random operators can be covered by the standard backend test suite and their support becomes measurable on the scoreboard — while the default behavior, including its freedom to be non-deterministic, stays unchanged. Computing in the target data type keeps the requirement on implementations minimal: no double-precision arithmetic is needed unless dtype is double.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PVuibCDPDmYP2zoMawf9sp

claude added 5 commits July 4, 2026 13:48
RandomUniform is non-deterministic, which makes its output unverifiable
in backend node tests (the operator had no node tests at all). This adds
an optional string attribute 'generator' (default "default") that can
select a fully specified pseudo-random number generator, making the
operator optionally deterministic and therefore testable.

- "default": implementation-defined generator, previous behavior
- "mersenne_twister": standard 32-bit MT19937 seeded with init_genrand
  (the std::mt19937 seeding) from the mandatory 'seed' attribute,
  doubles drawn with the genrand_res53 method in row-major order,
  scaled to [low, high) in double precision, then cast to dtype

The value list is extensible: more algorithms can be added in future
opset versions.

- defs.cc: RandomUniform-28 schema with generator attribute and
  attribute validation in the inference function
- old.cc: RandomUniform-22 schema preserved
- operator_sets.h: register RandomUniform in OpSet_Onnx_ver28
- convert.h: 27->28 CompatibleAdapter; 28->27 adapter that drops
  generator="default" and rejects deterministic generators
- reference implementation: _MT19937 class (verified against the
  canonical MT19937 test vector and std::mt19937) wired into
  RandomUniform
- node test cases with exact expected outputs plus schema, shape
  inference, version converter and reference evaluator tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVuibCDPDmYP2zoMawf9sp
Signed-off-by: Claude <noreply@anthropic.com>
- docs/Operators.md, docs/Changelog.md via onnx/defs/gen_doc.py
- docs/TestCoverage.md via onnx/backend/test/stat_coverage.py
- backend node test data via cmd_tools.py generate-data -t RandomUniform
- ruff format on the reference implementation files

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVuibCDPDmYP2zoMawf9sp
Signed-off-by: Claude <noreply@anthropic.com>
The previous wording only said results are not reproducible across
implementations. Make explicit that in "default" mode an implementation
may produce reproducible results but is not required to, even for a
fixed seed and even across runs of the same implementation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVuibCDPDmYP2zoMawf9sp
Signed-off-by: Claude <noreply@anthropic.com>
"default" only described that the value is the attribute default, not
its meaning. "unspecified" states the semantics directly: the generator
algorithm is left unspecified and implementation-defined, with no
determinism guarantee. It also stays accurate if a future opset ever
recommends a different generator as the default choice.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVuibCDPDmYP2zoMawf9sp
Signed-off-by: Claude <noreply@anthropic.com>
Double precision is now only used for dtype=double (two 32-bit outputs
via genrand_res53, as before). For bfloat16/float16/float, each element
draws a single 32-bit output and forms r = (a >> (32-p)) / 2^p with p
significand bits, which is exactly representable in the target type.
low + r * (high - low) is evaluated in the target type with IEEE 754
round-to-nearest-even instead of double-then-cast, so implementations
never need double arithmetic unless dtype is double.

The double test data is unchanged; float32/float16 expected values are
regenerated. The node test case now carries its own independent MT19937
implementation to cross-check the reference runtime.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PVuibCDPDmYP2zoMawf9sp
Signed-off-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants