Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion onnxruntime/python/tools/symbolic_shape_infer.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,20 @@ def _onnx_infer_single_node(self, node):
# (2) opset version >= 9. In older version, initializer is required in graph input by onnx spec.
# (3) The initializer is not in graph input. The means the node input is "constant" in inference.
initializers = []
if (get_opset(self.out_mp_) >= 9) and node.op_type in ["Unsqueeze"]:
if (get_opset(self.out_mp_) >= 9) and node.op_type in [
"Unsqueeze",
# Since opset 18 the reduction operators take axes as an input rather
# than an attribute. Without the axes value onnx shape inference cannot
# compute the output shape and returns nothing.
"ReduceL1",
"ReduceL2",
"ReduceLogSum",
"ReduceLogSumExp",
"ReduceMax",
"ReduceMin",
"ReduceProd",
"ReduceSumSquare",
]:
initializers = [
self.initializers_[name]
for name in node.input
Expand Down Expand Up @@ -1627,6 +1640,9 @@ def _infer_ReduceSum(self, node): # noqa: N802
shape = self._get_shape(node, 0)
output_shape = []
axes = [handle_negative_axis(a, len(shape)) for a in axes]
if not axes and not get_attribute(node, "noop_with_empty_axes", 0):
# An empty axes input reduces every axis unless noop_with_empty_axes is set.
axes = list(range(len(shape)))
for i, d in enumerate(shape):
if i in axes:
if keep_dims:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,75 @@ def test_qlinear_binary(self):
]
self._check_shapes(graph, inferred.graph, expected_shapes)

def test_reduce_ops_opset18_axes_input(self):
# Since opset 18 axes is an input. Without the initializer the output shape is lost.
for op_type in [
"ReduceL1",
"ReduceL2",
"ReduceLogSum",
"ReduceLogSumExp",
"ReduceMax",
"ReduceMin",
"ReduceProd",
"ReduceSumSquare",
]:
with self.subTest(op_type=op_type):
initializers = [numpy_helper.from_array(numpy.array([1], dtype=numpy.int64), "axes")]
graph = helper.make_graph(
[
helper.make_node(op_type, ["input", "axes"], ["temp"], keepdims=0),
helper.make_node("Identity", ["temp"], ["output"]),
],
f"{op_type}_Opset18_Test",
[
helper.make_tensor_value_info("input", TensorProto.FLOAT, [2, 3, 4]),
],
[
helper.make_tensor_value_info("output", TensorProto.FLOAT, [2, 4]),
],
initializers,
)
model = helper.make_model(graph, producer_name=f"{op_type}_Opset18_Test_Model")
model.opset_import[0].version = 18

inferred = SymbolicShapeInference.infer_shapes(model, auto_merge=True)
expected_shapes = [
helper.make_tensor_value_info("temp", TensorProto.FLOAT, [2, 4]),
helper.make_tensor_value_info("output", TensorProto.FLOAT, [2, 4]),
]
self._check_shapes(graph, inferred.graph, expected_shapes)

def test_reduce_sum_empty_axes(self):
# An empty axes input reduces every axis unless noop_with_empty_axes is set.
for noop, expected in [(0, []), (1, [2, 3, 4])]:
with self.subTest(noop_with_empty_axes=noop):
initializers = [numpy_helper.from_array(numpy.array([], dtype=numpy.int64), "axes")]
graph = helper.make_graph(
[
helper.make_node(
"ReduceSum", ["input", "axes"], ["temp"], keepdims=0, noop_with_empty_axes=noop
),
helper.make_node("Identity", ["temp"], ["output"]),
],
"ReduceSum_EmptyAxes_Test",
[
helper.make_tensor_value_info("input", TensorProto.FLOAT, [2, 3, 4]),
],
[
helper.make_tensor_value_info("output", TensorProto.FLOAT, expected),
],
initializers,
)
model = helper.make_model(graph, producer_name="ReduceSum_EmptyAxes_Test_Model")
model.opset_import[0].version = 18

inferred = SymbolicShapeInference.infer_shapes(model, auto_merge=True)
expected_shapes = [
helper.make_tensor_value_info("temp", TensorProto.FLOAT, expected),
helper.make_tensor_value_info("output", TensorProto.FLOAT, expected),
]
self._check_shapes(graph, inferred.graph, expected_shapes)


class TestSymbolicShapeInferenceForSlice(unittest.TestCase):
def check_slice_of_concat(self, input_dims, start, end, step, expected_output_dim):
Expand Down
Loading