From 12d4645f86352afcae216c7660fe51b70aaeb9a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 00:20:59 +0000 Subject: [PATCH 1/2] Initial plan From 7be260161ed0b54617d0ceb96567daa5c596c5d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Apr 2026 00:34:26 +0000 Subject: [PATCH 2/2] Fix non-deterministic rewriter behavior by using ordered dict instead of set for output_nodes Replace set[NodePattern] with dict[NodePattern, None] to preserve deterministic insertion order from the outputs sequence. Python sets have non-deterministic iteration order due to hash randomization, which caused the multi-output pattern matching to behave differently across runs. Fixes #2234 Agent-Logs-Url: https://github.com/microsoft/onnxscript/sessions/50f46d7a-beee-47c4-9369-3c28417380d1 Co-authored-by: justinchuby <11205048+justinchuby@users.noreply.github.com> --- onnxscript/rewriter/_pattern_ir.py | 8 ++++++-- onnxscript/rewriter/_pattern_ir_test.py | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/onnxscript/rewriter/_pattern_ir.py b/onnxscript/rewriter/_pattern_ir.py index 674d1fc593..03749fa0a4 100644 --- a/onnxscript/rewriter/_pattern_ir.py +++ b/onnxscript/rewriter/_pattern_ir.py @@ -836,7 +836,11 @@ def __init__( # Determine the output nodes of the pattern. These are a minimal set of nodes # whose backward-slices cover the entire pattern. - output_nodes: set[NodePattern] = set() + # Use a dict as an ordered set to preserve deterministic insertion order + # from the outputs sequence. Using a plain set would cause non-deterministic + # ordering due to Python's hash randomization, leading to non-deterministic + # pattern matching behavior. + output_nodes: dict[NodePattern, None] = {} covered: set[NodePattern] = set() choice_values_returned: set[ValuePattern] = set() covered_choice_values: set[ValuePattern] = set() @@ -848,7 +852,7 @@ def __init__( if isinstance(value_pattern, NodeOutputPattern): candidate = value_pattern.producer() if candidate not in covered: - output_nodes.add(candidate) + output_nodes[candidate] = None _add_backward_slice(candidate, covered, covered_choice_values) elif isinstance(value_pattern, (OpIdDispatchOr, BacktrackingOr)): choice_values_returned.add(value_pattern) diff --git a/onnxscript/rewriter/_pattern_ir_test.py b/onnxscript/rewriter/_pattern_ir_test.py index e5f826b191..88afb36632 100644 --- a/onnxscript/rewriter/_pattern_ir_test.py +++ b/onnxscript/rewriter/_pattern_ir_test.py @@ -71,6 +71,31 @@ def node_checker(context, node): self.assertTrue(hasattr(producer, "_check")) self.assertIs(producer._check, node_checker) + def test_graph_pattern_output_nodes_have_deterministic_order(self): + """Test that GraphPattern.output_nodes preserves insertion order from outputs. + + Regression test for https://github.com/microsoft/onnxscript/issues/2234. + When output_nodes was built from a set, Python's hash randomization could + cause non-deterministic ordering, leading to non-deterministic pattern + matching behavior for multi-output patterns. + """ + opset_builder = _pattern_ir.OpsetPatternBuilder("") + x = _pattern_ir.ValuePattern("x") + # Create two distinct node patterns via two separate ops + out_a = opset_builder.Relu(x, _outputs=["a"]) + out_b = opset_builder.Sigmoid(x, _outputs=["b"]) + outputs = [out_a, out_b] + + # Build the graph pattern multiple times and check the order is always the same + for _ in range(50): + graph_pattern = _pattern_ir.GraphPattern(inputs=[x], outputs=outputs, nodes=[]) + node_op_ids = [n._op_identifier for n in graph_pattern.output_nodes] + self.assertEqual( + node_op_ids, + [("", "Relu", ""), ("", "Sigmoid", "")], + "output_nodes order must match the order of outputs", + ) + if __name__ == "__main__": unittest.main()