Files
onnx--onnx/onnx/test/shape_inference_test.py
wehub-resource-sync 5cbd3f29e3
Fuzz / Run fuzz harnesses (${{ github.event_name == 'schedule' && 'nightly' || 'smoke' }}) (push) Has been cancelled
Create Releases / call-mac (push) Has been cancelled
Create Releases / call-linux (push) Has been cancelled
Create Releases / call-sdist (push) Has been cancelled
Create Releases / call-win (push) Has been cancelled
Create Releases / call-pyodide (push) Has been cancelled
Windows_No_Exception_CI / build (x64, 3.10) (push) Has been cancelled
Check URLs / build (push) Has been cancelled
Create Releases / Attest CI build artifacts (push) Has been cancelled
Create Releases / Check for Publish release build to pypi (push) Has been cancelled
Create Releases / Check for Publish preview build to test.pypi-weekly (push) Has been cancelled
Create Releases / Publish preview build to test.pypi-weekly (push) Has been cancelled
Create Releases / Check for Publish release build to test.pypi (rc-candidates) (push) Has been cancelled
Create Releases / Publish release build to test.pypi (push) Has been cancelled
Create Releases / Check for Publish preview build to pypi-weekly (push) Has been cancelled
Create Releases / Publish preview build to pypi-weekly (push) Has been cancelled
Create Releases / Publish release build to pypi (push) Has been cancelled
Create Releases / test source distribution (push) Has been cancelled
clang-tidy / clang-tidy (push) Has been cancelled
Lint / Validate SBOM (push) Has been cancelled
Lint / Enforce style (push) Has been cancelled
CI / Test windows-2022, 3.14, External, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test windows-latest, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, Internal, debug=1, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=1, onnx_ml=1, autogen=1 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=0, onnx_ml=0, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test macos-latest, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, External, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.10, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
CI / Test ubuntu-24.04, 3.14t, Internal, debug=0, unity_build=0, onnx_ml=1, autogen=0 (push) Has been cancelled
Pixi CI / Install and lint (ubuntu-24.04-arm) (push) Has been cancelled
Pixi CI / Install and lint (windows-2022) (push) Has been cancelled
Pixi CI / Xcode generator build (push) Has been cancelled
Pixi CI / Install and test (macos-latest, default) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-24.04-arm, default) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-latest, default) (push) Has been cancelled
Pixi CI / Install and test (windows-2022, default) (push) Has been cancelled
Pixi CI / Install and test (macos-latest, oldies) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-24.04-arm, oldies) (push) Has been cancelled
Pixi CI / Install and test (ubuntu-latest, oldies) (push) Has been cancelled
Pixi CI / Install and test (windows-2022, oldies) (push) Has been cancelled
CodeQL / Analyze (actions) (push) Has been cancelled
CodeQL / Analyze (cpp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Copilot Setup Steps / copilot-setup-steps (push) Has been cancelled
Generate and publish ONNX docs / build (push) Has been cancelled
Generate and publish ONNX docs / deploy (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:41:19 +08:00

13185 lines
471 KiB
Python

# Copyright (c) ONNX Project Contributors
# SPDX-License-Identifier: Apache-2.0
from __future__ import annotations
import contextlib
from pathlib import Path
from typing import TYPE_CHECKING, Any
import numpy as np
import pytest
from google.protobuf import text_format
import onnx.shape_inference
from onnx import (
ONNX_ML,
GraphProto,
ModelProto,
NodeProto,
OperatorSetIdProto,
SparseTensorProto,
TensorProto,
TensorShapeProto,
TypeProto,
ValueInfoProto,
checker,
defs,
helper,
numpy_helper,
)
from onnx.defs import (
AI_ONNX_PREVIEW_DOMAIN,
AI_ONNX_PREVIEW_TRAINING_DOMAIN,
ONNX_DOMAIN,
ONNX_ML_DOMAIN,
OpSchema,
SchemaError,
)
from onnx.helper import (
make_empty_tensor_value_info,
make_graph,
make_model,
make_node,
make_opsetid,
make_tensor,
make_tensor_sequence_value_info,
make_tensor_value_info,
)
from onnx.parser import parse_graph
if TYPE_CHECKING:
from collections.abc import Sequence
def get_available_versions(schema: OpSchema) -> set[int]:
versions: set[int] = set()
for version in range(schema.since_version, 0, -1):
try:
versions.add(
defs.get_schema(schema.name, version, schema.domain).since_version
)
except SchemaError: # noqa: PERF203
break
return versions
ALL_OP_VERSIONS: dict[str, tuple[str, frozenset[int]]] = {
schema.name: (schema.domain, frozenset(get_available_versions(schema)))
for schema in defs.get_all_schemas()
}
def all_versions_for(op_name: str) -> list[int]:
domain, versions_set = ALL_OP_VERSIONS[op_name]
if not versions_set:
raise ValueError(f"No versions available for operator {op_name}")
versions = sorted(versions_set)
return [
version
for version in versions
# FIXME(#5289): Reshape errors in self._make_graph when version <= 5.
# Issue reference: https://github.com/onnx/onnx/issues/5289.
if version > 5 or domain != ONNX_DOMAIN
]
class TestShapeInferenceHelper:
def _make_graph(
self,
seed_values: Sequence[str | tuple[str, TensorProto.DataType, Any]],
nodes: list[NodeProto],
value_info: list[ValueInfoProto],
initializer: Sequence[TensorProto] | None = None,
) -> GraphProto:
if initializer is None:
initializer = []
names_in_initializer = {x.name for x in initializer}
input_value_infos = []
# If the starting values are not also initializers,
# introduce the starting values as the output of reshape,
# so that the sizes are guaranteed to be unknown
for seed_value in seed_values:
if isinstance(seed_value, tuple):
seed_name, proto_type = seed_value[:2]
seed_value_info = make_tensor_value_info(*seed_value)
else:
seed_name, proto_type = seed_value, TensorProto.UNDEFINED
seed_value_info = make_empty_tensor_value_info(seed_value)
if seed_name in names_in_initializer:
input_value_infos.append(seed_value_info)
else:
value_info.append(seed_value_info)
input_value_infos.append(
make_tensor_value_info("SEED_" + seed_name, proto_type, ())
)
input_value_infos.append(
make_tensor_value_info(
"UNKNOWN_SHAPE_" + seed_name, TensorProto.INT64, (None,)
)
)
nodes[:0] = [
make_node(
"Reshape",
["SEED_" + seed_name, "UNKNOWN_SHAPE_" + seed_name],
[seed_name],
)
]
return helper.make_graph(
nodes,
"test",
input_value_infos,
[],
initializer=initializer,
value_info=value_info,
)
def _inferred(
self, graph_or_model: GraphProto | ModelProto, **kwargs: Any
) -> ModelProto:
data_prop = kwargs.pop("data_prop", False)
if isinstance(graph_or_model, GraphProto):
kwargs["producer_name"] = "onnx-test"
orig_model = helper.make_model(graph_or_model, **kwargs)
else:
orig_model = graph_or_model
inferred_model = onnx.shape_inference.infer_shapes(
orig_model, strict_mode=True, data_prop=data_prop
)
checker.check_model(inferred_model)
return inferred_model
def _assert_inferred(
self,
graph_or_model: GraphProto | ModelProto,
inferred_value_infos: list[ValueInfoProto],
**kwargs: Any,
) -> None:
graph = (
graph_or_model
if isinstance(graph_or_model, GraphProto)
else graph_or_model.graph
)
# "inferred_value_infos" specifies the expected delta produced by type/shape inference.
# The types/shapes specified in inferred_value_infos should be inferred by the inference implementation,
# while for names not in inferred_value_infos, the original type/shape in input model should be preserved.
names_in_inferred_value_infos = {x.name for x in inferred_value_infos}
# The types/shapes can be recorded in graph.output and/or graph.value_info.
# For the input model, if a name is specified in both, verify the two records
# agree (symmetric to the check applied to the inferred model below), to avoid
# masking inconsistent test inputs.
expected: dict[str, ValueInfoProto] = {}
for x in [*graph.value_info, *graph.output]:
if x.name in names_in_inferred_value_infos:
continue
if x.name in expected:
self._compare_value_infos(expected[x.name].type, x.type)
else:
expected[x.name] = x
expected.update({x.name: x for x in inferred_value_infos})
inferred_model = self._inferred(graph_or_model, **kwargs)
inferred_graph = inferred_model.graph
# Inferred type info may be recorded either in value_info (intermediate
# values, and outputs that were untyped in the input model) or directly on
# the graph outputs (outputs that were already typed). Merge both by name.
# An untyped graph output is recorded in BOTH value_info and output; when a
# name appears in both, verify that the two records agree.
inferred: dict[str, ValueInfoProto] = {}
for x in [*inferred_graph.value_info, *inferred_graph.output]:
if x.name in inferred:
self._compare_value_infos(inferred[x.name].type, x.type)
else:
inferred[x.name] = x
assert expected.keys() == inferred.keys(), (
f"\nExpected value infos for: {sorted(expected)}"
f"\nInferred value infos for: {sorted(inferred)}\n"
)
for name, expected_vi in expected.items():
self._compare_value_infos(expected_vi.type, inferred[name].type)
def _compare_value_infos(
self, vi_type: TypeProto, inferred_vi_type: TypeProto
) -> None:
if vi_type.HasField("tensor_type"):
assert inferred_vi_type.HasField("tensor_type")
assert vi_type.tensor_type.HasField("elem_type")
assert inferred_vi_type.tensor_type.HasField("elem_type")
assert (
vi_type.tensor_type.elem_type == inferred_vi_type.tensor_type.elem_type
)
assert vi_type.tensor_type.HasField(
"shape"
) == inferred_vi_type.tensor_type.HasField("shape")
if vi_type.tensor_type.HasField("shape"):
assert len(vi_type.tensor_type.shape.dim) == len(
inferred_vi_type.tensor_type.shape.dim
)
for dim_i, dim in enumerate(vi_type.tensor_type.shape.dim):
inferred_dim = inferred_vi_type.tensor_type.shape.dim[dim_i]
# if it is a symbolic shape, make sure the inferred symbol has generated (dim_param)
if dim.dim_param:
assert dim.dim_param == inferred_dim.dim_param, (
f"\n{vi_type}\n{inferred_vi_type}\n"
)
else:
assert dim.dim_value == inferred_dim.dim_value, (
f"\n{vi_type}\n{inferred_vi_type}\n"
)
elif vi_type.HasField("sequence_type"):
assert inferred_vi_type.HasField("sequence_type")
vi = vi_type.sequence_type.elem_type
inferred_vi = inferred_vi_type.sequence_type.elem_type
self._compare_value_infos(vi, inferred_vi)
elif vi_type.HasField("optional_type"):
assert inferred_vi_type.HasField("optional_type")
vi = vi_type.optional_type.elem_type
inferred_vi = inferred_vi_type.optional_type.elem_type
self._compare_value_infos(vi, inferred_vi)
elif vi_type.HasField("map_type"):
assert inferred_vi_type.HasField("map_type")
assert vi_type.map_type.key_type == vi_type.map_type.key_type
self._compare_value_infos(
vi_type.map_type.value_type, inferred_vi_type.map_type.value_type
)
elif vi_type == onnx.TypeProto():
assert inferred_vi_type == onnx.TypeProto()
else:
raise NotImplementedError(
"Unrecognized value info type in _compare_value_infos: ", str(vi_type)
)
def skipIf(self, condition, reason):
if condition:
pytest.skip(reason)
class TestShapeInference(TestShapeInferenceHelper):
def test_empty_graph(self) -> None:
graph = self._make_graph(["y"], [], [])
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def _identity_prop(self, op: str, **kwargs: Any) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (30, 4, 5))],
[make_node(op, "x", "y", **kwargs)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (30, 4, 5))]
)
@pytest.mark.parametrize("version", all_versions_for("Transpose"))
def test_transpose(self, version) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (2, 3, 4))],
[make_node("Transpose", ["X"], ["Y"], perm=[1, 0, 2])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.FLOAT, (3, 2, 4))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Transpose"))
def test_transpose_preexisting(self, version) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (2, 3, 4))],
[make_node("Transpose", ["X"], ["Y"], perm=[1, 0, 2])],
[make_tensor_value_info("Y", TensorProto.FLOAT, None)],
)
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.FLOAT, (3, 2, 4))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Transpose"))
def test_transpose_scalar(self, version) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, ())],
[make_node("Transpose", ["X"], ["Y"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.FLOAT, ())],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Transpose"))
def test_transpose_partial(self, version) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (2, 3, 4))],
[make_node("Transpose", ["X"], ["Y"], perm=[1, 0, 2])],
[make_tensor_value_info("Y", TensorProto.UNDEFINED, (3, "a", "b"))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.FLOAT, (3, 2, 4))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
def test_transpose_preexisting_incorrect_shape(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (2, 3, 4))],
[make_node("Transpose", ["X"], ["Y"], perm=[1, 0, 2])],
[make_tensor_value_info("Y", TensorProto.FLOAT, (5, 5, 5))],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_transpose_preexisting_incorrect_type(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (2, 3, 4))],
[make_node("Transpose", ["X"], ["Y"], perm=[1, 0, 2])],
[make_tensor_value_info("Y", TensorProto.STRING, (3, 2, 4))],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_transpose_incorrect_repeated_perm(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (2, 3, 4))],
[make_node("Transpose", ["X"], ["Y"], perm=[1, 0, 1])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def _make_matmul_test_all_dims_known(
self, version, shape1: Sequence[int], shape2: Sequence[int]
) -> None:
expected_out_shape = np.matmul(
np.arange(np.prod(shape1)).reshape(shape1),
np.arange(np.prod(shape2)).reshape(shape2),
).shape
graph = self._make_graph(
[("x", TensorProto.FLOAT, shape1), ("y", TensorProto.FLOAT, shape2)],
[make_node("MatMul", ["x", "y"], ["z"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("z", TensorProto.FLOAT, expected_out_shape)],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("MatMul"))
def test_matmul_all_dims_known(self, version) -> None:
self._make_matmul_test_all_dims_known(version, (2,), (2,))
self._make_matmul_test_all_dims_known(version, (4, 2), (2, 4))
self._make_matmul_test_all_dims_known(version, (5, 2), (2, 4))
self._make_matmul_test_all_dims_known(version, (5, 2), (2, 1))
self._make_matmul_test_all_dims_known(version, (1, 2), (2, 3))
self._make_matmul_test_all_dims_known(version, (2,), (2, 3))
self._make_matmul_test_all_dims_known(version, (4, 2), (2,))
self._make_matmul_test_all_dims_known(version, (1, 4, 2), (3, 2, 3))
self._make_matmul_test_all_dims_known(version, (3, 4, 2), (3, 2, 3))
self._make_matmul_test_all_dims_known(version, (5, 1, 4, 2), (1, 3, 2, 3))
self._make_matmul_test_all_dims_known(version, (4, 2), (3, 2, 3))
def _make_matmul_test_allow_unknown(
self, version, shape1: Any, shape2: Any, expected_out_shape: Any
) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, shape1), ("y", TensorProto.FLOAT, shape2)],
[make_node("MatMul", ["x", "y"], ["z"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("z", TensorProto.FLOAT, expected_out_shape)],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("MatMul"))
def test_matmul_allow_unknown(self, version) -> None:
self._make_matmul_test_allow_unknown(version, (None,), (None,), ())
self._make_matmul_test_allow_unknown(version, (3,), (None,), ())
self._make_matmul_test_allow_unknown(version, (2,), (2, "a"), ("a",))
self._make_matmul_test_allow_unknown(version, (4, 2), (2, "a"), (4, "a"))
self._make_matmul_test_allow_unknown(version, (4, None), (2, "a"), (4, "a"))
self._make_matmul_test_allow_unknown(version, (4, None), (None, "a"), (4, "a"))
self._make_matmul_test_allow_unknown(
version, (1, 4, 2), ("a", 2, 5), ("a", 4, 5)
)
self._make_matmul_test_allow_unknown(
version, (1, 3, 4, 2), ("a", 2, 5), (1, 3, 4, 5)
)
self._make_matmul_test_allow_unknown(version, (3,), None, None)
self._make_matmul_test_allow_unknown(version, None, None, None)
@pytest.mark.parametrize("version", all_versions_for("Cast"))
def test_cast(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4, 3))],
[make_node("Cast", ["x"], ["y"], to=TensorProto.UINT8)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.UINT8, (2, 4, 3))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.xfail(reason="Issue #5960")
def test_cast_to_complex(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4, 3))],
[make_node("Cast", ["x"], ["y"], to=TensorProto.COMPLEX128)],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
@pytest.mark.parametrize("version", all_versions_for("CastLike"))
def test_cast_like(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4, 3)), ("t", TensorProto.FLOAT16, ("N",))],
[make_node("CastLike", ["x", "t"], ["y"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT16, (2, 4, 3))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("BitCast"))
def test_bitcast_same_size(self, version) -> None:
# Test bitcast between types of same size (float32 -> int32)
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4, 3))],
[make_node("BitCast", ["x"], ["y"], to=TensorProto.INT32)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 4, 3))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("BitCast"))
def test_bitcast_scalar(self, version) -> None:
# Test bitcast with scalar input (same size)
graph = self._make_graph(
[("x", TensorProto.FLOAT, ())],
[make_node("BitCast", ["x"], ["y"], to=TensorProto.INT32)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, ())],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("BitCast"))
def test_bitcast_1d(self, version) -> None:
# Test bitcast with 1D tensor (float32 -> uint32, same size)
graph = self._make_graph(
[("x", TensorProto.FLOAT, (8,))],
[make_node("BitCast", ["x"], ["y"], to=TensorProto.UINT32)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.UINT32, (8,))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("BitCast"))
def test_bitcast_double_to_int64(self, version) -> None:
# Test bitcast between 64-bit types (double -> int64)
graph = self._make_graph(
[("x", TensorProto.DOUBLE, (3, 5))],
[make_node("BitCast", ["x"], ["y"], to=TensorProto.INT64)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT64, (3, 5))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("BitCast"))
def test_bitcast_int8_to_uint8(self, version) -> None:
# Test bitcast between 8-bit types (int8 -> uint8)
graph = self._make_graph(
[("x", TensorProto.INT8, (4, 6))],
[make_node("BitCast", ["x"], ["y"], to=TensorProto.UINT8)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.UINT8, (4, 6))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("BitCast"))
def test_bitcast_float16_to_int16(self, version) -> None:
# Test bitcast between 16-bit types (float16 -> int16)
graph = self._make_graph(
[("x", TensorProto.FLOAT16, (2, 3))],
[make_node("BitCast", ["x"], ["y"], to=TensorProto.INT16)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT16, (2, 3))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Col2Im"))
def test_col2im(self, version) -> None:
graph = self._make_graph(
[
("input", TensorProto.FLOAT, (1, 5, 5)),
("output_shape", TensorProto.INT64, (2,)),
("kernel_shape", TensorProto.INT64, (2,)),
],
[
make_node(
"Col2Im", ["input", "output_shape", "kernel_shape"], ["output"]
)
],
[],
initializer=[
make_tensor("output_shape", TensorProto.INT64, (2,), (5, 5)),
make_tensor("kernel_shape", TensorProto.INT64, (2,), (1, 5)),
],
)
self._assert_inferred(
graph,
[make_tensor_value_info("output", TensorProto.FLOAT, (1, 1, 5, 5))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Col2Im"))
def test_col2im_strides(self, version) -> None:
graph = self._make_graph(
[
("input", TensorProto.FLOAT, (1, 9, 4)),
("output_shape", TensorProto.INT64, (2,)),
("kernel_shape", TensorProto.INT64, (2,)),
],
[
make_node(
"Col2Im",
["input", "output_shape", "kernel_shape"],
["output"],
strides=[2, 2],
)
],
[],
initializer=[
make_tensor("output_shape", TensorProto.INT64, (2,), (5, 5)),
make_tensor("kernel_shape", TensorProto.INT64, (2,), (3, 3)),
],
)
self._assert_inferred(
graph,
[make_tensor_value_info("output", TensorProto.FLOAT, (1, 1, 5, 5))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Col2Im"))
def test_col2im_pads(self, version) -> None:
graph = self._make_graph(
[
("input", TensorProto.FLOAT, (1, 5, 15)),
("output_shape", TensorProto.INT64, (2,)),
("kernel_shape", TensorProto.INT64, (2,)),
],
[
make_node(
"Col2Im",
["input", "output_shape", "kernel_shape"],
["output"],
pads=[0, 1, 0, 1],
)
],
[],
initializer=[
make_tensor("output_shape", TensorProto.INT64, (2,), (5, 5)),
make_tensor("kernel_shape", TensorProto.INT64, (2,), (1, 5)),
],
)
self._assert_inferred(
graph,
[make_tensor_value_info("output", TensorProto.FLOAT, (1, 1, 5, 5))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Col2Im"))
def test_col2im_dilations(self, version) -> None:
graph = self._make_graph(
[
("input", TensorProto.FLOAT, (1, 4, 5)),
("output_shape", TensorProto.INT64, (2,)),
("kernel_shape", TensorProto.INT64, (2,)),
],
[
make_node(
"Col2Im",
["input", "output_shape", "kernel_shape"],
["output"],
dilations=[1, 5],
)
],
[],
initializer=[
make_tensor("output_shape", TensorProto.INT64, (2,), (6, 6)),
make_tensor("kernel_shape", TensorProto.INT64, (2,), (2, 2)),
],
)
self._assert_inferred(
graph,
[make_tensor_value_info("output", TensorProto.FLOAT, (1, 1, 6, 6))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Col2Im"))
def test_col2im_5d(self, version) -> None:
graph = self._make_graph(
[
("input", TensorProto.FLOAT, (1, 10, 12)),
("output_shape", TensorProto.INT64, (3,)),
("kernel_shape", TensorProto.INT64, (3,)),
],
[
make_node(
"Col2Im", ["input", "output_shape", "kernel_shape"], ["output"]
)
],
[],
initializer=[
make_tensor("output_shape", TensorProto.INT64, (3,), (3, 4, 5)),
make_tensor("kernel_shape", TensorProto.INT64, (3,), (1, 1, 5)),
],
)
self._assert_inferred(
graph,
[make_tensor_value_info("output", TensorProto.FLOAT, (1, 2, 3, 4, 5))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Concat"))
def test_concat(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4, 3)), ("y", TensorProto.FLOAT, (7, 4, 3))],
[make_node("Concat", ["x", "y"], ["z"], axis=0)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("z", TensorProto.FLOAT, (9, 4, 3))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
def test_concat_missing_shape(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (2, 4, 3)),
"y",
("z", TensorProto.FLOAT, (None, None, None)),
],
[make_node("Concat", ["x", "y", "z"], ["out"], axis=0)],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
@pytest.mark.parametrize("version", all_versions_for("Concat"))
def test_concat_3d_axis_2(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 2, 2)), ("y", TensorProto.FLOAT, (2, 2, 2))],
[make_node("Concat", ["x", "y"], ["z"], axis=2)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("z", TensorProto.FLOAT, (2, 2, 4))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Concat"))
def test_concat_param(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, ("a", 2)), ("y", TensorProto.FLOAT, ("a", 3))],
[make_node("Concat", ["x", "y"], ["z"], axis=1)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("z", TensorProto.FLOAT, ("a", 5))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Concat"))
def test_concat_param_single_input(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, ("a", 2))],
[make_node("Concat", ["x"], ["z"], axis=0)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("z", TensorProto.FLOAT, ("a", 2))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Reshape"))
def test_reshape_dynamic_shape_known_rank(self, version) -> None:
self.skipIf(version < 14, "Rank inference is added from Version 14")
graph = self._make_graph(
[("x", TensorProto.UINT8, (2, 4, 3)), ("shape", TensorProto.INT64, (2,))],
[make_node("Reshape", ["x", "shape"], ["y"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.UINT8, (None, None))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Reshape"))
def test_reshape_dynamic_shape_symbolic(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.UINT8, (2, 4, 3)), ("shape", TensorProto.INT64, ("M",))],
[make_node("Reshape", ["x", "shape"], ["y"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.UINT8, None)],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Reshape"))
def test_reshape_dynamic_unknown_shape(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.UINT8, (2, 4, 3)), ("shape", TensorProto.INT64, None)],
[make_node("Reshape", ["x", "shape"], ["y"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.UINT8, None)],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Reshape"))
def test_reshape_static_shape(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.UINT8, (2, 4, 3)), ("shape", TensorProto.INT64, (2,))],
[make_node("Reshape", ["x", "shape"], ["y"])],
[],
initializer=[make_tensor("shape", TensorProto.INT64, (2,), (3, 8))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.UINT8, (3, 8))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Reshape"))
def test_reshape_static_shape_inferred(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.UINT8, (2, 4, 3)), ("shape", TensorProto.INT64, (3,))],
[make_node("Reshape", ["x", "shape"], ["y"])],
[],
initializer=[make_tensor("shape", TensorProto.INT64, (3,), (0, 3, -1))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.UINT8, (2, 3, 4))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Reshape"))
def test_reshape_static_shape_zero(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.UINT8, (1, 1, 1)), ("shape", TensorProto.INT64, (3,))],
[make_node("Reshape", ["x", "shape"], ["y"])],
[],
initializer=[make_tensor("shape", TensorProto.INT64, (3,), (0, 1, 1))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.UINT8, (1, 1, 1))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Reshape"))
def test_reshape_static_shape_allowzero(self, version) -> None:
self.skipIf(version < 14, "allowzero is added from Version 14")
graph = self._make_graph(
[
("x", TensorProto.UINT8, (1, 0, 0)),
("shape", TensorProto.INT64, (3,)),
],
[make_node("Reshape", ["x", "shape"], ["y"], allowzero=1)],
[],
initializer=[make_tensor("shape", TensorProto.INT64, (3,), (0, 1, 1))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.UINT8, (0, 1, 1))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Reshape"))
def test_reshape_static_shape_constant(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.UINT8, (2, 4, 3))],
[
make_node(
"Constant",
[],
["shape"],
value=make_tensor("shape", TensorProto.INT64, (2,), (3, 8)),
),
make_node("Reshape", ["x", "shape"], ["y"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("shape", TensorProto.INT64, (2,)),
make_tensor_value_info("y", TensorProto.UINT8, (3, 8)),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Upsample"))
def test_upsample(self, version) -> None:
if version == 7:
graph = self._make_graph(
[("x", TensorProto.INT32, (2, 4, 3, 5))],
[make_node("Upsample", ["x"], ["y"], scales=[1.0, 1.1, 1.3, 1.9])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 4, 3, 9))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
else:
graph = self._make_graph(
[
("x", TensorProto.INT32, (2, 4, 3, 5)),
("scales", TensorProto.FLOAT, (4,)),
],
[make_node("Upsample", ["x", "scales"], ["y"])],
[],
initializer=[
make_tensor("scales", TensorProto.FLOAT, (4,), (1.0, 1.1, 1.3, 1.9))
],
)
def call_inference():
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 4, 3, 9))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
if version == 9:
call_inference()
else:
# Upsample is deprecated since Version 10.
with pytest.raises(
onnx.checker.ValidationError, match="Upsample is deprecated"
):
call_inference()
@pytest.mark.parametrize("version", all_versions_for("Upsample"))
def test_upsample_raw_data(self, version) -> None:
if version == 7:
graph = self._make_graph(
[("x", TensorProto.INT32, (1, 3, 4, 5))],
[make_node("Upsample", ["x"], ["y"], scales=[2.0, 1.1, 2.3, 1.9])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 3, 9, 9))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
else:
graph = self._make_graph(
[
("x", TensorProto.INT32, (2, 4, 3, 5)),
("scales", TensorProto.FLOAT, (4,)),
],
[make_node("Upsample", ["x", "scales"], ["y"])],
[],
initializer=[
make_tensor(
"scales",
TensorProto.FLOAT,
(4,),
vals=np.array([1.0, 1.1, 1.3, 1.9], dtype="<f4").tobytes(),
raw=True,
)
],
) # Feed raw bytes (force little endian ordering like onnx standard) for test purpose
def call_inference():
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 4, 3, 9))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
if version == 9:
call_inference()
else:
# Upsample is deprecated since Version 10.
with pytest.raises(
onnx.checker.ValidationError, match="Upsample is deprecated"
):
call_inference()
@pytest.mark.parametrize("version", all_versions_for("Expand"))
def test_expand(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.INT32, (3, 1)), ("shape", TensorProto.INT64, (3,))],
[make_node("Expand", ["x", "shape"], ["y"])],
[],
initializer=[make_tensor("shape", TensorProto.INT64, (3,), (2, 1, 6))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 3, 6))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Expand"))
def test_expand_scalar_input(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.INT32, ()), ("shape", TensorProto.INT64, (2,))],
[make_node("Expand", ["x", "shape"], ["y"])],
[],
initializer=[make_tensor("shape", TensorProto.INT64, (2,), (4, 8))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (4, 8))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Expand"))
def test_expand_raw_data(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.INT32, (3, 1)), ("shape", TensorProto.INT64, (2,))],
[make_node("Expand", ["x", "shape"], ["y"])],
[],
initializer=[
make_tensor(
"shape",
TensorProto.INT64,
(2,),
vals=np.array([3, 4], dtype="<i8").tobytes(),
raw=True,
)
],
) # Feed raw bytes (force little endian ordering like onnx standard) for test purpose
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (3, 4))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Expand"))
def test_expand_dynamic_shape(self, version) -> None:
graph = self._make_graph(
[
("x", TensorProto.INT32, (1, 2, None)),
("shape", TensorProto.INT64, (3,)),
],
[make_node("Expand", ["x", "shape"], ["y"])],
[],
initializer=[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (None, 2, None))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Expand"))
def test_expand_symbolic_shape(self, version) -> None:
graph = self._make_graph(
[
("x", TensorProto.INT32, (1, 2, None)),
("shape", TensorProto.INT64, ("unk__0",)),
],
[make_node("Expand", ["x", "shape"], ["y"])],
[],
initializer=[],
)
# if giving a symbolic shape, Expand should not infer any shape or rank inference
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, None)],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_size(self, version) -> None:
if version == 10:
graph = self._make_graph(
[
("x", TensorProto.INT32, (2, 4, 3, 5)),
("scales", TensorProto.FLOAT, (4,)),
],
[make_node("Resize", ["x", "scales"], ["y"])],
[],
initializer=[
make_tensor("scales", TensorProto.FLOAT, (4,), (1.0, 1.1, 1.3, 1.9))
],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 4, 3, 9))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
elif version == 11:
graph = self._make_graph(
[
("x", TensorProto.INT32, (2, 4, 3, 5)),
("roi", TensorProto.FLOAT, (8,)),
("scales", TensorProto.FLOAT, (4,)),
("sizes", TensorProto.INT64, (4,)),
],
[make_node("Resize", ["x", "roi", "scales", "sizes"], ["y"])],
[],
initializer=[
make_tensor("sizes", TensorProto.INT64, (4,), (3, 5, 6, 7))
],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (3, 5, 6, 7))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
else:
graph = self._make_graph(
[
("x", TensorProto.INT32, (2, 4, 3, 5)),
("roi", TensorProto.FLOAT, (8,)),
("sizes", TensorProto.INT64, (4,)),
],
[make_node("Resize", ["x", "roi", "", "sizes"], ["y"])],
[],
initializer=[
make_tensor("sizes", TensorProto.INT64, (4,), (3, 5, 6, 7))
],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (3, 5, 6, 7))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("RMSNormalization"))
def test_rms_normalization(self, version) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, ("N", "C", "H", "W")),
("scale", TensorProto.FLOAT, ("H", "W")),
],
[make_node("RMSNormalization", ["X", "scale"], ["y"], axis=2)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, ("N", "C", "H", "W"))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_size_axes_2_3(self, version) -> None:
self.skipIf(version < 18, "axes is from Version 18")
graph = self._make_graph(
[
("x", TensorProto.INT32, (2, 4, 3, 5)),
("roi", TensorProto.FLOAT, (4,)),
("sizes", TensorProto.INT64, (2,)),
],
[make_node("Resize", ["x", "roi", "", "sizes"], ["y"], axes=(2, 3))],
[],
initializer=[make_tensor("sizes", TensorProto.INT64, (2,), (6, 7))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 4, 6, 7))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_size_axes_3_2(self, version) -> None:
self.skipIf(version < 18, "axes is from Version 18")
graph = self._make_graph(
[
("x", TensorProto.INT32, (2, 4, 3, 5)),
("roi", TensorProto.FLOAT, (4,)),
("sizes", TensorProto.INT64, (2,)),
],
[make_node("Resize", ["x", "roi", "", "sizes"], ["y"], axes=(3, 2))],
[],
initializer=[make_tensor("sizes", TensorProto.INT64, (2,), (6, 7))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 4, 7, 6))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_size_not_larger(self, version) -> None:
self.skipIf(
version < 18,
"keep_aspect_ratio_policy is from Version 18",
)
graph = self._make_graph(
[
("x", TensorProto.INT32, (3, 5)),
("roi", TensorProto.FLOAT, (4,)),
("sizes", TensorProto.INT64, (2,)),
],
[
make_node(
"Resize",
["x", "roi", "", "sizes"],
["y"],
keep_aspect_ratio_policy="not_larger",
)
],
[],
initializer=[make_tensor("sizes", TensorProto.INT64, (2,), (6, 6))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (4, 6))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_size_axes_2_3_not_larger(self, version) -> None:
self.skipIf(
version < 18,
"axes & keep_aspect_ratio_policy are from Version 18",
)
graph = self._make_graph(
[
("x", TensorProto.INT32, (2, 4, 3, 5)),
("roi", TensorProto.FLOAT, (4,)),
("sizes", TensorProto.INT64, (2,)),
],
[
make_node(
"Resize",
["x", "roi", "", "sizes"],
["y"],
axes=(2, 3),
keep_aspect_ratio_policy="not_larger",
)
],
[],
initializer=[make_tensor("sizes", TensorProto.INT64, (2,), (6, 6))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 4, 4, 6))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_size_not_smaller(self, version) -> None:
self.skipIf(
version < 18,
"keep_aspect_ratio_policy is from Version 18",
)
graph = self._make_graph(
[
("x", TensorProto.INT32, (3, 5)),
("roi", TensorProto.FLOAT, (4,)),
("sizes", TensorProto.INT64, (2,)),
],
[
make_node(
"Resize",
["x", "roi", "", "sizes"],
["y"],
keep_aspect_ratio_policy="not_smaller",
)
],
[],
initializer=[make_tensor("sizes", TensorProto.INT64, (2,), (6, 6))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (6, 10))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_size_axes_2_3_not_smaller(self, version) -> None:
self.skipIf(
version < 18,
"axes & keep_aspect_ratio_policy are from Version 18",
)
graph = self._make_graph(
[
("x", TensorProto.INT32, (2, 4, 3, 5)),
("roi", TensorProto.FLOAT, (4,)),
("sizes", TensorProto.INT64, (2,)),
],
[
make_node(
"Resize",
["x", "roi", "", "sizes"],
["y"],
axes=(2, 3),
keep_aspect_ratio_policy="not_smaller",
)
],
[],
initializer=[make_tensor("sizes", TensorProto.INT64, (2,), (6, 6))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 4, 6, 10))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
def _check_keep_aspect_ratio_precision(
self,
version: int,
policy: str,
dims: tuple[int, ...],
sizes: tuple[int, ...],
expected: tuple[int, ...],
) -> None:
# Shared body for KeepAspectRatioHelper precision regression tests.
# Builds a Resize graph using the sizes-input path with the given
# keep_aspect_ratio_policy and asserts the inferred output shape.
graph = self._make_graph(
[
("x", TensorProto.INT32, dims),
("roi", TensorProto.FLOAT, (2 * len(dims),)),
("sizes", TensorProto.INT64, (len(sizes),)),
],
[
make_node(
"Resize",
["x", "roi", "", "sizes"],
["y"],
keep_aspect_ratio_policy=policy,
)
],
[],
initializer=[make_tensor("sizes", TensorProto.INT64, (len(sizes),), sizes)],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, expected)],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_keep_aspect_ratio_precision_not_larger(self, version) -> None:
# Regression: KeepAspectRatioHelper computed `sizes_data[i] /
# static_cast<float>(dim_value)` and `roundf(scale * dim_value)`.
# With float32, 2**24 + 1 = 16_777_217 is unrepresentable, so a 1:1
# request collapses the inferred dim to 16_777_216.
self.skipIf(version < 18, "keep_aspect_ratio_policy is from Version 18")
self._check_keep_aspect_ratio_precision(
version,
"not_larger",
dims=(16777217, 16777217),
sizes=(16777217, 16777217),
expected=(16777217, 16777217),
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_keep_aspect_ratio_precision_not_smaller(self, version) -> None:
# Same float32 mantissa boundary, NOT_SMALLER policy path.
self.skipIf(version < 18, "keep_aspect_ratio_policy is from Version 18")
self._check_keep_aspect_ratio_precision(
version,
"not_smaller",
dims=(16777217, 16777217),
sizes=(16777217, 16777217),
expected=(16777217, 16777217),
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_keep_aspect_ratio_precision_non_unit_scale(self, version) -> None:
# Non-unit scale path: dim 16_777_217 scaled by 2 should produce
# 33_554_434. With float32 the result collapses to 33_554_432.
self.skipIf(version < 18, "keep_aspect_ratio_policy is from Version 18")
self._check_keep_aspect_ratio_precision(
version,
"not_larger",
dims=(16777217,),
sizes=(33554434,),
expected=(33554434,),
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_scale(self, version) -> None:
self.skipIf(version < 11, "roi input is from Version 11")
graph = self._make_graph(
[
("x", TensorProto.INT32, (2, 4, 3, 5)),
("roi", TensorProto.FLOAT, (8,)),
("scales", TensorProto.FLOAT, (4,)),
],
[make_node("Resize", ["x", "roi", "scales"], ["y"])],
[],
initializer=[
make_tensor("scales", TensorProto.FLOAT, (4,), (1.0, 1.1, 1.3, 1.9))
],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 4, 3, 9))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_scale_axes_2_3(self, version) -> None:
self.skipIf(version < 18, "axes is from Version 18")
graph = self._make_graph(
[
("x", TensorProto.INT32, (2, 4, 3, 5)),
("roi", TensorProto.FLOAT, (8,)),
("scales", TensorProto.FLOAT, (2,)),
],
[make_node("Resize", ["x", "roi", "scales"], ["y"], axes=(2, 3))],
[],
initializer=[make_tensor("scales", TensorProto.FLOAT, (2,), (1.3, 1.9))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 4, 3, 9))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_scale_axes_3_2(self, version) -> None:
self.skipIf(version < 18, "axes is from Version 18")
graph = self._make_graph(
[
("x", TensorProto.INT32, (2, 4, 3, 5)),
("roi", TensorProto.FLOAT, (8,)),
("scales", TensorProto.FLOAT, (2,)),
],
[make_node("Resize", ["x", "roi", "scales"], ["y"], axes=(3, 2))],
[],
initializer=[make_tensor("scales", TensorProto.FLOAT, (2,), (1.9, 1.3))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 4, 3, 9))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_scale_raw_data(self, version) -> None:
self.skipIf(version < 11, "roi input is from Version 11")
graph = self._make_graph(
[
("x", TensorProto.INT32, (1, 3, 4, 5)),
("roi", TensorProto.FLOAT, (8,)),
("scales", TensorProto.FLOAT, (4,)),
],
[make_node("Resize", ["x", "roi", "scales"], ["y"])],
[],
initializer=[
make_tensor(
"scales",
TensorProto.FLOAT,
(4,),
vals=np.array([2.0, 1.1, 2.3, 1.9], dtype="<f4").tobytes(),
raw=True,
)
],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 3, 9, 9))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
def _check_resize_scale_precision(
self,
version: int,
scales: tuple[float, ...],
expected_last_dim: int,
) -> None:
# Shared body for #4919 regression tests. Builds a Resize graph whose
# last input dim is 16_777_217 (= 2**24 + 1, the smallest int that
# float32 cannot represent) and asserts the inferred output last dim.
if version >= 11:
inputs = [
("x", TensorProto.INT32, (1, 1, 1, 16777217)),
("roi", TensorProto.FLOAT, (8,)),
("scales", TensorProto.FLOAT, (4,)),
]
node = make_node("Resize", ["x", "roi", "scales"], ["y"])
else:
inputs = [
("x", TensorProto.INT32, (1, 1, 1, 16777217)),
("scales", TensorProto.FLOAT, (4,)),
]
node = make_node("Resize", ["x", "scales"], ["y"])
graph = self._make_graph(
inputs,
[node],
[],
initializer=[make_tensor("scales", TensorProto.FLOAT, (4,), scales)],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"y", TensorProto.INT32, (1, 1, 1, expected_last_dim)
)
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_scale_precision_large_dim(self, version) -> None:
# Regression for #4919, current-version helper
for scale in [1, 2]:
self._check_resize_scale_precision(
version, (1.0, 1.0, 1.0, float(scale)), 16777217 * scale
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_scale_and_size_but_one_is_empty(self, version) -> None:
self.skipIf(version < 11, "roi input is from Version 11")
graph = self._make_graph(
[
("x", TensorProto.INT32, (1, 3, 4, 5)),
("roi", TensorProto.FLOAT, (8,)),
("scales", TensorProto.FLOAT, (4,)),
("sizes", TensorProto.INT64, (0,)),
],
[make_node("Resize", ["x", "roi", "scales", "sizes"], ["y"])],
[],
initializer=[
make_tensor(
"scales",
TensorProto.FLOAT,
(4,),
vals=np.array([2.0, 1.1, 2.3, 1.9], dtype="<f4").tobytes(),
raw=True,
),
make_tensor(
"sizes",
TensorProto.INT64,
(0,),
vals=np.array([], dtype="<i8").tobytes(),
raw=True,
),
],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 3, 9, 9))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Resize"))
def test_resize_opset11_scales_is_empty(self, version) -> None:
self.skipIf(version != 11, "This test only works for Version 11")
# "scales" input in Resize in opset11 is not optional. It must be an empty tensor
# if sizes is needed. Shape inference for Resize shall handle this case.
graph = self._make_graph(
[
("x", TensorProto.INT32, (1, 3, 4, 5)),
("roi", TensorProto.FLOAT, (8,)),
("scales", TensorProto.FLOAT, (0,)),
("sizes", TensorProto.INT64, (4,)),
],
[make_node("Resize", ["x", "roi", "scales", "sizes"], ["y"])],
[],
initializer=[
make_tensor(
"sizes",
TensorProto.INT64,
(4,),
vals=np.array(
[2, 6, 8, 10], dtype="<i8"
).tobytes(), # double in all dimensions
raw=True,
),
],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT32, (2, 6, 8, 10))],
opset_imports=[helper.make_opsetid("", version)],
)
@pytest.mark.parametrize("version", all_versions_for("Shape"))
def test_shape(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4, 3))],
[make_node("Shape", ["x"], ["y"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT64, (3,))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Shape"))
def test_shape_start_1(self, version) -> None:
self.skipIf(version < 15, "start and end are from Version 15")
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4, 3))],
[make_node("Shape", ["x"], ["y"], start=1)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT64, (2,))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Shape"))
def test_shape_end_1(self, version) -> None:
self.skipIf(version < 15, "start and end are from Version 15")
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4, 3))],
[make_node("Shape", ["x"], ["y"], end=1)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT64, (1,))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Shape"))
def test_shape_negative_start(self, version) -> None:
self.skipIf(version < 15, "start and end are from Version 15")
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4, 3))],
[make_node("Shape", ["x"], ["y"], start=-1)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT64, (1,))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Shape"))
def test_shape_clip1(self, version) -> None:
self.skipIf(version < 15, "start and end are from Version 15")
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4, 3))],
[make_node("Shape", ["x"], ["y"], start=-5)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT64, (3,))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Shape"))
def test_shape_clip2(self, version) -> None:
self.skipIf(version < 15, "start and end are from Version 15")
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4, 3))],
[make_node("Shape", ["x"], ["y"], end=10)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT64, (3,))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Size"))
def test_size(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4, 3))], [make_node("Size", ["x"], ["y"])], []
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT64, ())],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Gather"))
def test_gather(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 3)), ("i", TensorProto.INT64, (2,))],
[make_node("Gather", ["x", "i"], ["y"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (2, 3))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Gather"))
def test_gather_axis1(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 3, 5)), ("i", TensorProto.INT64, (1, 2))],
[make_node("Gather", ["x", "i"], ["y"], axis=1)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (4, 1, 2, 5))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Gather"))
def test_gather_into_scalar(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3,)), ("i", TensorProto.INT64, ())],
[make_node("Gather", ["x", "i"], ["y"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, ())],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("GatherElements"))
def test_gather_elements(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 2)), ("i", TensorProto.INT64, (2, 2))],
[make_node("GatherElements", ["x", "i"], ["y"], axis=1)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (2, 2))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("GatherElements"))
def test_gather_elements_axis0(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3)), ("i", TensorProto.INT64, (2, 3))],
[make_node("GatherElements", ["x", "i"], ["y"], axis=0)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (2, 3))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Scatter"))
def test_scatter(self, version) -> None:
if version >= 11:
# Scatter is deprecated in domain_version of 11.
with pytest.raises(
onnx.checker.ValidationError, match="Scatter is deprecated"
):
self._test_scatter(version)
else:
self._test_scatter(version)
def _test_scatter(self, version) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 3)),
("i", TensorProto.INT64, (2, 3)),
("u", TensorProto.FLOAT, (2, 3)),
],
[make_node("Scatter", ["x", "i", "u"], ["y"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (3, 3))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Scatter"))
def test_scatter_axis1(self, version) -> None:
if version >= 11:
# Scatter is deprecated in domain_version of 11.
with pytest.raises(
onnx.checker.ValidationError, match="Scatter is deprecated"
):
self._test_scatter_axis1(version)
else:
self._test_scatter_axis1(version)
def _test_scatter_axis1(self, version) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 5)),
("i", TensorProto.INT64, (1, 2)),
("u", TensorProto.FLOAT, (1, 2)),
],
[make_node("Scatter", ["x", "i", "u"], ["y"], axis=1)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (1, 5))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("ScatterElements"))
def test_scatter_elements(self, version) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 3)),
("i", TensorProto.INT64, (2, 3)),
("u", TensorProto.FLOAT, (2, 3)),
],
[make_node("ScatterElements", ["x", "i", "u"], ["y"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (3, 3))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("ScatterElements"))
def test_scatter_elements_axis1(self, version) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 5)),
("i", TensorProto.INT64, (1, 2)),
("u", TensorProto.FLOAT, (1, 2)),
],
[make_node("ScatterElements", ["x", "i", "u"], ["y"], axis=1)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (1, 5))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("ScatterND"))
def test_scatternd(self, version) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (4, 5, 6)),
("indices", TensorProto.INT64, (3, 3, 2)),
("updates", TensorProto.FLOAT, (3, 3, 6)),
],
[make_node("ScatterND", ["x", "indices", "updates"], ["y"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (4, 5, 6))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("ScatterND"))
def test_scatternd_noshape(self, version) -> None:
# The shape of 'x_reshaped' cannot be inferred, since it is the output of a dynamic reshape.
# Thus the shape of 'y' is also None.
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (4, 5, 6)),
("indices", TensorProto.INT64, (3, 3, 2)),
("updates", TensorProto.FLOAT, (3, 3, 6)),
("shape", TensorProto.INT64, ("M",)),
],
[
make_node("Reshape", ["x", "shape"], ["x_reshaped"]),
make_node("ScatterND", ["x_reshaped", "indices", "updates"], ["y"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("x_reshaped", TensorProto.FLOAT, None),
make_tensor_value_info("y", TensorProto.FLOAT, None),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
def test_tensor_scatter(self) -> None:
graph = self._make_graph(
[
("past_cache", TensorProto.FLOAT, (2, 8, 128, 64)),
("update", TensorProto.FLOAT, (2, 8, 10, 64)),
("write_indices", TensorProto.INT64, (2,)),
],
[
make_node(
"TensorScatter",
["past_cache", "update", "write_indices"],
["present_cache"],
axis=2,
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"present_cache", TensorProto.FLOAT, (2, 8, 128, 64)
)
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 24)],
)
@pytest.mark.parametrize("version", all_versions_for("Squeeze"))
def test_squeeze(self, version) -> None:
if version == 11:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (1, 3, 1, 1, 2, 1))],
[make_node("Squeeze", "x", "y", axes=[0, 2, 3, 5])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (3, 2))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
else:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 3, 1, 1, 2, 1)),
("axes", TensorProto.INT64, (4,)),
],
[make_node("Squeeze", ["x", "axes"], "y")],
[],
initializer=[
make_tensor("axes", TensorProto.INT64, (4,), (0, 2, 3, 5))
],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (3, 2))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("StringConcat"))
def test_stringconcat(self, version) -> None:
graph = self._make_graph(
[
("x", TensorProto.STRING, (2, 3, 4)),
("y", TensorProto.STRING, (2, 3, 4)),
],
[make_node("StringConcat", ["x", "y"], "z")],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("z", TensorProto.STRING, (2, 3, 4))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("StringConcat"))
def test_stringconcat_broadcasting(self, version) -> None:
graph = self._make_graph(
[
("x", TensorProto.STRING, (2, 3, 4)),
("y", TensorProto.STRING, (1, 3, 1)),
],
[make_node("StringConcat", ["x", "y"], "z")],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("z", TensorProto.STRING, (2, 3, 4))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("RegexFullMatch"))
def test_regex_full_match(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.STRING, (2, 4, 3, 9))],
[make_node("RegexFullMatch", ["x"], ["y"], pattern=r"^[A-Z][a-z]*$")],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.BOOL, (2, 4, 3, 9))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("RegexFullMatch"))
def test_regex_full_match_empty_shape(self, version) -> None:
graph = self._make_graph(
[("x", TensorProto.STRING, ())],
[make_node("RegexFullMatch", ["x"], ["y"], pattern=r"^[A-Z][a-z]*$")],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.BOOL, ())],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
def test_squeeze_no_axes_opset11(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 3, 1, 1, 2, 1)),
],
[make_node("Squeeze", ["x"], "y")],
[],
)
operatorsetid = OperatorSetIdProto()
operatorsetid.domain = ""
operatorsetid.version = 11
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3, 2))]
)
def test_squeeze_no_axes_dynamic_input_opset11(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 3, 1, None, 2, 1)),
],
[make_node("Squeeze", ["x"], "y")],
[],
)
operatorsetid = OperatorSetIdProto()
operatorsetid.domain = ""
operatorsetid.version = 11
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, None)],
opset_imports=[operatorsetid],
)
def test_unsqueeze_regular(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 2)), ("axes", TensorProto.INT64, (4,))],
[make_node("Unsqueeze", ["x", "axes"], "y")],
[],
initializer=[make_tensor("axes", TensorProto.INT64, (4,), (0, 1, 3, 5))],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (1, 1, 3, 1, 2, 1))]
)
def test_unsqueeze_unsorted_axes(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4, 5)), ("axes", TensorProto.INT64, (2,))],
[make_node("Unsqueeze", ["x", "axes"], "y")],
[],
initializer=[make_tensor("axes", TensorProto.INT64, (2,), (4, 0))],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (1, 3, 4, 5, 1))]
)
def test_unsqueeze_negative_axes(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4, 5)), ("axes", TensorProto.INT64, (2,))],
[make_node("Unsqueeze", ["x", "axes"], "y")],
[],
initializer=[make_tensor("axes", TensorProto.INT64, (2,), (0, -1))],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (1, 3, 4, 5, 1))]
)
def test_unsqueeze_scalar(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, ()), ("axes", TensorProto.INT64, ())],
[make_node("Unsqueeze", ["x", "axes"], "y")],
[],
initializer=[make_tensor("axes", TensorProto.INT64, (), (-1,))],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (1,))]
)
def test_slice_without_input_shape(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 2, "a")),
("starts", TensorProto.INT64, (1,)),
("ends", TensorProto.INT64, (1,)),
],
[make_node("Slice", ["x", "starts", "ends"], ["y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (None, None, None))]
)
def test_slice_with_input_shape(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 2)),
("starts", TensorProto.INT64, (2,)),
("ends", TensorProto.INT64, (2,)),
],
[make_node("Slice", ["x", "starts", "ends"], ["y"])],
[],
initializer=[
make_tensor(
"starts",
TensorProto.INT64,
(2,),
vals=np.array([1, 0], dtype="<i8").tobytes(),
raw=True,
), # Feed raw bytes (force little endian ordering like onnx standard) for test purpose
make_tensor("ends", TensorProto.INT64, (2,), (2, 2)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (1, 2))]
)
def test_slice_with_input_shape_containing_dim_params(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, "a", 1)),
("starts", TensorProto.INT64, (3,)),
("ends", TensorProto.INT64, (3,)),
],
[make_node("Slice", ["x", "starts", "ends"], ["y"])],
[],
initializer=[
make_tensor("starts", TensorProto.INT64, (3,), (0, 0, 0)),
make_tensor("ends", TensorProto.INT64, (3,), (1, 1, 1)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (1, None, 1))]
)
def test_slice_with_input_shape_steps(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (5, 6, 7)),
("starts", TensorProto.INT64, (3,)),
("ends", TensorProto.INT64, (3,)),
("axes", TensorProto.INT64, (None)),
("steps", TensorProto.INT64, (3,)),
],
[make_node("Slice", ["x", "starts", "ends", "axes", "steps"], ["y"])],
[],
initializer=[
make_tensor("starts", TensorProto.INT64, (3,), (1, 0, 0)),
make_tensor("ends", TensorProto.INT64, (3,), (2, 6, 6)),
make_tensor("steps", TensorProto.INT64, (3,), (1, 4, 3)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (1, 2, 2))]
)
def test_slice_with_input_shape_axes(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 6, 2)),
("starts", TensorProto.INT64, (2,)),
("ends", TensorProto.INT64, (2,)),
("axes", TensorProto.INT64, (2,)),
("steps", TensorProto.INT64, (None)),
],
[make_node("Slice", ["x", "starts", "ends", "axes", "steps"], ["y"])],
[],
initializer=[
make_tensor("starts", TensorProto.INT64, (2,), (1, 0)),
make_tensor("ends", TensorProto.INT64, (2,), (2, 2)),
make_tensor("axes", TensorProto.INT64, (2,), (0, 2)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (1, 6, 2))]
)
def test_slice_unsorted_axes(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 2)),
("starts", TensorProto.INT64, (2,)),
("ends", TensorProto.INT64, (2,)),
("axes", TensorProto.INT64, (2,)),
],
[make_node("Slice", ["x", "starts", "ends", "axes"], "y")],
[],
initializer=[
make_tensor("starts", TensorProto.INT64, (2,), (1, 0)),
make_tensor("ends", TensorProto.INT64, (2,), (2, 2)),
make_tensor("axes", TensorProto.INT64, (2,), (1, 0)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (2, 1))]
) # can handle unsorted axes
def test_slice_giant_number(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 2)),
("starts", TensorProto.INT64, (2,)),
("ends", TensorProto.INT64, (2,)),
("axes", TensorProto.INT64, (2,)),
],
[make_node("Slice", ["x", "starts", "ends", "axes"], "y")],
[],
initializer=[
make_tensor("starts", TensorProto.INT64, (2,), (1, 0)),
make_tensor("ends", TensorProto.INT64, (2,), (200, 22000)),
make_tensor("axes", TensorProto.INT64, (2,), (0, 1)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (2, 2))]
)
def test_slice_giant_step(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 2)),
("starts", TensorProto.INT64, (2,)),
("ends", TensorProto.INT64, (2,)),
("axes", TensorProto.INT64, (2,)),
("steps", TensorProto.INT64, (2,)),
],
[make_node("Slice", ["x", "starts", "ends", "axes", "steps"], "y")],
[],
initializer=[
make_tensor("starts", TensorProto.INT64, (2,), (1, 0)),
make_tensor("ends", TensorProto.INT64, (2,), (200, 200)),
make_tensor("axes", TensorProto.INT64, (2,), (0, 1)),
make_tensor("steps", TensorProto.INT64, (2,), (1, 200)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (2, 1))]
)
def test_slice_negative_end(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 2)),
("starts", TensorProto.INT64, (2,)),
("ends", TensorProto.INT64, (2,)),
("axes", TensorProto.INT64, (2,)),
],
[make_node("Slice", ["x", "starts", "ends", "axes"], "y")],
[],
initializer=[
make_tensor("starts", TensorProto.INT64, (2,), (1, 0)),
make_tensor(
"ends", TensorProto.INT64, (2,), (200, -1)
), # negative end means begin from end of a dimension (here end = 2 - 1 = 1)
make_tensor("axes", TensorProto.INT64, (2,), (0, 1)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (2, 1))]
)
def test_slice_negative_start(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 2)),
("starts", TensorProto.INT64, (2,)),
("ends", TensorProto.INT64, (2,)),
("axes", TensorProto.INT64, (2,)),
],
[make_node("Slice", ["x", "starts", "ends", "axes"], "y")],
[],
initializer=[
make_tensor(
"starts", TensorProto.INT64, (2,), (1, -2)
), # negative start means begin from end of a dimension (here end = 2 - 2 = 0)
make_tensor("ends", TensorProto.INT64, (2,), (200, 3)),
make_tensor("axes", TensorProto.INT64, (2,), (0, 1)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (2, 2))]
)
def test_slice_negative_step(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 4)),
("starts", TensorProto.INT64, (2,)),
("ends", TensorProto.INT64, (2,)),
("axes", TensorProto.INT64, (2,)),
("steps", TensorProto.INT64, (2,)),
],
[make_node("Slice", ["x", "starts", "ends", "axes", "steps"], "y")],
[],
initializer=[
make_tensor(
"starts", TensorProto.INT64, (2,), (1, 4)
), # 4 will be clamped to 3 since we are negative stepping
make_tensor("ends", TensorProto.INT64, (2,), (200, 0)),
make_tensor("axes", TensorProto.INT64, (2,), (0, 1)),
make_tensor("steps", TensorProto.INT64, (2,), (1, -1)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (2, 3))]
)
def test_slice_variable_copy(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, ("a", 2)),
("starts", TensorProto.INT64, (1,)),
("ends", TensorProto.INT64, (1,)),
("axes", TensorProto.INT64, (1,)),
],
[make_node("Slice", ["x", "starts", "ends", "axes"], "y")],
[],
initializer=[
make_tensor("starts", TensorProto.INT64, (1,), (1,)),
make_tensor("ends", TensorProto.INT64, (1,), (200,)),
make_tensor("axes", TensorProto.INT64, (1,), (1,)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, ("a", 1))]
)
def test_slice_variable_input_types(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.DOUBLE, (3, 2)),
("starts", TensorProto.INT32, (2,)),
("ends", TensorProto.INT32, (2,)),
("axes", TensorProto.INT32, (2,)),
],
[make_node("Slice", ["x", "starts", "ends", "axes"], "y")],
[],
initializer=[
make_tensor("starts", TensorProto.INT32, (2,), (1, 0)),
make_tensor("ends", TensorProto.INT32, (2,), (200, 22000)),
make_tensor("axes", TensorProto.INT32, (2,), (0, 1)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.DOUBLE, (2, 2))]
)
def test_slice_empty_dim_positive_step(self) -> None:
"""Slice on empty dimension with positive step should produce dim_value=0."""
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (0, 6)),
("starts", TensorProto.INT64, (1,)),
("ends", TensorProto.INT64, (1,)),
("axes", TensorProto.INT64, (1,)),
("steps", TensorProto.INT64, (1,)),
],
[make_node("Slice", ["x", "starts", "ends", "axes", "steps"], "y")],
[],
initializer=[
make_tensor("starts", TensorProto.INT64, (1,), (0,)),
make_tensor("ends", TensorProto.INT64, (1,), (0,)),
make_tensor("axes", TensorProto.INT64, (1,), (0,)),
make_tensor("steps", TensorProto.INT64, (1,), (1,)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (0, 6))]
)
def test_slice_empty_dim_negative_step(self) -> None:
"""Regression test for issue #7735: std::clamp UB on empty dim with step=-1."""
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (0, 6)),
("starts", TensorProto.INT64, (1,)),
("ends", TensorProto.INT64, (1,)),
("axes", TensorProto.INT64, (1,)),
("steps", TensorProto.INT64, (1,)),
],
[make_node("Slice", ["x", "starts", "ends", "axes", "steps"], "y")],
[],
initializer=[
make_tensor("starts", TensorProto.INT64, (1,), (1,)),
make_tensor("ends", TensorProto.INT64, (1,), (0,)),
make_tensor("axes", TensorProto.INT64, (1,), (0,)),
make_tensor("steps", TensorProto.INT64, (1,), (-1,)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (0, 6))]
)
def test_slice_scalar_shape_output(self) -> None:
"""Shape(scalar) produces 0-length output; Slice on it should not crash."""
graph = self._make_graph(
[
("x", TensorProto.FLOAT, ()),
("starts", TensorProto.INT64, (1,)),
("ends", TensorProto.INT64, (1,)),
("axes", TensorProto.INT64, (1,)),
("steps", TensorProto.INT64, (1,)),
],
[
make_node("Shape", ["x"], ["shape"]),
make_node("Slice", ["shape", "starts", "ends", "axes", "steps"], ["y"]),
],
[],
initializer=[
make_tensor("starts", TensorProto.INT64, (1,), (0,)),
make_tensor("ends", TensorProto.INT64, (1,), (0,)),
make_tensor("axes", TensorProto.INT64, (1,), (0,)),
make_tensor("steps", TensorProto.INT64, (1,), (1,)),
],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("shape", TensorProto.INT64, (0,)),
make_tensor_value_info("y", TensorProto.INT64, (0,)),
],
)
def test_conv(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 4, 5, 6, 7)),
("y", TensorProto.FLOAT, (5, 4, 2, 4, 3)),
],
[
make_node(
"Conv",
["x", "y"],
"z",
pads=[0, 1, 1, 0, 0, 1],
dilations=[1, 2, 2],
strides=[1, 1, 2],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3, 5, 4, 1, 3))]
)
def test_conv_1d_simple(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, 5)),
("y", TensorProto.FLOAT, (50, 4, 2)),
],
[make_node("Conv", ["x", "y"], "z", dilations=[1])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (30, 50, 4))]
)
def test_conv_dilations(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, 8, 8, 8)),
("y", TensorProto.FLOAT, (50, 4, 3, 3, 3)),
],
[make_node("Conv", ["x", "y"], "z", dilations=[1, 2, 3])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (30, 50, 6, 4, 2))]
)
def test_conv_strides(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, 8, 8, 8)),
("y", TensorProto.FLOAT, (50, 4, 3, 3, 3)),
],
[make_node("Conv", ["x", "y"], "z", strides=[1, 2, 3])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (30, 50, 6, 3, 2))]
)
@pytest.mark.parametrize("version", all_versions_for("Conv"))
def test_conv_zero_strides(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 5, 5)),
("y", TensorProto.FLOAT, (1, 1, 3, 3)),
],
[make_node("Conv", ["x", "y"], "z", strides=[0, 1])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Conv"))
def test_conv_negative_strides(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 5, 5)),
("y", TensorProto.FLOAT, (1, 1, 3, 3)),
],
[make_node("Conv", ["x", "y"], "z", strides=[-1, 1])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
def test_conv_pads(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, 7, 6, 4)),
("y", TensorProto.FLOAT, (50, 4, 3, 3, 3)),
],
[make_node("Conv", ["x", "y"], "z", pads=[1, 1, 2, 0, 1, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (30, 50, 6, 6, 6))]
)
def test_conv_auto_pad(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, 7, 6, 4)),
("y", TensorProto.FLOAT, (50, 4, 4, 3, 2)),
],
[make_node("Conv", ["x", "y"], "z", auto_pad="SAME_UPPER")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (30, 50, 7, 6, 4))]
)
def test_conv_auto_pads(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, 7, 6, 4)),
("y", TensorProto.FLOAT, (50, 4, 4, 3, 2)),
],
[
make_node(
"Conv", ["x", "y"], "z", auto_pad="SAME_UPPER", strides=[2, 2, 1]
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (30, 50, 4, 3, 4))]
)
def test_conv_auto_pad_dilation(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, 65, 64, 63)),
("y", TensorProto.FLOAT, (50, 4, 4, 3, 2)),
],
[
make_node(
"Conv", ["x", "y"], "z", auto_pad="SAME_UPPER", dilations=[2, 3, 4]
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("z", TensorProto.FLOAT, (30, 50, 65, 64, 63))],
)
def test_conv_group(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, 8, 8, 8)),
("y", TensorProto.FLOAT, (4, 1, 8, 8, 8)),
],
[make_node("Conv", ["x", "y"], "z", group=4)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (30, 4, 1, 1, 1))]
)
def test_conv_only_one_pos(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, 5)),
("y", TensorProto.FLOAT, (50, 4, 5)),
],
[make_node("Conv", ["x", "y"], "z", strides=[2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (30, 50, 1))]
)
def test_conv_partial_missing_shape(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, None, 6, 4)),
("y", TensorProto.FLOAT, (50, 4, 3, 3, 3)),
],
[make_node("Conv", ["x", "y"], "z", pads=[1, 1, 2, 0, 1, 2])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("z", TensorProto.FLOAT, (30, 50, None, 6, 6))],
)
def test_conv_partial_missing_weight_shape(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, 7, 6, 4)),
("y", TensorProto.FLOAT, (50, 4, None, 3, 3)),
],
[make_node("Conv", ["x", "y"], "z", pads=[1, 1, 2, 0, 1, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, None)]
)
@pytest.mark.parametrize(
"version",
[
# opset 1-10 -> Conv-1 -> convPoolShapeInference_opset1_to_11
10,
# opset 11-21 -> Conv-11 -> convPoolShapeInference_opset19
19,
# opset 22+ -> Conv-22 -> convPoolShapeInference
defs.get_schema("Conv").since_version,
],
)
def test_conv_weight_more_spatial_dims_than_input_raises(
self, version: int
) -> None:
# Weight has more spatial dims than input and no explicit kernel_shape
# attribute, so kernel_shape is derived from the weight. This must fail
# rather than reading past the end of the dilations vector.
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 4, 8, 8)),
("w", TensorProto.FLOAT, (5, 4, 3, 3, 3)),
],
[make_node("Conv", ["x", "w"], "z")],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError, match="weight tensor"):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize(
"version",
[
# opset 1-10 -> Conv-1 -> convPoolShapeInference_opset1_to_11
10,
# opset 11-21 -> Conv-11 -> convPoolShapeInference_opset19
19,
# opset 22+ -> Conv-22 -> convPoolShapeInference
defs.get_schema("Conv").since_version,
],
)
def test_conv_weight_fewer_spatial_dims_than_input_raises(
self, version: int
) -> None:
# Weight has fewer spatial dims than input and no explicit kernel_shape
# attribute, so kernel_shape is derived from the weight. This must fail
# rather than silently inferring an inconsistent shape.
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 4, 8, 8, 8)),
("w", TensorProto.FLOAT, (5, 4, 3, 3)),
],
[make_node("Conv", ["x", "w"], "z")],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError, match="weight tensor"):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Conv"))
def test_conv_zero_dilations(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 5, 5)),
("y", TensorProto.FLOAT, (1, 1, 3, 3)),
],
[make_node("Conv", ["x", "y"], "z", dilations=[0, 1])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Conv"))
def test_conv_negative_dilations(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 5, 5)),
("y", TensorProto.FLOAT, (1, 1, 3, 3)),
],
[make_node("Conv", ["x", "y"], "z", dilations=[-1, 1])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Conv"))
def test_conv_negative_pads(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 5, 5)),
("y", TensorProto.FLOAT, (1, 1, 3, 3)),
],
[make_node("Conv", ["x", "y"], "z", pads=[-1, 0, 0, 0])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Conv"))
def test_conv_input_too_few_dims(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1)),
("y", TensorProto.FLOAT, (1, 1, 3)),
],
[make_node("Conv", ["x", "y"], "z")],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Conv"))
def test_conv_zero_kernel_shape(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 5, 5)),
("y", TensorProto.FLOAT, (1, 1, 3, 3)),
],
[make_node("Conv", ["x", "y"], "z", kernel_shape=[0, 3])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("Conv"))
def test_conv_negative_kernel_shape(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 5, 5)),
("y", TensorProto.FLOAT, (1, 1, 3, 3)),
],
[make_node("Conv", ["x", "y"], "z", kernel_shape=[-1, 3])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("MaxPool"))
def test_maxpool_zero_kernel_shape(self, version: int) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (1, 1, 5, 5))],
[make_node("MaxPool", ["X"], ["Y"], kernel_shape=[0, 3])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("MaxPool"))
def test_maxpool_negative_dilations(self, version: int) -> None:
if version < 10:
pytest.skip("dilations not supported before MaxPool-10")
graph = self._make_graph(
[("X", TensorProto.FLOAT, (1, 1, 5, 5))],
[
make_node(
"MaxPool",
["X"],
["Y"],
kernel_shape=[3, 3],
dilations=[-1, 1],
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("MaxPool"))
def test_maxpool_negative_pads(self, version: int) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (1, 1, 5, 5))],
[
make_node(
"MaxPool",
["X"],
["Y"],
kernel_shape=[3, 3],
pads=[-1, 0, 0, 0],
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("ConvTranspose"))
def test_conv_transpose_weight_spatial_rank_mismatch(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 4, 4)),
("w", TensorProto.FLOAT, (1, 1, 3, 3, 3)),
],
[make_node("ConvTranspose", ["x", "w"], "z")],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("ConvTranspose"))
def test_conv_transpose_negative_dilations(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 4, 4)),
("w", TensorProto.FLOAT, (1, 1, 3, 3)),
],
[make_node("ConvTranspose", ["x", "w"], "z", dilations=[-1, 1])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("ConvTranspose"))
def test_conv_transpose_zero_strides(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 4, 4)),
("w", TensorProto.FLOAT, (1, 1, 3, 3)),
],
[make_node("ConvTranspose", ["x", "w"], "z", strides=[0, 1])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("ConvTranspose"))
def test_conv_transpose_negative_pads(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 4, 4)),
("w", TensorProto.FLOAT, (1, 1, 3, 3)),
],
[make_node("ConvTranspose", ["x", "w"], "z", pads=[-1, 0, 0, 0])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("ConvTranspose"))
def test_conv_transpose_negative_output_padding(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 4, 4)),
("w", TensorProto.FLOAT, (1, 1, 3, 3)),
],
[
make_node(
"ConvTranspose",
["x", "w"],
"z",
strides=[2, 2],
output_padding=[-1, 0],
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("ConvTranspose"))
def test_conv_transpose_negative_output_shape(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 4, 4)),
("w", TensorProto.FLOAT, (1, 1, 3, 3)),
],
[
make_node(
"ConvTranspose",
["x", "w"],
"z",
output_shape=[-1, 6],
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("ConvTranspose"))
def test_conv_transpose_zero_kernel_shape(self, version: int) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 4, 4)),
("w", TensorProto.FLOAT, (1, 1, 3, 3)),
],
[make_node("ConvTranspose", ["x", "w"], "z", kernel_shape=[0, 3])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
def test_attention_4d(self) -> None:
graph = self._make_graph(
[
(
"Q",
TensorProto.FLOAT,
("B", "q_num_heads", "q_seq_length", "head_size"),
),
(
"K",
TensorProto.FLOAT,
("B", "kv_num_heads", "kv_seq_len", "head_size"),
),
(
"V",
TensorProto.FLOAT,
("B", "kv_num_heads", "kv_seq_len", "v_head_size"),
),
],
[
make_node(
"Attention",
["Q", "K", "V"],
["Y"],
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"Y",
TensorProto.FLOAT,
("B", "q_num_heads", "q_seq_length", "v_head_size"),
)
],
)
def test_linear_attention_basic_mha(self) -> None:
# update_rule="linear" with no optional inputs: baseline shape/dtype plumbing.
# H_q == H_kv == 4, d_k == d_v == 16. Output is 3D packed; state is 4D.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,64] Q, float[2,4,64] K, float[2,4,64] V)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="linear"
> (Q, K, V)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, (2, 4, 64)),
make_tensor_value_info(
"present_state", TensorProto.FLOAT, (2, 4, 16, 16)
),
],
)
def test_linear_attention_gated_delta(self) -> None:
# Default update_rule with both decay (per-keydim) and beta required.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,64] Q, float[2,4,64] K, float[2,4,64] V,
float[2,4,64] decay, float[2,4,4] beta)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="gated_delta"
> (Q, K, V, "", decay, beta)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, (2, 4, 64)),
make_tensor_value_info(
"present_state", TensorProto.FLOAT, (2, 4, 16, 16)
),
],
)
def test_linear_attention_gqa(self) -> None:
# H_q=8, H_kv=2 (4 query heads share each KV head). Output last dim must
# be H_q * d_v while present_state's H dim must be H_kv. This is the
# highest-risk regression for confusing q vs kv head counts.
# Q: 8 * 16 = 128. K, V: 2 * 16 = 32.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,128] Q, float[2,4,32] K, float[2,4,32] V)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=8, kv_num_heads=2, update_rule="linear"
> (Q, K, V)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, (2, 4, 128)),
make_tensor_value_info(
"present_state", TensorProto.FLOAT, (2, 2, 16, 16)
),
],
)
def test_linear_attention_mqa(self) -> None:
# Multi-query attention: kv_num_heads == 1.
# Q: 4 * 16 = 64. K, V: 1 * 16 = 16.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,64] Q, float[2,4,16] K, float[2,4,16] V)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=1, update_rule="linear"
> (Q, K, V)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, (2, 4, 64)),
make_tensor_value_info(
"present_state", TensorProto.FLOAT, (2, 1, 16, 16)
),
],
)
def test_linear_attention_with_past_state(self) -> None:
# past_state has dtype S (FLOAT16) different from T (FLOAT). present_state
# must inherit dtype from past_state, not from query.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,1,64] Q, float[2,1,64] K, float[2,1,64] V,
float16[2,4,16,16] past_state,
float[2,1,64] decay, float[2,1,4] beta)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="gated_delta"
> (Q, K, V, past_state, decay, beta)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, (2, 1, 64)),
make_tensor_value_info(
"present_state", TensorProto.FLOAT16, (2, 4, 16, 16)
),
],
)
def test_linear_attention_per_head_decay(self) -> None:
# update_rule="gated" with per-head scalar decay shape (B, T, H_kv).
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,3,16] Q, float[2,3,16] K, float[2,3,16] V,
float[2,3,2] decay)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=2, kv_num_heads=2, update_rule="gated"
> (Q, K, V, "", decay)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, (2, 3, 16)),
make_tensor_value_info(
"present_state", TensorProto.FLOAT, (2, 2, 8, 8)
),
],
)
def test_linear_attention_per_keydim_decay(self) -> None:
# update_rule="gated" with per-key-dimension decay shape (B, T, H_kv * d_k).
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,3,16] Q, float[2,3,16] K, float[2,3,16] V,
float[2,3,16] decay)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=2, kv_num_heads=2, update_rule="gated"
> (Q, K, V, "", decay)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, (2, 3, 16)),
make_tensor_value_info(
"present_state", TensorProto.FLOAT, (2, 2, 8, 8)
),
],
)
def test_linear_attention_dynamic_T(self) -> None:
# Symbolic batch and sequence-length dims must propagate to output and state.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[B,T,64] Q, float[B,T,64] K, float[B,T,64] V)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="linear"
> (Q, K, V)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, ("B", "T", 64)),
make_tensor_value_info(
"present_state", TensorProto.FLOAT, ("B", 4, 16, 16)
),
],
)
def test_linear_attention_unknown_value_last_dim(self) -> None:
# value's last dim is symbolic: output last dim and state's d_v are unknown,
# but rank stays 3/4 and known dims (B, T, H_kv, d_k) are still computed.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,64] Q, float[2,4,64] K, float[2,4,VLast] V)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="linear"
> (Q, K, V)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, (2, 4, None)),
make_tensor_value_info(
"present_state", TensorProto.FLOAT, (2, 4, 16, None)
),
],
)
def test_linear_attention_unknown_qkv_with_past_state(self) -> None:
# Q/K/V have no shapes; only past_state has a shape. Dim unification
# propagates B, d_k, d_v from past_state into both output and
# present_state (only T remains unknown for output). Uses _make_graph
# because shapeless graph inputs are rejected by the model checker;
# _make_graph wraps them behind a Reshape so the shape-erasure happens
# inside the graph.
graph = self._make_graph(
[
("Q", TensorProto.FLOAT, None),
("K", TensorProto.FLOAT, None),
("V", TensorProto.FLOAT, None),
("past_state", TensorProto.FLOAT, (2, 4, 16, 16)),
],
[
make_node(
"LinearAttention",
["Q", "K", "V", "past_state"],
["output", "present_state"],
q_num_heads=4,
kv_num_heads=4,
update_rule="linear",
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("output", TensorProto.FLOAT, (2, None, 64)),
make_tensor_value_info(
"present_state", TensorProto.FLOAT, (2, 4, 16, 16)
),
],
)
def test_linear_attention_query_rank_not_3(self) -> None:
# Query given as rank-4 (internal layout leaked to op boundary).
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,4,16] Q, float[2,4,64] K, float[2,4,64] V)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="linear"
> (Q, K, V)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_linear_attention_past_state_rank_not_4(self) -> None:
# past_state must be rank 4.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,1,64] Q, float[2,1,64] K, float[2,1,64] V,
float[2,4,16] past_state)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="linear"
> (Q, K, V, past_state)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_linear_attention_decay_rank_not_3(self) -> None:
# decay must be rank 3 (3D packed).
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,3,16] Q, float[2,3,16] K, float[2,3,16] V,
float[2,3] decay)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=2, kv_num_heads=2, update_rule="gated"
> (Q, K, V, "", decay)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_linear_attention_gqa_indivisible(self) -> None:
# q_num_heads must be a positive multiple of kv_num_heads.
# Q: 6 * 16 = 96. K, V: 4 * 16 = 64.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,96] Q, float[2,4,64] K, float[2,4,64] V)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=6, kv_num_heads=4, update_rule="linear"
> (Q, K, V)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_linear_attention_gated_delta_missing_decay(self) -> None:
# gated_delta requires decay; only beta provided.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,64] Q, float[2,4,64] K, float[2,4,64] V,
float[2,4,4] beta)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="gated_delta"
> (Q, K, V, "", "", beta)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_linear_attention_gated_delta_missing_beta(self) -> None:
# gated_delta requires beta; only decay provided.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,64] Q, float[2,4,64] K, float[2,4,64] V,
float[2,4,64] decay)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="gated_delta"
> (Q, K, V, "", decay)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_linear_attention_linear_with_decay(self) -> None:
# update_rule="linear" forbids decay.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,64] Q, float[2,4,64] K, float[2,4,64] V,
float[2,4,64] decay)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="linear"
> (Q, K, V, "", decay)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_linear_attention_linear_with_beta(self) -> None:
# update_rule="linear" forbids beta.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,64] Q, float[2,4,64] K, float[2,4,64] V,
float[2,4,4] beta)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="linear"
> (Q, K, V, "", "", beta)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_linear_attention_unknown_update_rule(self) -> None:
# Only "linear", "gated", "delta", and "gated_delta" are accepted.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,64] Q, float[2,4,64] K, float[2,4,64] V)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="forza inter"
> (Q, K, V)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_linear_attention_q_num_heads_zero(self) -> None:
# Non-positive head counts must be rejected explicitly, not silently
# skipped by the GQA divisibility check (0 % anything == 0).
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,3,16] Q, float[2,3,16] K, float[2,3,16] V)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=0, kv_num_heads=2, update_rule="linear"
> (Q, K, V)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_linear_attention_qpack_indivisible(self) -> None:
# Packed query last dim must be divisible by q_num_heads so the
# function body can reshape (B, T, H_q * d_k) to (B, T, H_q, d_k).
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,3,15] Q, float[2,3,16] K, float[2,3,16] V)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=2, kv_num_heads=2, update_rule="linear"
> (Q, K, V)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_linear_attention_decay_last_dim_wrong(self) -> None:
# With H_kv=4 and d_k=16, decay last dim must be 4 (per-head) or 64
# (per-key-dim). 7 is neither and must be rejected.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,3,64] Q, float[2,3,64] K, float[2,3,64] V,
float[2,3,7] decay)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="gated"
> (Q, K, V, "", decay)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_linear_attention_beta_last_dim_wrong(self) -> None:
# With H_kv=4, beta last dim must be 1 (broadcast) or 4 (per-head).
# 3 is neither and must be rejected.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,3,64] Q, float[2,3,64] K, float[2,3,64] V,
float[2,3,4] decay, float[2,3,3] beta)
=> (output, present_state)
{
output, present_state = LinearAttention <
q_num_heads=4, kv_num_heads=4, update_rule="gated_delta"
> (Q, K, V, "", decay, beta)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_causal_conv_with_state_static(self) -> None:
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,8] input, float[4,1,4] weight)
=> (output, present_state)
{
output, present_state = CausalConvWithState (input, weight)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, (2, 4, 8)),
make_tensor_value_info("present_state", TensorProto.FLOAT, (2, 4, 3)),
],
)
def test_causal_conv_with_state_dynamic_length(self) -> None:
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[B,C,L] input, float[C,1,4] weight)
=> (output, present_state)
{
output, present_state = CausalConvWithState (input, weight)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, ("B", "C", "L")),
make_tensor_value_info(
"present_state", TensorProto.FLOAT, ("B", "C", 3)
),
],
)
def test_causal_conv_with_state_dynamic_kernel(self) -> None:
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,8] input, float[4,1,k] weight)
=> (output, present_state)
{
output, present_state = CausalConvWithState (input, weight)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, (2, 4, 8)),
make_tensor_value_info(
"present_state", TensorProto.FLOAT, (2, 4, None)
),
],
)
def test_causal_conv_with_state_input_rank2_fails(self) -> None:
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4] input, float[4,1,4] weight)
=> (output, present_state)
{
output, present_state = CausalConvWithState (input, weight)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_causal_conv_with_state_kernel_zero_fails(self) -> None:
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,8] input, float[4,1,0] weight)
=> (output, present_state)
{
output, present_state = CausalConvWithState (input, weight)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_causal_conv_with_state_weight_rank2_fails(self) -> None:
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,8] input, float[4,4] weight)
=> (output, present_state)
{
output, present_state = CausalConvWithState (input, weight)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_causal_conv_with_state_kernel_size_one(self) -> None:
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,8] input, float[4,1,1] weight)
=> (output, present_state)
{
output, present_state = CausalConvWithState (input, weight)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, (2, 4, 8)),
make_tensor_value_info("present_state", TensorProto.FLOAT, (2, 4, 0)),
],
)
def test_causal_conv_with_state_fp16_type_propagation(self) -> None:
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float16[2,4,8] input, float16[4,1,4] weight)
=> (output, present_state)
{
output, present_state = CausalConvWithState (input, weight)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT16, (2, 4, 8)),
make_tensor_value_info("present_state", TensorProto.FLOAT16, (2, 4, 3)),
],
)
def test_causal_conv_with_state_all_optional_inputs(self) -> None:
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,8] input, float[4,1,4] weight,
float[4] bias, float[2,4,3] past_state)
=> (output, present_state)
{
output, present_state = CausalConvWithState (
input, weight, bias, past_state
)
}
"""
)
self._assert_inferred(
model,
[
make_tensor_value_info("output", TensorProto.FLOAT, (2, 4, 8)),
make_tensor_value_info("present_state", TensorProto.FLOAT, (2, 4, 3)),
],
)
def test_causal_conv_with_state_unknown_activation_fails(self) -> None:
# Only "none", "silu", and "swish" are accepted; anything else must be
# rejected by shape inference rather than silently inlined as Identity.
model = onnx.parser.parse_model(
"""
<ir_version: 10, opset_import: ["" : 27]>
g (float[2,4,8] input, float[4,1,4] weight)
=> (output, present_state)
{
output, present_state = CausalConvWithState <activation = "forza inter"> (input, weight)
}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(model)
def test_flex_attention_basic(self) -> None:
"""Test FlexAttention basic shape inference with symbolic dimensions."""
graph = self._make_graph(
[
("Q", TensorProto.FLOAT, ("B", "Hq", "L", "Dqk")),
("K", TensorProto.FLOAT, ("B", "Hkv", "S", "Dqk")),
("V", TensorProto.FLOAT, ("B", "Hkv", "S", "Dv")),
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.FLOAT, ("B", "Hq", "L", "Dv"))],
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_concrete_dims(self) -> None:
"""Test FlexAttention shape inference with concrete dimensions."""
graph = self._make_graph(
[
("Q", TensorProto.FLOAT, (2, 8, 128, 64)),
("K", TensorProto.FLOAT, (2, 8, 256, 64)),
("V", TensorProto.FLOAT, (2, 8, 256, 64)),
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.FLOAT, (2, 8, 128, 64))],
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_different_v_head_size(self) -> None:
"""Test FlexAttention with different head sizes for Q/K vs V."""
graph = self._make_graph(
[
("Q", TensorProto.FLOAT, (2, 8, 128, 64)),
("K", TensorProto.FLOAT, (2, 8, 256, 64)),
("V", TensorProto.FLOAT, (2, 8, 256, 128)),
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.FLOAT, (2, 8, 128, 128))],
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_gqa_enabled(self) -> None:
"""Test FlexAttention with Grouped Query Attention."""
graph = self._make_graph(
[
("Q", TensorProto.FLOAT, (2, 8, 128, 64)),
("K", TensorProto.FLOAT, (2, 2, 256, 64)),
("V", TensorProto.FLOAT, (2, 2, 256, 64)),
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.FLOAT, (2, 8, 128, 64))],
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_float16(self) -> None:
"""Test FlexAttention shape inference with FLOAT16 type."""
graph = self._make_graph(
[
("Q", TensorProto.FLOAT16, (2, 8, 128, 64)),
("K", TensorProto.FLOAT16, (2, 8, 256, 64)),
("V", TensorProto.FLOAT16, (2, 8, 256, 64)),
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.FLOAT16, (2, 8, 128, 64))],
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_bfloat16(self) -> None:
"""Test FlexAttention shape inference with BFLOAT16 type."""
graph = self._make_graph(
[
("Q", TensorProto.BFLOAT16, (2, 8, 128, 64)),
("K", TensorProto.BFLOAT16, (2, 8, 256, 64)),
("V", TensorProto.BFLOAT16, (2, 8, 256, 64)),
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.BFLOAT16, (2, 8, 128, 64))],
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_double(self) -> None:
"""Test FlexAttention shape inference with DOUBLE type."""
graph = self._make_graph(
[
("Q", TensorProto.DOUBLE, (2, 8, 128, 64)),
("K", TensorProto.DOUBLE, (2, 8, 256, 64)),
("V", TensorProto.DOUBLE, (2, 8, 256, 64)),
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.DOUBLE, (2, 8, 128, 64))],
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_with_scale(self) -> None:
"""Test FlexAttention with explicit scale attribute."""
graph = self._make_graph(
[
("Q", TensorProto.FLOAT, (2, 8, 128, 64)),
("K", TensorProto.FLOAT, (2, 8, 256, 64)),
("V", TensorProto.FLOAT, (2, 8, 256, 64)),
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
scale=0.125,
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.FLOAT, (2, 8, 128, 64))],
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_with_score_mod(self) -> None:
"""Test FlexAttention with score_mod subgraph."""
# The whole graph (including the score_mod subgraph) is expressed in one parse_graph call.
graph = parse_graph("""
agraph (float[2,8,128,64] Q, float[2,8,256,64] K, float[2,8,256,64] V) => (Y)
{
Y = ai.onnx.preview.FlexAttention (Q, K, V) <
score_mod = score_mod (float[B,Hq,L,S] scores) => (float[B,Hq,L,S] scores_out) {
scores_out = Identity(scores)
}
>
}
""")
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.FLOAT, (2, 8, 128, 64))],
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_with_prob_mod(self) -> None:
"""Test FlexAttention with prob_mod subgraph."""
# The whole graph (including the prob_mod subgraph) is expressed in one parse_graph call.
graph = parse_graph("""
agraph (float[2,8,128,64] Q, float[2,8,256,64] K, float[2,8,256,64] V) => (Y)
{
Y = ai.onnx.preview.FlexAttention (Q, K, V) <
prob_mod = prob_mod (float[B,Hq,L,S] probs) => (float[B,Hq,L,S] probs_out) {
probs_out = Identity(probs)
}
>
}
""")
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.FLOAT, (2, 8, 128, 64))],
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_with_all_modifiers(self) -> None:
"""Test FlexAttention with all modifier subgraphs."""
# The whole graph (including the score_mod and prob_mod subgraphs) is expressed in one parse_graph call.
graph = parse_graph("""
agraph (float[2,8,128,64] Q, float[2,8,256,64] K, float[2,8,256,64] V) => (Y)
{
Y = ai.onnx.preview.FlexAttention (Q, K, V) <
score_mod = score_mod (float[B,Hq,L,S] scores) => (float[B,Hq,L,S] scores_out) {
scores_out = Identity(scores)
},
prob_mod = prob_mod (float[B,Hq,L,S] probs) => (float[B,Hq,L,S] probs_out) {
probs_out = Identity(probs)
}
>
}
""")
self._assert_inferred(
graph,
[make_tensor_value_info("Y", TensorProto.FLOAT, (2, 8, 128, 64))],
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_rank_not_4_fails(self) -> None:
"""Test FlexAttention fails when input rank is not 4."""
graph = self._make_graph(
[
("Q", TensorProto.FLOAT, (2, 8, 64)), # rank 3 instead of 4
("K", TensorProto.FLOAT, (2, 8, 256, 64)),
("V", TensorProto.FLOAT, (2, 8, 256, 64)),
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_mismatched_elem_type_fails(self) -> None:
"""Test FlexAttention fails when Q, K, V have different element types."""
graph = self._make_graph(
[
("Q", TensorProto.FLOAT, (2, 8, 128, 64)),
("K", TensorProto.FLOAT16, (2, 8, 256, 64)), # different type
("V", TensorProto.FLOAT, (2, 8, 256, 64)),
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_gqa_not_divisible_fails(self) -> None:
"""Test FlexAttention fails when Hq is not divisible by Hkv."""
graph = self._make_graph(
[
("Q", TensorProto.FLOAT, (2, 8, 128, 64)),
("K", TensorProto.FLOAT, (2, 3, 256, 64)), # 8 % 3 != 0
("V", TensorProto.FLOAT, (2, 3, 256, 64)),
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_mismatched_kv_seq_len_fails(self) -> None:
"""Test FlexAttention fails when K and V have different sequence lengths."""
graph = self._make_graph(
[
("Q", TensorProto.FLOAT, (2, 8, 128, 64)),
("K", TensorProto.FLOAT, (2, 8, 256, 64)),
("V", TensorProto.FLOAT, (2, 8, 512, 64)), # S_v=512 != S_k=256
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_mismatched_kv_heads_fails(self) -> None:
"""Test FlexAttention fails when K and V have different head counts."""
graph = self._make_graph(
[
("Q", TensorProto.FLOAT, (2, 8, 128, 64)),
("K", TensorProto.FLOAT, (2, 8, 256, 64)),
("V", TensorProto.FLOAT, (2, 4, 256, 64)), # H_v=4 != H_k=8
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_flex_attention_mismatched_qk_head_size_fails(self) -> None:
"""Test FlexAttention fails when Q and K have different head sizes."""
graph = self._make_graph(
[
("Q", TensorProto.FLOAT, (2, 8, 128, 64)),
("K", TensorProto.FLOAT, (2, 8, 256, 128)), # Dqk=128 != 64
("V", TensorProto.FLOAT, (2, 8, 256, 64)),
],
[
make_node(
"FlexAttention",
["Q", "K", "V"],
["Y"],
domain=AI_ONNX_PREVIEW_DOMAIN,
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[
make_opsetid(ONNX_DOMAIN, 21),
make_opsetid(AI_ONNX_PREVIEW_DOMAIN, 1),
],
)
def test_average_pool_auto_pads(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (30, 4, 7, 6, 4))],
[
make_node(
"AveragePool",
["x"],
"z",
auto_pad="SAME_UPPER",
kernel_shape=[4, 3, 2],
strides=[2, 2, 1],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (30, 4, 4, 3, 4))]
)
def test_average_pool_with_dilations(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"AveragePool", ["X"], ["Y"], kernel_shape=[2, 2], dilations=[2, 2]
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 2, 2))]
)
def test_average_pool_with_same_upper_padding_and_stride_and_dilation(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"AveragePool",
["X"],
["Y"],
auto_pad="SAME_UPPER",
kernel_shape=[2, 2],
strides=[2, 2],
dilations=[2, 3],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 2, 2))]
)
def test_relu(self) -> None:
self._identity_prop("Relu")
def test_identity(self) -> None:
self._identity_prop("Identity")
def test_identity_sequence(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, 4)),
("input2", TensorProto.FLOAT, (2, 3, 4)),
("input3", TensorProto.FLOAT, (2, 5, 4)),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node("Identity", ["in_sequence"], ["output_sequence"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, None, 4)
),
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (2, None, 4)
),
],
)
def test_identity_optional(self) -> None:
graph = self._make_graph(
[("in_tensor", TensorProto.FLOAT, (2, 3, 4))],
[
make_node("Optional", ["in_tensor"], ["in_optional"]),
make_node("Identity", ["in_optional"], ["output_optional"]),
],
[],
)
tensor_type_proto = helper.make_tensor_type_proto(TensorProto.FLOAT, (2, 3, 4))
optional_type_proto = helper.make_optional_type_proto(tensor_type_proto)
self._assert_inferred(
graph,
[
helper.make_value_info("in_optional", optional_type_proto),
helper.make_value_info("output_optional", optional_type_proto),
],
)
def test_identity_optional_sequence(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, 4)),
("input2", TensorProto.FLOAT, (2, 3, 4)),
("input3", TensorProto.FLOAT, (2, 5, 4)),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node("Optional", ["in_sequence"], ["in_optional"]),
make_node("Identity", ["in_optional"], ["output_optional"]),
],
[],
)
tensor_type_proto = helper.make_tensor_type_proto(
TensorProto.FLOAT, (2, None, 4)
)
sequence_type_proto = helper.make_sequence_type_proto(tensor_type_proto)
optional_type_proto = helper.make_optional_type_proto(sequence_type_proto)
self._assert_inferred(
graph,
[
helper.make_value_info("in_sequence", sequence_type_proto),
helper.make_value_info("in_optional", optional_type_proto),
helper.make_value_info("output_optional", optional_type_proto),
],
)
def test_add(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, 5)),
("y", TensorProto.FLOAT, (30, 4, 5)),
],
[make_node("Add", ["x", "y"], "z")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (30, 4, 5))]
)
def test_pow(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, 5)),
("y", TensorProto.FLOAT, (30, 4, 5)),
],
[make_node("Pow", ["x", "y"], "z")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (30, 4, 5))]
)
def test_bitshift(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.UINT32, (2, 3, 1)),
("y", TensorProto.UINT32, (2, 3, 1)),
],
[make_node("BitShift", ["x", "y"], "z", direction="RIGHT")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.UINT32, (2, 3, 1))]
)
def test_bitshift_broadcast_to_first(self) -> None:
graph = self._make_graph(
[("x", TensorProto.UINT32, (16, 4, 1)), ("y", TensorProto.UINT32, (1,))],
[make_node("BitShift", ["x", "y"], "z", direction="RIGHT")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.UINT32, (16, 4, 1))]
)
def test_bitshift_broadcast_to_second(self) -> None:
graph = self._make_graph(
[("x", TensorProto.UINT32, (1,)), ("y", TensorProto.UINT32, (2, 3, 1))],
[make_node("BitShift", ["x", "y"], "z", direction="RIGHT")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.UINT32, (2, 3, 1))]
)
def test_sum_single(self) -> None:
self._identity_prop("Sum")
def test_sum_multi(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 4, 5)),
("y", TensorProto.FLOAT, (30, 4, 5)),
("z", TensorProto.FLOAT, (30, 4, 5)),
],
[make_node("Sum", ["x", "y", "z"], ["out"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (30, 4, 5))]
)
def test_sum_multi_broadcasting(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (30, 1, 5)),
("y", TensorProto.FLOAT, ("a", 4, 1)),
("z", TensorProto.FLOAT, (4, "b")),
],
[make_node("Sum", ["x", "y", "z"], ["out"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (30, 4, 5))]
)
def test_sum_broadcasting_param(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, ("a", 1, 5)),
("y", TensorProto.FLOAT, ("a", 4, 1)),
],
[make_node("Sum", ["x", "y"], ["out"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, ("a", 4, 5))]
)
def test_random_normal(self) -> None:
graph = self._make_graph(
[],
[
make_node(
"RandomNormal",
[],
["out"],
dtype=TensorProto.DOUBLE,
shape=(3, 4, 5),
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.DOUBLE, (3, 4, 5))]
)
def test_random_normal_like(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (2, 3, 4))],
[make_node("RandomNormalLike", ["X"], ["out"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (2, 3, 4))]
)
def test_random_normal_like_with_dtype(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (2, 3, 4))],
[
make_node(
"RandomNormalLike",
["X"],
["out"],
dtype=TensorProto.DOUBLE,
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.DOUBLE, (2, 3, 4))]
)
def test_bernoulli(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4))],
[make_node("Bernoulli", ["x"], ["out"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (3, 4))]
)
def test_bernoulli_with_dtype(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3, 4))],
[
make_node(
"Bernoulli",
["x"],
["out"],
dtype=TensorProto.DOUBLE,
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.DOUBLE, (2, 3, 4))]
)
def _logical_binary_op(self, op: str, input_type: TensorProto.DataType) -> None:
graph = self._make_graph(
[("x", input_type, (30, 4, 5)), ("y", input_type, (30, 4, 5))],
[make_node(op, ["x", "y"], "z")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.BOOL, (30, 4, 5))]
)
def _logical_binary_op_with_broadcasting(
self, op: str, input_type: TensorProto.DataType
) -> None:
graph = self._make_graph(
[("x", input_type, (1, 5)), ("y", input_type, (30, 4, 5))],
[make_node(op, ["x", "y"], "z")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.BOOL, (30, 4, 5))]
)
def test_logical_and(self) -> None:
self._logical_binary_op("And", TensorProto.BOOL)
self._logical_binary_op_with_broadcasting("And", TensorProto.BOOL)
def test_logical_or(self) -> None:
self._logical_binary_op("Or", TensorProto.BOOL)
self._logical_binary_op_with_broadcasting("Or", TensorProto.BOOL)
def test_logical_xor(self) -> None:
self._logical_binary_op("Xor", TensorProto.BOOL)
self._logical_binary_op_with_broadcasting("Xor", TensorProto.BOOL)
def test_greater(self) -> None:
self._logical_binary_op("Greater", TensorProto.BOOL)
self._logical_binary_op_with_broadcasting("Greater", TensorProto.BOOL)
def test_less(self) -> None:
self._logical_binary_op("Less", TensorProto.BOOL)
self._logical_binary_op_with_broadcasting("Less", TensorProto.BOOL)
def test_equal(self) -> None:
self._logical_binary_op("Equal", TensorProto.BOOL)
self._logical_binary_op_with_broadcasting("Equal", TensorProto.BOOL)
def test_equal_string(self) -> None:
self._logical_binary_op("Equal", TensorProto.STRING)
self._logical_binary_op_with_broadcasting("Equal", TensorProto.STRING)
def test_logical_not(self) -> None:
graph = self._make_graph(
[("x", TensorProto.BOOL, (30, 4, 5))], [make_node("Not", ["x"], "z")], []
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.BOOL, (30, 4, 5))]
)
def test_less_or_equal(self) -> None:
self._logical_binary_op("LessOrEqual", TensorProto.BOOL)
self._logical_binary_op_with_broadcasting("LessOrEqual", TensorProto.BOOL)
def test_greater_or_equal(self) -> None:
self._logical_binary_op("GreaterOrEqual", TensorProto.BOOL)
self._logical_binary_op_with_broadcasting("GreaterOrEqual", TensorProto.BOOL)
def test_flatten(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3, 4, 5))],
[make_node("Flatten", ["x"], ["z"], axis=2)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (6, 20))]
)
def test_flatten_default_axis(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3, 4, 5))],
[make_node("Flatten", ["x"], ["z"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (2, 60))]
)
def test_flatten_zero_axis(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3, 4, 5))],
[make_node("Flatten", ["x"], ["z"], axis=0)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (1, 120))]
)
def test_flatten_unknown_dim(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, "N", 4, 5))],
[make_node("Flatten", ["x"], ["z"], axis=2)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (None, 20))]
)
def test_space_to_depth(self) -> None:
b = 10
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3, 100, 100))],
[make_node("SpaceToDepth", ["x"], ["z"], blocksize=b)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (2, 300, 10, 10))]
)
def test_space_to_depth_unknown_dim(self) -> None:
b = 10
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, "N", 100, 100))],
[make_node("SpaceToDepth", ["x"], ["z"], blocksize=b)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (2, None, 10, 10))]
)
def test_depth_to_space(self) -> None:
b = 10
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 300, 10, 10))],
[make_node("DepthToSpace", ["x"], ["z"], blocksize=b, mode="DCR")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (2, 3, 100, 100))]
)
def _rnn_forward(
self, seqlen: int, batchsize: int, inpsize: int, hiddensize: int
) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (seqlen, batchsize, inpsize)),
("w", TensorProto.FLOAT, (1, hiddensize, inpsize)),
("r", TensorProto.FLOAT, (1, hiddensize, hiddensize)),
],
[
make_node(
"RNN", ["x", "w", "r"], ["all", "last"], hidden_size=hiddensize
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"all", TensorProto.FLOAT, (seqlen, 1, batchsize, hiddensize)
),
make_tensor_value_info(
"last", TensorProto.FLOAT, (1, batchsize, hiddensize)
),
],
)
def test_rnn_forward(self) -> None:
self._rnn_forward(64, 32, 10, 4)
def _rnn_bidirectional(
self, seqlen: int, batchsize: int, inpsize: int, hiddensize: int
) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (seqlen, batchsize, inpsize)),
("w", TensorProto.FLOAT, (2, hiddensize, inpsize)),
("r", TensorProto.FLOAT, (2, hiddensize, hiddensize)),
],
[
make_node(
"RNN",
["x", "w", "r"],
["all", "last"],
hidden_size=hiddensize,
direction="bidirectional",
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"all", TensorProto.FLOAT, (seqlen, 2, batchsize, hiddensize)
),
make_tensor_value_info(
"last", TensorProto.FLOAT, (2, batchsize, hiddensize)
),
],
)
def test_rnn_layout(self) -> None:
self._rnn_layout(64, 32, 10, 4)
self._rnn_layout(64, 32, 10, 4, "bidirectional")
def _rnn_layout(
self,
seqlen: int,
batchsize: int,
inpsize: int,
hiddensize: int,
direction: str = "forward",
) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (batchsize, seqlen, inpsize)),
("w", TensorProto.FLOAT, (1, hiddensize, inpsize)),
("r", TensorProto.FLOAT, (1, hiddensize, hiddensize)),
],
[
make_node(
"RNN",
["x", "w", "r"],
["all", "last"],
hidden_size=hiddensize,
layout=1,
direction=direction,
)
],
[],
)
if direction == "bidirectional":
num_directions = 2
else:
num_directions = 1
self._assert_inferred(
graph,
[
make_tensor_value_info(
"all",
TensorProto.FLOAT,
(batchsize, seqlen, num_directions, hiddensize),
),
make_tensor_value_info(
"last", TensorProto.FLOAT, (batchsize, num_directions, hiddensize)
),
],
)
def test_rnn_bidirectional(self) -> None:
self._rnn_bidirectional(64, 32, 10, 4)
def _lstm_forward(
self, seqlen: int, batchsize: int, inpsize: int, hiddensize: int
) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (seqlen, batchsize, inpsize)),
("w", TensorProto.FLOAT, (1, 4 * hiddensize, inpsize)),
("r", TensorProto.FLOAT, (1, 4 * hiddensize, hiddensize)),
],
[
make_node(
"LSTM",
["x", "w", "r"],
["all", "hidden", "last"],
hidden_size=hiddensize,
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"all", TensorProto.FLOAT, (seqlen, 1, batchsize, hiddensize)
),
make_tensor_value_info(
"hidden", TensorProto.FLOAT, (1, batchsize, hiddensize)
),
make_tensor_value_info(
"last", TensorProto.FLOAT, (1, batchsize, hiddensize)
),
],
)
def test_lstm_forward(self) -> None:
self._lstm_forward(64, 32, 10, 4)
def test_rnn_opset1_to_6_invalid_input_rank(self) -> None:
# RNNShapeInference_opset1_to_6 must reject non-rank-3 input X and raise
# InferenceError rather than accessing dim(0)/dim(1) out-of-bounds.
for op, w_shape, r_shape in [
("RNN", (1, 4, 5), (1, 4, 4)),
("GRU", (1, 12, 5), (1, 12, 4)),
("LSTM", (1, 16, 5), (1, 16, 4)),
]:
graph = helper.make_graph(
[make_node(op, ["x", "w", "r"], [], hidden_size=4)],
"test",
[
make_tensor_value_info("x", TensorProto.FLOAT, (4, 5)),
make_tensor_value_info("w", TensorProto.FLOAT, w_shape),
make_tensor_value_info("r", TensorProto.FLOAT, r_shape),
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph, opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 6)]
)
def test_topk_default_axis(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4, 5, 10))],
[make_node("TopK", ["x", "k"], ["y", "z"])],
[],
initializer=[make_tensor("k", TensorProto.INT64, (1,), (2,))],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.FLOAT, (3, 4, 5, 2)),
make_tensor_value_info("z", TensorProto.INT64, (3, 4, 5, 2)),
],
)
def test_topk(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4, 5, 10))],
[make_node("TopK", ["x", "k"], ["y", "z"], axis=2)],
[],
initializer=[make_tensor("k", TensorProto.INT64, (1,), (2,))],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.FLOAT, (3, 4, 2, 10)),
make_tensor_value_info("z", TensorProto.INT64, (3, 4, 2, 10)),
],
)
def test_topk_raw_data(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4, 5, 10))],
[make_node("TopK", ["x", "k"], ["y", "z"], axis=2)],
[],
initializer=[
make_tensor(
"k",
TensorProto.INT64,
(1,),
vals=np.array([3], dtype="<i8").tobytes(),
raw=True,
)
],
) # Feed raw bytes (force little endian ordering like onnx standard) for test purpose
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.FLOAT, (3, 4, 3, 10)),
make_tensor_value_info("z", TensorProto.INT64, (3, 4, 3, 10)),
],
)
def test_topk_missing_k_value_output_rank_check(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4, 5, 10)), ("k", TensorProto.INT64, (1,))],
[make_node("TopK", ["x", "k"], ["y", "z"], axis=2)],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"y", TensorProto.FLOAT, (None, None, None, None)
),
make_tensor_value_info(
"z", TensorProto.INT64, (None, None, None, None)
),
],
)
def test_gemm(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (7, 5)),
("y", TensorProto.FLOAT, (5, 11)),
("z", TensorProto.FLOAT, None),
],
[make_node("Gemm", ["x", "y", "z"], ["out"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (7, 11))]
)
def test_gemm_transA(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (5, 7)),
("y", TensorProto.FLOAT, (5, 11)),
("z", TensorProto.FLOAT, None),
],
[make_node("Gemm", ["x", "y", "z"], ["out"], transA=1)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (7, 11))]
)
def test_gemm_transB(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (7, 5)),
("y", TensorProto.FLOAT, (11, 5)),
("z", TensorProto.FLOAT, None),
],
[make_node("Gemm", ["x", "y", "z"], ["out"], transB=1)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (7, 11))]
)
def test_gemm_transA_and_transB(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (5, 7)),
("y", TensorProto.FLOAT, (11, 5)),
("z", TensorProto.FLOAT, None),
],
[make_node("Gemm", ["x", "y", "z"], ["out"], transA=1, transB=1)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (7, 11))]
)
def test_gemm_no_bias(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (13, 7)), ("y", TensorProto.FLOAT, (7, 17))],
[make_node("Gemm", ["x", "y"], ["out"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (13, 17))]
)
def test_reduce_op_shape_2_axis_opset13(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (24, 4, 11))],
[make_node("ReduceL1", "x", "y", axes=(1, 2), keepdims=0)],
[],
initializer=[make_tensor("axes", TensorProto.INT64, (2,), (1, 2))],
)
operatorsetid = OperatorSetIdProto()
operatorsetid.domain = ""
operatorsetid.version = 13
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (24,))],
opset_imports=[operatorsetid],
)
def test_reduce_op_shape_2_axis_opset18(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (24, 4, 11)), ("axes", TensorProto.INT64, (2,))],
[make_node("ReduceL1", ["x", "axes"], "y", keepdims=0)],
[],
initializer=[make_tensor("axes", TensorProto.INT64, (2,), (1, 2))],
)
operatorsetid = OperatorSetIdProto()
operatorsetid.domain = ""
operatorsetid.version = 18
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (24,))],
opset_imports=[operatorsetid],
)
def test_reduce_op_empty_set_opset13(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (24, 0, 11))],
[make_node("ReduceL1", "x", "y", axes=(1,), keepdims=1)],
[],
initializer=[],
)
operatorsetid = OperatorSetIdProto(domain="", version=13)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (24, 1, 11))],
opset_imports=[operatorsetid],
)
def test_reduce_op_empty_set_opset18(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (24, 0, 11)), ("axes", TensorProto.INT64, (1,))],
[make_node("ReduceL1", ["x", "axes"], "y", keepdims=1)],
[],
initializer=[make_tensor("axes", TensorProto.INT64, (1,), (1,))],
)
operatorsetid = OperatorSetIdProto(domain="", version=18)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (24, 1, 11))],
opset_imports=[operatorsetid],
)
def test_reduce_op_shape_keep_dims_opset13(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (24, 4, 11))],
[make_node("ReduceL1", "x", "y", axes=(1, 2), keepdims=1)],
[],
initializer=[make_tensor("axes", TensorProto.INT64, (2,), (1, 2))],
)
operatorsetid = OperatorSetIdProto()
operatorsetid.domain = ""
operatorsetid.version = 13
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (24, 1, 1))],
opset_imports=[operatorsetid],
)
def test_reduce_op_shape_keep_dims_opset18(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (24, 4, 11)), ("axes", TensorProto.INT64, (2,))],
[make_node("ReduceL1", ["x", "axes"], "y", keepdims=1)],
[],
initializer=[make_tensor("axes", TensorProto.INT64, (2,), (1, 2))],
)
operatorsetid = OperatorSetIdProto()
operatorsetid.domain = ""
operatorsetid.version = 18
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (24, 1, 1))],
opset_imports=[operatorsetid],
)
def test_reduce_op_shape_default_value(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (24, 4, 11))],
[make_node("ReduceL1", "x", "y")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (1, 1, 1))]
)
def test_reduce_op_shape_no_axes_do_not_keep_dims(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (24, 4, 11))],
[make_node("ReduceL1", "x", "y", keepdims=0)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, ())]
)
def test_reduce_op_shape_negative_axis(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (24, 4, 11)), ("axes", TensorProto.INT64, (2,))],
[make_node("ReduceL1", ["x", "axes"], "y")],
[],
initializer=[make_tensor("axes", TensorProto.INT64, (2,), (-1, -2))],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (24, 1, 1))]
)
def test_argmax_shape(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (24, 4, 11))],
[make_node("ArgMax", "x", "y", axis=1, keepdims=1)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.INT64, (24, 1, 11))]
)
def test_argmax_shape_keepdims(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (24, 4, 11))],
[make_node("ArgMax", "x", "y", axis=0, keepdims=0)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.INT64, (4, 11))]
)
def test_argmax_shape_default_value(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (24, 4, 11))], [make_node("ArgMax", "x", "y")], []
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.INT64, (1, 4, 11))]
)
def test_argmax_shape_negative_axis(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (24, 4, 11))],
[make_node("ArgMax", "x", "y", axis=-2)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.INT64, (24, 1, 11))]
)
def test_dropout(self) -> None:
graph = self._make_graph(
[
(
"data",
TensorProto.FLOAT,
(
3,
4,
5,
),
),
("ratio", TensorProto.FLOAT, ()),
],
[make_node("Dropout", ["data", "ratio"], ["out"])],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"out",
TensorProto.FLOAT,
(
3,
4,
5,
),
)
],
)
def test_LRN(self) -> None:
self._identity_prop("LRN", alpha=0.5, beta=0.5, size=1)
def test_batch_norm(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 4, 5, 6, 7)),
("scale", TensorProto.FLOAT, (4,)),
("b", TensorProto.FLOAT, (4,)),
("mean", TensorProto.FLOAT, (4,)),
("var", TensorProto.FLOAT, (4,)),
],
[
make_node(
"BatchNormalization", ["x", "scale", "b", "mean", "var"], ["out"]
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (3, 4, 5, 6, 7))]
)
def test_batch_norm_rank1(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (128,)), # 1-dimensional permitted
("scale", TensorProto.FLOAT, (1,)),
("b", TensorProto.FLOAT, (1,)),
("mean", TensorProto.FLOAT, (1,)),
("var", TensorProto.FLOAT, (1,)),
],
[
make_node(
"BatchNormalization", ["x", "scale", "b", "mean", "var"], ["out"]
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (128,))]
)
def test_batch_norm_invalid(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (128,)),
("scale", TensorProto.FLOAT, (1, 2)), # invalid rank
("b", TensorProto.FLOAT, (1,)),
("mean", TensorProto.FLOAT, (1,)),
("var", TensorProto.FLOAT, (1,)),
],
[
make_node(
"BatchNormalization", ["x", "scale", "b", "mean", "var"], ["out"]
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_split_negative_axis(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4))],
[make_node("Split", ["x"], ["y", "z"], axis=-1, num_outputs=2)],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.FLOAT, (2, 2)),
make_tensor_value_info("z", TensorProto.FLOAT, (2, 2)),
],
)
def test_split_with_split_attribute(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 4)), ("split", TensorProto.INT64, (2,))],
[make_node("Split", ["x", "split"], ["y", "z"], axis=1)],
[],
initializer=[make_tensor("split", TensorProto.INT64, (2,), (3, 1))],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.FLOAT, (2, 3)),
make_tensor_value_info("z", TensorProto.FLOAT, (2, 1)),
],
)
def test_split_with_split_attribute_unknown_split_dim(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (2, "a", "b")),
("split", TensorProto.INT64, (2,)),
],
[make_node("Split", ["x", "split"], ["y", "z"], axis=1)],
[],
initializer=[make_tensor("split", TensorProto.INT64, (2,), (3, 1))],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.FLOAT, (2, None, "b")),
make_tensor_value_info("z", TensorProto.FLOAT, (2, None, "b")),
],
)
def test_split_from_GLU(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (5, 6, 7))],
[make_node("Split", ["x"], ["y", "z"], axis=1, num_outputs=2)],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.FLOAT, (5, 3, 7)),
make_tensor_value_info("z", TensorProto.FLOAT, (5, 3, 7)),
],
)
def test_split_uneven_split_2d(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (8, 2))],
[make_node("Split", ["x"], ["y", "z", "a"], axis=0, num_outputs=3)],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.FLOAT, (3, 2)),
make_tensor_value_info("z", TensorProto.FLOAT, (3, 2)),
make_tensor_value_info("a", TensorProto.FLOAT, (2, 2)),
],
)
def test_split_uneven_split_3d(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 7, 3))],
[make_node("Split", ["x"], ["y", "z", "a"], axis=1, num_outputs=3)],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.FLOAT, (2, 3, 3)),
make_tensor_value_info("z", TensorProto.FLOAT, (2, 3, 3)),
make_tensor_value_info("a", TensorProto.FLOAT, (2, 1, 3)),
],
)
def test_GLU_partial(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (5, 6, 7))],
[
make_node("Split", ["x"], ["y", "z"], axis=1, num_outputs=2),
make_node("Sigmoid", ["z"], ["a"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.FLOAT, (5, 3, 7)),
make_tensor_value_info("z", TensorProto.FLOAT, (5, 3, 7)),
make_tensor_value_info("a", TensorProto.FLOAT, (5, 3, 7)),
],
)
def test_GLU(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (5, 6, 7))],
[
make_node("Split", ["x"], ["y", "z"], axis=1, num_outputs=2),
make_node("Sigmoid", ["z"], ["a"]),
make_node("Mul", ["y", "a"], ["b"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.FLOAT, (5, 3, 7)),
make_tensor_value_info("z", TensorProto.FLOAT, (5, 3, 7)),
make_tensor_value_info("a", TensorProto.FLOAT, (5, 3, 7)),
make_tensor_value_info("b", TensorProto.FLOAT, (5, 3, 7)),
],
)
def test_softmax_2d(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 5))], [make_node("Softmax", ["x"], "z")], []
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (4, 5))]
)
def test_softmax_3d(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 5, 6))],
[make_node("Softmax", ["x"], "z")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (4, 5, 6))]
)
def test_softmax_scalar_invalid(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, ())], [make_node("Softmax", ["x"], "z")], []
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_hardmax_2d(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 5))], [make_node("Hardmax", ["x"], "z")], []
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (4, 5))]
)
def test_hardmax_3d(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 5, 6))],
[make_node("Hardmax", ["x"], "z")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (4, 5, 6))]
)
def test_hardmax_scalar_invalid(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, ())], [make_node("Hardmax", ["x"], "z")], []
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_logsoftmax_2d(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 5))],
[make_node("LogSoftmax", ["x"], "z")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (4, 5))]
)
def test_logsoftmax_3d(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 5, 6))],
[make_node("LogSoftmax", ["x"], "z")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (4, 5, 6))]
)
def test_logsoftmax_scalar_invalid(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, ())], [make_node("LogSoftmax", ["x"], "z")], []
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_logsoftmax_3d_negative_axis(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 5, 6))],
[make_node("LogSoftmax", ["x"], "z", axis=-1)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (4, 5, 6))]
)
def test_maxpool(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[make_node("MaxPool", ["X"], ["Y"], kernel_shape=[2, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 3, 3))]
)
def test_maxpool_with_indices(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[make_node("MaxPool", ["X"], ["Y", "Z"], kernel_shape=[2, 2])],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 3, 3)),
make_tensor_value_info("Z", TensorProto.INT64, (5, 3, 3, 3)),
],
)
def test_maxpool_3D(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4, 4))],
[make_node("MaxPool", ["X"], ["Y"], kernel_shape=[2, 2, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 3, 3, 3))]
)
def test_maxpool_with_padding(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"MaxPool", ["X"], ["Y"], kernel_shape=[2, 2], pads=[1, 1, 2, 2]
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 6, 6))]
)
def test_maxpool_with_padding_and_stride(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"MaxPool",
["X"],
["Y"],
kernel_shape=[2, 2],
pads=[1, 1, 2, 2],
strides=[2, 2],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 3, 3))]
)
@pytest.mark.parametrize("version", all_versions_for("MaxPool"))
def test_maxpool_zero_strides(self, version: int) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"MaxPool",
["X"],
["Y"],
kernel_shape=[2, 2],
strides=[0, 2],
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("MaxPool"))
def test_maxpool_negative_strides(self, version: int) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"MaxPool",
["X"],
["Y"],
kernel_shape=[2, 2],
strides=[-1, 2],
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
def test_maxpool_with_floor_mode(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (32, 288, 35, 35))],
[
make_node(
"MaxPool",
["X"],
["Y"],
kernel_shape=[2, 2],
strides=[2, 2],
ceil_mode=False,
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (32, 288, 17, 17))]
)
def test_maxpool_with_ceil_mode(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (32, 288, 35, 35))],
[
make_node(
"MaxPool",
["X"],
["Y"],
kernel_shape=[2, 2],
strides=[2, 2],
ceil_mode=True,
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (32, 288, 18, 18))]
)
def test_maxpool_ceil(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (1, 1, 4, 4))],
[
make_node(
"MaxPool",
["X"],
["Y"],
kernel_shape=[3, 3],
strides=[2, 2],
ceil_mode=True,
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (1, 1, 2, 2))]
)
def test_maxpool_with_dilations(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[make_node("MaxPool", ["X"], ["Y"], kernel_shape=[2, 2], dilations=[2, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 2, 2))]
)
def test_maxpool_with_same_upper_padding_and_stride(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"MaxPool",
["X"],
["Y"],
auto_pad="SAME_UPPER",
kernel_shape=[2, 2],
strides=[2, 2],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 2, 2))]
)
def test_maxpool_with_same_upper_padding_and_stride_and_dilation(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"MaxPool",
["X"],
["Y"],
auto_pad="SAME_UPPER",
kernel_shape=[2, 2],
strides=[2, 2],
dilations=[2, 3],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 2, 2))]
)
def test_maxpool_with_same_upper_padding_and_stride_one(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"MaxPool",
["X"],
["Y"],
auto_pad="SAME_UPPER",
kernel_shape=[2, 2],
strides=[1, 1],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 4, 4))]
)
def test_maxpool_with_same_lower_padding_and_stride(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 9, 9))],
[
make_node(
"MaxPool",
["X"],
["Y"],
auto_pad="SAME_LOWER",
kernel_shape=[2, 2],
strides=[2, 2],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 5, 5))]
)
def test_maxpool_with_same_lower_padding_and_stride_and_dilation(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 9, 9))],
[
make_node(
"MaxPool",
["X"],
["Y"],
auto_pad="SAME_LOWER",
kernel_shape=[2, 2],
strides=[2, 2],
dilations=[2, 3],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 5, 5))]
)
def test_maxpool_with_same_lower_padding_and_big_stride(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"MaxPool",
["X"],
["Y"],
auto_pad="SAME_LOWER",
kernel_shape=[2, 2],
strides=[4, 4],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 1, 1))]
)
def test_averagepool(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[make_node("AveragePool", ["X"], ["Y"], kernel_shape=[2, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 3, 3))]
)
def test_averagepool_3D(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4, 4))],
[make_node("AveragePool", ["X"], ["Y"], kernel_shape=[2, 2, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 3, 3, 3))]
)
def test_averagepool_with_padding(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"AveragePool", ["X"], ["Y"], kernel_shape=[2, 2], pads=[1, 1, 2, 2]
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 6, 6))]
)
def test_averagepool_with_padding_and_stride(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"AveragePool",
["X"],
["Y"],
kernel_shape=[2, 2],
pads=[1, 1, 2, 2],
strides=[2, 2],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 3, 3))]
)
@pytest.mark.parametrize("version", all_versions_for("AveragePool"))
def test_averagepool_zero_strides(self, version: int) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"AveragePool",
["X"],
["Y"],
kernel_shape=[2, 2],
strides=[2, 0],
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("AveragePool"))
def test_averagepool_negative_strides(self, version: int) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"AveragePool",
["X"],
["Y"],
kernel_shape=[2, 2],
strides=[2, -1],
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
def test_averagepool_ceil(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (1, 1, 4, 4))],
[
make_node(
"AveragePool",
["X"],
["Y"],
kernel_shape=[3, 3],
strides=[2, 2],
ceil_mode=True,
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (1, 1, 2, 2))]
)
def test_lppool(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[make_node("LpPool", ["X"], ["Y"], kernel_shape=[2, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 3, 3))]
)
def test_lppool_3D(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4, 4))],
[make_node("LpPool", ["X"], ["Y"], kernel_shape=[2, 2, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 3, 3, 3))]
)
def test_lppool_with_padding(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[make_node("LpPool", ["X"], ["Y"], kernel_shape=[2, 2], pads=[1, 1, 2, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 6, 6))]
)
def test_lppool_with_padding_and_stride(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"LpPool",
["X"],
["Y"],
kernel_shape=[2, 2],
pads=[1, 1, 2, 2],
strides=[2, 2],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 3, 3))]
)
def test_lppool_with_dilations(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[make_node("LpPool", ["X"], ["Y"], kernel_shape=[2, 2], dilations=[2, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 2, 2))]
)
def test_lppool_with_same_upper_padding_and_stride_and_dilation(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[
make_node(
"LpPool",
["X"],
["Y"],
auto_pad="SAME_UPPER",
kernel_shape=[2, 2],
strides=[2, 2],
dilations=[2, 3],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 2, 2))]
)
def test_roipool(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (5, 3, 4, 4)),
("rois", TensorProto.INT64, (2, 5)),
],
[make_node("MaxRoiPool", ["X", "rois"], ["Y"], pooled_shape=[2, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (2, 3, 2, 2))]
)
@pytest.mark.parametrize("version", all_versions_for("MaxRoiPool"))
def test_roipool_negative_pooled_shape(self, version: int) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (5, 3, 4, 4)),
("rois", TensorProto.INT64, (2, 5)),
],
[make_node("MaxRoiPool", ["X", "rois"], ["Y"], pooled_shape=[-1, 2])],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
def test_lp_norm(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4, 5, 6, 7))],
[make_node("LpNormalization", ["x"], ["out"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (3, 4, 5, 6, 7))]
)
def test_instance_norm(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 4, 5, 6, 7)),
("scale", TensorProto.FLOAT, (4,)),
("b", TensorProto.FLOAT, (4,)),
],
[make_node("InstanceNormalization", ["x", "scale", "b"], ["out"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (3, 4, 5, 6, 7))]
)
def test_global_maxpool(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[make_node("GlobalMaxPool", ["X"], ["Y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 1, 1))]
)
def test_global_averagepool(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[make_node("GlobalAveragePool", ["X"], ["Y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 1, 1))]
)
def test_global_lppool(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (5, 3, 4, 4))],
[make_node("GlobalLpPool", ["X"], ["Y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (5, 3, 1, 1))]
)
def test_conv_transpose(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48, 16, 16)),
("W", TensorProto.FLOAT, (48, 32, 3, 3)),
],
[make_node("ConvTranspose", ["X", "W"], "Y", strides=[2, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (25, 32, 33, 33))]
)
def test_conv_transpose_with_pads(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48, 16, 16)),
("W", TensorProto.FLOAT, (48, 32, 3, 3)),
],
[
make_node(
"ConvTranspose", ["X", "W"], "Y", strides=[2, 2], pads=[1, 1, 2, 2]
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (25, 32, 30, 30))]
)
def test_conv_transpose_with_output_shape(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48, 16, 16)),
("W", TensorProto.FLOAT, (48, 32, 3, 3)),
],
[
make_node(
"ConvTranspose",
["X", "W"],
"Y",
strides=[2, 2],
pads=[1, 1, 2, 2],
output_shape=[36, 36],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (25, 32, 36, 36))]
)
def test_conv_transpose_with_kernel_shape(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48, 16, 16)),
("W", TensorProto.FLOAT, (48, 32, None, None)),
],
[
make_node(
"ConvTranspose",
["X", "W"],
"Y",
kernel_shape=[3, 3],
strides=[2, 2],
pads=[1, 1, 2, 2],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (25, 32, 30, 30))]
)
def test_conv_transpose_with_dilations(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48, 16, 16)),
("W", TensorProto.FLOAT, (48, 32, 3, 3)),
],
[
make_node(
"ConvTranspose",
["X", "W"],
"Y",
strides=[2, 2],
pads=[1, 1, 2, 2],
dilations=[3, 3],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (25, 32, 34, 34))]
)
def test_conv_transpose_with_group(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48, 16, 16)),
("W", TensorProto.FLOAT, (48, 32, 3, 3)),
],
[
make_node(
"ConvTranspose",
["X", "W"],
"Y",
strides=[2, 2],
pads=[1, 1, 2, 2],
group=2,
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (25, 64, 30, 30))]
)
def test_conv_transpose_with_group_and_output_shape(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48, 16, 16)),
("W", TensorProto.FLOAT, (48, 32, 3, 3)),
],
[
make_node(
"ConvTranspose",
["X", "W"],
"Y",
strides=[2, 2],
pads=[1, 1, 2, 2],
group=2,
output_shape=[36, 36],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (25, 64, 36, 36))]
)
def test_conv_transpose_with_pads_and_auto_pads(self) -> None:
# This test should fail because pads cannot be used simultaneously with auto_pad
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (1, 1, 2, 2)),
("W", TensorProto.FLOAT, (1, 1, 3, 3)),
("B", TensorProto.FLOAT, (1,)),
],
[
make_node(
"ConvTranspose",
["X", "W", "B"],
"Y",
auto_pad="SAME_UPPER",
strides=[1, 1],
pads=[0, 1, 1, 0],
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
onnx.shape_inference.infer_shapes(
helper.make_model(graph),
strict_mode=True,
)
def test_conv_transpose_auto_pads(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48, 16, 16)),
("W", TensorProto.FLOAT, (48, 32, 3, 3)),
],
[
make_node(
"ConvTranspose",
["X", "W"],
"Y",
auto_pad="SAME_UPPER",
strides=[2, 2],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (25, 32, 32, 32))]
)
def test_mvn_function_output_shape(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (25, 48, 16, 16))],
[make_node("MeanVarianceNormalization", "X", "Y", axes=[0, 2, 3])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (25, 48, 16, 16))]
)
def test_scan(self) -> None:
# The whole graph (including the Scan body subgraph) is expressed in one parse_graph call;
# the Scan outputs are left untyped so that shape inference must compute their type/shape.
# The leading "" input is the (unused) optional sequence_lens input of opset-8 Scan.
# Test-input alteration: the body placeholders (loop_state_in/input/loop_state_out/output) were declared
# with an explicit UNDEFINED element type in the original make_graph version; the parser leaves them
# untyped, which shape inference fills identically.
graph = parse_graph("""
agraph (float[1, 3] loop_state_orig, float[1, sequence, 2] scan_input)
=> (loop_state_final, scan_output)
{
loop_state_final, scan_output = Scan ("", loop_state_orig, scan_input) <
num_scan_inputs = 1,
body = subgraph (loop_state_in, input) => (loop_state_out, output) {
loop_state_out = Identity(loop_state_in)
output = Identity(input)
}
>
}
""")
self._assert_inferred(
graph,
[
make_tensor_value_info("loop_state_final", TensorProto.FLOAT, (1, 3)),
make_tensor_value_info(
"scan_output", TensorProto.FLOAT, (1, "sequence", 2)
),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 8)],
)
@pytest.mark.parametrize(
"model_text",
[
pytest.param(
"""
<ir_version: 10, opset_import: ["" : 21]>
agraph (float[2] in0, float[3, 2] in1) => (out) {
out = Scan <num_scan_inputs = 9, body = b (float a, float x) => (float c) {
c = Add(a, x)
}> (in0, in1)
}
""",
id="opset21",
),
pytest.param(
"""
<ir_version: 8, opset_import: ["" : 9]>
agraph (float[2] in0, float[3, 2] in1) => (out) {
out = Scan <num_scan_inputs = 9, body = b (float a, float x) => (float c) {
c = Add(a, x)
}> (in0, in1)
}
""",
id="opset9",
),
pytest.param(
"""
<ir_version: 8, opset_import: ["" : 8]>
agraph (float[1, 3] ls, float[1, 2, 2] si) => (out) {
out = Scan <num_scan_inputs = 9, body = b (float a, float x) => (float c) {
c = Add(a, x)
}> ("", ls, si)
}
""",
id="opset8",
),
],
)
def test_scan_num_scan_inputs_out_of_range(self, model_text: str) -> None:
# num_scan_inputs > input count must raise, not underflow (opsets 8, 9, 21).
model = onnx.parser.parse_model(model_text)
with pytest.raises(
onnx.shape_inference.InferenceError, match="num_scan_inputs"
):
self._inferred(model)
@pytest.mark.parametrize(
("opset", "ir_version"),
[(21, 10), (9, 8)],
ids=["opset21", "opset9"],
)
def test_scan_loop_state_vars_exceed_outputs(
self, opset: int, ir_version: int
) -> None:
# More loop state vars than outputs must raise, not underflow (opsets 9, 21).
model = onnx.parser.parse_model(
f"""
<ir_version: {ir_version}, opset_import: ["" : {opset}]>
agraph (float[2] in0, float[2] in1, float[3, 2] in2) => (out) {{
out = Scan <num_scan_inputs = 1, body = b (float a, float s0, float s1) => (float c) {{
c = Identity(a)
}}> (in0, in1, in2)
}}
"""
)
with pytest.raises(
onnx.shape_inference.InferenceError, match="loop state variables"
):
self._inferred(model)
def test_scan_opset9(self) -> None:
# The whole graph (including the Scan body subgraph) is expressed in one parse_graph call;
# the Scan outputs are left untyped so that shape inference must compute their type/shape.
# Test-input alteration: the body placeholders (loop_state_in/input/loop_state_out/output) were declared
# with an explicit UNDEFINED element type in the original make_graph version; the parser leaves them
# untyped, which shape inference fills identically.
graph = parse_graph("""
agraph (float[3] loop_state_orig, float[sequence, 2] scan_input)
=> (loop_state_final, scan_output)
{
loop_state_final, scan_output = Scan (loop_state_orig, scan_input) <
num_scan_inputs = 1,
body = subgraph (loop_state_in, input) => (loop_state_out, output) {
loop_state_out = Identity(loop_state_in)
output = Identity(input)
}
>
}
""")
self._assert_inferred(
graph,
[
make_tensor_value_info("loop_state_final", TensorProto.FLOAT, (3,)),
make_tensor_value_info(
"scan_output", TensorProto.FLOAT, ("sequence", 2)
),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 9)],
)
def test_scan_opset9_axes(self) -> None:
# The whole graph (including the Scan body subgraph) is expressed in one parse_graph call;
# the Scan outputs are left untyped so that shape inference must compute their type/shape.
# Test-input alteration: the body placeholders (loop_state_in/input/loop_state_out/output) were declared
# with an explicit UNDEFINED element type in the original make_graph version; the parser leaves them
# untyped, which shape inference fills identically.
graph = parse_graph("""
agraph (float[3] loop_state_orig, float[axis0, sequence, 2] scan_input)
=> (loop_state_final, scan_output)
{
loop_state_final, scan_output = Scan (loop_state_orig, scan_input) <
num_scan_inputs = 1, scan_input_axes = [1],
body = subgraph (loop_state_in, input) => (loop_state_out, output) {
loop_state_out = Identity(loop_state_in)
output = Identity(input)
}
>
}
""")
self._assert_inferred(
graph,
[
make_tensor_value_info("loop_state_final", TensorProto.FLOAT, (3,)),
make_tensor_value_info(
"scan_output", TensorProto.FLOAT, ("sequence", "axis0", 2)
),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 9)],
)
def test_scan_opset9_output_axes(self) -> None:
# Test-input alteration: the body placeholders (loop_state_in/input/loop_state_out/output) were declared
# with an explicit UNDEFINED element type in the original make_graph version; the new version leaves them
# untyped, which shape inference fills identically.
graph = parse_graph("""
agraph (float[3] loop_state_orig, float[axis0, sequence, 2] scan_input)
=> (loop_state_final, scan_output)
{
loop_state_final, scan_output = Scan (loop_state_orig, scan_input) <
num_scan_inputs = 1, scan_input_axes = [1], scan_output_axes = [1],
body = subgraph (loop_state_in, input) => (loop_state_out, output) {
loop_state_out = Identity(loop_state_in)
output = Identity(input)
}
>
}
""")
self._assert_inferred(
graph,
[
make_tensor_value_info("loop_state_final", TensorProto.FLOAT, (3,)),
make_tensor_value_info(
"scan_output", TensorProto.FLOAT, ("axis0", "sequence", 2)
),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 9)],
)
def test_scan_opset9_negative_axes(self) -> None:
# Test-input alteration: the body placeholders (loop_state_in/input/loop_state_out/output) were declared
# with an explicit UNDEFINED element type in the original make_graph version; the new version leaves them
# untyped, which shape inference fills identically.
graph = parse_graph("""
agraph (float[3] loop_state_orig, float[axis0, sequence, 2] scan_input)
=> (loop_state_final, scan_output)
{
loop_state_final, scan_output = Scan (loop_state_orig, scan_input) <
num_scan_inputs = 1, scan_input_axes = [-2], scan_output_axes = [-2],
body = subgraph (loop_state_in, input) => (loop_state_out, output) {
loop_state_out = Identity(loop_state_in)
output = Identity(input)
}
>
}
""")
self._assert_inferred(
graph,
[
make_tensor_value_info("loop_state_final", TensorProto.FLOAT, (3,)),
make_tensor_value_info(
"scan_output", TensorProto.FLOAT, ("axis0", "sequence", 2)
),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 9)],
)
def test_if_ver1(self) -> None:
# Create a simple If node where the 'then' subgraph adds to the current value, and the 'else' subgraph
# subtracts.
# Test-input alteration: the branch outputs (then_output/else_output) were declared with an explicit
# UNDEFINED element type in the original make_graph version; the parser leaves them untyped, which
# shape inference fills identically.
graph = parse_graph("""
agraph (bool[1] cond, float[1] current_value, float[1] add_value, float[1] sub_value) => (if_output)
{
if_output = If (cond) <
then_branch = then_subgraph () => (then_output) { then_output = Add(current_value, add_value) },
else_branch = else_subgraph () => (else_output) { else_output = Sub(current_value, sub_value) }
>
}
""")
self._assert_inferred(
graph,
[make_tensor_value_info("if_output", TensorProto.FLOAT, (1,))],
opset_imports=[make_opsetid(ONNX_DOMAIN, 10)],
)
def test_if(self) -> None:
# Create a simple If node where the 'then' subgraph adds to the current value, and the 'else' subgraph
# subtracts.
# Test-input alteration: the branch outputs (then_output/else_output) were declared with an explicit
# UNDEFINED element type in the original make_graph version; the new version leaves them untyped, which
# shape inference fills identically.
graph = parse_graph("""
agraph (bool[1] cond, float[1] current_value, float[1] add_value, float[1] sub_value) => (if_output)
{
if_output = If (cond) <
then_branch = then_subgraph () => (then_output) { then_output = Add(current_value, add_value) },
else_branch = else_subgraph () => (else_output) { else_output = Sub(current_value, sub_value) }
>
}
""")
self._assert_inferred(
graph, [make_tensor_value_info("if_output", TensorProto.FLOAT, (1,))]
)
def test_if_with_different_shapes_in_then_else_branches(self) -> None:
# Create a simple If node where the 'then' subgraph adds to the current value, and the 'else' subgraph
# subtracts. The then/else branches produce different shapes ((1,) vs (5,)), so inference merges them
# to an unknown dimension.
# Test-input alteration: the branch outputs were declared as UNDEFINED with shapes (1,)/(5,) in the
# original make_graph version; the new version leaves them untyped, dropping those (redundant) declared
# shapes -- shape inference computes the merged shape (None,) either way.
graph = parse_graph("""
agraph (bool[1] cond, float[1] current_value, float[1] add_value, float[5] sub_value) => (if_output)
{
if_output = If (cond) <
then_branch = then_subgraph () => (then_output) { then_output = Add(current_value, add_value) },
else_branch = else_subgraph () => (else_output) { else_output = Sub(current_value, sub_value) }
>
}
""")
self._assert_inferred(
graph, [make_tensor_value_info("if_output", TensorProto.FLOAT, (None,))]
)
def test_if_no_shape_in_then_branch(self) -> None:
# The branches reference X/axes from the enclosing scope. if_output's inferred type has unknown rank (no
# shape field), so it is kept as an intermediate value (checked via value_info) rather than a graph
# output: the checker requires graph inputs/outputs to carry a shape (i.e. a known rank), while
# value_info entries have no such requirement.
graph = parse_graph("""
agraph (bool[1] cond, float[4,8,16] X, int64[1] axes) => ()
{
if_output = If (cond) <
then_branch = then_graph () => (then_output) { then_output = ReduceSum <keepdims=0> (X, axes) },
else_branch = else_graph () => (else_output) { else_output = ReduceSum <keepdims=0> (X) }
>
}
""")
self._assert_inferred(
graph, [make_tensor_value_info("if_output", TensorProto.FLOAT, None)]
)
def test_if_no_shape_in_else_branch(self) -> None:
# The branches reference X/axes from the enclosing scope.
graph = parse_graph("""
agraph (bool[1] cond, float[4,8,16] X, int64[1] axes) => ()
{
if_output = If (cond) <
then_branch = then_graph () => (then_output) { then_output = ReduceSum <keepdims=0> (X) },
else_branch = else_graph () => (else_output) { else_output = ReduceSum <keepdims=0> (X, axes) }
>
}
""")
self._assert_inferred(
graph, [make_tensor_value_info("if_output", TensorProto.FLOAT, None)]
)
def test_if_with_different_optional_shapes_in_then_else_branches(self) -> None:
# Each branch wraps an outer-scope tensor (then_tensor_value FLOAT[1] / else_tensor_value FLOAT[5]) with Optional; the
# branch outputs are left untyped so shape inference must compute them, and the If-merge yields
# optional<FLOAT[None]> (shapes 1 and 5 differ).
#
# NOTE (test-input alteration): the original make_graph version pre-declared each branch output as
# optional<tensor(UNDEFINED)[1]> / optional<tensor(UNDEFINED)[5]>. Those pre-declared types are
# overwritten by shape inference (Optional re-derives the type from the wrapped tensor), so leaving the
# branch outputs untyped produces an identical inferred result; the subgraph output protos differ
# slightly from the pre-conversion version.
graph = parse_graph("""
agraph (bool[1] cond, float[1] then_tensor_value, float[5] else_tensor_value) => ()
{
if_output = If (cond) <
then_branch = then_subgraph () => (then_optional_output) {
then_optional_output = Optional(then_tensor_value)
},
else_branch = else_subgraph () => (else_optional_output) {
else_optional_output = Optional(else_tensor_value)
}
>
}
""")
output_tensor_proto = helper.make_tensor_type_proto(
elem_type=TensorProto.FLOAT, shape=(None,)
)
output_optional_type_proto = helper.make_optional_type_proto(
output_tensor_proto
)
output_optional_vi = helper.make_value_info(
"if_output", output_optional_type_proto
)
self._assert_inferred(graph, [output_optional_vi])
def test_maxunpool_shape_without_output_shape(self) -> None:
graph = self._make_graph(
[
("xT", TensorProto.FLOAT, (1, 1, 2, 2)),
("xI", TensorProto.FLOAT, (1, 1, 2, 2)),
],
[
make_node(
"MaxUnpool", ["xT", "xI"], "Y", kernel_shape=[2, 2], strides=[2, 2]
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (1, 1, 4, 4))]
)
def test_maxunpool_shape_with_output_shape(self) -> None:
graph = self._make_graph(
[
("xT", TensorProto.FLOAT, (1, 1, 2, 2)),
("xI", TensorProto.FLOAT, (1, 1, 2, 2)),
("output_shape", TensorProto.FLOAT, (4,)),
],
[
make_node(
"MaxUnpool",
["xT", "xI", "output_shape"],
"Y",
kernel_shape=[2, 2],
strides=[2, 2],
)
],
[make_tensor_value_info("Y", TensorProto.FLOAT, None)],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, None)]
)
def test_maxunpool_rank0_indices_raises(self) -> None:
graph = self._make_graph(
[
("xT", TensorProto.FLOAT, (1, 1, 2, 2)),
("xI", TensorProto.INT64, ()),
],
[
make_node(
"MaxUnpool", ["xT", "xI"], "Y", kernel_shape=[2, 2], strides=[2, 2]
)
],
[],
initializer=[
make_tensor("xI", TensorProto.INT64, (), [0]),
],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_conv_transpose_rank0_weight_raises(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48, 16, 16)),
("W", TensorProto.FLOAT, ()),
],
[make_node("ConvTranspose", ["X", "W"], "Y", strides=[2, 2])],
[],
initializer=[
make_tensor("W", TensorProto.FLOAT, (), [0.0]),
],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_conv_transpose_rank1_weight_raises(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48, 16, 16)),
("W", TensorProto.FLOAT, (9,)),
],
[make_node("ConvTranspose", ["X", "W"], "Y", strides=[2, 2])],
[],
initializer=[
make_tensor("W", TensorProto.FLOAT, (9,), list(range(9))),
],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_conv_transpose_rank2_weight_raises(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48, 16, 16)),
("W", TensorProto.FLOAT, (48, 32)),
],
[make_node("ConvTranspose", ["X", "W"], "Y", strides=[2, 2])],
[],
initializer=[
make_tensor("W", TensorProto.FLOAT, (48, 32), list(range(48 * 32))),
],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_conv_transpose_rank2_input_raises(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48)),
("W", TensorProto.FLOAT, (48, 32, 3, 3)),
],
[make_node("ConvTranspose", ["X", "W"], "Y", strides=[2, 2])],
[],
initializer=[
make_tensor("X", TensorProto.FLOAT, (25, 48), list(range(25 * 48))),
],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_conv_transpose_rank0_weight_raises_opset11(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48, 16, 16)),
("W", TensorProto.FLOAT, ()),
],
[make_node("ConvTranspose", ["X", "W"], "Y", strides=[2, 2])],
[],
initializer=[
make_tensor("W", TensorProto.FLOAT, (), [0.0]),
],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 11)],
)
def test_conv_transpose_rank0_weight_raises_opset1(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, (25, 48, 16, 16)),
("W", TensorProto.FLOAT, ()),
],
[make_node("ConvTranspose", ["X", "W"], "Y", strides=[2, 2])],
[],
initializer=[
make_tensor("W", TensorProto.FLOAT, (), [0.0]),
],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 1)],
)
def test_maxunpool_rank0_indices_raises_opset9(self) -> None:
graph = self._make_graph(
[
("xT", TensorProto.FLOAT, (1, 1, 2, 2)),
("xI", TensorProto.INT64, ()),
],
[
make_node(
"MaxUnpool", ["xT", "xI"], "Y", kernel_shape=[2, 2], strides=[2, 2]
)
],
[],
initializer=[
make_tensor("xI", TensorProto.INT64, (), [0]),
],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 9)],
)
def test_maxunpool_rank0_indices_raises_opset11(self) -> None:
graph = self._make_graph(
[
("xT", TensorProto.FLOAT, (1, 1, 2, 2)),
("xI", TensorProto.INT64, ()),
],
[
make_node(
"MaxUnpool", ["xT", "xI"], "Y", kernel_shape=[2, 2], strides=[2, 2]
)
],
[],
initializer=[
make_tensor("xI", TensorProto.INT64, (), [0]),
],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 11)],
)
def test_onehot_without_axis(self) -> None:
graph = self._make_graph(
[
("indices", TensorProto.INT64, (2, 2)),
("depth", TensorProto.INT64, ()),
("values", TensorProto.FLOAT, (2,)),
],
[make_node("OneHot", ["indices", "depth", "values"], "Y")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (2, 2, None))]
)
def test_onehot_with_axis(self) -> None:
graph = self._make_graph(
[
("indices", TensorProto.INT64, (2, 3, 5)),
("depth", TensorProto.INT64, (1,)),
("values", TensorProto.FLOAT, (2,)),
],
[make_node("OneHot", ["indices", "depth", "values"], "Y", axis=1)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (2, None, 3, 5))]
)
def test_onehot_without_axis_2(self) -> None:
graph = self._make_graph(
[
("indices", TensorProto.INT64, (2, 2)),
("depth", TensorProto.INT64, ()),
("values", TensorProto.FLOAT, (2,)),
],
[make_node("OneHot", ["indices", "depth", "values"], "Y")],
[],
initializer=[make_tensor("depth", TensorProto.INT64, (), (256,))],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (2, 2, 256))]
)
def test_onehot_with_axis_2(self) -> None:
graph = self._make_graph(
[
("indices", TensorProto.INT64, (2, 3, 5)),
("depth", TensorProto.INT64, (1,)),
("values", TensorProto.FLOAT, (2,)),
],
[make_node("OneHot", ["indices", "depth", "values"], "Y", axis=1)],
[],
initializer=[make_tensor("depth", TensorProto.INT64, (1,), (256,))],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (2, 256, 3, 5))]
)
def test_loop(self) -> None:
# The body's cond_in and loop_state_in are left untyped (their types are supplied by Loop), and the
# body references outer_scope_input from the enclosing graph. loop_state_final's rank may change
# between iterations, so its inferred type has unknown rank (no shape field); it is kept as an
# intermediate value (checked via value_info) rather than a graph output, since the checker requires
# graph inputs/outputs to carry a shape (i.e. a known rank). loop_output is declared as an untyped
# graph output so that shape inference must compute its type/shape.
#
# NOTE (test-input alteration): the original make_graph version declared cond_in/loop_state_in with an
# explicit UNDEFINED element type (type field present, elem_type 0, no shape); the parser instead emits
# no type field at all. Both are accepted by Loop shape inference and yield identical inferred results,
# but the body input protos differ slightly from the pre-conversion version.
graph = parse_graph("""
agraph (
int64[1] max_trip_count, float[1] cond_orig, float[2] loop_state_orig, float[3] outer_scope_input
) => (loop_output)
{
loop_state_final, loop_output = Loop (max_trip_count, cond_orig, loop_state_orig) <
body = subgraph (int64[1] iter_num_in, cond_in, loop_state_in)
=> (cond_out, loop_state_out, float[3] output) {
cond_out = Identity(cond_in)
loop_state_out = Identity(loop_state_in)
output = Identity(outer_scope_input)
}
>
}
""")
self._assert_inferred(
graph,
[
make_tensor_value_info(
"loop_state_final", TensorProto.FLOAT, None
), # shape may change between iterations
make_tensor_value_info("loop_output", TensorProto.FLOAT, (None, 3)),
],
)
def test_loop_no_state(self) -> None:
# The body's cond_in is left untyped (its type is supplied by Loop), and the body references
# outer_scope_input from the enclosing graph. The Loop output is left untyped so that shape
# inference must compute its type/shape.
# Test-input alteration: cond_in/cond_out were declared with an explicit UNDEFINED element type in the
# original make_graph version; the parser leaves them untyped, which shape inference fills identically.
graph = parse_graph("""
agraph (int64[1] max_trip_count, float[1] cond_orig, float[3] outer_scope_input)
=> (loop_output)
{
loop_output = Loop (max_trip_count, cond_orig) <
body = subgraph (int64[1] iter_num_in, cond_in) => (cond_out, float[3] output) {
cond_out = Identity(cond_in)
output = Identity(outer_scope_input)
}
>
}
""")
self._assert_inferred(
graph, [make_tensor_value_info("loop_output", TensorProto.FLOAT, (None, 3))]
)
def test_loop_with_constant_trip_count_and_early_exit(self) -> None:
model = onnx.parser.parse_model(
"""
<ir_version: 8, opset_import: ["" : 13]>
test () => ()
<int64 max_trip_count = {5}, bool cond_orig = {1}, float[3] outer_scope_input = {1, 2, 3}>
{
loop_output = Loop (max_trip_count, cond_orig) <body: graph = subgraph (int64 iter_num_in, bool cond_in) => (bool cond_out, float[3] output) {
cond_out = Constant <value: tensor = bool cond_out_value {0}> ()
output = Identity (outer_scope_input)
}>
}
"""
)
inferred_model = self._inferred(model, data_prop=True)
loop_output = next(
value_info
for value_info in inferred_model.graph.value_info
if value_info.name == "loop_output"
)
first_dim = loop_output.type.tensor_type.shape.dim[0]
assert not (first_dim.HasField("dim_value") and first_dim.dim_value == 5)
assert loop_output.type.tensor_type.shape.dim[1].dim_value == 3
def test_constantofshape_with_input_shape(self) -> None:
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["shape"],
value=make_tensor("shape", TensorProto.INT64, (3,), (3, 4, 5)),
),
make_node(
"ConstantOfShape",
["shape"],
["y"],
value=make_tensor("value", TensorProto.INT32, (1,), (2,)),
),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("shape", TensorProto.INT64, (3,)),
make_tensor_value_info("y", TensorProto.INT32, (3, 4, 5)),
],
)
def test_constantofshape_without_input_shape(self) -> None:
graph = self._make_graph(
[("shape", TensorProto.INT64, (3,))],
[
make_node(
"ConstantOfShape",
["shape"],
["y"],
value=make_tensor("value", TensorProto.UINT8, (1,), (2,)),
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT8, (None, None, None))]
)
def test_constantofshape_without_input_shape_scalar(self) -> None:
graph = self._make_graph(
[("shape", TensorProto.INT64, (0,))],
[
make_node(
"ConstantOfShape",
["shape"],
["y"],
value=make_tensor("value", TensorProto.UINT8, (1,), (2,)),
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT8, ())]
)
def test_constantofshape_with_shape_zero(self) -> None:
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["shape"],
value=make_tensor("shape", TensorProto.INT64, (1,), (0,)),
),
make_node(
"ConstantOfShape",
["shape"],
["y"],
value=make_tensor("value", TensorProto.INT32, (1,), (2,)),
),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("shape", TensorProto.INT64, (1,)),
make_tensor_value_info("y", TensorProto.INT32, (0,)),
],
)
def test_convinteger(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.UINT8, (3, 4, 5, 6, 7)),
("y", TensorProto.UINT8, (5, 4, 2, 4, 3)),
],
[
make_node(
"ConvInteger",
["x", "y"],
"z",
pads=[0, 1, 1, 0, 0, 1],
dilations=[1, 2, 2],
strides=[1, 1, 2],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.INT32, (3, 5, 4, 1, 3))]
)
def test_convinetger_dilations(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.UINT8, (30, 4, 8, 8, 8)),
("y", TensorProto.INT8, (50, 4, 3, 3, 3)),
("x_zero_point", TensorProto.UINT8, ()),
("y_zero_point", TensorProto.UINT8, ()),
],
[
make_node(
"ConvInteger",
["x", "y", "x_zero_point", "y_zero_point"],
"z",
dilations=[1, 2, 3],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.INT32, (30, 50, 6, 4, 2))]
)
def test_convinteger_strides(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.INT8, (30, 4, 8, 8, 8)),
("y", TensorProto.INT8, (50, 4, 3, 3, 3)),
("x_zero_point", TensorProto.UINT8, ()),
("y_zero_point", TensorProto.UINT8, ()),
],
[
make_node(
"ConvInteger",
["x", "y", "x_zero_point", "y_zero_point"],
"z",
strides=[1, 2, 3],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.INT32, (30, 50, 6, 3, 2))]
)
def test_convineteger_pads(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.UINT8, (30, 4, 7, 6, 4)),
("y", TensorProto.INT8, (50, 4, 3, 3, 3)),
],
[make_node("ConvInteger", ["x", "y"], "z", pads=[1, 1, 2, 0, 1, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.INT32, (30, 50, 6, 6, 6))]
)
def test_convineteger_group(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.INT8, (30, 4, 8, 8, 8)),
("y", TensorProto.INT8, (4, 1, 8, 8, 8)),
],
[make_node("ConvInteger", ["x", "y"], "z", group=4)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.INT32, (30, 4, 1, 1, 1))]
)
def test_convineteger_partial_missing_shape(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.UINT8, (30, 4, None, 6, 4)),
("y", TensorProto.UINT8, (50, 4, 3, 3, 3)),
("x_zero_point", TensorProto.UINT8, ()),
("y_zero_point", TensorProto.UINT8, ()),
],
[
make_node(
"ConvInteger",
["x", "y", "x_zero_point", "y_zero_point"],
"z",
pads=[1, 1, 2, 0, 1, 2],
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("z", TensorProto.INT32, (30, 50, None, 6, 6))],
)
def test_convineteger_partial_missing_weight_shape(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.UINT8, (30, 4, 7, 6, 4)),
("y", TensorProto.UINT8, (50, 4, None, 3, 3)),
],
[make_node("ConvInteger", ["x", "y"], "z", pads=[1, 1, 2, 0, 1, 2])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.INT32, None)]
)
def test_qlinearconv(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.UINT8, (3, 4, 5, 6, 7)),
("x_scale", TensorProto.FLOAT, ()),
("x_zero_point", TensorProto.UINT8, ()),
("w", TensorProto.UINT8, (5, 4, 2, 4, 3)),
("w_scale", TensorProto.FLOAT, ()),
("w_zero_point", TensorProto.UINT8, ()),
("y_scale", TensorProto.FLOAT, ()),
("y_zero_point", TensorProto.UINT8, ()),
],
[
make_node(
"QLinearConv",
[
"x",
"x_scale",
"x_zero_point",
"w",
"w_scale",
"w_zero_point",
"y_scale",
"y_zero_point",
],
"y",
pads=[0, 1, 1, 0, 0, 1],
dilations=[1, 2, 2],
strides=[1, 1, 2],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT8, (3, 5, 4, 1, 3))]
)
def test_qlinearconv_dilations(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.UINT8, (30, 4, 8, 8, 8)),
("x_scale", TensorProto.FLOAT, ()),
("x_zero_point", TensorProto.UINT8, ()),
("w", TensorProto.UINT8, (50, 4, 3, 3, 3)),
("w_scale", TensorProto.FLOAT, ()),
("w_zero_point", TensorProto.UINT8, ()),
("y_scale", TensorProto.FLOAT, ()),
("y_zero_point", TensorProto.UINT8, ()),
],
[
make_node(
"QLinearConv",
[
"x",
"x_scale",
"x_zero_point",
"w",
"w_scale",
"w_zero_point",
"y_scale",
"y_zero_point",
],
"y",
dilations=[1, 2, 3],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT8, (30, 50, 6, 4, 2))]
)
def test_qlinearconv_strides(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.INT8, (30, 4, 8, 8, 8)),
("x_scale", TensorProto.FLOAT, ()),
("x_zero_point", TensorProto.INT8, ()),
("w", TensorProto.INT8, (50, 4, 3, 3, 3)),
("w_scale", TensorProto.FLOAT, ()),
("w_zero_point", TensorProto.INT8, ()),
("y_scale", TensorProto.FLOAT, ()),
("y_zero_point", TensorProto.INT8, ()),
],
[
make_node(
"QLinearConv",
[
"x",
"x_scale",
"x_zero_point",
"w",
"w_scale",
"w_zero_point",
"y_scale",
"y_zero_point",
],
"y",
strides=[1, 2, 3],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.INT8, (30, 50, 6, 3, 2))]
)
def test_qlinearconv_pads(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.UINT8, (30, 4, 7, 6, 4)),
("x_scale", TensorProto.FLOAT, ()),
("x_zero_point", TensorProto.UINT8, ()),
("w", TensorProto.INT8, (50, 4, 3, 3, 3)),
("w_scale", TensorProto.FLOAT, ()),
("w_zero_point", TensorProto.INT8, ()),
("y_scale", TensorProto.FLOAT, ()),
("y_zero_point", TensorProto.UINT8, ()),
],
[
make_node(
"QLinearConv",
[
"x",
"x_scale",
"x_zero_point",
"w",
"w_scale",
"w_zero_point",
"y_scale",
"y_zero_point",
],
"y",
pads=[1, 1, 2, 0, 1, 2],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT8, (30, 50, 6, 6, 6))]
)
def test_qlinearconv_group(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.INT8, (30, 4, 8, 8, 8)),
("x_scale", TensorProto.FLOAT, ()),
("x_zero_point", TensorProto.INT8, ()),
("w", TensorProto.INT8, (4, 1, 8, 8, 8)),
("w_scale", TensorProto.FLOAT, ()),
("w_zero_point", TensorProto.INT8, ()),
("y_scale", TensorProto.FLOAT, ()),
("y_zero_point", TensorProto.INT8, ()),
],
[
make_node(
"QLinearConv",
[
"x",
"x_scale",
"x_zero_point",
"w",
"w_scale",
"w_zero_point",
"y_scale",
"y_zero_point",
],
"y",
group=4,
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.INT8, (30, 4, 1, 1, 1))]
)
def test_qlinearconv_partial_missing_shape(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.UINT8, (30, 4, None, 6, 4)),
("x_scale", TensorProto.FLOAT, ()),
("x_zero_point", TensorProto.UINT8, ()),
("w", TensorProto.UINT8, (50, 4, 3, 3, 3)),
("w_scale", TensorProto.FLOAT, ()),
("w_zero_point", TensorProto.UINT8, ()),
("y_scale", TensorProto.FLOAT, ()),
("y_zero_point", TensorProto.UINT8, ()),
],
[
make_node(
"QLinearConv",
[
"x",
"x_scale",
"x_zero_point",
"w",
"w_scale",
"w_zero_point",
"y_scale",
"y_zero_point",
],
"y",
pads=[1, 1, 2, 0, 1, 2],
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.UINT8, (30, 50, None, 6, 6))],
)
def test_qlinearconv_partial_missing_weight_shape(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.UINT8, (30, 4, 7, 6, 4)),
("x_scale", TensorProto.FLOAT, ()),
("x_zero_point", TensorProto.UINT8, ()),
("w", TensorProto.UINT8, (50, 4, None, 3, 3)),
("w_scale", TensorProto.FLOAT, ()),
("w_zero_point", TensorProto.UINT8, ()),
("y_scale", TensorProto.FLOAT, ()),
("y_zero_point", TensorProto.UINT8, ()),
],
[
make_node(
"QLinearConv",
[
"x",
"x_scale",
"x_zero_point",
"w",
"w_scale",
"w_zero_point",
"y_scale",
"y_zero_point",
],
"y",
pads=[1, 1, 2, 0, 1, 2],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT8, None)]
)
def _make_qlinearmatmul_test(
self, shape1: Sequence[int], shape2: Sequence[int]
) -> None:
expected_out_shape = np.matmul(
np.arange(np.prod(shape1)).reshape(shape1),
np.arange(np.prod(shape2)).reshape(shape2),
).shape
graph = self._make_graph(
[
("a", TensorProto.UINT8, shape1),
("a_scale", TensorProto.FLOAT, ()),
("a_zero_point", TensorProto.UINT8, ()),
("b", TensorProto.UINT8, shape2),
("b_scale", TensorProto.FLOAT, ()),
("b_zero_point", TensorProto.UINT8, ()),
("y_scale", TensorProto.FLOAT, ()),
("y_zero_point", TensorProto.UINT8, ()),
],
[
make_node(
"QLinearMatMul",
[
"a",
"a_scale",
"a_zero_point",
"b",
"b_scale",
"b_zero_point",
"y_scale",
"y_zero_point",
],
["y"],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT8, expected_out_shape)]
)
def test_qlinearmatmul(self) -> None:
self._make_qlinearmatmul_test((3,), (3,))
self._make_qlinearmatmul_test((4, 2), (2, 4))
self._make_qlinearmatmul_test((2,), (2, 3))
self._make_qlinearmatmul_test((4, 2), (2,))
self._make_qlinearmatmul_test((5, 1, 4, 2), (1, 3, 2, 3))
self._make_qlinearmatmul_test((4, 2), (3, 2, 3))
def _make_qlinearmatmul_test_allow_unknown(
self, shape1: Any, shape2: Any, expected_out_shape: Any
) -> None:
graph = self._make_graph(
[
("a", TensorProto.UINT8, shape1),
("a_scale", TensorProto.FLOAT, ()),
("a_zero_point", TensorProto.UINT8, ()),
("b", TensorProto.UINT8, shape2),
("b_scale", TensorProto.FLOAT, ()),
("b_zero_point", TensorProto.UINT8, ()),
("y_scale", TensorProto.FLOAT, ()),
("y_zero_point", TensorProto.UINT8, ()),
],
[
make_node(
"QLinearMatMul",
[
"a",
"a_scale",
"a_zero_point",
"b",
"b_scale",
"b_zero_point",
"y_scale",
"y_zero_point",
],
["y"],
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT8, expected_out_shape)]
)
def test_qlinearmatmul_allow_unknown(self) -> None:
self._make_qlinearmatmul_test_allow_unknown((None,), (None,), ())
self._make_qlinearmatmul_test_allow_unknown((3,), (None,), ())
self._make_qlinearmatmul_test_allow_unknown((2,), (2, "a"), ("a",))
self._make_qlinearmatmul_test_allow_unknown((4, 2), (2, "a"), (4, "a"))
self._make_qlinearmatmul_test_allow_unknown((4, None), (2, "a"), (4, "a"))
self._make_qlinearmatmul_test_allow_unknown((4, None), (None, "a"), (4, "a"))
self._make_qlinearmatmul_test_allow_unknown((1, 4, 2), ("a", 2, 5), ("a", 4, 5))
self._make_qlinearmatmul_test_allow_unknown(
(1, 3, 4, 2), ("a", 2, 5), (1, 3, 4, 5)
)
self._make_qlinearmatmul_test_allow_unknown(None, ("a", 2, 5), None)
self._make_qlinearmatmul_test_allow_unknown(None, None, None)
def _make_matmulinteger_test(
self, shape1: Sequence[int], shape2: Sequence[int]
) -> None:
expected_out_shape = np.matmul(
np.arange(np.prod(shape1)).reshape(shape1),
np.arange(np.prod(shape2)).reshape(shape2),
).shape
graph = self._make_graph(
[
("A", TensorProto.UINT8, shape1),
("B", TensorProto.UINT8, shape2),
("a_zero_point", TensorProto.UINT8, ()),
("b_zero_point", TensorProto.UINT8, ()),
],
[
make_node(
"MatMulInteger", ["A", "B", "a_zero_point", "b_zero_point"], ["Y"]
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.INT32, expected_out_shape)]
)
def test_matmulinteger(self) -> None:
self._make_matmulinteger_test((2,), (2,))
self._make_matmulinteger_test((1, 2), (2, 3))
self._make_matmulinteger_test((2,), (2, 3))
self._make_matmulinteger_test((4, 2), (2,))
self._make_matmulinteger_test((5, 1, 4, 2), (1, 3, 2, 3))
self._make_matmulinteger_test((4, 2), (3, 2, 3))
@pytest.mark.parametrize(
"elem_type",
[onnx.TensorProto.FLOAT, onnx.TensorProto.FLOAT16, onnx.TensorProto.BFLOAT16],
)
def test_quantizelinear(self, elem_type) -> None:
graph = self._make_graph(
[
("x", elem_type, (30, 4, 5)),
("y_scale", elem_type, ()),
("y_zero_point", TensorProto.UINT8, ()),
],
[make_node("QuantizeLinear", ["x", "y_scale", "y_zero_point"], ["y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT8, (30, 4, 5))]
)
def test_quantizelinear_default_zp(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (30, 4, 5)), ("y_scale", TensorProto.FLOAT, ())],
[make_node("QuantizeLinear", ["x", "y_scale"], ["y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT8, (30, 4, 5))]
)
def test_quantizelinear_optional_input(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (30, 4, 5)), ("y_scale", TensorProto.FLOAT, ())],
[make_node("QuantizeLinear", ["x", "y_scale", ""], ["y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT8, (30, 4, 5))]
)
def test_quantizelinear_output_dtype(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4, 5)), ("y_scale", TensorProto.FLOAT, ())],
[
make_node(
"QuantizeLinear",
["x", "y_scale"],
["y"],
output_dtype=TensorProto.UINT4,
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT4, (3, 4, 5))]
)
def test_quantizelinear_zp_output_dtype(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 4, 5)),
("y_scale", TensorProto.FLOAT, ()),
("y_zero_point", TensorProto.UINT16, ()),
],
[
make_node(
"QuantizeLinear",
["x", "y_scale", "y_zero_point"],
["y"],
output_dtype=TensorProto.UINT16,
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT16, (3, 4, 5))]
)
def test_quantizelinear_zp_output_dtype_conflicted(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 4, 5)),
("y_scale", TensorProto.FLOAT, ()),
("y_zero_point", TensorProto.UINT16, ()),
],
[
make_node(
"QuantizeLinear",
["x", "y_scale", "y_zero_point"],
["y"],
output_dtype=TensorProto.INT4,
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
)
@pytest.mark.xfail(reason="Issue #5960")
def test_quantizelinear_invalid_output_dtype(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4, 5)), ("y_scale", TensorProto.FLOAT, ())],
[
make_node(
"QuantizeLinear",
["x", "y_scale"],
["y"],
output_dtype=TensorProto.FLOAT16,
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
)
@pytest.mark.parametrize(
"elem_type",
[onnx.TensorProto.FLOAT, onnx.TensorProto.FLOAT16, onnx.TensorProto.BFLOAT16],
)
def test_dequantizelinear(self, elem_type) -> None:
graph = self._make_graph(
[
("x", TensorProto.UINT8, (30, 4, 5)),
("x_scale", elem_type, ()),
("x_zero_point", TensorProto.UINT8, ()),
],
[make_node("DequantizeLinear", ["x", "x_scale", "x_zero_point"], ["y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", elem_type, (30, 4, 5))]
)
def test_dynamicquantizelinear(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (30, 4, 5))],
[
make_node(
"DynamicQuantizeLinear", ["x"], ["y", "y_scale", "y_zero_point"]
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.UINT8, (30, 4, 5)),
make_tensor_value_info("y_scale", TensorProto.FLOAT, ()),
make_tensor_value_info("y_zero_point", TensorProto.UINT8, ()),
],
)
def test_reversesequence(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (4, 5, 6)),
("sequence_lens", TensorProto.INT64, (5,)),
],
[make_node("ReverseSequence", ["x", "sequence_lens"], ["y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (4, 5, 6))]
)
def test_unique_without_axis(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (2, 4, 2))],
[make_node("Unique", ["X"], ["Y", "indices", "inverse_indices", "counts"])],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("Y", TensorProto.FLOAT, (None,)),
make_tensor_value_info("indices", TensorProto.INT64, (None,)),
make_tensor_value_info("inverse_indices", TensorProto.INT64, (None,)),
make_tensor_value_info("counts", TensorProto.INT64, (None,)),
],
)
def test_unique_with_axis(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (2, 4, 2))],
[
make_node(
"Unique",
["X"],
["Y", "indices", "inverse_indices", "counts"],
axis=1,
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("Y", TensorProto.FLOAT, (2, None, 2)),
make_tensor_value_info("indices", TensorProto.INT64, (None,)),
make_tensor_value_info("inverse_indices", TensorProto.INT64, (None,)),
make_tensor_value_info("counts", TensorProto.INT64, (None,)),
],
)
def test_det(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (3, 3))], [make_node("Det", ["X"], ["Y"])], []
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, ())]
)
graph = self._make_graph(
[("X", TensorProto.FLOAT, (4, 5, 6, 7, 7))],
[make_node("Det", ["X"], ["Y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (4, 5, 6))]
)
def test_tile(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 5, 6)), ("repeats", TensorProto.INT64, (3,))],
[make_node("Tile", ["x", "repeats"], ["y"])],
[],
initializer=[make_tensor("repeats", TensorProto.INT64, (3,), (1, 2, 3))],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (4, 10, 18))]
)
def test_tile_raw_input_data(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 5, 6)), ("repeats", TensorProto.INT64, (3,))],
[make_node("Tile", ["x", "repeats"], ["y"])],
[],
initializer=[
make_tensor(
"repeats",
TensorProto.INT64,
(3,),
vals=np.array([1, 2, 3], dtype="<i8").tobytes(),
raw=True,
)
],
) # Feed raw bytes (force little endian ordering like onnx standard) for test purpose
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (4, 10, 18))]
)
def test_tile_rank_inference(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 5, 6)), ("repeats", TensorProto.INT64, (3,))],
[make_node("Tile", ["x", "repeats"], ["y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (None, None, None))]
)
@pytest.mark.skipif(
not ONNX_ML, reason="ONNX_ML required to test ai.onnx.ml operators"
)
def test_linearclassifier_1D_input(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (5,))],
[
make_node(
"LinearClassifier",
["x"],
["y", "z"],
domain=ONNX_ML_DOMAIN,
coefficients=[0.0008, -0.0008],
intercepts=[2.0, 2.0],
classlabels_ints=[1, 2],
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.INT64, (1,)),
make_tensor_value_info("z", TensorProto.FLOAT, (1, 2)),
],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 1),
make_opsetid(ONNX_DOMAIN, 11),
],
)
@pytest.mark.skipif(
not ONNX_ML, reason="ONNX_ML required to test ai.onnx.ml operators"
)
def test_linearclassifier_2D_input(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 5))],
[
make_node(
"LinearClassifier",
["x"],
["y", "z"],
domain=ONNX_ML_DOMAIN,
coefficients=[0.1, 0.2, 0.3, 0.4, 0.5, 0.6],
intercepts=[2.0, 2.0, 3.0],
classlabels_ints=[1, 2, 3],
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.INT64, (4,)),
make_tensor_value_info("z", TensorProto.FLOAT, (4, 3)),
],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 1),
make_opsetid(ONNX_DOMAIN, 11),
],
)
def test_roialign_symbolic(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, ("N", "C", "H", "W")),
("rois", TensorProto.FLOAT, ("num_rois", 4)),
("batch_indices", TensorProto.INT64, ("num_rois",)),
],
[
make_node(
"RoiAlign",
["x", "rois", "batch_indices"],
["y"],
output_height=10,
output_width=5,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, ("num_rois", "C", 10, 5))],
)
def test_roialign_symbolic_defaults(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, ("N", "C", "H", "W")),
("rois", TensorProto.FLOAT, ("num_rois", 4)),
("batch_indices", TensorProto.INT64, ("num_rois",)),
],
[make_node("RoiAlign", ["x", "rois", "batch_indices"], ["y"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, ("num_rois", "C", 1, 1))],
)
def test_roialign_num_rois(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, ("N", "C", "H", "W")),
("rois", TensorProto.FLOAT, ("num_rois", 4)),
("batch_indices", TensorProto.INT64, (15,)),
],
[make_node("RoiAlign", ["x", "rois", "batch_indices"], ["y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (15, "C", 1, 1))]
)
def test_rotaryembedding_4d(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, ("B", "num_heads", "seq_len", "head_size")),
("cos_cache", TensorProto.FLOAT, ("max_seq_len", "head_size_div_2")),
("sin_cache", TensorProto.FLOAT, ("max_seq_len", "head_size_div_2")),
("position_ids", TensorProto.INT64, ("B", "seq_len")),
],
[
make_node(
"RotaryEmbedding",
["X", "cos_cache", "sin_cache", "position_ids"],
["Y"],
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"Y", TensorProto.FLOAT, ("B", "num_heads", "seq_len", "head_size")
)
],
)
def test_rotaryembedding_3d(self) -> None:
graph = self._make_graph(
[
("X", TensorProto.FLOAT, ("B", "seq_len", "hidden_size")),
("cos_cache", TensorProto.FLOAT, ("max_seq_len", "head_size_div_2")),
("sin_cache", TensorProto.FLOAT, ("max_seq_len", "head_size_div_2")),
("position_ids", TensorProto.INT64, ("B", "seq_len")),
],
[
make_node(
"RotaryEmbedding",
["X", "cos_cache", "sin_cache", "position_ids"],
["Y"],
num_heads=4,
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"Y", TensorProto.FLOAT, ("B", "seq_len", "hidden_size")
)
],
)
@pytest.mark.parametrize(
"version",
all_versions_for("LabelEncoder") if ONNX_ML else [],
)
def test_label_encoder_string_int64(self, version) -> None:
self.skipIf(
version < 2, "keys_* attributes were introduced in ai.onnx.ml opset 2"
)
string_list = ["A", "m", "y"]
float_list = [94.17, 36.00, -99.0]
int64_list = [12, 28, 86]
graph = self._make_graph(
[("x", TensorProto.STRING, (6, 1))],
[
make_node(
"LabelEncoder",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
keys_strings=string_list,
values_int64s=int64_list,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT64, (6, 1))],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, version),
make_opsetid(ONNX_DOMAIN, 11),
],
)
graph = self._make_graph(
[("x", TensorProto.INT64, (2, 3))],
[
make_node(
"LabelEncoder",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
keys_int64s=int64_list,
values_strings=string_list,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.STRING, (2, 3))],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, version),
make_opsetid(ONNX_DOMAIN, 11),
],
)
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2,))],
[
make_node(
"LabelEncoder",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
keys_floats=float_list,
values_int64s=int64_list,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT64, (2,))],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, version),
make_opsetid(ONNX_DOMAIN, 11),
],
)
graph = self._make_graph(
[("x", TensorProto.INT64, (8,))],
[
make_node(
"LabelEncoder",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
keys_int64s=int64_list,
values_floats=float_list,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (8,))],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, version),
make_opsetid(ONNX_DOMAIN, 11),
],
)
graph = self._make_graph(
[("x", TensorProto.FLOAT, ())],
[
make_node(
"LabelEncoder",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
keys_floats=float_list,
values_strings=string_list,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.STRING, ())],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, version),
make_opsetid(ONNX_DOMAIN, 11),
],
)
graph = self._make_graph(
[("x", TensorProto.STRING, (1, 2))],
[
make_node(
"LabelEncoder",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
keys_strings=string_list,
values_floats=float_list,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (1, 2))],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, version),
make_opsetid(ONNX_DOMAIN, 11),
],
)
@pytest.mark.parametrize(
"version", all_versions_for("LabelEncoder") if ONNX_ML else []
)
def test_label_encoder_tensor_attributes(self, version) -> None:
self.skipIf(
version < 4, "tensor attributes were introduced in ai.onnx.ml opset 4"
)
key_tensor = make_tensor(
"keys_tensor", TensorProto.STRING, [4], ["a", "b", "cc", "ddd"]
)
values_tensor = make_tensor(
"values_tensor", TensorProto.INT64, [4], [1, 2, 3, 4]
)
graph = self._make_graph(
[("x", TensorProto.STRING, ("M", None, 3, 12))],
[
make_node(
"LabelEncoder",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
keys_tensor=key_tensor,
values_tensor=values_tensor,
default_tensor=make_tensor(
"default_tensor", TensorProto.INT64, [1], [0]
),
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT64, ("M", None, 3, 12))],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, version),
make_opsetid(ONNX_DOMAIN, 11),
],
)
@pytest.mark.parametrize(
"version", all_versions_for("LabelEncoder") if ONNX_ML else []
)
def test_label_encoder_tensor_attributes_invalid_configurations(
self, version
) -> None:
self.skipIf(version < 4, "tensor attributes introduced in ai.onnx.ml opset 4")
key_tensor = make_tensor(
"keys_tensor", TensorProto.STRING, [4], ["a", "b", "cc", "ddd"]
)
values_tensor = make_tensor(
"values_tensor", TensorProto.INT64, [4], [1, 2, 3, 4]
)
opset_imports = [
make_opsetid(ONNX_ML_DOMAIN, version),
make_opsetid(ONNX_DOMAIN, 11),
]
# default_tensor should be INT64, same type as values_tensor
graph = self._make_graph(
[("x", TensorProto.STRING, ("M", None, 3, 12))],
[
make_node(
"LabelEncoder",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
keys_tensor=key_tensor,
values_tensor=values_tensor,
default_tensor=make_tensor(
"default_tensor", TensorProto.INT32, [1], [0]
),
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=opset_imports,
)
# default_tensor should be a singleton of shape (1,)
graph = self._make_graph(
[("x", TensorProto.STRING, ("M", None, 3, 12))],
[
make_node(
"LabelEncoder",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
keys_tensor=key_tensor,
values_strings=["a", "b", "cc", "ddd"],
default_tensor=make_tensor(
"default_tensor", TensorProto.STRING, [1, 2], ["a", "b"]
),
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=opset_imports,
)
def make_sparse(
self,
shape: Sequence[int],
values: Sequence[int],
indices_shape: Sequence[int],
indices: Sequence[int],
) -> SparseTensorProto:
sparse = SparseTensorProto()
sparse.dims.extend(shape)
nnz = len(values)
sparse.values.CopyFrom(
helper.make_tensor("spval", TensorProto.INT64, (nnz,), values)
)
sparse.indices.CopyFrom(
helper.make_tensor("spind", TensorProto.INT64, indices_shape, indices)
)
return sparse
def test_constant_sparse(self) -> None:
y_shape = [100]
y_value = self.make_sparse(y_shape, [13, 17, 19], [3], [9, 27, 81])
graph = self._make_graph(
[], [make_node("Constant", [], ["y"], sparse_value=y_value)], []
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.INT64, y_shape)]
)
def test_constant_value_int(self) -> None:
graph = self._make_graph(
[], [make_node("Constant", [], ["y"], value_int=42)], []
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.INT64, [])]
)
def test_constant_value_ints(self) -> None:
value_ints = [1, 2, 3]
graph = self._make_graph(
[], [make_node("Constant", [], ["y"], value_ints=value_ints)], []
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.INT64, [len(value_ints)])]
)
def test_constant_value_float(self) -> None:
graph = self._make_graph(
[], [make_node("Constant", [], ["y"], value_float=1.42)], []
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, [])]
)
def test_constant_value_floats(self) -> None:
value_floats = [1.0, 1.1, 1.2]
graph = self._make_graph(
[], [make_node("Constant", [], ["y"], value_floats=value_floats)], []
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, [len(value_floats)])]
)
def test_constant_value_string(self) -> None:
graph = self._make_graph(
[], [make_node("Constant", [], ["y"], value_string="String value")], []
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.STRING, [])]
)
def test_constant_value_strings(self) -> None:
value_strings = ["o", "n", "n", "x"]
graph = self._make_graph(
[], [make_node("Constant", [], ["y"], value_strings=value_strings)], []
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.STRING, [len(value_strings)])],
)
def test_range(self) -> None:
graph = self._make_graph(
[
("start", TensorProto.FLOAT, ()),
("limit", TensorProto.FLOAT, ()),
("delta", TensorProto.FLOAT, ()),
],
[make_node("Range", ["start", "limit", "delta"], ["output"])],
[],
initializer=[
make_tensor("start", TensorProto.FLOAT, (), (1,)),
make_tensor("limit", TensorProto.FLOAT, (), (5,)),
make_tensor("delta", TensorProto.FLOAT, (), (2,)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("output", TensorProto.FLOAT, (2,))]
)
def test_range_initializer_invalid(self) -> None:
# Create a TensorProto with incorrect data type for `delta`.
# This should lead to an error when ParseData is called during shape inferencing
# as the expected float data is missing.
bad_tensor = TensorProto()
bad_tensor.name = "delta"
bad_tensor.data_type = TensorProto.FLOAT
bad_tensor.int64_data.extend([2]) # incorrect data type
graph = self._make_graph(
[
("start", TensorProto.FLOAT, ()),
("limit", TensorProto.FLOAT, ()),
("delta", TensorProto.FLOAT, ()),
],
[make_node("Range", ["start", "limit", "delta"], ["output"])],
[],
initializer=[
make_tensor("start", TensorProto.FLOAT, (), (1,)),
make_tensor("limit", TensorProto.FLOAT, (), (5,)),
bad_tensor,
],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_range_initializer_invalid_rawdata(self) -> None:
# Create a TensorProto with empty raw data for `delta`.
# This should lead to an error when ParseData is called during shape inferencing
# as the expected raw data is missing.
bad_tensor = make_tensor(
"delta",
TensorProto.FLOAT,
(),
vals=np.array([1.0], dtype="<f4").tobytes(),
raw=True,
)
bad_tensor.raw_data = b"" # Clear raw data to simulate missing data
graph = self._make_graph(
[
("start", TensorProto.FLOAT, ()),
("limit", TensorProto.FLOAT, ()),
("delta", TensorProto.FLOAT, ()),
],
[make_node("Range", ["start", "limit", "delta"], ["output"])],
[],
initializer=[
make_tensor("start", TensorProto.FLOAT, (), (1,)),
make_tensor("limit", TensorProto.FLOAT, (), (5,)),
bad_tensor,
],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_range_rank_inference(self) -> None:
graph = self._make_graph(
[
("start", TensorProto.INT32, ()),
("limit", TensorProto.INT32, ()),
("delta", TensorProto.INT32, ()),
],
[make_node("Range", ["start", "limit", "delta"], ["output"])],
[],
initializer=[
make_tensor("start", TensorProto.INT32, (), (1,)),
make_tensor("limit", TensorProto.INT32, (), (5,)),
],
) # Missing 'delta' initializer
self._assert_inferred(
graph, [make_tensor_value_info("output", TensorProto.INT32, (None,))]
)
def test_range_float16(self) -> None:
graph = self._make_graph(
[
("start", TensorProto.FLOAT16, ()),
("limit", TensorProto.FLOAT16, ()),
("delta", TensorProto.FLOAT16, ()),
],
[make_node("Range", ["start", "limit", "delta"], ["output"])],
[],
initializer=[
make_tensor("start", TensorProto.FLOAT16, (), (1,)),
make_tensor("limit", TensorProto.FLOAT16, (), (5,)),
make_tensor("delta", TensorProto.FLOAT16, (), (2,)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("output", TensorProto.FLOAT16, (None,))]
)
def test_range_bfloat16(self) -> None:
graph = self._make_graph(
[
("start", TensorProto.BFLOAT16, ()),
("limit", TensorProto.BFLOAT16, ()),
("delta", TensorProto.BFLOAT16, ()),
],
[make_node("Range", ["start", "limit", "delta"], ["output"])],
[],
initializer=[
make_tensor("start", TensorProto.BFLOAT16, (), (1,)),
make_tensor("limit", TensorProto.BFLOAT16, (), (5,)),
make_tensor("delta", TensorProto.BFLOAT16, (), (2,)),
],
)
self._assert_inferred(
graph, [make_tensor_value_info("output", TensorProto.BFLOAT16, (None,))]
)
def test_range_float16_rank_inference(self) -> None:
graph = self._make_graph(
[
("start", TensorProto.FLOAT16, ()),
("limit", TensorProto.FLOAT16, ()),
("delta", TensorProto.FLOAT16, ()),
],
[make_node("Range", ["start", "limit", "delta"], ["output"])],
[],
initializer=[
make_tensor("start", TensorProto.FLOAT16, (), (1,)),
make_tensor("limit", TensorProto.FLOAT16, (), (5,)),
],
) # Missing 'delta' initializer
self._assert_inferred(
graph, [make_tensor_value_info("output", TensorProto.FLOAT16, (None,))]
)
def test_range_bfloat16_rank_inference(self) -> None:
graph = self._make_graph(
[
("start", TensorProto.BFLOAT16, ()),
("limit", TensorProto.BFLOAT16, ()),
("delta", TensorProto.BFLOAT16, ()),
],
[make_node("Range", ["start", "limit", "delta"], ["output"])],
[],
initializer=[
make_tensor("start", TensorProto.BFLOAT16, (), (1,)),
make_tensor("limit", TensorProto.BFLOAT16, (), (5,)),
],
) # Missing 'delta' initializer
self._assert_inferred(
graph, [make_tensor_value_info("output", TensorProto.BFLOAT16, (None,))]
)
def test_gathernd(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (4, 5, 6)), ("indices", TensorProto.INT64, (2,))],
[make_node("GatherND", ["x", "indices"], ["y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (6,))]
)
def test_gathernd_batchdim_1(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (2, 2, 2)),
("indices", TensorProto.INT64, (2, 1)),
],
[make_node("GatherND", ["x", "indices"], ["y"], batch_dims=1)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (2, 2))]
)
def test_cumprod(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 2)), ("axis", TensorProto.FLOAT, (1,))],
[make_node("CumProd", ["x", "axis"], "z")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3, 2))]
)
def test_cumsum(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3)), ("axis", TensorProto.FLOAT, (1,))],
[make_node("CumSum", ["x", "axis"], "z")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (2, 3))]
)
def test_nonmaxsuppression(self) -> None:
graph = self._make_graph(
[
("boxes", TensorProto.FLOAT, (1, 3, 4)),
("scores", TensorProto.FLOAT, (1, 5, 3)),
],
[make_node("NonMaxSuppression", ["boxes", "scores"], ["y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.INT64, (None, 3))]
)
def test_sequence_empty(self) -> None:
graph = self._make_graph([], [make_node("SequenceEmpty", [], ["output"])], [])
self._assert_inferred(
graph, [make_tensor_sequence_value_info("output", TensorProto.FLOAT, None)]
)
def test_sequence_construct(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, 4)),
("input2", TensorProto.FLOAT, (2, 3, 4)),
("input3", TensorProto.FLOAT, (2, 3, 4)),
],
[
make_node(
"SequenceConstruct",
["input1", "input2", "input3"],
["output_sequence"],
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (2, 3, 4)
)
],
)
def test_sequence_construct_one_input(self) -> None:
graph = self._make_graph(
[("input1", TensorProto.FLOAT, (2, 3, 4))],
[make_node("SequenceConstruct", ["input1"], ["output_sequence"])],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (2, 3, 4)
)
],
)
def test_sequence_construct_diff_rank(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, 4)),
("input2", TensorProto.FLOAT, (2, 3)),
("input3", TensorProto.FLOAT, (2, 3)),
],
[
make_node(
"SequenceConstruct",
["input1", "input2", "input3"],
["output_sequence"],
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, None
)
],
)
def test_sequence_construct_diff_dim_size(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, 4)),
("input2", TensorProto.FLOAT, (2, 3, 5)),
("input3", TensorProto.FLOAT, (2, 3, 6)),
],
[
make_node(
"SequenceConstruct",
["input1", "input2", "input3"],
["output_sequence"],
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (2, 3, None)
)
],
)
def test_sequence_insert(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, 4)),
("input2", TensorProto.FLOAT, (2, 3, 4)),
("input3", TensorProto.FLOAT, (2, 3, 4)),
("input4", TensorProto.FLOAT, (2, 3, 4)),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node(
"SequenceInsert", ["in_sequence", "input4"], ["output_sequence"]
),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, 3, 4)
),
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (2, 3, 4)
),
],
)
def test_sequence_insert_diff_rank(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, 4)),
("input2", TensorProto.FLOAT, (2, 3, 4)),
("input3", TensorProto.FLOAT, (2, 3, 4)),
("input4", TensorProto.FLOAT, (2, 3)),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node(
"SequenceInsert", ["in_sequence", "input4"], ["output_sequence"]
),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, 3, 4)
),
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, None
),
],
)
def test_sequence_insert_diff_shape(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, 4)),
("input2", TensorProto.FLOAT, (2, 3, 4)),
("input3", TensorProto.FLOAT, (2, 5, 4)),
("input4", TensorProto.FLOAT, (2, 5, 2)),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node(
"SequenceInsert", ["in_sequence", "input4"], ["output_sequence"]
),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, None, 4)
),
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (2, None, None)
),
],
)
def test_sequence_at(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, 4)),
("input2", TensorProto.FLOAT, (2, 3, 4)),
("input3", TensorProto.FLOAT, (2, 3, 4)),
("ind", TensorProto.INT64, ()),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node("SequenceAt", ["in_sequence", "ind"], ["output"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, 3, 4)
),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 3, 4)),
],
)
def test_sequence_at_unknown_shape(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, 4)),
("input2", TensorProto.FLOAT, (2, 3)),
("input3", TensorProto.FLOAT, (2, 3, 4)),
("ind", TensorProto.INT64, ()),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node("SequenceAt", ["in_sequence", "ind"], ["output"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info("in_sequence", TensorProto.FLOAT, None),
make_tensor_value_info("output", TensorProto.FLOAT, None),
],
)
def test_sequence_at_unknown_dim_size(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, 4)),
("input2", TensorProto.FLOAT, (2, 3, 5)),
("input3", TensorProto.FLOAT, (2, 3, 4)),
("ind", TensorProto.INT64, ()),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node("SequenceAt", ["in_sequence", "ind"], ["output"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, 3, None)
),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 3, None)),
],
)
def test_sequence_erase(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, 4)),
("input2", TensorProto.FLOAT, (2, 3, 4)),
("input3", TensorProto.FLOAT, (2, 3, 4)),
("ind", TensorProto.INT64, ()),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node("SequenceErase", ["in_sequence", "ind"], ["output_sequence"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, 3, 4)
),
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (2, 3, 4)
),
],
)
def test_sequence_erase_diff_dim_size(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, "x")),
("input2", TensorProto.FLOAT, (2, 3, "x")),
("input3", TensorProto.FLOAT, (2, 5, "x")),
("ind", TensorProto.INT64, ()),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node("SequenceErase", ["in_sequence", "ind"], ["output_sequence"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, None, "x")
),
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (2, None, "x")
),
],
)
def test_sequence_length(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, "x")),
("input2", TensorProto.FLOAT, (2, 3, "x")),
("input3", TensorProto.FLOAT, (2, 3, "x")),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node("SequenceLength", ["in_sequence"], ["len"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, 3, "x")
),
make_tensor_value_info("len", TensorProto.INT64, ()),
],
)
def test_split_to_sequence(self) -> None:
graph = self._make_graph(
[("input", TensorProto.FLOAT, (6, 4)), ("split", TensorProto.INT32, (2,))],
[make_node("SplitToSequence", ["input", "split"], ["output_sequence"])],
[],
initializer=[make_tensor("split", TensorProto.INT32, (2,), (3, 3))],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (3, 4)
)
],
)
def test_split_to_sequence_scalar(self) -> None:
graph = self._make_graph(
[("input", TensorProto.FLOAT, (6, 4)), ("split", TensorProto.INT32, ())],
[make_node("SplitToSequence", ["input", "split"], ["output_sequence"])],
[],
initializer=[make_tensor("split", TensorProto.INT32, (), (2,))],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (2, 4)
)
],
)
def test_split_to_sequence_keepdims(self) -> None:
graph = self._make_graph(
[("input", TensorProto.FLOAT, (6, 4))],
[make_node("SplitToSequence", ["input"], ["output_sequence"], keepdims=1)],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (1, 4)
)
],
)
def test_split_to_sequence_not_keepdims(self) -> None:
graph = self._make_graph(
[("input", TensorProto.FLOAT, (6, 4))],
[make_node("SplitToSequence", ["input"], ["output_sequence"], keepdims=0)],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (4,)
)
],
)
def test_split_to_sequence_ignore_keepdims(self) -> None:
graph = self._make_graph(
[("input", TensorProto.FLOAT, (6, 4)), ("split", TensorProto.INT32, (2,))],
[
make_node(
"SplitToSequence",
["input", "split"],
["output_sequence"],
keepdims=0,
)
],
[],
initializer=[make_tensor("split", TensorProto.INT32, (2,), (3, 3))],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (3, 4)
)
],
)
def test_split_to_sequence_axis(self) -> None:
graph = self._make_graph(
[("input", TensorProto.FLOAT, (6, 4))],
[make_node("SplitToSequence", ["input"], ["output_sequence"], axis=1)],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (6, 1)
)
],
)
def test_split_to_sequence_neg_axis(self) -> None:
graph = self._make_graph(
[("input", TensorProto.FLOAT, (6, 4))],
[make_node("SplitToSequence", ["input"], ["output_sequence"], axis=-2)],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (1, 4)
)
],
)
def test_split_to_sequence_split_sizes(self) -> None:
graph = self._make_graph(
[("input", TensorProto.FLOAT, (6, 4)), ("split", TensorProto.INT32, (3,))],
[make_node("SplitToSequence", ["input", "split"], ["output_sequence"])],
[],
initializer=[make_tensor("split", TensorProto.INT32, (3,), (2, 1, 3))],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (None, 4)
)
],
)
def test_split_to_sequence_non_divisible(self) -> None:
graph = self._make_graph(
[("input", TensorProto.FLOAT, (6, 4)), ("split", TensorProto.INT32, ())],
[make_node("SplitToSequence", ["input", "split"], ["output_sequence"])],
[],
initializer=[make_tensor("split", TensorProto.INT32, (), (4,))],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"output_sequence", TensorProto.FLOAT, (None, 4)
)
],
)
def test_concat_from_sequence(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, "x")),
("input2", TensorProto.FLOAT, (2, 3, "x")),
("input3", TensorProto.FLOAT, (2, 3, "x")),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node("ConcatFromSequence", ["in_sequence"], ["out"], axis=0),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, 3, "x")
),
make_tensor_value_info("out", TensorProto.FLOAT, (None, 3, "x")),
],
)
def test_concat_from_sequence_unknown_shape(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, "x")),
("input2", TensorProto.FLOAT, (2, 3)),
("input3", TensorProto.FLOAT, (2, 3, "x")),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node("ConcatFromSequence", ["in_sequence"], ["out"], axis=0),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info("in_sequence", TensorProto.FLOAT, None),
make_tensor_value_info("out", TensorProto.FLOAT, None),
],
)
def test_concat_from_sequence_unknown_dim_size(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, "x")),
("input2", TensorProto.FLOAT, (2, 4, "x")),
("input3", TensorProto.FLOAT, (2, 3, "x")),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node("ConcatFromSequence", ["in_sequence"], ["out"], axis=0),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, None, "x")
),
make_tensor_value_info("out", TensorProto.FLOAT, (None, None, "x")),
],
)
def test_concat_from_sequence_axis(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, "x")),
("input2", TensorProto.FLOAT, (2, 4, "x")),
("input3", TensorProto.FLOAT, (2, 3, "x")),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node("ConcatFromSequence", ["in_sequence"], ["out"], axis=2),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, None, "x")
),
make_tensor_value_info("out", TensorProto.FLOAT, (2, None, None)),
],
)
def test_concat_from_sequence_neg_axis(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, "x")),
("input2", TensorProto.FLOAT, (2, 4, "x")),
("input3", TensorProto.FLOAT, (2, 3, "x")),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node("ConcatFromSequence", ["in_sequence"], ["out"], axis=-3),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, None, "x")
),
make_tensor_value_info("out", TensorProto.FLOAT, (None, None, "x")),
],
)
def test_concat_from_sequence_new_axis(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, "x")),
("input2", TensorProto.FLOAT, (2, 3, "x")),
("input3", TensorProto.FLOAT, (2, 3, "x")),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node(
"ConcatFromSequence", ["in_sequence"], ["out"], axis=2, new_axis=1
),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, 3, "x")
),
make_tensor_value_info("out", TensorProto.FLOAT, (2, 3, None, "x")),
],
)
def test_concat_from_sequence_neg_new_axis(self) -> None:
graph = self._make_graph(
[
("input1", TensorProto.FLOAT, (2, 3, "x")),
("input2", TensorProto.FLOAT, (2, 3, "x")),
("input3", TensorProto.FLOAT, (2, 3, "x")),
],
[
make_node(
"SequenceConstruct", ["input1", "input2", "input3"], ["in_sequence"]
),
make_node(
"ConcatFromSequence", ["in_sequence"], ["out"], axis=-1, new_axis=1
),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (2, 3, "x")
),
make_tensor_value_info("out", TensorProto.FLOAT, (2, 3, "x", None)),
],
)
def test_adagrad(self) -> None:
graph = self._make_graph(
[
("R", TensorProto.FLOAT, ()), # scalar's shape is ()
("T", TensorProto.INT64, ()), # scalar's shape is ()
("X", TensorProto.FLOAT, (1, 2)),
("G", TensorProto.FLOAT, (1, 2)),
("H", TensorProto.FLOAT, (1, 2)),
],
[
make_node(
"Adagrad",
["R", "T", "X", "G", "H"],
["X_new", "H_new"],
domain=AI_ONNX_PREVIEW_TRAINING_DOMAIN,
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("X_new", TensorProto.FLOAT, (1, 2)),
make_tensor_value_info("H_new", TensorProto.FLOAT, (1, 2)),
],
opset_imports=[
helper.make_opsetid(ONNX_DOMAIN, 12),
helper.make_opsetid(AI_ONNX_PREVIEW_TRAINING_DOMAIN, 1),
],
)
def test_adagrad_multiple(self) -> None:
graph = self._make_graph(
[
("R", TensorProto.FLOAT, ()), # scalar's shape is ()
("T", TensorProto.INT64, ()), # scalar's shape is ()
("X1", TensorProto.FLOAT, (1, 2)),
("X2", TensorProto.FLOAT, (3, 4)),
("G1", TensorProto.FLOAT, (1, 2)),
("G2", TensorProto.FLOAT, (3, 4)),
("H1", TensorProto.FLOAT, (1, 2)),
("H2", TensorProto.FLOAT, (3, 4)),
],
[
make_node(
"Adagrad",
["R", "T", "X1", "X2", "G1", "G2", "H1", "H2"],
["X1_new", "X2_new", "H1_new", "H2_new"],
domain=AI_ONNX_PREVIEW_TRAINING_DOMAIN,
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("X1_new", TensorProto.FLOAT, (1, 2)),
make_tensor_value_info("X2_new", TensorProto.FLOAT, (3, 4)),
make_tensor_value_info("H1_new", TensorProto.FLOAT, (1, 2)),
make_tensor_value_info("H2_new", TensorProto.FLOAT, (3, 4)),
],
opset_imports=[
helper.make_opsetid(ONNX_DOMAIN, 12),
helper.make_opsetid(AI_ONNX_PREVIEW_TRAINING_DOMAIN, 1),
],
)
def test_momentum(self) -> None:
graph = self._make_graph(
[
("R", TensorProto.FLOAT, ()), # scalar's shape is ()
("T", TensorProto.INT64, ()), # scalar's shape is ()
("X", TensorProto.FLOAT, (1, 2)),
("G", TensorProto.FLOAT, (1, 2)),
("V", TensorProto.FLOAT, (1, 2)),
],
[
make_node(
"Momentum",
["R", "T", "X", "G", "V"],
["X_new", "V_new"],
alpha=0.9,
beta=1.0,
norm_coefficient=0.02,
mode="standard",
domain=AI_ONNX_PREVIEW_TRAINING_DOMAIN,
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("X_new", TensorProto.FLOAT, (1, 2)),
make_tensor_value_info("V_new", TensorProto.FLOAT, (1, 2)),
],
opset_imports=[
helper.make_opsetid(ONNX_DOMAIN, 12),
helper.make_opsetid(AI_ONNX_PREVIEW_TRAINING_DOMAIN, 1),
],
)
def test_momentum_multiple(self) -> None:
graph = self._make_graph(
[
("R", TensorProto.FLOAT, ()), # scalar's shape is ()
("T", TensorProto.INT64, ()), # scalar's shape is ()
("X1", TensorProto.FLOAT, (1, 2)),
("X2", TensorProto.FLOAT, (3, 4)),
("G1", TensorProto.FLOAT, (1, 2)),
("G2", TensorProto.FLOAT, (3, 4)),
("V1", TensorProto.FLOAT, (1, 2)),
("V2", TensorProto.FLOAT, (3, 4)),
],
[
make_node(
"Momentum",
["R", "T", "X1", "X2", "G1", "G2", "V1", "V2"],
["X1_new", "X2_new", "V1_new", "V2_new"],
alpha=0.9,
beta=1.0,
norm_coefficient=0.02,
mode="nesterov",
domain=AI_ONNX_PREVIEW_TRAINING_DOMAIN,
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("X1_new", TensorProto.FLOAT, (1, 2)),
make_tensor_value_info("X2_new", TensorProto.FLOAT, (3, 4)),
make_tensor_value_info("V1_new", TensorProto.FLOAT, (1, 2)),
make_tensor_value_info("V2_new", TensorProto.FLOAT, (3, 4)),
],
opset_imports=[
helper.make_opsetid(ONNX_DOMAIN, 12),
helper.make_opsetid(AI_ONNX_PREVIEW_TRAINING_DOMAIN, 1),
],
)
def test_adam(self) -> None:
graph = self._make_graph(
[
("R", TensorProto.FLOAT, ()), # scalar's shape is ()
("T", TensorProto.INT64, ()), # scalar's shape is ()
("X", TensorProto.FLOAT, (1, 2)),
("G", TensorProto.FLOAT, (1, 2)),
("V", TensorProto.FLOAT, (1, 2)),
("H", TensorProto.FLOAT, (1, 2)),
],
[
make_node(
"Adam",
["R", "T", "X", "G", "V", "H"],
["X_new", "V_new", "H_new"],
domain=AI_ONNX_PREVIEW_TRAINING_DOMAIN,
alpha=0.9,
beta=1.0,
norm_coefficient=0.02,
)
],
[],
)
infos = [
make_tensor_value_info("X_new", TensorProto.FLOAT, (1, 2)),
make_tensor_value_info("V_new", TensorProto.FLOAT, (1, 2)),
make_tensor_value_info("H_new", TensorProto.FLOAT, (1, 2)),
]
self._assert_inferred(
graph,
infos,
opset_imports=[
make_opsetid(AI_ONNX_PREVIEW_TRAINING_DOMAIN, 1),
make_opsetid(ONNX_DOMAIN, 12),
],
)
def test_adam_multiple(self) -> None:
graph = self._make_graph(
[
("R", TensorProto.FLOAT, ()), # scalar's shape is ()
("T", TensorProto.INT64, ()), # scalar's shape is ()
("X1", TensorProto.FLOAT, (1, 2)),
("X2", TensorProto.FLOAT, (3, 4)),
("G1", TensorProto.FLOAT, (1, 2)),
("G2", TensorProto.FLOAT, (3, 4)),
("V1", TensorProto.FLOAT, (1, 2)),
("V2", TensorProto.FLOAT, (3, 4)),
("H1", TensorProto.FLOAT, (1, 2)),
("H2", TensorProto.FLOAT, (3, 4)),
],
[
make_node(
"Adam",
["R", "T", "X1", "X2", "G1", "G2", "V1", "V2", "H1", "H2"],
["X1_new", "X2_new", "V1_new", "V2_new", "H1_new", "H2_new"],
domain=AI_ONNX_PREVIEW_TRAINING_DOMAIN,
alpha=0.9,
beta=1.0,
norm_coefficient=0.02,
)
],
[],
)
infos = [
make_tensor_value_info("X1_new", TensorProto.FLOAT, (1, 2)),
make_tensor_value_info("X2_new", TensorProto.FLOAT, (3, 4)),
make_tensor_value_info("V1_new", TensorProto.FLOAT, (1, 2)),
make_tensor_value_info("V2_new", TensorProto.FLOAT, (3, 4)),
make_tensor_value_info("H1_new", TensorProto.FLOAT, (1, 2)),
make_tensor_value_info("H2_new", TensorProto.FLOAT, (3, 4)),
]
self._assert_inferred(
graph,
infos,
opset_imports=[
make_opsetid(AI_ONNX_PREVIEW_TRAINING_DOMAIN, 1),
make_opsetid(ONNX_DOMAIN, 12),
],
)
def test_pad_opset10(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (1, None, 2))],
[make_node("Pad", "x", "y", pads=[1, 3, 1, 1, 0, 1])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (3, None, 4))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 10)],
)
def test_constant_pad_2d_opset10(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3, 4, 4))],
[
make_node(
"Pad",
"x",
"y",
pads=[0, 0, 3, 1, 0, 0, 4, 2],
mode="constant",
value=2.0,
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (2, 3, 11, 7))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 10)],
)
def test_pad(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (1, None, 2)), ("pads", TensorProto.INT64, (6,))],
[make_node("Pad", ["x", "pads"], "y")],
[],
initializer=[
make_tensor(
"pads",
TensorProto.INT64,
(6,),
(
1,
3,
1,
1,
0,
1,
),
)
],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3, None, 4))]
)
def test_gatherelements_basic(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (6,)), ("indices", TensorProto.INT64, (2,))],
[make_node("GatherElements", ["x", "indices"], ["y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (2,))]
)
def test_gatherelements_indices_missing_shape(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (6,)),
("indices", TensorProto.INT64, None),
],
[make_node("GatherElements", ["x", "indices"], ["y"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, None)]
)
def test_einsum_transpose(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4))],
[make_node("Einsum", ["x"], ["y"], equation="ij->ji")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (4, 3))]
)
def test_einsum_dot(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (1,)), ("y", TensorProto.FLOAT, (1,))],
[make_node("Einsum", ["x", "y"], ["z"], equation="i,i->")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, ())]
)
def test_einsum_scalar(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, ()), ("y", TensorProto.FLOAT, ())],
[make_node("Einsum", ["x", "y"], ["z"], equation=",->")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, ())]
)
def test_einsum_scalar_invalid_equation(self) -> None:
# Test that scalar inputs with incompatible equations fail gracefully
# instead of causing segfaults (issue #6981)
graph = self._make_graph(
[("x", TensorProto.FLOAT, ())],
[make_node("Einsum", ["x"], ["y"], equation="i->i")],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_einsum_outer_prod(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 5)), ("y", TensorProto.FLOAT, (7, 9))],
[make_node("Einsum", ["x", "y"], ["z"], equation="ij,ab->ijab")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3, 5, 7, 9))]
)
def test_einsum_sum_along_dim(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4))],
[make_node("Einsum", ["x"], ["y"], equation="i j->i ")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3,))]
)
def test_einsum_ellipsis(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4, 4))],
[make_node("Einsum", ["x"], ["y"], equation="... ii ->... i")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3, 4))]
)
def test_einsum_ellipsis_2(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3, 4)), ("y", TensorProto.FLOAT, (2, 4, 5))],
[make_node("Einsum", ["x", "y"], ["z"], equation="...ij,...jk->...ik")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (2, 3, 5))]
)
def test_einsum_ellipsis_3(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3, 4)), ("y", TensorProto.FLOAT, (2, 4, 5))],
[make_node("Einsum", ["x", "y"], ["z"], equation="...ij,...jk")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (2, 3, 5))]
)
def test_einsum_ellipsis_broadcast(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (1, 3, 4)), ("y", TensorProto.FLOAT, (32, 4, 5))],
[make_node("Einsum", ["x", "y"], ["z"], equation="...ij,...jk->...ik")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (32, 3, 5))]
)
def test_einsum_contraction(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (5, 6, 7, 8)),
("y", TensorProto.FLOAT, (8, 9, 10)),
],
[make_node("Einsum", ["x", "y"], ["z"], equation="abcd,dfg->abcfg")],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("z", TensorProto.FLOAT, (5, 6, 7, 9, 10))],
)
def test_einsum_contraction_2(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4, 5)), ("y", TensorProto.FLOAT, (3, 5))],
[make_node("Einsum", ["x", "y"], ["z"], equation="ijk,ik->jk")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (4, 5))]
)
def test_einsum_batch_matmul(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (5, 2, 3)), ("y", TensorProto.FLOAT, (5, 3, 4))],
[make_node("Einsum", ["x", "y"], ["z"], equation="bij , b jk-> bik")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (5, 2, 4))]
)
def test_einsum_left_hand_eqn(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3)), ("y", TensorProto.FLOAT, (3, 4))],
[make_node("Einsum", ["x", "y"], ["z"], equation="ij,kl")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (2, 3, 3, 4))]
)
def test_einsum_incorrect_num_inputs(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (2, 3)),
("y", TensorProto.FLOAT, (2, 3)),
("z", TensorProto.FLOAT, (2, 3)),
],
[make_node("Einsum", ["x", "y"], ["z"], equation="i,...j, k, l-> i")],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_einsum_view_A1(self) -> None: # returns a view of A1
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3,))],
[make_node("Einsum", ["x"], ["y"], equation="i")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3,))]
)
def test_einsum_sum_A1(self) -> None: # sums the values of A1
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3,))],
[make_node("Einsum", ["x"], ["y"], equation="i->")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, ())]
)
def test_einsum_element_wise_multiplication_A1_B1(
self,
) -> None: # element-wise multiplication of A1 and B1
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3,)), ("y", TensorProto.FLOAT, (3,))],
[make_node("Einsum", ["x", "y"], ["z"], equation="i,i->i")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3,))]
)
def test_einsum_inner_product_A1_B1(self) -> None: # inner product of A1 and B1
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3,)), ("y", TensorProto.FLOAT, (3,))],
[make_node("Einsum", ["x", "y"], ["z"], equation="i,i->")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, ())]
)
def test_einsum_outer_product_A1_B1(self) -> None: # outer product of A1 and B1
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3,)), ("y", TensorProto.FLOAT, (3,))],
[make_node("Einsum", ["x", "y"], ["z"], equation="i,j->ij")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3, 3))]
)
def test_einsum_view_A2(self) -> None: # returns a view of A2
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x"], ["y"], equation="ij->ij")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3, 3))]
)
def test_einsum_view_A2_2(self) -> None: # returns a view of A2, another case
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x"], ["y"], equation="ij")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3, 3))]
)
def test_einsum_transpose_A2(self) -> None: # view transpose of A2
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x"], ["y"], equation="ji")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3, 3))]
)
def test_einsum_transpose_A2_to_ij(self) -> None: # view transpose of A2
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x"], ["y"], equation="ji->ij")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3, 3))]
)
def test_einsum_diag_A2(self) -> None: # view main diagonal of A2
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x"], ["y"], equation="ii->i")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3,))]
)
def test_einsum_trace_A2(self) -> None: # sums main diagonal of A2
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x"], ["y"], equation="ii->")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, ())]
)
def test_einsum_sum_A2(self) -> None: # sums the values of A2
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x"], ["y"], equation="ij->")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, ())]
)
def test_einsum_sum_columns_A2(
self,
) -> None: # sum down the columns of A2 (across rows)
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x"], ["y"], equation="ij->j")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3,))]
)
def test_einsum_sum_rows_A2(self) -> None: # sum horizontally along the rows of A2
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x"], ["y"], equation="ij->i")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3,))]
)
def test_einsum_element_wise_multiplication_A2_B2(
self,
) -> None: # element-wise multiplication of A2 and B2
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3)), ("y", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x", "y"], ["z"], equation="ij,ij->ij")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3, 3))]
)
def test_einsum_element_wise_multiplication_A2_B2_transpose(
self,
) -> None: # element-wise multiplication of A2 and B2.T
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3)), ("y", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x", "y"], ["z"], equation="ij,ji->ij")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3, 3))]
)
def test_einsum_matrix_multiplication_A2_B2(
self,
) -> None: # matrix multiplication of A2 and B2
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3)), ("y", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x", "y"], ["z"], equation="ij,jk")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3, 3))]
)
def test_einsum_matrix_multiplication_A2_B2_to_ik(
self,
) -> None: # matrix multiplication of A2 and B2
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3)), ("y", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x", "y"], ["z"], equation="ij,jk->ik")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3, 3))]
)
def test_einsum_matrix_multiplication_A3_B3(
self,
) -> None: # matrix multiplication of A3 and B3 (a stack of 2D matrices)
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3, 3)), ("y", TensorProto.FLOAT, (2, 3, 3))],
[make_node("Einsum", ["x", "y"], ["z"], equation="bij,bjk->bik")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (2, 3, 3))]
)
def test_einsum_matrix_multiplication_A3_B3_transpose(
self,
) -> None: # matrix multiplication of A3 and B3 (a stack of 2D matrices)
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3, 3)), ("y", TensorProto.FLOAT, (2, 3, 3))],
[make_node("Einsum", ["x", "y"], ["z"], equation="bij,bkj->bik")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (2, 3, 3))]
)
def test_einsum_inner_product_A2_B2(self) -> None: # inner product of A2 and B2
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3)), ("y", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x", "y"], ["z"], equation="ij,kj->ik")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3, 3))]
)
def test_einsum_row_multiplication_A2_B2(
self,
) -> None: # each row of A2 multiplied by B2
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3)), ("y", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x", "y"], ["z"], equation="ij,kj->ikj")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3, 3, 3))]
)
def test_einsum_value_multiplication_A2_B2(
self,
) -> None: # each value of A2 multiplied by B2
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3)), ("y", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x", "y"], ["z"], equation="ij,kl->ijkl")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3, 3, 3, 3))]
)
def test_einsum_scalar_times_array(self) -> None: # Scalar times array
graph = self._make_graph(
[("x", TensorProto.FLOAT, ()), ("y", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x", "y"], ["z"], equation=",ij->ij")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3, 3))]
)
def test_einsum_matrix_vector_A2_B1(self) -> None: # Matrix and vector.
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3)), ("y", TensorProto.FLOAT, (3,))],
[make_node("Einsum", ["x", "y"], ["z"], equation="ij,j->i")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3,))]
)
def test_einsum_diag_multiplication_A2_B2(
self,
) -> None: # diagonals multiplied by each other
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3)), ("y", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x", "y"], ["z"], equation="ii,ii->i")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (3,))]
)
def test_einsum_diag_dot_product_A2_B2(self) -> None: # dot product of diagonals
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 3)), ("y", TensorProto.FLOAT, (3, 3))],
[make_node("Einsum", ["x", "y"], ["z"], equation="ii,ii->")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, ())]
)
def test_negative_log_likelihood_shape_is_NCdd(self) -> None:
N, C = 3, 4
graph = self._make_graph(
[("input", TensorProto.FLOAT, (N, C)), ("target", TensorProto.INT64, (N,))],
[
make_node(
"NegativeLogLikelihoodLoss",
["input", "target"],
["loss"],
reduction="none",
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("loss", TensorProto.FLOAT, (N,))]
)
def test_negative_log_likelihood_shape_is_NC_with_weight(self) -> None:
N, C = 3, 4
graph = self._make_graph(
[
("input", TensorProto.FLOAT, (N, C)),
("target", TensorProto.INT64, (N,)),
("weight", TensorProto.FLOAT, (C,)),
],
[
make_node(
"NegativeLogLikelihoodLoss",
["input", "target", "weight"],
["loss"],
reduction="none",
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("loss", TensorProto.FLOAT, (N,))]
)
def test_negative_log_likelihood_shape_is_NC_reduction_mean(self) -> None:
N, C = 3, 4
graph = self._make_graph(
[("input", TensorProto.FLOAT, (N, C)), ("target", TensorProto.INT64, (N,))],
[
make_node(
"NegativeLogLikelihoodLoss",
["input", "target"],
["loss"],
reduction="mean",
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("loss", TensorProto.FLOAT, ())]
)
def test_negative_log_likelihood_shape_is_NC_with_weight_reduction_mean(
self,
) -> None:
N, C = 3, 4
graph = self._make_graph(
[
("input", TensorProto.FLOAT, (N, C)),
("target", TensorProto.INT64, (N,)),
("weight", TensorProto.FLOAT, (C,)),
],
[
make_node(
"NegativeLogLikelihoodLoss",
["input", "target", "weight"],
["loss"],
reduction="mean",
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("loss", TensorProto.FLOAT, ())]
)
def test_negative_log_likelihood_shape_is_NCd1d2(self) -> None:
N, C, d1, d2 = 3, 4, 5, 6
graph = self._make_graph(
[
("input", TensorProto.FLOAT, (N, C, d1, d2)),
("target", TensorProto.INT64, (N, d1, d2)),
],
[
make_node(
"NegativeLogLikelihoodLoss",
["input", "target"],
["loss"],
reduction="none",
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("loss", TensorProto.FLOAT, (N, d1, d2))]
)
def test_negative_log_likelihood_shape_is_NCd1d2_with_weight(self) -> None:
N, C, d1, d2 = 3, 4, 5, 6
graph = self._make_graph(
[
("input", TensorProto.FLOAT, (N, C, d1, d2)),
("target", TensorProto.INT64, (N, d1, d2)),
("weight", TensorProto.FLOAT, (C,)),
],
[
make_node(
"NegativeLogLikelihoodLoss",
["input", "target", "weight"],
["loss"],
reduction="none",
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("loss", TensorProto.FLOAT, (N, d1, d2))]
)
def test_negative_log_likelihood_shape_is_NCd1d2_reduction_sum(self) -> None:
N, C, d1, d2 = 3, 4, 5, 6
graph = self._make_graph(
[
("input", TensorProto.FLOAT, (N, C, d1, d2)),
("target", TensorProto.INT64, (N, d1, d2)),
],
[
make_node(
"NegativeLogLikelihoodLoss",
["input", "target"],
["loss"],
reduction="sum",
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("loss", TensorProto.FLOAT, ())]
)
def test_negative_log_likelihood_shape_is_NCd1d2_with_weight_reduction_mean(
self,
) -> None:
N, C, d1, d2 = 3, 4, 5, 6
graph = self._make_graph(
[
("input", TensorProto.FLOAT, (N, C, d1, d2)),
("target", TensorProto.INT64, (N, d1, d2)),
("weight", TensorProto.FLOAT, (C,)),
],
[
make_node(
"NegativeLogLikelihoodLoss",
["input", "target", "weight"],
["loss"],
reduction="mean",
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("loss", TensorProto.FLOAT, ())]
)
def test_negative_log_likelihood_input_target_shape_mismatch(self) -> None:
N, C, d1, d2 = 3, 4, 5, 6
graph = self._make_graph(
[
("input", TensorProto.FLOAT, (N, d1, d2)),
("target", TensorProto.INT64, (N, d1 + 1, d2)),
("weight", TensorProto.FLOAT, (C,)),
("loss", TensorProto.FLOAT, ()),
],
[
make_node(
"NegativeLogLikelihoodLoss",
["input", "target", "weight"],
["loss"],
reduction="mean",
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(graph)
def test_negative_log_likelihood_input_weight_shape_mismatch(self) -> None:
N, C, d1, d2 = 3, 4, 5, 6
graph = self._make_graph(
[
("input", TensorProto.FLOAT, (N, C, d1, d2)),
("target", TensorProto.INT64, (N, d1, d2)),
("weight", TensorProto.FLOAT, (C + 1,)),
("loss", TensorProto.FLOAT, (N, d1, d2)),
],
[
make_node(
"NegativeLogLikelihoodLoss",
["input", "target", "weight"],
["loss"],
reduction="none",
)
],
[],
)
with pytest.raises(
(checker.ValidationError, onnx.shape_inference.InferenceError)
):
self._inferred(
graph,
)
def test_softmax_cross_entropy_none(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3)), ("y", TensorProto.FLOAT, (2,))],
[make_node("SoftmaxCrossEntropyLoss", ["x", "y"], ["z"], reduction="none")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (2,))]
)
def test_softmax_cross_entropy_mean(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (2, 3)), ("y", TensorProto.FLOAT, (2,))],
[make_node("SoftmaxCrossEntropyLoss", ["x", "y"], ["z"], reduction="mean")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, ())]
)
def test_softmax_cross_entropy_none_NCD1D2(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (2, 3, 5, 8)),
("y", TensorProto.FLOAT, (2, 5, 8)),
],
[make_node("SoftmaxCrossEntropyLoss", ["x", "y"], ["z"], reduction="none")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, (2, 5, 8))]
)
def test_softmax_cross_entropy_mean_NCD1D2(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (2, 3, 4, 5)),
("y", TensorProto.FLOAT, (2, 4, 5)),
],
[make_node("SoftmaxCrossEntropyLoss", ["x", "y"], ["z"], reduction="mean")],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("z", TensorProto.FLOAT, ())]
)
def test_celu_function_output_shape(self) -> None:
graph = self._make_graph(
[("X", TensorProto.FLOAT, (25, 48, 16, 16))],
[make_node("Celu", ["X"], ["Y"], alpha=2.0)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", TensorProto.FLOAT, (25, 48, 16, 16))]
)
def test_celu_float16_and_bfloat16(self) -> None:
for elem_type in (TensorProto.FLOAT16, TensorProto.BFLOAT16):
graph = self._make_graph(
[("X", elem_type, (3, 4))],
[make_node("Celu", ["X"], ["Y"], alpha=2.0)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("Y", elem_type, (3, 4))]
)
def prepare_input_initializer_tensors(self, initializer_shape, input_shape):
nodes = [make_node("Add", ["x", "y"], "z")]
if initializer_shape is None:
initializer = []
else:
size = 1
for d in initializer_shape:
size = size * d
vals = [0.0 for i in range(size)]
initializer = [
make_tensor("x", TensorProto.FLOAT, initializer_shape, vals),
make_tensor("y", TensorProto.FLOAT, initializer_shape, vals),
]
if input_shape is None:
inputs = []
else:
inputs = [
helper.make_tensor_value_info("x", TensorProto.FLOAT, input_shape),
helper.make_tensor_value_info("y", TensorProto.FLOAT, input_shape),
]
graph = helper.make_graph(
nodes,
"test",
inputs=inputs,
outputs=[],
initializer=initializer,
value_info=[],
)
return helper.make_model(graph)
def test_infer_with_initializer_without_input_above_ir4(self) -> None:
# This is for testing IR>=4: some tensors can only exist in initializer and not in input
# So shape_inference should make use of initializer shapes
initializer_shape = (8, 7)
original_model = self.prepare_input_initializer_tensors(initializer_shape, None)
inferred_model = onnx.shape_inference.infer_shapes(
original_model, strict_mode=True
)
# If shape inference fails, it will throw IndexError
z_tenor = inferred_model.graph.value_info.pop()
z_shape = (
z_tenor.type.tensor_type.shape.dim[0].dim_value,
z_tenor.type.tensor_type.shape.dim[1].dim_value,
)
assert z_shape == initializer_shape
def test_infer_with_initializer_without_input_below_ir4(self) -> None:
# This is for testing IR<4: tensors must exist both in initializer and input
# So shape_inference should not make use of initializer shapes
# Use (None, None) as empty input
initializer_shape = (8, 7)
input_shape = (None, None)
original_model = self.prepare_input_initializer_tensors(
initializer_shape, input_shape
)
original_model.ir_version = 3 # test ir_version < 4
inferred_model = onnx.shape_inference.infer_shapes(
original_model, strict_mode=True
)
z_tenor = inferred_model.graph.value_info.pop()
z_shape = (
z_tenor.type.tensor_type.shape.dim[0].dim_value,
z_tenor.type.tensor_type.shape.dim[1].dim_value,
)
# If the input is not updated by the initializer, the output shape will keep empty (0, 0)
assert z_shape == (0, 0)
def test_infer_initializer_input_mismatch(self) -> None:
# Catch error if initializer and input mismatch
initializer_shape = (8, 7)
input_shape = (4, 3)
original_model = self.prepare_input_initializer_tensors(
initializer_shape, input_shape
)
# Inferred shape and existing shape differ in dimension 0
with pytest.raises(onnx.shape_inference.InferenceError):
onnx.shape_inference.infer_shapes(
original_model,
strict_mode=True,
)
def test_infer_initializer_input_consistency_all_none(self) -> None:
initializer_shape = (8, 7)
input_shape = (None, None) # acceptable
original_model = self.prepare_input_initializer_tensors(
initializer_shape, input_shape
)
onnx.shape_inference.infer_shapes(original_model, strict_mode=True)
def test_infer_initializer_input_consistency_single_none(self) -> None:
initializer_shape = (8, 7)
input_shape = (None, 7) # acceptable
original_model = self.prepare_input_initializer_tensors(
initializer_shape, input_shape
)
onnx.shape_inference.infer_shapes(original_model, strict_mode=True)
def test_infer_initializer_input_consistency_different_rank(self) -> None:
initializer_shape = (8, 7, 9)
input_shape = (None, 7) # acceptable
original_model = self.prepare_input_initializer_tensors(
initializer_shape, input_shape
)
# Inferred shape and existing shape differ in rank: (3) vs (2)
with pytest.raises(onnx.shape_inference.InferenceError):
onnx.shape_inference.infer_shapes(
original_model,
strict_mode=True,
)
def test_infer_initializer_input_consistency_all_none_serialized(self) -> None:
# Reuse test_infer_initializer_input_consistency_all_none test case and check with
# Serialized model
initializer_shape = (8, 7)
input_shape = (None, None) # acceptable
original_model = self.prepare_input_initializer_tensors(
initializer_shape, input_shape
)
onnx.shape_inference.infer_shapes(
original_model.SerializeToString(), strict_mode=True
)
def test_trilu_upper(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4, 5)), ("k", TensorProto.INT64, ())],
[make_node("Trilu", ["x", "k"], ["y"])],
[],
initializer=[make_tensor("k", TensorProto.INT64, (), (2,))],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3, 4, 5))]
)
def test_trilu_lower(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3, 4, 5)), ("k", TensorProto.INT64, ())],
[make_node("Trilu", ["x", "k"], ["y"], upper=0)],
[],
initializer=[make_tensor("k", TensorProto.INT64, (), (10,))],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (3, 4, 5))]
)
def test_trilu_upper_zero(self) -> None:
graph = self._make_graph(
[("x", TensorProto.INT64, (0, 5)), ("k", TensorProto.INT64, ())],
[make_node("Trilu", ["x", "k"], ["y"], upper=1)],
[],
initializer=[make_tensor("k", TensorProto.INT64, (), (5,))],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.INT64, (0, 5))]
)
def test_trilu_lower_one(self) -> None:
graph = self._make_graph(
[("x", TensorProto.INT32, (3, 1, 5))],
[make_node("Trilu", ["x"], ["y"], upper=0)],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.INT32, (3, 1, 5))]
)
def test_batch_norm_train(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 4, 5, 6, 7)),
("scale", TensorProto.FLOAT, (4,)),
("b", TensorProto.FLOAT, (4,)),
("input_mean", TensorProto.FLOAT, (4,)),
("input_var", TensorProto.FLOAT, (4,)),
],
[
make_node(
"BatchNormalization",
["x", "scale", "b", "input_mean", "input_var"],
["out", "output_mean", "output_var"],
training_mode=1,
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("out", TensorProto.FLOAT, (3, 4, 5, 6, 7)),
make_tensor_value_info("output_mean", TensorProto.FLOAT, (4,)),
make_tensor_value_info("output_var", TensorProto.FLOAT, (4,)),
],
)
def test_batch_norm_train_dim_param(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, "C", 5, 6, 7)),
("scale", TensorProto.FLOAT, ("C",)),
("b", TensorProto.FLOAT, ("C",)),
("input_mean", TensorProto.FLOAT, ("C",)),
("input_var", TensorProto.FLOAT, ("C",)),
],
[
make_node(
"BatchNormalization",
["x", "scale", "b", "input_mean", "input_var"],
["out", "output_mean", "output_var"],
training_mode=1,
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("out", TensorProto.FLOAT, (3, "C", 5, 6, 7)),
make_tensor_value_info("output_mean", TensorProto.FLOAT, ("C",)),
make_tensor_value_info("output_var", TensorProto.FLOAT, ("C",)),
],
)
def test_batch_norm_train_with_diff_type(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT16, (3, 4, 5, 6, 7)),
("scale", TensorProto.FLOAT16, (4,)),
("b", TensorProto.FLOAT16, (4,)),
("input_mean", TensorProto.FLOAT, (4,)),
("input_var", TensorProto.FLOAT, (4,)),
],
[
make_node(
"BatchNormalization",
["x", "scale", "b", "input_mean", "input_var"],
["out", "output_mean", "output_var"],
training_mode=1,
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("out", TensorProto.FLOAT16, (3, 4, 5, 6, 7)),
make_tensor_value_info("output_mean", TensorProto.FLOAT, (4,)),
make_tensor_value_info("output_var", TensorProto.FLOAT, (4,)),
],
)
def test_batch_norm_test(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 4, 5, 6, 7)),
("scale", TensorProto.FLOAT, (4,)),
("b", TensorProto.FLOAT, (4,)),
("input_mean", TensorProto.FLOAT, (4,)),
("input_var", TensorProto.FLOAT, (4,)),
],
[
make_node(
"BatchNormalization",
["x", "scale", "b", "input_mean", "input_var"],
["out"],
training_mode=0,
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.FLOAT, (3, 4, 5, 6, 7))]
)
def test_batch_norm_test_no_dim(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (3, 4, None, None, None)),
("scale", TensorProto.FLOAT, (4,)),
("b", TensorProto.FLOAT, (4,)),
("input_mean", TensorProto.FLOAT, (None,)),
("input_var", TensorProto.FLOAT, (4,)),
],
[
make_node(
"BatchNormalization",
["x", "scale", "b", "input_mean", "input_var"],
["out"],
training_mode=0,
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"out", TensorProto.FLOAT, (3, 4, None, None, None)
)
],
)
def test_batch_norm_train_no_shape(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, None),
("scale", TensorProto.FLOAT, None),
("b", TensorProto.FLOAT, None),
("input_mean", TensorProto.FLOAT, ("C",)),
("input_var", TensorProto.FLOAT, ("C",)),
],
[
make_node(
"BatchNormalization",
["x", "scale", "b", "input_mean", "input_var"],
["out", "running_mean", "running_var"],
training_mode=1,
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("out", TensorProto.FLOAT, None),
make_tensor_value_info("running_mean", TensorProto.FLOAT, ("C",)),
make_tensor_value_info("running_var", TensorProto.FLOAT, ("C",)),
],
)
def test_nonzero(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (None,))],
[make_node("NonZero", ["x"], ["out"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.INT64, (1, None))]
)
def test_nonzero_no_shape(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, None)], [make_node("NonZero", ["x"], ["out"])], []
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.INT64, (None, None))]
)
def test_nonzero_existing_dim_param(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, (3,))],
[make_node("NonZero", ["x"], ["y"])],
[make_tensor_value_info("y", TensorProto.INT64, (None, "NZ"))],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.INT64, (1, "NZ"))]
)
def test_nonzero_scalar(self) -> None:
graph = self._make_graph(
[("x", TensorProto.FLOAT, ())], [make_node("NonZero", ["x"], ["out"])], []
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.INT64, (0, None))]
)
def test_optional_construct_empty_tensor(self) -> None:
tensor_type_proto = helper.make_tensor_type_proto(
elem_type=TensorProto.FLOAT, shape=[1, 2, 3]
)
optional_type_proto = helper.make_optional_type_proto(tensor_type_proto)
optional_val_info = helper.make_value_info(
name="output", type_proto=optional_type_proto
)
graph = self._make_graph(
[], [make_node("Optional", [], ["output"], type=tensor_type_proto)], []
)
self._assert_inferred(graph, [optional_val_info])
def test_optional_construct_empty_sequence(self) -> None:
tensor_type_proto = helper.make_tensor_type_proto(
elem_type=TensorProto.INT32, shape=[1, 2, 3]
)
sequence_type_proto = helper.make_sequence_type_proto(tensor_type_proto)
optional_type_proto = helper.make_optional_type_proto(sequence_type_proto)
optional_val_info = helper.make_value_info(
name="output_sequence", type_proto=optional_type_proto
)
graph = self._make_graph(
[],
[make_node("Optional", [], ["output_sequence"], type=sequence_type_proto)],
[],
)
self._assert_inferred(graph, [optional_val_info])
def test_optional_construct_tensor(self) -> None:
tensor_type_proto = helper.make_tensor_type_proto(
elem_type=TensorProto.FLOAT, shape=[2, 3, 4]
)
optional_type_proto = helper.make_optional_type_proto(tensor_type_proto)
optional_val_info = helper.make_value_info(
name="output", type_proto=optional_type_proto
)
graph = self._make_graph(
[("input1", TensorProto.FLOAT, (2, 3, 4))],
[make_node("Optional", ["input1"], ["output"])],
[],
)
self._assert_inferred(graph, [optional_val_info])
def test_optional_construct_sequence(self) -> None:
tensor_type_proto = helper.make_tensor_type_proto(
elem_type=TensorProto.INT64, shape=[2, 3, 0]
)
sequence_type_proto = helper.make_sequence_type_proto(tensor_type_proto)
sequence_val_info = helper.make_value_info(
name="input_sequence", type_proto=sequence_type_proto
)
optional_type_proto = helper.make_optional_type_proto(sequence_type_proto)
optional_val_info = helper.make_value_info(
name="output_sequence", type_proto=optional_type_proto
)
graph = self._make_graph(
[("input1", TensorProto.INT64, (2, 3, 0))],
[
make_node("SequenceConstruct", ["input1"], ["input_sequence"]),
make_node("Optional", ["input_sequence"], ["output_sequence"]),
],
[],
)
self._assert_inferred(graph, [sequence_val_info, optional_val_info])
def test_optional_tensor_has_element(self) -> None:
tensor_type_proto = helper.make_tensor_type_proto(
elem_type=TensorProto.FLOAT, shape=[2, 3, 4]
)
optional_type_proto = helper.make_optional_type_proto(tensor_type_proto)
optional_val_info = helper.make_value_info(
name="sequence", type_proto=optional_type_proto
)
graph = self._make_graph(
[("input1", TensorProto.FLOAT, (2, 3, 4))],
[
make_node("Optional", ["input1"], ["sequence"]),
make_node("OptionalHasElement", ["sequence"], ["output"]),
],
[],
)
self._assert_inferred(
graph,
[optional_val_info, make_tensor_value_info("output", TensorProto.BOOL, ())],
)
def test_optional_sequence_has_element(self) -> None:
tensor_type_proto = helper.make_tensor_type_proto(
elem_type=TensorProto.FLOAT, shape=[0, 3, 4]
)
sequence_type_proto = helper.make_sequence_type_proto(tensor_type_proto)
sequence_val_info = helper.make_value_info(
name="sequence", type_proto=sequence_type_proto
)
optional_type_proto = helper.make_optional_type_proto(sequence_type_proto)
optional_val_info = helper.make_value_info(
name="optional", type_proto=optional_type_proto
)
graph = self._make_graph(
[("input1", TensorProto.FLOAT, (0, 3, 4))],
[
make_node("SequenceConstruct", ["input1"], ["sequence"]),
make_node("Optional", ["sequence"], ["optional"]),
make_node("OptionalHasElement", ["optional"], ["output"]),
],
[],
)
self._assert_inferred(
graph,
[
sequence_val_info,
optional_val_info,
make_tensor_value_info("output", TensorProto.BOOL, ()),
],
)
def test_tensor_get_element(self) -> None:
tensor_type_proto = helper.make_tensor_type_proto(
elem_type=TensorProto.DOUBLE, shape=[2, 1, 4]
)
output_tensor_val_info = helper.make_value_info(
name="output", type_proto=tensor_type_proto
)
graph = self._make_graph(
[("input", TensorProto.DOUBLE, (2, 1, 4))],
[
make_node("OptionalGetElement", ["input"], ["output"]),
],
[],
)
self._assert_inferred(graph, [output_tensor_val_info])
@pytest.mark.parametrize("version", all_versions_for("StringSplit"))
def test_string_split_basic(self, version) -> None:
substrings = make_tensor_value_info(
"substrings",
TensorProto.STRING,
(2, None),
)
length = make_tensor_value_info("length", TensorProto.INT64, (2,))
graph = self._make_graph(
[
("x", TensorProto.STRING, (2,)),
],
[make_node("StringSplit", ["x"], ["substrings", "length"])],
[substrings, length],
)
self._assert_inferred(
graph,
[substrings, length],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("StringSplit"))
def test_string_split_symbolic(self, version) -> None:
substrings = make_tensor_value_info(
"substrings",
TensorProto.STRING,
("A", None),
)
length = make_tensor_value_info("length", TensorProto.INT64, ("A",))
graph = self._make_graph(
[
("x", TensorProto.STRING, ("A",)),
],
[make_node("StringSplit", ["x"], ["substrings", "length"])],
[substrings, length],
)
self._assert_inferred(
graph,
[substrings, length],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("StringSplit"))
def test_string_split_nested(self, version) -> None:
substrings = make_tensor_value_info(
"substrings", TensorProto.STRING, (2, 4, 3, None)
)
length = make_tensor_value_info("length", TensorProto.INT64, (2, 4, 3))
graph = self._make_graph(
[
("x", TensorProto.STRING, (2, 4, 3)),
],
[make_node("StringSplit", ["x"], ["substrings", "length"], maxsplit=2)],
[substrings, length],
)
self._assert_inferred(
graph,
[substrings, length],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("StringSplit"))
def test_string_split_zero_dimensional_input(self, version) -> None:
substrings = make_tensor_value_info("substrings", TensorProto.STRING, (None,))
length = make_tensor_value_info("length", TensorProto.INT64, ())
graph = self._make_graph(
[
("x", TensorProto.STRING, ()),
],
[make_node("StringSplit", ["x"], ["substrings", "length"], maxsplit=2)],
[substrings, length],
)
self._assert_inferred(
graph,
[substrings, length],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("StringSplit"))
def test_string_split_empty_input(self, version) -> None:
substrings = make_tensor_value_info(
"substrings", TensorProto.STRING, ("M", 3, 0, None)
)
length = make_tensor_value_info("length", TensorProto.INT64, ("M", 3, 0))
graph = self._make_graph(
[
("x", TensorProto.STRING, ("M", 3, 0)),
],
[make_node("StringSplit", ["x"], ["substrings", "length"], maxsplit=2)],
[substrings, length],
)
self._assert_inferred(
graph,
[substrings, length],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
def test_optional_tensor_get_element(self) -> None:
tensor_type_proto = helper.make_tensor_type_proto(
elem_type=TensorProto.DOUBLE, shape=[2, 1, 4]
)
tensor_val_into = helper.make_value_info(
name="output", type_proto=tensor_type_proto
)
optional_type_proto = helper.make_optional_type_proto(tensor_type_proto)
optional_val_info = helper.make_value_info(
name="optional", type_proto=optional_type_proto
)
graph = self._make_graph(
[("input1", TensorProto.DOUBLE, (2, 1, 4))],
[
make_node("Optional", ["input1"], ["optional"]),
make_node("OptionalGetElement", ["optional"], ["output"]),
],
[],
)
self._assert_inferred(graph, [optional_val_info, tensor_val_into])
def test_optional_sequence_get_element(self) -> None:
tensor_type_proto = helper.make_tensor_type_proto(
elem_type=TensorProto.INT32, shape=[2, 0, 4]
)
sequence_type_proto = helper.make_sequence_type_proto(tensor_type_proto)
sequence_val_into = helper.make_value_info(
name="sequence", type_proto=sequence_type_proto
)
optional_type_proto = helper.make_optional_type_proto(sequence_type_proto)
optional_val_info = helper.make_value_info(
name="optional", type_proto=optional_type_proto
)
output_val_into = helper.make_value_info(
name="output", type_proto=sequence_type_proto
)
graph = self._make_graph(
[("input1", TensorProto.INT32, (2, 0, 4))],
[
make_node("SequenceConstruct", ["input1"], ["sequence"]),
make_node("Optional", ["sequence"], ["optional"]),
make_node("OptionalGetElement", ["optional"], ["output"]),
],
[],
)
self._assert_inferred(
graph, [optional_val_info, sequence_val_into, output_val_into]
)
def test_where_bfloat(self) -> None:
graph = self._make_graph(
[
("cond", TensorProto.BOOL, (10,)),
("x", TensorProto.BFLOAT16, (10,)),
("y", TensorProto.BFLOAT16, (10,)),
],
[make_node("Where", ["cond", "x", "y"], ["out"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("out", TensorProto.BFLOAT16, (10,))]
)
def test_parse_data_with_unsupported_tensor_type(self) -> None:
model = helper.make_model(
graph=helper.make_graph(
name="graph_with_unsupported_type",
inputs=[],
outputs=[
helper.make_tensor_value_info("y", TensorProto.FLOAT, shape=None)
],
nodes=[make_node("ConstantOfShape", ["x"], ["y"])],
# ConstantOfShape only accepts np.int64 instead of np.int32
initializer=[
numpy_helper.from_array(np.array([4, 3], dtype=np.int32), name="x")
],
)
)
# Strict shape inference should catch this invalid type error (int32 is not supported)
with pytest.raises(onnx.shape_inference.InferenceError):
onnx.shape_inference.infer_shapes(
model,
strict_mode=True,
)
# Even nornmal shape inference should not produce any invalid shape due to wrong type for ParseData
inferred_model = onnx.shape_inference.infer_shapes(model)
assert not inferred_model.graph.output[0].type.tensor_type.HasField("shape")
def test_parse_data_with_undefined_tensor_type(self) -> None:
model = helper.make_model(
graph=helper.make_graph(
name="graph_with_undefined_type",
inputs=[],
outputs=[
helper.make_tensor_value_info("y", TensorProto.FLOAT, shape=None)
],
nodes=[make_node("ConstantOfShape", ["x"], ["y"])],
initializer=[
numpy_helper.from_array(np.array([4, 3], dtype=np.int64), name="x")
],
)
)
# Hardcode the tensor type as UNDEFINED to test catching undefined type error
model.graph.initializer[0].data_type = TensorProto.UNDEFINED
# Strict shape inference should catch this undefined type error
with pytest.raises(onnx.shape_inference.InferenceError):
onnx.shape_inference.infer_shapes(
model,
strict_mode=True,
)
# Even nornmal shape inference should not produce any invalid shape due to undefined type for ParseData
inferred_model = onnx.shape_inference.infer_shapes(model)
assert not inferred_model.graph.output[0].type.tensor_type.HasField("shape")
graph = self._make_graph(
[("x", TensorProto.UINT8, (1, 0, 0)), ("shape", TensorProto.INT64, (3,))],
[make_node("Reshape", ["x", "shape"], ["y"], allowzero=1)],
[],
initializer=[make_tensor("shape", TensorProto.INT64, (3,), (0, 1, 1))],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.UINT8, (0, 1, 1))]
)
def test_affinegrid_2d(self) -> None:
N, C, H, W = 2, 3, 4, 5
graph = self._make_graph(
[
("theta", TensorProto.FLOAT, (N, 2, 3)),
("size", TensorProto.INT64, (4,)),
],
[
make_node(
"AffineGrid",
["theta", "size"],
["grid"],
align_corners=1,
)
],
[],
initializer=[make_tensor("size", TensorProto.INT64, (4,), (N, C, H, W))],
)
self._assert_inferred(
graph, [make_tensor_value_info("grid", TensorProto.FLOAT, (N, H, W, 2))]
)
def test_affinegrid_3d(self) -> None:
N, C, D, H, W = 2, 3, 4, 5, 6
graph = self._make_graph(
[
("theta", TensorProto.FLOAT, (N, 3, 4)),
("size", TensorProto.INT64, (5,)),
],
[
make_node(
"AffineGrid",
["theta", "size"],
["grid"],
)
],
[],
initializer=[make_tensor("size", TensorProto.INT64, (5,), (N, C, D, H, W))],
)
self._assert_inferred(
graph, [make_tensor_value_info("grid", TensorProto.FLOAT, (N, D, H, W, 3))]
)
def test_gridsample_2d(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 3, 3)),
("grid", TensorProto.INT64, (1, 3, 3, 2)),
],
[
make_node(
"GridSample",
["x", "grid"],
["y"],
mode="nearest",
padding_mode="border",
align_corners=1,
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (1, 1, 3, 3))]
)
def test_gridsample_3d(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, (1, 1, 3, 3, 3)),
("grid", TensorProto.INT64, (1, 3, 2, 3, 3)),
],
[
make_node(
"GridSample",
["x", "grid"],
["y"],
mode="nearest",
padding_mode="border",
align_corners=1,
)
],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("y", TensorProto.FLOAT, (1, 1, 3, 2, 3))]
)
def test_gridsample_2d_defaults(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, ("N", "C", "H", "W")),
("grid", TensorProto.FLOAT, ("N", "H_out", "W_out", 2)),
],
[make_node("GridSample", ["x", "grid"], ["y"])],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"y", TensorProto.FLOAT, ("N", "C", "H_out", "W_out")
)
],
)
def test_gridsample_3d_defaults(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, ("N", "C", "D", "H", "W")),
("grid", TensorProto.FLOAT, ("N", "D_out", "H_out", "W_out", 3)),
],
[make_node("GridSample", ["x", "grid"], ["y"])],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"y", TensorProto.FLOAT, ("N", "C", "D_out", "H_out", "W_out")
)
],
)
def test_gridsample_2d_no_dim(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, ("N", "C", None, None)),
("grid", TensorProto.FLOAT, ("N", None, None, 2)),
],
[
make_node(
"GridSample",
["x", "grid"],
["y"],
mode="linear",
padding_mode="border",
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, ("N", "C", None, None))],
)
def test_gridsample_3d_no_dim(self) -> None:
graph = self._make_graph(
[
("x", TensorProto.FLOAT, ("N", "C", None, None, None)),
("grid", TensorProto.FLOAT, ("N", None, None, None, 3)),
],
[
make_node(
"GridSample",
["x", "grid"],
["y"],
mode="linear",
padding_mode="border",
)
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info(
"y", TensorProto.FLOAT, ("N", "C", None, None, None)
)
],
)
def test_sequence_map_identity_known_dims(self):
graph = parse_graph("""
agraph (float[220,220,3] input1, float[220,220,3] input2, float[220,220,3] input3)
=> (out_sequence)
{
in_sequence = SequenceConstruct(input1, input2, input3)
out_sequence = SequenceMap (in_sequence) <
body = body_graph (float[220,220,3] input) => (float[220,220,3] output) {
output = Identity(input)
}
>
}
""")
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (220, 220, 3)
),
make_tensor_sequence_value_info(
"out_sequence", TensorProto.FLOAT, (220, 220, 3)
),
],
)
def test_sequence_map_identity_unknown_dims(self):
graph = parse_graph("""
agraph (float[200,300,3] input1, float[100,200,3] input2, float[5,1,3] input3)
=> (out_sequence)
{
in_sequence = SequenceConstruct(input1, input2, input3)
out_sequence = SequenceMap (in_sequence) <
body = body_graph (float[H,W,3] input) => (float[H,W,3] output) {
output = Identity(input)
}
>
}
""")
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (None, None, 3)
),
make_tensor_sequence_value_info(
"out_sequence", TensorProto.FLOAT, (None, None, 3)
),
],
)
def test_sequence_map_slice_outs_known_dims(self):
# The body's y1/y2 outputs are declared with concrete shapes; SequenceMap propagates those
# element shapes into the output sequences.
graph = parse_graph("""
agraph (float[220,310,3] input1, float[110,210,3] input2, float[90,110,3] input3)
=> (out_sequence1, out_sequence2)
{
in_sequence = SequenceConstruct(input1, input2, input3)
out_sequence1, out_sequence2 = SequenceMap (in_sequence) <
body = body_graph (float[H,W,3] x) => (float[10,20,3] y1, float[30,40,3] y2)
<
int64[2] axes = {0,1},
int64[2] starts1 = {0,0}, int64[2] ends1 = {10,20},
int64[2] starts2 = {0,0}, int64[2] ends2 = {30,40}
>
{
y1 = Slice(x, starts1, ends1, axes)
y2 = Slice(x, starts2, ends2, axes)
}
>
}
""")
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (None, None, 3)
),
make_tensor_sequence_value_info(
"out_sequence1", TensorProto.FLOAT, (10, 20, 3)
),
make_tensor_sequence_value_info(
"out_sequence2", TensorProto.FLOAT, (30, 40, 3)
),
],
)
def test_sequence_map_slice_outs_unknown_dims(self):
graph = parse_graph("""
agraph (float[220,310,3] input1, float[110,210,3] input2, float[90,110,3] input3)
=> (out_sequence1, out_sequence2)
{
in_sequence = SequenceConstruct(input1, input2, input3)
out_sequence1, out_sequence2 = SequenceMap (in_sequence) <
body = body_graph (float[H,W,3] x) => (float[H1,W1,3] y1, float[H2,W2,3] y2)
<
int64[2] axes = {0,1},
int64[2] starts1 = {0,0}, int64[2] ends1 = {10,20},
int64[2] starts2 = {0,0}, int64[2] ends2 = {30,40}
>
{
y1 = Slice(x, starts1, ends1, axes)
y2 = Slice(x, starts2, ends2, axes)
}
>
}
""")
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (None, None, 3)
),
make_tensor_sequence_value_info(
"out_sequence1", TensorProto.FLOAT, (None, None, 3)
),
make_tensor_sequence_value_info(
"out_sequence2", TensorProto.FLOAT, (None, None, 3)
),
],
)
def test_sequence_map_different_tensor_type(self):
graph = parse_graph("""
agraph (float[220,310,3] input1, float[110,210,3] input2, float[90,110,3] input3)
=> (shapes)
{
in_sequence = SequenceConstruct(input1, input2, input3)
shapes = SequenceMap (in_sequence) <
body = body_graph (float[H,W,C] x) => (int64[3] shape) {
shape = Shape(x)
}
>
}
""")
self._assert_inferred(
graph,
[
make_tensor_sequence_value_info(
"in_sequence", TensorProto.FLOAT, (None, None, 3)
),
make_tensor_sequence_value_info("shapes", TensorProto.INT64, (3,)),
],
)
def test_hammingwindow(self):
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["shape"],
value=make_tensor("shape", TensorProto.INT64, (), (10,)),
),
make_node("HammingWindow", ["shape"], ["y"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("shape", TensorProto.INT64, ()),
make_tensor_value_info("y", TensorProto.FLOAT, (10,)),
],
)
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["shape"],
value=make_tensor("shape", TensorProto.INT64, (), (10,)),
),
make_node("HammingWindow", ["shape"], ["y"], periodic=0),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("shape", TensorProto.INT64, ()),
make_tensor_value_info("y", TensorProto.FLOAT, (10,)),
],
)
def test_hannwindow(self):
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["shape"],
value=make_tensor("shape", TensorProto.INT64, (), (10,)),
),
make_node("HannWindow", ["shape"], ["y"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("shape", TensorProto.INT64, ()),
make_tensor_value_info("y", TensorProto.FLOAT, (10,)),
],
)
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["shape"],
value=make_tensor("shape", TensorProto.INT64, (), (10,)),
),
make_node("HannWindow", ["shape"], ["y"], periodic=0),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("shape", TensorProto.INT64, ()),
make_tensor_value_info("y", TensorProto.FLOAT, (10,)),
],
)
def test_blackmanwindow(self):
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["shape"],
value=make_tensor("shape", TensorProto.INT64, (), (10,)),
),
make_node("BlackmanWindow", ["shape"], ["y"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("shape", TensorProto.INT64, ()),
make_tensor_value_info("y", TensorProto.FLOAT, (10,)),
],
)
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["shape"],
value=make_tensor("shape", TensorProto.INT64, (), (10,)),
),
make_node("BlackmanWindow", ["shape"], ["y"], periodic=0),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("shape", TensorProto.INT64, ()),
make_tensor_value_info("y", TensorProto.FLOAT, (10,)),
],
)
@pytest.mark.parametrize("version", all_versions_for("DFT"))
@pytest.mark.parametrize(
"input_shape, axis, onesided, inverse, expected_shape",
[
((2, 5, 1), None, None, None, (2, 5, 2)),
((3, 5, 10, 1), 0, 0, 0, (3, 5, 10, 2)),
((3, 5, 10, 1), 1, 0, 0, (3, 5, 10, 2)),
((3, 5, 10, 1), 2, 0, 0, (3, 5, 10, 2)),
((3, 5, 10, 1), -2, 0, 0, (3, 5, 10, 2)),
((3, 5, 10, 1), 0, 1, 0, (2, 5, 10, 2)),
((3, 5, 10, 1), 1, 1, 0, (3, 3, 10, 2)),
((3, 5, 10, 1), 2, 1, 0, (3, 5, 6, 2)),
((3, 5, 10, 1), -2, 1, 0, (3, 5, 6, 2)),
((2, 5, 2), None, None, None, (2, 5, 2)),
((2, 5, 1), 1, None, 1, (2, 5, 2)),
((2, 5, 2), 1, None, 1, (2, 5, 2)),
((2, 5, 10, 2), 0, 1, 1, (2, 5, 10, 1)),
((3, 3, 10, 2), 1, 1, 1, (3, 4, 10, 1)),
((3, 5, 6, 2), 2, 1, 1, (3, 5, 10, 1)),
((3, 5, 6, 2), -2, 1, 1, (3, 5, 10, 1)),
],
)
def test_dft(
self,
version: int,
input_shape: tuple[int],
axis: int | None,
onesided: int | None,
inverse: int | None,
expected_shape: tuple[int],
) -> None:
# Build the attributes for different opset versions
attributes = {}
if onesided is not None:
attributes["onesided"] = onesided
if inverse is not None:
attributes["inverse"] = inverse
if version < 20:
if axis is not None:
attributes["axis"] = axis
nodes = [make_node("DFT", ["input", ""], ["output"], **attributes)]
value_infos = []
else:
assert version >= 20
if axis is not None:
nodes = [
make_node(
"Constant",
[],
["axis"],
value=make_tensor("axis", TensorProto.INT64, (), (axis,)),
),
make_node("DFT", ["input", "", "axis"], ["output"], **attributes),
]
value_infos = [make_tensor_value_info("axis", TensorProto.INT64, ())]
else:
nodes = [
make_node("DFT", ["input", "", ""], ["output"], **attributes),
]
value_infos = []
# Construct the graph
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["input"],
value=make_tensor(
"input",
TensorProto.FLOAT,
input_shape,
np.ones(input_shape, dtype=np.float32).flatten(),
),
),
*nodes,
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("input", TensorProto.FLOAT, input_shape),
*value_infos,
make_tensor_value_info("output", TensorProto.FLOAT, expected_shape),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("version", all_versions_for("DFT"))
@pytest.mark.parametrize(
"input_shape, axis, onesided, inverse, expected_shape",
[
((2, 5, 1), None, None, None, (2, 42, 2)),
((3, 5, 10, 1), 0, 0, 0, (42, 5, 10, 2)),
((3, 5, 10, 1), 1, 0, 0, (3, 42, 10, 2)),
((3, 5, 10, 1), 2, 0, 0, (3, 5, 42, 2)),
((3, 5, 10, 1), -2, 0, 0, (3, 5, 42, 2)),
((3, 5, 10, 1), 0, 1, 0, (22, 5, 10, 2)),
((3, 5, 10, 1), 1, 1, 0, (3, 22, 10, 2)),
((3, 5, 10, 1), 2, 1, 0, (3, 5, 22, 2)),
((3, 5, 10, 1), -2, 1, 0, (3, 5, 22, 2)),
((2, 5, 2), None, None, None, (2, 42, 2)),
((2, 5, 1), 1, None, 1, (2, 42, 2)),
((2, 5, 2), 1, None, 1, (2, 42, 2)),
((2, 5, 10, 2), 0, 1, 1, (42, 5, 10, 1)),
((3, 3, 10, 2), 1, 1, 1, (3, 42, 10, 1)),
((3, 5, 6, 2), 2, 1, 1, (3, 5, 42, 1)),
((3, 5, 6, 2), -2, 1, 1, (3, 5, 42, 1)),
],
)
def test_dft_dft_length(
self,
version: int,
input_shape: tuple[int],
axis: int | None,
onesided: int | None,
inverse: int | None,
expected_shape: tuple[int],
) -> None:
# Build the attributes for different opset versions
attributes = {}
if onesided is not None:
attributes["onesided"] = onesided
if inverse is not None:
attributes["inverse"] = inverse
dft_length = 42
if version < 20:
if axis is not None:
attributes["axis"] = axis
nodes = [
make_node(
"Constant",
[],
["dft_length"],
value=make_tensor(
"dft_length", TensorProto.INT64, (), (dft_length,)
),
),
make_node("DFT", ["input", "dft_length"], ["output"], **attributes),
]
value_infos = [make_tensor_value_info("dft_length", TensorProto.INT64, ())]
else:
assert version >= 20
if axis is not None:
nodes = [
make_node(
"Constant",
[],
["axis"],
value=make_tensor("axis", TensorProto.INT64, (), (axis,)),
),
make_node(
"Constant",
[],
["dft_length"],
value=make_tensor(
"dft_length", TensorProto.INT64, (), (dft_length,)
),
),
make_node(
"DFT",
["input", "dft_length", "axis"],
["output"],
**attributes,
),
]
value_infos = [
make_tensor_value_info("dft_length", TensorProto.INT64, ()),
make_tensor_value_info("axis", TensorProto.INT64, ()),
]
else:
nodes = [
make_node(
"Constant",
[],
["dft_length"],
value=make_tensor(
"dft_length", TensorProto.INT64, (), (dft_length,)
),
),
make_node(
"DFT",
["input", "dft_length", ""],
["output"],
**attributes,
),
]
value_infos = [
make_tensor_value_info("dft_length", TensorProto.INT64, ())
]
# Construct the graph
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["input"],
value=make_tensor(
"input",
TensorProto.FLOAT,
input_shape,
np.ones(input_shape, dtype=np.float32).flatten(),
),
),
*nodes,
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("input", TensorProto.FLOAT, input_shape),
*value_infos,
make_tensor_value_info("output", TensorProto.FLOAT, expected_shape),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
@pytest.mark.parametrize("axis", [3, -1, 4, -5])
def test_dft_invalid_axis_opset17(self, axis: int) -> None:
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["input"],
value=make_tensor(
"input",
TensorProto.FLOAT,
(2, 5, 5, 2),
np.ones((2, 5, 5, 2), dtype=np.float32).flatten(),
),
),
make_node("DFT", ["input", ""], ["output"], onesided=1, axis=axis),
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._assert_inferred(
graph,
[
make_tensor_value_info("input", TensorProto.FLOAT, (2, 5, 5, 2)),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 3, 5, 2)),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 17)],
)
@pytest.mark.parametrize("axis", [3, -1, 4, -5])
def test_dft_invalid_axis_opset20(self, axis: int) -> None:
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["input"],
value=make_tensor(
"input",
TensorProto.FLOAT,
(2, 5, 5, 2),
np.ones((2, 5, 5, 2), dtype=np.float32).flatten(),
),
),
make_node(
"Constant",
[],
["axis"],
value=make_tensor("axis", TensorProto.INT64, (), (axis,)),
),
make_node("DFT", ["input", "", "axis"], ["output"]),
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._assert_inferred(
graph,
[
make_tensor_value_info("input", TensorProto.FLOAT, (2, 5, 5, 2)),
make_tensor_value_info("axis", TensorProto.INT64, ()),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 3, 5, 2)),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 20)],
)
def test_dft_rfft_invalid_complex_input_opset17(self) -> None:
"""Test that RFFT (onesided=1, inverse=0) rejects complex input"""
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["input"],
value=make_tensor(
"input",
TensorProto.FLOAT,
(2, 5, 2), # Complex input (last dim = 2)
np.ones((2, 5, 2), dtype=np.float32).flatten(),
),
),
make_node("DFT", ["input", ""], ["output"], onesided=1, axis=1),
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._assert_inferred(
graph,
[
make_tensor_value_info("input", TensorProto.FLOAT, (2, 5, 2)),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 3, 2)),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 17)],
)
def test_dft_rfft_invalid_complex_input_opset20(self) -> None:
"""Test that RFFT (onesided=1, inverse=0) rejects complex input"""
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["input"],
value=make_tensor(
"input",
TensorProto.FLOAT,
(2, 5, 2), # Complex input (last dim = 2)
np.ones((2, 5, 2), dtype=np.float32).flatten(),
),
),
make_node(
"Constant",
[],
["axis"],
value=make_tensor("axis", TensorProto.INT64, (), (1,)),
),
make_node("DFT", ["input", "", "axis"], ["output"], onesided=1),
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._assert_inferred(
graph,
[
make_tensor_value_info("input", TensorProto.FLOAT, (2, 5, 2)),
make_tensor_value_info("axis", TensorProto.INT64, ()),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 3, 2)),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 20)],
)
def test_dft_irfft_invalid_real_input_opset17(self) -> None:
"""Test that IRFFT (onesided=1, inverse=1) rejects real input"""
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["input"],
value=make_tensor(
"input",
TensorProto.FLOAT,
(2, 5, 1), # Real input (last dim = 1)
np.ones((2, 5, 1), dtype=np.float32).flatten(),
),
),
make_node(
"DFT", ["input", ""], ["output"], onesided=1, inverse=1, axis=1
),
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._assert_inferred(
graph,
[
make_tensor_value_info("input", TensorProto.FLOAT, (2, 5, 1)),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 8, 1)),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 17)],
)
def test_dft_irfft_invalid_real_input_opset20(self) -> None:
"""Test that IRFFT (onesided=1, inverse=1) rejects real input"""
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["input"],
value=make_tensor(
"input",
TensorProto.FLOAT,
(2, 5, 1), # Real input (last dim = 1)
np.ones((2, 5, 1), dtype=np.float32).flatten(),
),
),
make_node(
"Constant",
[],
["axis"],
value=make_tensor("axis", TensorProto.INT64, (), (1,)),
),
make_node(
"DFT", ["input", "", "axis"], ["output"], onesided=1, inverse=1
),
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._assert_inferred(
graph,
[
make_tensor_value_info("input", TensorProto.FLOAT, (2, 5, 1)),
make_tensor_value_info("axis", TensorProto.INT64, ()),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 8, 1)),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 20)],
)
@pytest.mark.parametrize("shape", [(2, 5, 5, 1), (2, 5, 5, 2)])
def test_dft_dynamic_axis_opset20(self, shape: tuple[int, ...]) -> None:
graph = self._make_graph(
[("axis", TensorProto.INT64, ())],
[
make_node(
"Constant",
[],
["input"],
value=make_tensor(
"input",
TensorProto.FLOAT,
shape,
np.ones(shape, dtype=np.float32).flatten(),
),
),
make_node("DFT", ["input", "", "axis"], ["output"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("input", TensorProto.FLOAT, shape),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 5, 5, 2)),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 20)],
)
@pytest.mark.parametrize("shape", [(2, 5, 5, 1)])
def test_dft_dynamic_axis_onesided_dft_length_opset20(
self, shape: tuple[int, ...]
) -> None:
graph = self._make_graph(
[("axis", TensorProto.INT64, ())],
[
make_node(
"Constant",
[],
["input"],
value=make_tensor(
"input",
TensorProto.FLOAT,
shape,
np.ones(shape, dtype=np.float32).flatten(),
),
),
make_node(
"Constant",
[],
["dft_length"],
value=make_tensor(
"dft_length",
TensorProto.INT64,
(),
np.array([42], dtype=np.int64),
),
),
make_node(
"DFT", ["input", "dft_length", "axis"], ["output"], onesided=1
),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("input", TensorProto.FLOAT, shape),
make_tensor_value_info("dft_length", TensorProto.INT64, ()),
make_tensor_value_info(
"output", TensorProto.FLOAT, (None, None, None, 2)
),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 20)],
)
@pytest.mark.parametrize("shape", [(2, 5, 5, 1)])
def test_dft_dynamic_axis_onesided_opset20(self, shape: tuple[int, ...]) -> None:
graph = self._make_graph(
[("axis", TensorProto.INT64, ())],
[
make_node(
"Constant",
[],
["input"],
value=make_tensor(
"input",
TensorProto.FLOAT,
shape,
np.ones(shape, dtype=np.float32).flatten(),
),
),
make_node("DFT", ["input", "", "axis"], ["output"], onesided=1),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("input", TensorProto.FLOAT, shape),
make_tensor_value_info(
"output", TensorProto.FLOAT, (None, None, None, 2)
),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 20)],
)
def test_dft_onesided_default_axis_opset17(self) -> None:
# Opset 17 sets default axis to be 1.
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["input"],
value=make_tensor(
"input",
TensorProto.FLOAT,
(2, 5, 5, 1),
np.ones((2, 5, 5, 1), dtype=np.float32).flatten(),
),
),
make_node("DFT", ["input", ""], ["output"], onesided=1),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("input", TensorProto.FLOAT, (2, 5, 5, 1)),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 3, 5, 2)),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 17)],
)
def test_dft_onesided_default_axis_opset20(self) -> None:
# Opset 20 sets default axis to be -2.
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["input"],
value=make_tensor(
"input",
TensorProto.FLOAT,
(2, 5, 5, 1),
np.ones((2, 5, 5, 1), dtype=np.float32).flatten(),
),
),
make_node("DFT", ["input", "", ""], ["output"], onesided=1),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("input", TensorProto.FLOAT, (2, 5, 5, 1)),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 5, 3, 2)),
],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 20)],
)
def test_stft_reals(self):
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["signal"],
value=make_tensor(
"signal",
TensorProto.FLOAT,
(2, 10, 1),
(0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3),
),
),
make_node(
"Constant",
[],
["frame_step"],
value=make_tensor("frame_step", TensorProto.INT64, (), (2,)),
),
make_node(
"Constant",
[],
["window"],
value=make_tensor(
"window", TensorProto.INT64, (5,), (1, 2, 3, 4, 5)
),
),
make_node("STFT", ["signal", "frame_step", "window"], ["output"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("signal", TensorProto.FLOAT, (2, 10, 1)),
make_tensor_value_info("frame_step", TensorProto.INT64, ()),
make_tensor_value_info("window", TensorProto.INT64, (5,)),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 3, 5, 2)),
],
)
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["signal"],
value=make_tensor(
"signal",
TensorProto.FLOAT,
(2, 10, 1),
(0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3),
),
),
make_node(
"Constant",
[],
["frame_step"],
value=make_tensor("frame_step", TensorProto.INT64, (), (2,)),
),
make_node(
"Constant",
[],
["window"],
value=make_tensor(
"window", TensorProto.INT64, (5,), (1, 2, 3, 4, 5)
),
),
make_node(
"Constant",
[],
["frame_length"],
value=make_tensor("frame_length", TensorProto.INT64, (), (5,)),
),
make_node("STFT", ["signal", "frame_step", "window"], ["output"]),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("signal", TensorProto.FLOAT, (2, 10, 1)),
make_tensor_value_info("frame_step", TensorProto.INT64, ()),
make_tensor_value_info("window", TensorProto.INT64, (5,)),
make_tensor_value_info("frame_length", TensorProto.INT64, ()),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 3, 5, 2)),
],
)
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["signal"],
value=make_tensor(
"signal",
TensorProto.FLOAT,
(2, 10, 1),
(0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3),
),
),
make_node(
"Constant",
[],
["frame_step"],
value=make_tensor("frame_step", TensorProto.INT64, (), (2,)),
),
make_node(
"Constant",
[],
["frame_length"],
value=make_tensor("frame_length", TensorProto.INT64, (), (5,)),
),
make_node(
"STFT", ["signal", "frame_step", "", "frame_length"], ["output"]
),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("signal", TensorProto.FLOAT, (2, 10, 1)),
make_tensor_value_info("frame_step", TensorProto.INT64, ()),
make_tensor_value_info("frame_length", TensorProto.INT64, ()),
make_tensor_value_info("output", TensorProto.FLOAT, (2, 3, 5, 2)),
],
)
def test_melweightmatrix(self):
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["num_mel_bins"],
value=make_tensor("num_mel_bins", TensorProto.INT64, (), (10,)),
),
make_node(
"Constant",
[],
["dft_length"],
value=make_tensor("dft_length", TensorProto.INT64, (), (128,)),
),
make_node(
"Constant",
[],
["sample_rate"],
value=make_tensor("sample_rate", TensorProto.INT64, (), (10,)),
),
make_node(
"Constant",
[],
["lower_edge_hertz"],
value=make_tensor(
"lower_edge_hertz", TensorProto.FLOAT, (), (10.0,)
),
),
make_node(
"Constant",
[],
["upper_edge_hertz"],
value=make_tensor(
"upper_edge_hertz", TensorProto.FLOAT, (), (100.0,)
),
),
make_node(
"MelWeightMatrix",
[
"num_mel_bins",
"dft_length",
"sample_rate",
"lower_edge_hertz",
"upper_edge_hertz",
],
["output"],
),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("num_mel_bins", TensorProto.INT64, ()),
make_tensor_value_info("dft_length", TensorProto.INT64, ()),
make_tensor_value_info("sample_rate", TensorProto.INT64, ()),
make_tensor_value_info("lower_edge_hertz", TensorProto.FLOAT, ()),
make_tensor_value_info("upper_edge_hertz", TensorProto.FLOAT, ()),
make_tensor_value_info("output", TensorProto.FLOAT, (65, 10)),
],
)
def test_melweightmatrix_with_output_datatype(self):
graph = self._make_graph(
[],
[
make_node(
"Constant",
[],
["num_mel_bins"],
value=make_tensor("num_mel_bins", TensorProto.INT64, (), (10,)),
),
make_node(
"Constant",
[],
["dft_length"],
value=make_tensor("dft_length", TensorProto.INT64, (), (128,)),
),
make_node(
"Constant",
[],
["sample_rate"],
value=make_tensor("sample_rate", TensorProto.INT64, (), (10,)),
),
make_node(
"Constant",
[],
["lower_edge_hertz"],
value=make_tensor(
"lower_edge_hertz", TensorProto.FLOAT, (), (10.0,)
),
),
make_node(
"Constant",
[],
["upper_edge_hertz"],
value=make_tensor(
"upper_edge_hertz", TensorProto.FLOAT, (), (100.0,)
),
),
make_node(
"MelWeightMatrix",
[
"num_mel_bins",
"dft_length",
"sample_rate",
"lower_edge_hertz",
"upper_edge_hertz",
],
["output"],
output_datatype=TensorProto.DOUBLE,
),
],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("num_mel_bins", TensorProto.INT64, ()),
make_tensor_value_info("dft_length", TensorProto.INT64, ()),
make_tensor_value_info("sample_rate", TensorProto.INT64, ()),
make_tensor_value_info("lower_edge_hertz", TensorProto.FLOAT, ()),
make_tensor_value_info("upper_edge_hertz", TensorProto.FLOAT, ()),
make_tensor_value_info("output", TensorProto.DOUBLE, (65, 10)),
],
)
def test_center_crop_pad_hwc_crop(self):
graph = self._make_graph(
[
("input_data", TensorProto.FLOAT, (20, 10, 3)),
("shape", TensorProto.INT64, (2,)),
],
[make_node("CenterCropPad", ["input_data", "shape"], ["y"], axes=[0, 1])],
[],
initializer=[make_tensor("shape", TensorProto.INT64, (2,), (10, 8))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (10, 8, 3))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 18)],
)
def test_center_crop_pad_chw_crop(self):
graph = self._make_graph(
[
("input_data", TensorProto.FLOAT, (3, 20, 10)),
("shape", TensorProto.INT64, (2,)),
],
[make_node("CenterCropPad", ["input_data", "shape"], ["y"], axes=[1, 2])],
[],
initializer=[make_tensor("shape", TensorProto.INT64, (2,), (10, 8))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (3, 10, 8))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 18)],
)
def test_center_crop_pad_hwc_croppad(self):
graph = self._make_graph(
[
("input_data", TensorProto.FLOAT, (10, 10, 3)),
("shape", TensorProto.INT64, (2,)),
],
[make_node("CenterCropPad", ["input_data", "shape"], ["y"], axes=[0, 1])],
[],
initializer=[make_tensor("shape", TensorProto.INT64, (2,), (20, 8))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (20, 8, 3))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 18)],
)
def test_center_crop_pad_chw_croppad(self):
graph = self._make_graph(
[
("input_data", TensorProto.FLOAT, (3, 10, 10)),
("shape", TensorProto.INT64, (2,)),
],
[make_node("CenterCropPad", ["input_data", "shape"], ["y"], axes=[1, 2])],
[],
initializer=[make_tensor("shape", TensorProto.INT64, (2,), (20, 8))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (3, 20, 8))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 18)],
)
def test_center_crop_pad_without_input_shape(self):
graph = self._make_graph(
[
("input_data", TensorProto.FLOAT, (3, 2)),
("shape", TensorProto.INT64, (2,)),
],
[make_node("CenterCropPad", ["input_data", "shape"], ["y"])],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, None)],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 18)],
)
def test_center_crop_pad_with_input_shape_containing_dim_params(
self,
):
graph = self._make_graph(
[
("input_data", TensorProto.FLOAT, (20, "W", 3)),
("shape", TensorProto.INT64, (2,)),
],
[make_node("CenterCropPad", ["input_data", "shape"], ["y"], axes=[0, 1])],
[],
initializer=[make_tensor("shape", TensorProto.INT64, (2,), (10, 8))],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (10, 8, 3))],
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, 18)],
)
@pytest.mark.skipif(
not ONNX_ML, reason="ONNX_ML required to test ai.onnx.ml operators"
)
def test_category_mapper(self) -> None:
cat = make_node(
"CategoryMapper",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
cats_int64s=[1, 2, 3],
cats_strings=["1", "2", "3"],
)
graph_int = self._make_graph(
[("x", TensorProto.INT64, (30, 4, 5))],
[cat],
[],
)
self._assert_inferred(
graph_int,
[make_tensor_value_info("y", TensorProto.STRING, (30, 4, 5))],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 1),
make_opsetid(ONNX_DOMAIN, 11),
],
)
graph_str = self._make_graph(
[("x", TensorProto.STRING, (30, 5, 4))],
[cat],
[],
)
self._assert_inferred(
graph_str,
[make_tensor_value_info("y", TensorProto.INT64, (30, 5, 4))],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 1),
make_opsetid(ONNX_DOMAIN, 11),
],
)
@pytest.mark.parametrize(
"cats_int64s, cats_strings",
[
([1, 2, 3], ["1", "2"]),
([1, 2, 3], None),
(None, ["1", "2", "3"]),
(None, None),
],
)
@pytest.mark.skipif(
not ONNX_ML, reason="ONNX_ML required to test ai.onnx.ml operators"
)
def test_category_mapper_fails_if_invalid_attributes(
self, cats_int64s, cats_strings
) -> None:
cat = make_node(
"CategoryMapper",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
cats_int64s=cats_int64s,
cats_strings=cats_strings,
)
graph = self._make_graph(
[("x", TensorProto.INT64, (30, 4, 5))],
[cat],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 1),
make_opsetid(ONNX_DOMAIN, 11),
],
)
@pytest.mark.skipif(
not ONNX_ML, reason="ONNX_ML required to test ai.onnx.ml operators"
)
def test_tree_ensemble_regressor(self) -> None:
tree = make_node(
"TreeEnsembleRegressor",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
n_targets=5,
)
graph = self._make_graph(
[("x", TensorProto.DOUBLE, (30, 3))],
[tree],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.FLOAT, (30, 5))],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 3),
make_opsetid(ONNX_DOMAIN, 11),
],
)
@pytest.mark.parametrize(
"dtype", [TensorProto.FLOAT, TensorProto.DOUBLE, TensorProto.FLOAT16]
)
@pytest.mark.skipif(
not ONNX_ML, reason="ONNX_ML required to test ai.onnx.ml operators"
)
def test_tree_ensemble(self, dtype) -> None:
interior_nodes = 5
leaves = 9
tree = make_node(
"TreeEnsemble",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
n_targets=5,
nodes_featureids=[0] * interior_nodes,
nodes_splits=make_tensor(
"nodes_splits",
dtype,
(interior_nodes,),
list(range(interior_nodes)),
),
nodes_modes=make_tensor(
"nodes_modes",
TensorProto.UINT8,
(interior_nodes,),
[0] * interior_nodes,
),
nodes_truenodeids=[0] * interior_nodes,
nodes_falsenodeids=[0] * interior_nodes,
nodes_trueleafs=[0] * interior_nodes,
nodes_falseleafs=[0] * interior_nodes,
membership_values=make_tensor(
"membership_values",
dtype,
(7,),
[0.0, 0.1, 0.2, np.nan, 0.4, 0.5, 1.0],
),
leaf_targetids=[0] * leaves,
leaf_weights=make_tensor("leaf_weights", dtype, (leaves,), [1] * leaves),
tree_roots=[0],
)
graph = self._make_graph(
[("x", dtype, ("Batch Size", "Features"))],
[tree],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", dtype, ("Batch Size", 5))],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 5),
make_opsetid(ONNX_DOMAIN, 11),
],
)
@pytest.mark.parametrize(
"nodes_truenodeids, leaf_weights, nodes_splits",
[
(
[0] * 6,
make_tensor("leaf_weights", TensorProto.DOUBLE, (9,), [1] * 9),
make_tensor("nodes_splits", TensorProto.DOUBLE, (5,), [1] * 5),
),
(
[0] * 5,
make_tensor("leaf_weights", TensorProto.FLOAT, (9,), [1] * 9),
make_tensor("nodes_splits", TensorProto.DOUBLE, (5,), [1] * 5),
),
(
[0] * 5,
make_tensor("leaf_weights", TensorProto.DOUBLE, (18,), [1] * 18),
make_tensor("nodes_splits", TensorProto.DOUBLE, (5,), [1] * 5),
),
(
[0] * 5,
make_tensor("leaf_weights", TensorProto.DOUBLE, (9,), [1] * 9),
make_tensor("nodes_splits", TensorProto.FLOAT, (5,), [1] * 5),
),
],
)
@pytest.mark.skipif(
not ONNX_ML, reason="ONNX_ML required to test ai.onnx.ml operators"
)
def test_tree_ensemble_fails_if_invalid_attributes(
self,
nodes_truenodeids,
leaf_weights,
nodes_splits,
) -> None:
interior_nodes = 5
leaves = 9
tree = make_node(
"TreeEnsemble",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
n_targets=5,
nodes_featureids=[0] * interior_nodes,
nodes_splits=nodes_splits,
nodes_modes=make_tensor(
"nodes_modes",
TensorProto.UINT8,
(interior_nodes,),
[0] * interior_nodes,
),
nodes_truenodeids=nodes_truenodeids,
nodes_falsenodeids=[0] * interior_nodes,
nodes_trueleafs=[0] * interior_nodes,
nodes_falseleafs=[0] * interior_nodes,
leaf_targetids=[0] * leaves,
leaf_weights=leaf_weights,
tree_roots=[0],
)
graph = self._make_graph(
[("x", TensorProto.DOUBLE, ("Batch Size", "Features"))],
[tree],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 5),
make_opsetid(ONNX_DOMAIN, 11),
],
)
@pytest.mark.skipif(
not ONNX_ML, reason="ONNX_ML required to test ai.onnx.ml operators"
)
def test_tree_ensemble_classifier(self) -> None:
tree = make_node(
"TreeEnsembleClassifier",
["x"],
["y", "z"],
classlabels_int64s=[0, 1, 2, 3, 4],
domain=ONNX_ML_DOMAIN,
)
graph = self._make_graph(
[("x", TensorProto.DOUBLE, (30, 3))],
[tree],
[],
)
self._assert_inferred(
graph,
[
make_tensor_value_info("y", TensorProto.INT64, (30,)),
make_tensor_value_info("z", TensorProto.FLOAT, (30, 5)),
],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 3),
make_opsetid(ONNX_DOMAIN, 11),
],
)
@pytest.mark.skipif(
not ONNX_ML, reason="ONNX_ML required to test ai.onnx.ml operators"
)
def test_array_feature_extractor(self) -> None:
node = make_node(
"ArrayFeatureExtractor",
["x", "y"],
["z"],
domain=ONNX_ML_DOMAIN,
)
for axes_shape, expected in [
((2,), 2),
((), "unk__0"),
(("N",), "N"),
]:
graph = self._make_graph(
[
("x", TensorProto.INT64, (3, 4, 5)),
("y", TensorProto.INT64, axes_shape),
],
[node],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("z", TensorProto.INT64, (3, 4, expected))],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 3),
make_opsetid(ONNX_DOMAIN, 18),
],
)
@pytest.mark.skipif(
not ONNX_ML, reason="ONNX_ML required to test ai.onnx.ml operators"
)
def test_binarizer(self) -> None:
node = make_node(
"Binarizer",
["x"],
["y"],
domain=ONNX_ML_DOMAIN,
)
graph = self._make_graph(
[
("x", TensorProto.INT64, (3, 4, 5)),
],
[node],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("y", TensorProto.INT64, (3, 4, 5))],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 3),
make_opsetid(ONNX_DOMAIN, 18),
],
)
@pytest.mark.skipif(
not ONNX_ML, reason="ONNX_ML required to test ai.onnx.ml operators"
)
def test_one_hot_encoder(self) -> None:
graph = self._make_graph(
[("input", TensorProto.INT64, (2, "N", 3))],
[
make_node(
"OneHotEncoder",
["input"],
["output"],
cats_int64s=[1, 2, 3, 4],
domain="ai.onnx.ml",
)
],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("output", TensorProto.FLOAT, (2, "N", 3, 4))],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 1),
make_opsetid(ONNX_DOMAIN, 18),
],
)
@pytest.mark.parametrize(
"cats_int64s, cats_strings",
[
([1, 2, 3], ["1", "2", "3"]),
(None, None),
],
)
@pytest.mark.skipif(
not ONNX_ML, reason="ONNX_ML required to test ai.onnx.ml operators"
)
def test_one_hot_encoder_fails_if_invalid_attributes(
self, cats_int64s, cats_strings
) -> None:
graph = self._make_graph(
[("input", TensorProto.INT64, (2, "N", 3))],
[
make_node(
"OneHotEncoder",
["input"],
["output"],
cats_int64s=cats_int64s,
cats_strings=cats_strings,
domain="ai.onnx.ml",
)
],
[],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 1),
make_opsetid(ONNX_DOMAIN, 11),
],
)
@pytest.mark.parametrize(
"attrs, input_type",
[
({"classlabels_int64s": [1, 2, 3]}, onnx.TensorProto.INT64),
({"classlabels_strings": ["a", "b", "c"]}, onnx.TensorProto.STRING),
],
)
@pytest.mark.skipif(
not ONNX_ML, reason="ONNX_ML required to test ai.onnx.ml operators"
)
def test_zip_map(self, attrs, input_type) -> None:
graph = self._make_graph(
[("input", TensorProto.FLOAT, ("N", 3))],
[
make_node(
"ZipMap",
["input"],
["output"],
**attrs,
domain="ai.onnx.ml",
)
],
[],
)
typ = onnx.helper.make_map_type_proto(
input_type, onnx.helper.make_tensor_type_proto(TensorProto.FLOAT, ())
)
self._assert_inferred(
graph,
[
onnx.helper.make_value_info(
"output", onnx.helper.make_sequence_type_proto(typ)
)
],
opset_imports=[
make_opsetid(ONNX_ML_DOMAIN, 1),
make_opsetid(ONNX_DOMAIN, 18),
],
)
def test_compress_without_axis(self) -> None:
graph = self._make_graph(
[
("input", TensorProto.INT64, (2, "N", 3, 4)),
("condition", TensorProto.BOOL, (None,)),
],
[make_node("Compress", ["input", "condition"], ["output"])],
[],
)
self._assert_inferred(
graph, [make_tensor_value_info("output", TensorProto.INT64, (None,))]
)
def test_compress_with_axis(self) -> None:
graph = self._make_graph(
[
("input", TensorProto.INT64, (2, "N", 3, 4)),
("condition", TensorProto.BOOL, (None,)),
],
[make_node("Compress", ["input", "condition"], ["output"], axis=-1)],
[],
)
self._assert_inferred(
graph,
[make_tensor_value_info("output", TensorProto.INT64, (2, "N", 3, None))],
)
def test_check_type_when_schema_has_empty_io(self):
input = """
<
ir_version: 7,
opset_import: ["" : 1]
>
agraph (X, Y) => (Z)
{
Z = CustomOp(X, Y)
}
"""
model = onnx.parser.parse_model(input)
op_schema = defs.OpSchema(
"CustomOp",
"",
1,
inputs=[],
outputs=[],
)
onnx.defs.register_schema(op_schema)
with pytest.raises(onnx.shape_inference.InferenceError):
onnx.shape_inference.infer_shapes(model, True)
onnx.defs.deregister_schema(
op_schema.name, op_schema.since_version, op_schema.domain
)
def test_issue_layer_normalization_6187(self):
modeltxt = """
<
ir_version: 10,
opset_import: ["" : 17]
>
graph (float in0, float[2,7,8,1,3] in1, float[3,7] in2) => () {
out0, out1, out2 = LayerNormalization <epsilon: float = -841.058, stash_type: int = -940> (in0, in1, in2)
}
"""
model = onnx.parser.parse_model(modeltxt)
with pytest.raises(onnx.shape_inference.InferenceError):
onnx.checker.check_model(model, full_check=True)
onnx.shape_inference.infer_shapes(model)
def test_issue_conv_6180(self):
modeltxt = """
<
ir_version: 9,
opset_import: ["" : 11]
>
graph (float[7,6,1,5] in0, float in1, float[7,2,3,2,1] in2) => () {
out0 = Conv <auto_pad = "NOTSET", group = 1> (in0, in1, in2)
}
"""
model = onnx.parser.parse_model(modeltxt)
with pytest.raises(onnx.shape_inference.InferenceError):
onnx.checker.check_model(model, full_check=True)
onnx.shape_inference.infer_shapes(model)
def test_issue_gemm_6185(self):
modeltxt = """
<
ir_version: 10,
opset_import: ["" : 6]
>
graph (double[2,1] in0, double in1, double[2] in2) => () {
out0 = Gemm <alpha: float = 1, beta: float = -693.752, broadcast: int = -436, transB: int = 823> (in0, in1, in2)
}
"""
model = onnx.parser.parse_model(modeltxt)
with pytest.raises(onnx.shape_inference.InferenceError):
onnx.checker.check_model(model, full_check=True)
onnx.shape_inference.infer_shapes(model)
def test_issue_stft_6186(self):
modeltxt = """
<
ir_version: 10,
opset_import: ["" : 17]
>
graph (float16[3] in0, int32[2] in1, float16[7,8,8,8] in2, int32[8,1,7,2] in3) => () {
out0 = STFT (in0, in1, in2, in3)
}
"""
model = onnx.parser.parse_model(modeltxt)
with pytest.raises(onnx.shape_inference.InferenceError):
onnx.checker.check_model(model, full_check=True)
onnx.shape_inference.infer_shapes(model)
@pytest.mark.parametrize("version", all_versions_for("ConstantOfShape"))
def test_issue_constantofshape_6135(self, version):
graph = self._make_graph(
[("std.constant", TensorProto.INT64, (1,)), "output"],
[
make_node(
"ConstantOfShape",
inputs=["std.constant"],
outputs=["output"],
name="invalid_node",
)
],
[],
initializer=[make_tensor("std.constant", TensorProto.FLOAT, (1,), (-10,))],
)
with pytest.raises(onnx.shape_inference.InferenceError):
self._inferred(
graph,
opset_imports=[helper.make_opsetid(ONNX_DOMAIN, version)],
)
def test_protobuf_default(self) -> None:
model_text = """
ir_version: 8
producer_name: "test"
graph {
node {
input: "in"
output: "out"
op_type: "Flatten"
attribute {
name: "axis"
type: INT
}
}
name: "g"
input {
name: "in"
type {
tensor_type {
elem_type: 1
shape {
dim {
dim_value: 2
}
dim {
dim_value: 3
}
}
}
}
}
output {
name: "out"
type {
tensor_type {
elem_type: 1
shape {
dim {
dim_value: 1
}
dim {
dim_value: 6
}
}
}
}
}
}
opset_import {
version: 18
}
"""
model = text_format.Parse(model_text, onnx.ModelProto())
self._assert_inferred(model, [])
def test_infer_shapes_rejects_cyclic_function(self):
model = onnx.parser.parse_model(
"""
<ir_version: 8, opset_import: [ "" : 17, "local" : 1 ]>
agraph (float[N] X) => (float[N] Y) { Y = local.foo (X) }
<opset_import: [ "" : 17, "local" : 1 ], domain: "local">
foo (x) => (y) { y = local.foo (x) }
"""
)
with pytest.raises(onnx.checker.ValidationError):
onnx.shape_inference.infer_shapes(model)
@pytest.mark.parametrize("attrs", ["", "num_scan_inputs = -1, "])
def test_scan_invalid_num_scan_inputs_does_not_crash(self, attrs):
# Missing attr null-derefs; -1 overflows size_t. Both must raise, not crash.
scan_body = (
"body = b (float[1] si, float[1] xi) => (float[1] so, float[1] xo) "
"{ so = Identity(si) xo = Identity(xi) }"
)
model = onnx.parser.parse_model(
f"""
<ir_version: 8, opset_import: [ "" : 9 ]>
g (float[1] s, float[3,1] x) => (float[1] so, float[3,1] xo) {{
so, xo = Scan <{attrs}{scan_body}> (s, x)
}}
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
onnx.shape_inference.infer_shapes(model, strict_mode=True)
def test_function_output_count_mismatch_does_not_crash(self):
# Function declares 2 outputs; calling node declares 1.
# Must raise InferenceError on the second output, not crash.
model = onnx.parser.parse_model(
"""
<ir_version: 8, opset_import: [ "" : 18, "local" : 1 ]>
agraph (float[1] X) => (float[1] Y) { Y = local.F (X) }
<opset_import: [ "" : 18 ], domain: "local">
F (x) => (y1, y2) { y1 = Identity(x) y2 = Identity(x) }
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
onnx.shape_inference.infer_shapes(model, strict_mode=True)
def test_conv_transpose_undersized_weight_raises(self):
# Weight rank < 3 violates ConvTranspose spec (C x M/group x k1...kn).
model = onnx.parser.parse_model(
"""
<ir_version: 8, opset_import: [ "" : 11 ]>
g (float[1,1,5] X, float[1,3] W) => (float[1,1,?] Y) { Y = ConvTranspose(X, W) }
"""
)
with pytest.raises(onnx.shape_inference.InferenceError):
onnx.shape_inference.infer_shapes(model, strict_mode=True)
def test_infer_shapes_pathlike_error(self) -> None:
with pytest.raises(
TypeError,
match=r"For Model paths \(str or os.PathLike\), use infer_shapes_path\(\)\.",
):
onnx.shape_inference.infer_shapes(Path("model.onnx"))
class TestCustomSchemaShapeInference(TestShapeInferenceHelper):
custom_op_type: str = "CustomOp"
dummy_graph_op_type: str = "DummyGraph"
op_version: int = 1
op_domain: str = ""
@pytest.fixture(autouse=True)
def schema_cleanup(self):
# Ensure the schema is unregistered
assert not onnx.defs.has(self.custom_op_type, self.op_domain)
assert not onnx.defs.has(self.dummy_graph_op_type, self.op_domain)
yield
# Clean up the registered schema
with contextlib.suppress(onnx.defs.SchemaError):
onnx.defs.deregister_schema(
self.custom_op_type, self.op_version, self.op_domain
)
onnx.defs.deregister_schema(
self.dummy_graph_op_type, self.op_version, self.op_domain
)
def get_custom_op_schema(self):
# CustomOp schema:
# attrs:
# out_len: [L0, L1, ...]
# inputs:
# a[N, La]
# b[N, Lb]
# outputs:
# out0[N, La * Lb, L0]
# out1[N, La * Lb, L1]
# ...
schema = OpSchema(
self.custom_op_type,
self.op_domain,
self.op_version,
inputs=[
defs.OpSchema.FormalParameter("a", "float"),
defs.OpSchema.FormalParameter("b", "float"),
],
outputs=[
defs.OpSchema.FormalParameter(
"out", "float", param_option=OpSchema.FormalParameterOption.Variadic
),
],
attributes=[
defs.OpSchema.Attribute("out_len", defs.OpSchema.AttrType.INTS)
],
)
def schema_shape_infer_func(ctx: onnx.shape_inference.InferenceContext):
def parse_tensor_input(t: TypeProto):
assert isinstance(t, TypeProto)
return (
t.tensor_type.elem_type,
[
d.dim_value if d.HasField("dim_value") else None
for d in t.tensor_type.shape.dim
],
)
assert ctx.get_num_inputs() == 2
in0 = ctx.get_input_type(0)
in1 = ctx.get_input_type(1)
in0_type, in0_shape = parse_tensor_input(in0)
in1_type, in1_shape = parse_tensor_input(in1)
assert in0_type == TensorProto.FLOAT
assert in1_type == TensorProto.FLOAT
assert len(in0_shape) == 2
assert len(in1_shape) == 2
assert in0_shape[0] == in1_shape[0]
N, La = in0_shape
_, Lb = in1_shape
attr = ctx.get_attribute("out_len")
out_len = attr.ints
assert len(out_len) == ctx.get_num_outputs()
for i in range(ctx.get_num_outputs()):
out = ctx.get_output_type(i)
out.tensor_type.elem_type = in0_type
out.tensor_type.shape.dim.add().dim_value = N
out.tensor_type.shape.dim.add().dim_value = La * Lb
out.tensor_type.shape.dim.add().dim_value = out_len[i]
ctx.set_output_type(i, out)
schema.set_type_and_shape_inference_function(schema_shape_infer_func)
return schema
def get_dummy_graph_schema(self):
# DummyGraph schema:
# attrs:
# graph: OnnxGraph
# inputs:
# as same as the graph attribute
# outputs:
# as same as the graph attribute
schema = OpSchema(
self.dummy_graph_op_type,
self.op_domain,
self.op_version,
inputs=[
defs.OpSchema.FormalParameter(
"in", "float", param_option=OpSchema.FormalParameterOption.Variadic
),
],
outputs=[
defs.OpSchema.FormalParameter(
"out", "float", param_option=OpSchema.FormalParameterOption.Variadic
),
],
attributes=[defs.OpSchema.Attribute("graph", defs.OpSchema.AttrType.GRAPH)],
)
def schema_shape_infer_func(ctx: onnx.shape_inference.InferenceContext):
assert ctx.get_num_inputs() == 2
assert ctx.get_attribute("graph") is not None
gctx = ctx.get_graph_attribute_inferencer("graph")
outputs = gctx.do_inferencing(
[ctx.get_input_type(i) for i in range(ctx.get_num_inputs())],
[ctx.get_input_data(i) for i in range(ctx.get_num_inputs())],
)
for idx, out in enumerate(outputs):
ctx.set_output_type(idx, out)
schema.set_type_and_shape_inference_function(schema_shape_infer_func)
return schema
def gen_custom_op_graph(self, N, La, Lb, out_len, mark_output=False):
a = make_tensor_value_info("a", TensorProto.FLOAT, (N, La))
b = make_tensor_value_info("b", TensorProto.FLOAT, (N, Lb))
outs = [
make_tensor_value_info(f"out{i}", TensorProto.FLOAT, None)
for i in range(len(out_len))
]
node = make_node(
self.custom_op_type, ["a", "b"], [v.name for v in outs], out_len=out_len
)
return make_graph(
[node], "test", [a, b], outs if mark_output else [], value_info=outs
)
def gen_dummy_graph_graph(self, N, La, Lb, out_len):
subgraph = self.gen_custom_op_graph(N, La, Lb, out_len, True)
a = make_tensor_value_info("a", TensorProto.FLOAT, (N, La))
b = make_tensor_value_info("b", TensorProto.FLOAT, (N, Lb))
outs = [
make_tensor_value_info(f"out{i}", TensorProto.FLOAT, None)
for i in range(len(out_len))
]
node = make_node(
self.dummy_graph_op_type, ["a", "b"], [v.name for v in outs], graph=subgraph
)
return make_graph([node], "test", [a, b], [], value_info=outs)
def shape_infer_once(self, graph, N, La, Lb, out_len):
self._assert_inferred(
graph,
[
make_tensor_value_info(f"out{i}", TensorProto.FLOAT, (N, La * Lb, Li))
for i, Li in enumerate(out_len)
],
)
def test_custom_schema_shape_inference(self) -> None:
# generate graph
N = 3
La = 32
Lb = 64
out_len = [1, 2]
graph = self.gen_custom_op_graph(N, La, Lb, out_len)
# shape inference before register
with pytest.raises(onnx.checker.ValidationError):
self.shape_infer_once(graph, N, La, Lb, out_len)
# register schema
schema = self.get_custom_op_schema()
onnx.defs.register_schema(schema)
# shape inference with registered schema
self.shape_infer_once(graph, N, La, Lb, out_len)
# clean up
onnx.defs.deregister_schema(schema.name, schema.since_version, schema.domain)
def test_dummy_graph_schema_shape_inference(self) -> None:
# generate graph
N = 3
La = 32
Lb = 64
out_len = [1, 2]
graph = self.gen_dummy_graph_graph(N, La, Lb, out_len)
# shape inference before register
with pytest.raises(onnx.checker.ValidationError):
self.shape_infer_once(graph, N, La, Lb, out_len)
# register schema
custom_op_schema = self.get_custom_op_schema()
dummy_graph_schema = self.get_dummy_graph_schema()
onnx.defs.register_schema(custom_op_schema)
onnx.defs.register_schema(dummy_graph_schema)
# shape inference with registered schema
self.shape_infer_once(graph, N, La, Lb, out_len)
# clean up
onnx.defs.deregister_schema(
custom_op_schema.name,
custom_op_schema.since_version,
custom_op_schema.domain,
)
onnx.defs.deregister_schema(
dummy_graph_schema.name,
dummy_graph_schema.since_version,
dummy_graph_schema.domain,
)
def test_invalid_field_in_inference_func(self) -> None:
N = 3
La = 32
Lb = 64
out_len = [1]
graph = self.gen_custom_op_graph(N, La, Lb, out_len)
schema = self.get_custom_op_schema()
raw_func = schema.get_type_and_shape_inference_function()
def schema_shape_infer_func(ctx: onnx.shape_inference.InferenceContext):
raw_func(ctx)
assert ctx.get_attribute("not-exist-attr") is None
assert ctx.has_input(0)
assert not ctx.has_input(2)
with pytest.raises(TypeError):
assert not ctx.has_input(-1)
assert ctx.has_output(0)
assert not ctx.has_output(1)
with pytest.raises(TypeError):
assert not ctx.has_output(-1)
with pytest.raises(onnx.shape_inference.InferenceError):
ctx.get_graph_attribute_inferencer("not-exist-attr")
assert ctx.get_input_data(0) is None
with pytest.raises(RuntimeError):
assert ctx.get_input_data(10) is None
assert ctx.get_input_sparse_data(0) is None
with pytest.raises(RuntimeError):
assert ctx.get_input_sparse_data(10) is None
assert ctx.get_input_type(0) is not None
with pytest.raises(RuntimeError):
ctx.get_input_type(10)
assert ctx.get_symbolic_input(0) is None
with pytest.raises(RuntimeError):
ctx.get_symbolic_input(10)
assert ctx.get_output_type(0) is not None
with pytest.raises(RuntimeError):
ctx.get_output_type(10)
assert ctx.get_num_inputs() == 2
assert ctx.get_num_outputs() == 1
schema.set_type_and_shape_inference_function(schema_shape_infer_func)
onnx.defs.register_schema(schema)
# shape inference with registered schema
self.shape_infer_once(graph, N, La, Lb, out_len)
# clean up
onnx.defs.deregister_schema(schema.name, schema.since_version, schema.domain)
def test_get_symbolic_input_returns_tensor_shape_proto(self) -> None:
# Regression test: when data propagation feeds a symbolic shape into a
# node input, ctx.get_symbolic_input(...) must return a TensorShapeProto
# (the binding casts TensorShapeProto, which needs a registered caster).
op_type = "ReadSymbolicInput"
domain = "test.symbolic_input"
captured: dict[str, object] = {}
schema = OpSchema(
op_type,
domain,
1,
inputs=[OpSchema.FormalParameter("s", "T")],
outputs=[OpSchema.FormalParameter("y", "T")],
type_constraints=[("T", ["tensor(int64)"], "")],
)
def infer(ctx: onnx.shape_inference.InferenceContext) -> None:
captured["sym"] = ctx.get_symbolic_input(0)
ctx.set_output_type(0, ctx.get_input_type(0))
schema.set_type_and_shape_inference_function(infer)
onnx.defs.register_schema(schema)
try:
graph = make_graph(
[
make_node("Shape", ["x"], ["s"]),
make_node(op_type, ["s"], ["y"], domain=domain),
],
"g",
[make_tensor_value_info("x", TensorProto.FLOAT, [2, 3])],
[make_tensor_value_info("y", TensorProto.INT64, None)],
)
model = make_model(
graph,
opset_imports=[make_opsetid("", 21), make_opsetid(domain, 1)],
)
onnx.shape_inference.infer_shapes(model, data_prop=True, strict_mode=True)
sym = captured["sym"]
assert isinstance(sym, TensorShapeProto)
assert [d.dim_value for d in sym.dim] == [2, 3]
finally:
onnx.defs.deregister_schema(op_type, 1, domain)