From 945749ce4446efeede1c24e01d6e1884576163da Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 12:04:48 +0200 Subject: [PATCH 01/26] rename ArrayApi into BaseArrayApi --- onnx_array_api/npx/npx_array_api.py | 86 ++++++++++++++--------------- onnx_array_api/npx/npx_functions.py | 6 +- onnx_array_api/npx/npx_tensors.py | 10 ++-- onnx_array_api/npx/npx_var.py | 4 +- onnx_array_api/ort/ort_tensors.py | 4 +- 5 files changed, 55 insertions(+), 55 deletions(-) diff --git a/onnx_array_api/npx/npx_array_api.py b/onnx_array_api/npx/npx_array_api.py index 05bfe14..d5b2096 100644 --- a/onnx_array_api/npx/npx_array_api.py +++ b/onnx_array_api/npx/npx_array_api.py @@ -13,7 +13,7 @@ class ArrayApiError(RuntimeError): pass -class ArrayApi: +class BaseArrayApi: """ List of supported method by a tensor. """ @@ -36,145 +36,145 @@ def generic_method(self, method_name, *args: Any, **kwargs: Any) -> Any: f"for class {self.__class__.__name__!r}. " f"Method 'generic_method' can be overwritten " f"as well to change the behaviour " - f"for all methods supported by class ArrayApi." + f"for all methods supported by class BaseArrayApi." ) def numpy(self) -> np.ndarray: return self.generic_method("numpy") - def __neg__(self) -> "ArrayApi": + def __neg__(self) -> "BaseArrayApi": return self.generic_method("__neg__") - def __invert__(self) -> "ArrayApi": + def __invert__(self) -> "BaseArrayApi": return self.generic_method("__invert__") - def __add__(self, ov: "ArrayApi") -> "ArrayApi": + def __add__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__add__", ov) - def __radd__(self, ov: "ArrayApi") -> "ArrayApi": + def __radd__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__radd__", ov) - def __sub__(self, ov: "ArrayApi") -> "ArrayApi": + def __sub__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__sub__", ov) - def __rsub__(self, ov: "ArrayApi") -> "ArrayApi": + def __rsub__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__rsub__", ov) - def __mul__(self, ov: "ArrayApi") -> "ArrayApi": + def __mul__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__mul__", ov) - def __rmul__(self, ov: "ArrayApi") -> "ArrayApi": + def __rmul__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__rmul__", ov) - def __matmul__(self, ov: "ArrayApi") -> "ArrayApi": + def __matmul__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__matmul__", ov) - def __truediv__(self, ov: "ArrayApi") -> "ArrayApi": + def __truediv__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__truediv__", ov) - def __rtruediv__(self, ov: "ArrayApi") -> "ArrayApi": + def __rtruediv__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__rtruediv__", ov) - def __mod__(self, ov: "ArrayApi") -> "ArrayApi": + def __mod__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__mod__", ov) - def __rmod__(self, ov: "ArrayApi") -> "ArrayApi": + def __rmod__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__rmod__", ov) - def __pow__(self, ov: "ArrayApi") -> "ArrayApi": + def __pow__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__pow__", ov) - def __rpow__(self, ov: "ArrayApi") -> "ArrayApi": + def __rpow__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__rpow__", ov) - def __lt__(self, ov: "ArrayApi") -> "ArrayApi": + def __lt__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__lt__", ov) - def __le__(self, ov: "ArrayApi") -> "ArrayApi": + def __le__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__le__", ov) - def __gt__(self, ov: "ArrayApi") -> "ArrayApi": + def __gt__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__gt__", ov) - def __ge__(self, ov: "ArrayApi") -> "ArrayApi": + def __ge__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__ge__", ov) - def __eq__(self, ov: "ArrayApi") -> "ArrayApi": + def __eq__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__eq__", ov) - def __ne__(self, ov: "ArrayApi") -> "ArrayApi": + def __ne__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__ne__", ov) - def __lshift__(self, ov: "ArrayApi") -> "ArrayApi": + def __lshift__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__lshift__", ov) - def __rshift__(self, ov: "ArrayApi") -> "ArrayApi": + def __rshift__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__rshift__", ov) - def __and__(self, ov: "ArrayApi") -> "ArrayApi": + def __and__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__and__", ov) - def __rand__(self, ov: "ArrayApi") -> "ArrayApi": + def __rand__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__rand__", ov) - def __or__(self, ov: "ArrayApi") -> "ArrayApi": + def __or__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__or__", ov) - def __ror__(self, ov: "ArrayApi") -> "ArrayApi": + def __ror__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__ror__", ov) - def __xor__(self, ov: "ArrayApi") -> "ArrayApi": + def __xor__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__xor__", ov) - def __rxor__(self, ov: "ArrayApi") -> "ArrayApi": + def __rxor__(self, ov: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("__rxor__", ov) @property - def T(self) -> "ArrayApi": + def T(self) -> "BaseArrayApi": return self.generic_method("T") - def astype(self, dtype: Any) -> "ArrayApi": + def astype(self, dtype: Any) -> "BaseArrayApi": return self.generic_method("astype", dtype) @property - def shape(self) -> "ArrayApi": + def shape(self) -> "BaseArrayApi": return self.generic_method("shape") - def reshape(self, shape: "ArrayApi") -> "ArrayApi": + def reshape(self, shape: "BaseArrayApi") -> "BaseArrayApi": return self.generic_method("reshape", shape) def sum( self, axis: OptParType[TupleType[int]] = None, keepdims: ParType[int] = 0 - ) -> "ArrayApi": + ) -> "BaseArrayApi": return self.generic_method("sum", axis=axis, keepdims=keepdims) def mean( self, axis: OptParType[TupleType[int]] = None, keepdims: ParType[int] = 0 - ) -> "ArrayApi": + ) -> "BaseArrayApi": return self.generic_method("mean", axis=axis, keepdims=keepdims) def min( self, axis: OptParType[TupleType[int]] = None, keepdims: ParType[int] = 0 - ) -> "ArrayApi": + ) -> "BaseArrayApi": return self.generic_method("min", axis=axis, keepdims=keepdims) def max( self, axis: OptParType[TupleType[int]] = None, keepdims: ParType[int] = 0 - ) -> "ArrayApi": + ) -> "BaseArrayApi": return self.generic_method("max", axis=axis, keepdims=keepdims) def prod( self, axis: OptParType[TupleType[int]] = None, keepdims: ParType[int] = 0 - ) -> "ArrayApi": + ) -> "BaseArrayApi": return self.generic_method("prod", axis=axis, keepdims=keepdims) - def copy(self) -> "ArrayApi": + def copy(self) -> "BaseArrayApi": return self.generic_method("copy") - def flatten(self) -> "ArrayApi": + def flatten(self) -> "BaseArrayApi": return self.generic_method("flatten") - def __getitem__(self, index: Any) -> "ArrayApi": + def __getitem__(self, index: Any) -> "BaseArrayApi": return self.generic_method("__getitem__", index) def __setitem__(self, index: Any, values: Any): diff --git a/onnx_array_api/npx/npx_functions.py b/onnx_array_api/npx/npx_functions.py index fb455cc..f335bd0 100644 --- a/onnx_array_api/npx/npx_functions.py +++ b/onnx_array_api/npx/npx_functions.py @@ -8,7 +8,7 @@ from .npx_constants import FUNCTION_DOMAIN from .npx_core_api import cst, make_tuple, npxapi_inline, npxapi_no_inline, var -from .npx_tensors import ArrayApi +from .npx_tensors import BaseArrayApi from .npx_types import ( DType, ElemType, @@ -173,7 +173,7 @@ def asarray( raise RuntimeError("Method 'astype' should be used to change the type.") if order is not None: raise NotImplementedError(f"order={order!r} not implemented.") - if isinstance(a, ArrayApi): + if isinstance(a, BaseArrayApi): if copy: return a.__class__(a, copy=copy) return a @@ -404,7 +404,7 @@ def isdtype( dtype: DType, kind: Union[DType, str, Tuple[Union[DType, str], ...]] ) -> bool: """ - See :epkg:`ArrayAPI:isdtype`. + See :epkg:`BaseArrayAPI:isdtype`. This function is not converted into an onnx graph. """ return np_array_api.isdtype(dtype, kind) diff --git a/onnx_array_api/npx/npx_tensors.py b/onnx_array_api/npx/npx_tensors.py index 5863dfe..136def5 100644 --- a/onnx_array_api/npx/npx_tensors.py +++ b/onnx_array_api/npx/npx_tensors.py @@ -3,7 +3,7 @@ import numpy as np from onnx.helper import np_dtype_to_tensor_dtype -from .npx_array_api import ArrayApi, ArrayApiError +from .npx_array_api import BaseArrayApi, ArrayApiError class JitTensor: @@ -14,11 +14,11 @@ class JitTensor: pass -class EagerTensor(ArrayApi): +class EagerTensor(BaseArrayApi): """ Defines a value for a specific eager mode. An eager tensor must overwrite every call to a method listed in class - :class:`ArrayApi`. + :class:`BaseArrayApi`. """ def __iter__(self): @@ -215,7 +215,7 @@ def generic_method(self, method_name, *args: Any, **kwargs: Any) -> Any: return self._generic_method_getitem(method_name, *args, **kwargs) if method_name == "__setitem__": - return ArrayApi.generic_method(self, method_name, *args, **kwargs) + return BaseArrayApi.generic_method(self, method_name, *args, **kwargs) if method_name in {"mean", "sum", "min", "max", "prod"}: return self._generic_method_reduce(method_name, *args, **kwargs) @@ -226,4 +226,4 @@ def generic_method(self, method_name, *args: Any, **kwargs: Any) -> Any: if method_name.startswith("__") and method_name.endswith("__"): return self._generic_method_operator(method_name, *args, **kwargs) - return ArrayApi.generic_method(self, method_name, *args, **kwargs) + return BaseArrayApi.generic_method(self, method_name, *args, **kwargs) diff --git a/onnx_array_api/npx/npx_var.py b/onnx_array_api/npx/npx_var.py index 3b60e01..c67e0ff 100644 --- a/onnx_array_api/npx/npx_var.py +++ b/onnx_array_api/npx/npx_var.py @@ -4,7 +4,7 @@ from onnx import FunctionProto, ModelProto, NodeProto, TensorProto from onnx.helper import np_dtype_to_tensor_dtype -from .npx_array_api import ArrayApi, ArrayApiError +from .npx_array_api import BaseArrayApi, ArrayApiError from .npx_constants import DEFAULT_OPSETS, ONNX_DOMAIN from .npx_types import ElemType, OptParType, ParType, TensorType, TupleType @@ -181,7 +181,7 @@ def to_onnx( return onx -class Var(ArrayApi): +class Var(BaseArrayApi): """ Defines a variable, a result... diff --git a/onnx_array_api/ort/ort_tensors.py b/onnx_array_api/ort/ort_tensors.py index 4f317a5..63bc378 100644 --- a/onnx_array_api/ort/ort_tensors.py +++ b/onnx_array_api/ort/ort_tensors.py @@ -231,7 +231,7 @@ def get_ir_version(cls, ir_version): class EagerOrtTensor(OrtTensor, OrtCommon, EagerTensor): """ - Defines a value for a specific backend. + Defines a value for :epkg:`onnxruntime` as a backend. """ pass @@ -239,7 +239,7 @@ class EagerOrtTensor(OrtTensor, OrtCommon, EagerTensor): class JitOrtTensor(OrtTensor, OrtCommon, JitTensor): """ - Defines a value for a specific backend. + Defines a value for :epkg:`onnxruntime` as a backend. """ pass From 38c3c0e3e22972b85d7b92e9823e1356724c6ab4 Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 12:49:47 +0200 Subject: [PATCH 02/26] Implements ArrayAPI --- onnx_array_api/array_api_onnx_numpy.py | 20 ++++++++++++++++++ onnx_array_api/array_api_onnx_ort.py | 21 +++++++++++++++++++ onnx_array_api/npx/npx_array_api.py | 28 ++++++++++++++++++------- onnx_array_api/npx/npx_numpy_tensors.py | 14 +++++++++++-- onnx_array_api/ort/ort_tensors.py | 11 +++++++++- 5 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 onnx_array_api/array_api_onnx_numpy.py create mode 100644 onnx_array_api/array_api_onnx_ort.py diff --git a/onnx_array_api/array_api_onnx_numpy.py b/onnx_array_api/array_api_onnx_numpy.py new file mode 100644 index 0000000..923047d --- /dev/null +++ b/onnx_array_api/array_api_onnx_numpy.py @@ -0,0 +1,20 @@ +import inspect +from .npx import npx_functions + + +class onnx_numpy_array_api: + """ + Defines the ArrayApi for tensors based on numpy. + It is an extension of module :mod:`onnx_array_api.npx.npx_functions`. + """ + + pass + + +def _setup(): + for k, v in npx_functions.__dict__.items(): + if inspect.isfunction(v): + setattr(onnx_numpy_array_api, k, v) + + +_setup() diff --git a/onnx_array_api/array_api_onnx_ort.py b/onnx_array_api/array_api_onnx_ort.py new file mode 100644 index 0000000..5660a16 --- /dev/null +++ b/onnx_array_api/array_api_onnx_ort.py @@ -0,0 +1,21 @@ +import inspect +from .npx import npx_functions + + +class onnx_ort_array_api: + """ + Defines the ArrayApi for tensors based on :epkg:`onnxruntime`. + It is an extension of module :mod:`onnx_array_api.npx.npx_functions`. + """ + + pass + + +def _setup(): + for k, v in npx_functions.__dict__.items(): + if inspect.isfunction(v): + setattr(onnx_ort_array_api, k, v) + + +_setup() + diff --git a/onnx_array_api/npx/npx_array_api.py b/onnx_array_api/npx/npx_array_api.py index d5b2096..2dee246 100644 --- a/onnx_array_api/npx/npx_array_api.py +++ b/onnx_array_api/npx/npx_array_api.py @@ -1,6 +1,7 @@ from typing import Any, Optional import numpy as np +from onnx import TensorProto from .npx_types import OptParType, ParType, TupleType @@ -18,17 +19,24 @@ class BaseArrayApi: List of supported method by a tensor. """ + float16 = TensorProto.FLOAT16 + float32 = TensorProto.FLOAT + float64 = TensorProto.DOUBLE + int8 = TensorProto.INT8 + int16 = TensorProto.INT16 + int32 = TensorProto.INT32 + int64 = TensorProto.INT64 + uint8 = TensorProto.UINT8 + uint16 = TensorProto.UINT16 + uint32 = TensorProto.UINT32 + uint64 = TensorProto.UINT64 + bfloat16 = TensorProto.BFLOAT16 + def __array_namespace__(self, api_version: Optional[str] = None): """ - Returns the module holding all the available functions. + This method must be overloaded. """ - if api_version is None or api_version == "2022.12": - from onnx_array_api.npx import npx_functions - - return npx_functions - raise ValueError( - f"Unable to return an implementation for api_version={api_version!r}." - ) + raise NotImplementedError("Method '__array_namespace__' must be implemented.") def generic_method(self, method_name, *args: Any, **kwargs: Any) -> Any: raise NotImplementedError( @@ -179,3 +187,7 @@ def __getitem__(self, index: Any) -> "BaseArrayApi": def __setitem__(self, index: Any, values: Any): return self.generic_method("__setitem__", index, values) + + +setattr(BaseArrayApi, "bool", TensorProto.BOOL) +setattr(BaseArrayApi, "str", TensorProto.STRING) diff --git a/onnx_array_api/npx/npx_numpy_tensors.py b/onnx_array_api/npx/npx_numpy_tensors.py index 3197f60..20e1d28 100644 --- a/onnx_array_api/npx/npx_numpy_tensors.py +++ b/onnx_array_api/npx/npx_numpy_tensors.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, List, Tuple +from typing import Any, Callable, List, Optional, Tuple import numpy as np from onnx import ModelProto @@ -171,7 +171,17 @@ class EagerNumpyTensor(NumpyTensor, EagerTensor): Defines a value for a specific backend. """ - pass + def __array_namespace__(self, api_version: Optional[str] = None): + """ + Returns the module holding all the available functions. + """ + if api_version is None or api_version == "2022.12": + from onnx_array_api.array_api_onnx_numpy import onnx_numpy_array_api + + return onnx_numpy_array_api + raise ValueError( + f"Unable to return an implementation for api_version={api_version!r}." + ) class JitNumpyTensor(NumpyTensor, JitTensor): diff --git a/onnx_array_api/ort/ort_tensors.py b/onnx_array_api/ort/ort_tensors.py index 63bc378..324dd10 100644 --- a/onnx_array_api/ort/ort_tensors.py +++ b/onnx_array_api/ort/ort_tensors.py @@ -233,8 +233,17 @@ class EagerOrtTensor(OrtTensor, OrtCommon, EagerTensor): """ Defines a value for :epkg:`onnxruntime` as a backend. """ + def __array_namespace__(self, api_version: Optional[str] = None): + """ + Returns the module holding all the available functions. + """ + if api_version is None or api_version == "2022.12": + from onnx_array_api.array_api_onnx_ort import onnx_ort_array_api - pass + return onnx_ort_array_api + raise ValueError( + f"Unable to return an implementation for api_version={api_version!r}." + ) class JitOrtTensor(OrtTensor, OrtCommon, JitTensor): From f0d4ebac7c4dbf4b434d618c7893d709e3ff252b Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 12:55:00 +0200 Subject: [PATCH 03/26] documentation --- CHANGELOGS.rst | 3 ++- _doc/api/array_api_numpy.rst | 5 +++++ _doc/api/array_api_ort.rst | 5 +++++ _doc/api/index.rst | 2 ++ onnx_array_api/array_api_onnx_ort.py | 1 - onnx_array_api/ort/ort_tensors.py | 1 + 6 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 _doc/api/array_api_numpy.rst create mode 100644 _doc/api/array_api_ort.rst diff --git a/CHANGELOGS.rst b/CHANGELOGS.rst index 4e5aeb5..e807b02 100644 --- a/CHANGELOGS.rst +++ b/CHANGELOGS.rst @@ -4,4 +4,5 @@ Change Logs 0.2.0 +++++ -* :pr:`3`: fixes Array API with onnxruntime +* :pr:`17`: implements ArrayAPI +* :pr:`3`: fixes Array API with onnxruntime and scikit-learn diff --git a/_doc/api/array_api_numpy.rst b/_doc/api/array_api_numpy.rst new file mode 100644 index 0000000..3c530e8 --- /dev/null +++ b/_doc/api/array_api_numpy.rst @@ -0,0 +1,5 @@ +onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api +======================================================== + +.. autoclass:: onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api + :members: diff --git a/_doc/api/array_api_ort.rst b/_doc/api/array_api_ort.rst new file mode 100644 index 0000000..77925d3 --- /dev/null +++ b/_doc/api/array_api_ort.rst @@ -0,0 +1,5 @@ +onnx_array_api.array_api_onnx_ort.onnx_ort_array_api +==================================================== + +.. autoclass:: onnx_array_api.array_api_onnx_ort.onnx_ort_array_api + :members: diff --git a/_doc/api/index.rst b/_doc/api/index.rst index 7750a5b..dac74ad 100644 --- a/_doc/api/index.rst +++ b/_doc/api/index.rst @@ -6,6 +6,8 @@ API .. toctree:: :maxdepth: 1 + array_api_numpy + array_api_ort npx_functions npx_var npx_jit diff --git a/onnx_array_api/array_api_onnx_ort.py b/onnx_array_api/array_api_onnx_ort.py index 5660a16..77e5a7e 100644 --- a/onnx_array_api/array_api_onnx_ort.py +++ b/onnx_array_api/array_api_onnx_ort.py @@ -18,4 +18,3 @@ def _setup(): _setup() - diff --git a/onnx_array_api/ort/ort_tensors.py b/onnx_array_api/ort/ort_tensors.py index 324dd10..82d8f92 100644 --- a/onnx_array_api/ort/ort_tensors.py +++ b/onnx_array_api/ort/ort_tensors.py @@ -233,6 +233,7 @@ class EagerOrtTensor(OrtTensor, OrtCommon, EagerTensor): """ Defines a value for :epkg:`onnxruntime` as a backend. """ + def __array_namespace__(self, api_version: Optional[str] = None): """ Returns the module holding all the available functions. From 0ffa4b44e78d36a1e08d4ef55ffc71ecd088440d Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 12:58:24 +0200 Subject: [PATCH 04/26] ci --- azure-pipelines.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index aa1a59b..cde1cac 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -90,6 +90,36 @@ jobs: python -m pytest -v displayName: 'Runs Unit Tests' +- job: 'TestLinuxArrayApiNumpy' + pool: + vmImage: 'ubuntu-latest' + strategy: + matrix: + Python310-Linux: + python.version: '3.11' + maxParallel: 3 + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + path: your-array-library + + - name: Checkout array-api-tests + uses: actions/checkout@v3 + with: + repository: data-apis/array-api-tests + submodules: 'true' + path: array-api-tests + + - name: Run the array API test suite + env: + ARRAY_API_TESTS_MODULE: onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api + run: | + export PYTHONPATH="${GITHUB_WORKSPACE}/onnx_array_api" + cd ${GITHUB_WORKSPACE}/array-api-tests + pytest -v -rxXfE --ci --xfails-file ${GITHUB_WORKSPACE}/onnx_array_api/array-api-tests-xfails.txt array_api_tests/ + - job: 'TestLinux' pool: vmImage: 'ubuntu-latest' From 293c570a90b01b384fd710b0623db2dc440e9563 Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 13:03:06 +0200 Subject: [PATCH 05/26] fix ci --- azure-pipelines.yml | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cde1cac..56d2d52 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -90,7 +90,7 @@ jobs: python -m pytest -v displayName: 'Runs Unit Tests' -- job: 'TestLinuxArrayApiNumpy' +- job: 'TestLinuxNightly' pool: vmImage: 'ubuntu-latest' strategy: @@ -99,26 +99,26 @@ jobs: python.version: '3.11' maxParallel: 3 - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - path: your-array-library + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + path: your-array-library - - name: Checkout array-api-tests - uses: actions/checkout@v3 - with: - repository: data-apis/array-api-tests - submodules: 'true' - path: array-api-tests + - name: Checkout array-api-tests + uses: actions/checkout@v3 + with: + repository: data-apis/array-api-tests + submodules: 'true' + path: array-api-tests - - name: Run the array API test suite - env: - ARRAY_API_TESTS_MODULE: onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api - run: | - export PYTHONPATH="${GITHUB_WORKSPACE}/onnx_array_api" - cd ${GITHUB_WORKSPACE}/array-api-tests - pytest -v -rxXfE --ci --xfails-file ${GITHUB_WORKSPACE}/onnx_array_api/array-api-tests-xfails.txt array_api_tests/ + - name: Run the array API test suite + env: + ARRAY_API_TESTS_MODULE: onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api + run: | + export PYTHONPATH="${GITHUB_WORKSPACE}/onnx_array_api" + cd ${GITHUB_WORKSPACE}/array-api-tests + pytest -v -rxXfE --ci --xfails-file ${GITHUB_WORKSPACE}/onnx_array_api/array-api-tests-xfails.txt array_api_tests/ - job: 'TestLinux' pool: From caf23247f17e2a98814e060c269bc7551989eef1 Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 13:09:54 +0200 Subject: [PATCH 06/26] xi --- azure-pipelines.yml | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 56d2d52..c38f5b1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -100,17 +100,32 @@ jobs: maxParallel: 3 steps: - - name: Checkout - uses: actions/checkout@v3 - with: - path: your-array-library - - - name: Checkout array-api-tests - uses: actions/checkout@v3 - with: - repository: data-apis/array-api-tests - submodules: 'true' - path: array-api-tests + - task: UsePythonVersion@0 + inputs: + versionSpec: '$(python.version)' + architecture: 'x64' + - script: sudo apt-get update + displayName: 'AptGet Update' + - script: python -m pip install --upgrade pip setuptools wheel + displayName: 'Install tools' + - script: pip install -r requirements.txt + displayName: 'Install Requirements' + - script: pip install -r requirements-dev.txt + displayName: 'Install Requirements dev' + - script: python setup.py install + displayName: 'Install onnx_array_api' + - script: | + git clone https://github.com/data-apis/array-api-tests.git + git submodule update --init + displayName: 'clone array-api-tests' + - script: | + export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api + pytest array_api_tests/test_creation_functions.py::test_zeros + displayName: "test_creation_functions.py::test_zeros" + - script: | + export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api + pytest array_api_tests + displayName: "all test" - name: Run the array API test suite env: From 768eb855d72dd3c7dd49c6489153d5210e5c9730 Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 13:11:37 +0200 Subject: [PATCH 07/26] ci --- azure-pipelines.yml | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c38f5b1..cd1a953 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -125,15 +125,7 @@ jobs: - script: | export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api pytest array_api_tests - displayName: "all test" - - - name: Run the array API test suite - env: - ARRAY_API_TESTS_MODULE: onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api - run: | - export PYTHONPATH="${GITHUB_WORKSPACE}/onnx_array_api" - cd ${GITHUB_WORKSPACE}/array-api-tests - pytest -v -rxXfE --ci --xfails-file ${GITHUB_WORKSPACE}/onnx_array_api/array-api-tests-xfails.txt array_api_tests/ + displayName: "all tests" - job: 'TestLinux' pool: From 692774e78147a0f993665c0a0eb3ae1338532e4e Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 13:12:12 +0200 Subject: [PATCH 08/26] ci --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index cd1a953..9087d64 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -90,7 +90,7 @@ jobs: python -m pytest -v displayName: 'Runs Unit Tests' -- job: 'TestLinuxNightly' +- job: 'TestLinuxArrayApi' pool: vmImage: 'ubuntu-latest' strategy: From 5c7dae18dbd1ade982605ebaae358c0ba18edc4c Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 13:15:15 +0200 Subject: [PATCH 09/26] ci --- azure-pipelines.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9087d64..3dea680 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -110,21 +110,25 @@ jobs: displayName: 'Install tools' - script: pip install -r requirements.txt displayName: 'Install Requirements' - - script: pip install -r requirements-dev.txt - displayName: 'Install Requirements dev' - script: python setup.py install displayName: 'Install onnx_array_api' - script: | git clone https://github.com/data-apis/array-api-tests.git git submodule update --init displayName: 'clone array-api-tests' + - script: pip install -r array-api-tests/requirements-dev.txt + displayName: 'Install Requirements dev' - script: | export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api + cd array-api-tests pytest array_api_tests/test_creation_functions.py::test_zeros + cd .. displayName: "test_creation_functions.py::test_zeros" - script: | export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api + cd array-api-tests pytest array_api_tests + cd .. displayName: "all tests" - job: 'TestLinux' From 1a1cd35eb004d165479961d4268a9762a504f1cd Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 13:53:42 +0200 Subject: [PATCH 10/26] ci --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 3dea680..6a84f8c 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -116,7 +116,7 @@ jobs: git clone https://github.com/data-apis/array-api-tests.git git submodule update --init displayName: 'clone array-api-tests' - - script: pip install -r array-api-tests/requirements-dev.txt + - script: pip install -r array-api-tests/requirements.txt displayName: 'Install Requirements dev' - script: | export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api From da3a7c855cee8674150259aeb6f84ff59e421a9c Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 15:47:23 +0200 Subject: [PATCH 11/26] many changes to follow the Array API --- _doc/api/array_api.rst | 7 + _doc/api/array_api_numpy.rst | 6 +- _doc/api/array_api_ort.rst | 6 +- _doc/api/index.rst | 3 +- _unittests/ut_npx/test_npx.py | 10 +- _unittests/ut_npx/test_sklearn_array_api.py | 3 +- onnx_array_api/array_api/__init__.py | 18 +++ onnx_array_api/array_api/onnx_numpy.py | 55 ++++++++ onnx_array_api/array_api/onnx_ort.py | 55 ++++++++ onnx_array_api/array_api_onnx_numpy.py | 20 --- onnx_array_api/array_api_onnx_ort.py | 20 --- onnx_array_api/npx/npx_array_api.py | 18 --- onnx_array_api/npx/npx_functions.py | 7 +- onnx_array_api/npx/npx_graph_builder.py | 7 +- onnx_array_api/npx/npx_numpy_tensors.py | 11 +- onnx_array_api/npx/npx_tensors.py | 11 +- onnx_array_api/npx/npx_types.py | 148 ++++++++++++++------ onnx_array_api/npx/npx_var.py | 4 +- onnx_array_api/ort/ort_tensors.py | 11 +- onnx_array_api/plotting/_helper.py | 3 +- 20 files changed, 289 insertions(+), 134 deletions(-) create mode 100644 _doc/api/array_api.rst create mode 100644 onnx_array_api/array_api/__init__.py create mode 100644 onnx_array_api/array_api/onnx_numpy.py create mode 100644 onnx_array_api/array_api/onnx_ort.py delete mode 100644 onnx_array_api/array_api_onnx_numpy.py delete mode 100644 onnx_array_api/array_api_onnx_ort.py diff --git a/_doc/api/array_api.rst b/_doc/api/array_api.rst new file mode 100644 index 0000000..f07716a --- /dev/null +++ b/_doc/api/array_api.rst @@ -0,0 +1,7 @@ +onnx_array_api.array_api +======================== + +.. toctree:: + + array_api_onnx_numpy + array_api_onnx_ort diff --git a/_doc/api/array_api_numpy.rst b/_doc/api/array_api_numpy.rst index 3c530e8..f57089a 100644 --- a/_doc/api/array_api_numpy.rst +++ b/_doc/api/array_api_numpy.rst @@ -1,5 +1,5 @@ -onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api -======================================================== +onnx_array_api.array_api.onnx_numpy +============================================= -.. autoclass:: onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api +.. automodule:: onnx_array_api.array_api.onnx_numpy :members: diff --git a/_doc/api/array_api_ort.rst b/_doc/api/array_api_ort.rst index 77925d3..cc21311 100644 --- a/_doc/api/array_api_ort.rst +++ b/_doc/api/array_api_ort.rst @@ -1,5 +1,5 @@ -onnx_array_api.array_api_onnx_ort.onnx_ort_array_api -==================================================== +onnx_array_api.array_api.onnx_ort +================================= -.. autoclass:: onnx_array_api.array_api_onnx_ort.onnx_ort_array_api +.. automodule:: onnx_array_api.array_api.onnx_ort :members: diff --git a/_doc/api/index.rst b/_doc/api/index.rst index dac74ad..75c0aa4 100644 --- a/_doc/api/index.rst +++ b/_doc/api/index.rst @@ -6,8 +6,7 @@ API .. toctree:: :maxdepth: 1 - array_api_numpy - array_api_ort + array_api npx_functions npx_var npx_jit diff --git a/_unittests/ut_npx/test_npx.py b/_unittests/ut_npx/test_npx.py index f550896..d515738 100644 --- a/_unittests/ut_npx/test_npx.py +++ b/_unittests/ut_npx/test_npx.py @@ -95,6 +95,7 @@ from onnx_array_api.npx.npx_numpy_tensors import EagerNumpyTensor from onnx_array_api.npx.npx_types import ( Bool, + DType, Float32, Float64, Int64, @@ -2318,7 +2319,7 @@ def compute_labels(X, centers): self.assertEqual(f.n_versions, 1) self.assertEqual(len(f.available_versions), 1) self.assertEqual(f.available_versions, [((np.float64, 2), (np.float64, 2))]) - key = ((np.dtype("float64"), 2), (np.dtype("float64"), 2)) + key = ((DType(TensorProto.DOUBLE), 2), (DType(TensorProto.DOUBLE), 2)) onx = f.get_onnx(key) self.assertIsInstance(onx, ModelProto) self.assertRaise(lambda: f.get_onnx(2), ValueError) @@ -2379,7 +2380,12 @@ def compute_labels(X, centers, use_sqrt=False): self.assertEqualArray(got[1], dist) self.assertEqual(f.n_versions, 1) self.assertEqual(len(f.available_versions), 1) - key = ((np.dtype("float64"), 2), (np.dtype("float64"), 2), "use_sqrt", True) + key = ( + (DType(TensorProto.DOUBLE), 2), + (DType(TensorProto.DOUBLE), 2), + "use_sqrt", + True, + ) self.assertEqual(f.available_versions, [key]) onx = f.get_onnx(key) self.assertIsInstance(onx, ModelProto) diff --git a/_unittests/ut_npx/test_sklearn_array_api.py b/_unittests/ut_npx/test_sklearn_array_api.py index 016a170..5694f83 100644 --- a/_unittests/ut_npx/test_sklearn_array_api.py +++ b/_unittests/ut_npx/test_sklearn_array_api.py @@ -4,7 +4,7 @@ from onnx.defs import onnx_opset_version from sklearn import config_context, __version__ as sklearn_version from sklearn.discriminant_analysis import LinearDiscriminantAnalysis -from onnx_array_api.ext_test_case import ExtTestCase +from onnx_array_api.ext_test_case import ExtTestCase, ignore_warnings from onnx_array_api.npx.npx_numpy_tensors import EagerNumpyTensor @@ -16,6 +16,7 @@ class TestSklearnArrayAPI(ExtTestCase): Version(sklearn_version) <= Version("1.2.2"), reason="reshape ArrayAPI not followed", ) + @ignore_warnings(DeprecationWarning) def test_sklearn_array_api_linear_discriminant(self): X = np.array([[-1, -1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]]) y = np.array([1, 1, 1, 2, 2, 2]) diff --git a/onnx_array_api/array_api/__init__.py b/onnx_array_api/array_api/__init__.py new file mode 100644 index 0000000..66a6f7f --- /dev/null +++ b/onnx_array_api/array_api/__init__.py @@ -0,0 +1,18 @@ +from onnx import TensorProto + + +def _finalize_array_api(module): + module.float16 = TensorProto.FLOAT16 + module.float32 = TensorProto.FLOAT + module.float64 = TensorProto.DOUBLE + module.int8 = TensorProto.INT8 + module.int16 = TensorProto.INT16 + module.int32 = TensorProto.INT32 + module.int64 = TensorProto.INT64 + module.uint8 = TensorProto.UINT8 + module.uint16 = TensorProto.UINT16 + module.uint32 = TensorProto.UINT32 + module.uint64 = TensorProto.UINT64 + module.bfloat16 = TensorProto.BFLOAT16 + setattr(module, "bool", TensorProto.BOOL) + # setattr(module, "str", TensorProto.STRING) diff --git a/onnx_array_api/array_api/onnx_numpy.py b/onnx_array_api/array_api/onnx_numpy.py new file mode 100644 index 0000000..f40b686 --- /dev/null +++ b/onnx_array_api/array_api/onnx_numpy.py @@ -0,0 +1,55 @@ +""" +Array API valid for an :class:`EagerNumpyTensor`. +""" +from typing import Any, Optional +import numpy as np +from ..npx.npx_array_api import BaseArrayApi +from ..npx.npx_functions import ( + abs, + absolute, + astype, + isdtype, + reshape, + take, +) +from ..npx.npx_functions import asarray as generic_asarray +from ..npx.npx_numpy_tensors import EagerNumpyTensor +from . import _finalize_array_api + +__all__ = [ + "abs", + "absolute", + "asarray", + "astype", + "isdtype", + "reshape", + "take", +] + + +def asarray( + a: Any, + dtype: Any = None, + order: Optional[str] = None, + like: Any = None, + copy: bool = False, +): + """ + Converts anything into an array. + """ + if isinstance(a, BaseArrayApi): + return generic_asarray(a, dtype=dtype, order=order, like=like, copy=copy) + if isinstance(a, int): + return EagerNumpyTensor(np.array(a, dtype=np.int64)) + if isinstance(a, float): + return EagerNumpyTensor(np.array(a, dtype=np.float32)) + raise NotImplementedError(f"asarray not implemented for type {type(a)}.") + + +def _finalize(): + from . import onnx_numpy + + _finalize_array_api(onnx_numpy) + + +_finalize() diff --git a/onnx_array_api/array_api/onnx_ort.py b/onnx_array_api/array_api/onnx_ort.py new file mode 100644 index 0000000..621823e --- /dev/null +++ b/onnx_array_api/array_api/onnx_ort.py @@ -0,0 +1,55 @@ +""" +Array API valid for an :class:`EagerOrtTensor`. +""" +from typing import Any, Optional +import numpy as np +from ..npx.npx_array_api import BaseArrayApi +from ..npx.npx_functions import ( + abs, + absolute, + astype, + isdtype, + reshape, + take, +) +from ..npx.npx_functions import asarray as generic_asarray +from ..ort.ort_tensors import EagerOrtTensor +from . import _finalize_array_api + +__all__ = [ + "abs", + "absolute", + "asarray", + "astype", + "isdtype", + "reshape", + "take", +] + + +def asarray( + a: Any, + dtype: Any = None, + order: Optional[str] = None, + like: Any = None, + copy: bool = False, +): + """ + Converts anything into an array. + """ + if isinstance(a, BaseArrayApi): + return generic_asarray(a, dtype=dtype, order=order, like=like, copy=copy) + if isinstance(a, int): + return EagerOrtTensor(np.array(a, dtype=np.int64)) + if isinstance(a, float): + return EagerOrtTensor(np.array(a, dtype=np.float32)) + raise NotImplementedError(f"asarray not implemented for type {type(a)}.") + + +def _finalize(): + from . import onnx_ort + + _finalize_array_api(onnx_ort) + + +_finalize() diff --git a/onnx_array_api/array_api_onnx_numpy.py b/onnx_array_api/array_api_onnx_numpy.py deleted file mode 100644 index 923047d..0000000 --- a/onnx_array_api/array_api_onnx_numpy.py +++ /dev/null @@ -1,20 +0,0 @@ -import inspect -from .npx import npx_functions - - -class onnx_numpy_array_api: - """ - Defines the ArrayApi for tensors based on numpy. - It is an extension of module :mod:`onnx_array_api.npx.npx_functions`. - """ - - pass - - -def _setup(): - for k, v in npx_functions.__dict__.items(): - if inspect.isfunction(v): - setattr(onnx_numpy_array_api, k, v) - - -_setup() diff --git a/onnx_array_api/array_api_onnx_ort.py b/onnx_array_api/array_api_onnx_ort.py deleted file mode 100644 index 77e5a7e..0000000 --- a/onnx_array_api/array_api_onnx_ort.py +++ /dev/null @@ -1,20 +0,0 @@ -import inspect -from .npx import npx_functions - - -class onnx_ort_array_api: - """ - Defines the ArrayApi for tensors based on :epkg:`onnxruntime`. - It is an extension of module :mod:`onnx_array_api.npx.npx_functions`. - """ - - pass - - -def _setup(): - for k, v in npx_functions.__dict__.items(): - if inspect.isfunction(v): - setattr(onnx_ort_array_api, k, v) - - -_setup() diff --git a/onnx_array_api/npx/npx_array_api.py b/onnx_array_api/npx/npx_array_api.py index 2dee246..6816745 100644 --- a/onnx_array_api/npx/npx_array_api.py +++ b/onnx_array_api/npx/npx_array_api.py @@ -1,7 +1,6 @@ from typing import Any, Optional import numpy as np -from onnx import TensorProto from .npx_types import OptParType, ParType, TupleType @@ -19,19 +18,6 @@ class BaseArrayApi: List of supported method by a tensor. """ - float16 = TensorProto.FLOAT16 - float32 = TensorProto.FLOAT - float64 = TensorProto.DOUBLE - int8 = TensorProto.INT8 - int16 = TensorProto.INT16 - int32 = TensorProto.INT32 - int64 = TensorProto.INT64 - uint8 = TensorProto.UINT8 - uint16 = TensorProto.UINT16 - uint32 = TensorProto.UINT32 - uint64 = TensorProto.UINT64 - bfloat16 = TensorProto.BFLOAT16 - def __array_namespace__(self, api_version: Optional[str] = None): """ This method must be overloaded. @@ -187,7 +173,3 @@ def __getitem__(self, index: Any) -> "BaseArrayApi": def __setitem__(self, index: Any, values: Any): return self.generic_method("__setitem__", index, values) - - -setattr(BaseArrayApi, "bool", TensorProto.BOOL) -setattr(BaseArrayApi, "str", TensorProto.STRING) diff --git a/onnx_array_api/npx/npx_functions.py b/onnx_array_api/npx/npx_functions.py index f335bd0..9001ddb 100644 --- a/onnx_array_api/npx/npx_functions.py +++ b/onnx_array_api/npx/npx_functions.py @@ -3,7 +3,7 @@ import array_api_compat.numpy as np_array_api import numpy as np from onnx import FunctionProto, ModelProto, NodeProto, TensorProto -from onnx.helper import np_dtype_to_tensor_dtype +from onnx.helper import np_dtype_to_tensor_dtype, tensor_dtype_to_np_dtype from onnx.numpy_helper import from_array from .npx_constants import FUNCTION_DOMAIN @@ -407,6 +407,11 @@ def isdtype( See :epkg:`BaseArrayAPI:isdtype`. This function is not converted into an onnx graph. """ + if isinstance(dtype, DType): + dti = tensor_dtype_to_np_dtype(dtype.code) + return np_array_api.isdtype(dti, kind) + if isinstance(dtype, int): + raise TypeError(f"Unexpected type {type(dtype)}.") return np_array_api.isdtype(dtype, kind) diff --git a/onnx_array_api/npx/npx_graph_builder.py b/onnx_array_api/npx/npx_graph_builder.py index 92c1412..ec91b91 100644 --- a/onnx_array_api/npx/npx_graph_builder.py +++ b/onnx_array_api/npx/npx_graph_builder.py @@ -38,6 +38,7 @@ rename_in_onnx_graph, ) from .npx_types import ( + DType, ElemType, OptParType, ParType, @@ -226,6 +227,8 @@ def make_node( protos.append(att) elif v.value is not None: new_kwargs[k] = v.value + elif isinstance(v, DType): + new_kwargs[k] = v.code else: new_kwargs[k] = v @@ -337,7 +340,7 @@ def _io( if tensor_type.shape is None: type_proto = TypeProto() tensor_type_proto = type_proto.tensor_type - tensor_type_proto.elem_type = tensor_type.dtypes[0].dtype + tensor_type_proto.elem_type = tensor_type.dtypes[0].dtype.code value_info_proto = ValueInfoProto() value_info_proto.name = name # tensor_type_proto.shape.dim.extend([]) @@ -348,7 +351,7 @@ def _io( # with fixed rank. This can be changed here and in methods # `make_key`. shape = [None for _ in tensor_type.shape] - info = make_tensor_value_info(name, tensor_type.dtypes[0].dtype, shape) + info = make_tensor_value_info(name, tensor_type.dtypes[0].dtype.code, shape) # check_value_info fails if the shape is left undefined check_value_info(info, self.check_context) return info diff --git a/onnx_array_api/npx/npx_numpy_tensors.py b/onnx_array_api/npx/npx_numpy_tensors.py index 20e1d28..8e0d368 100644 --- a/onnx_array_api/npx/npx_numpy_tensors.py +++ b/onnx_array_api/npx/npx_numpy_tensors.py @@ -2,10 +2,11 @@ import numpy as np from onnx import ModelProto +from onnx.helper import np_dtype_to_tensor_dtype from onnx.reference import ReferenceEvaluator from .npx_tensors import EagerTensor, JitTensor -from .npx_types import TensorType +from .npx_types import DType, TensorType class NumpyTensor: @@ -80,9 +81,9 @@ def numpy(self): return self._tensor @property - def dtype(self) -> Any: + def dtype(self) -> DType: "Returns the element type of this tensor." - return self._tensor.dtype + return DType(np_dtype_to_tensor_dtype(self._tensor.dtype)) @property def key(self) -> Any: @@ -176,9 +177,9 @@ def __array_namespace__(self, api_version: Optional[str] = None): Returns the module holding all the available functions. """ if api_version is None or api_version == "2022.12": - from onnx_array_api.array_api_onnx_numpy import onnx_numpy_array_api + from onnx_array_api.array_api import onnx_numpy - return onnx_numpy_array_api + return onnx_numpy raise ValueError( f"Unable to return an implementation for api_version={api_version!r}." ) diff --git a/onnx_array_api/npx/npx_tensors.py b/onnx_array_api/npx/npx_tensors.py index 136def5..094ea70 100644 --- a/onnx_array_api/npx/npx_tensors.py +++ b/onnx_array_api/npx/npx_tensors.py @@ -1,8 +1,9 @@ -from typing import Any +from typing import Any, Optional import numpy as np from onnx.helper import np_dtype_to_tensor_dtype +from .npx_types import DType from .npx_array_api import BaseArrayApi, ArrayApiError @@ -73,7 +74,7 @@ def _getitem_impl_var(obj, index, method_name=None): return meth(obj, index) @staticmethod - def _astype_impl(x, dtype: int = None, method_name=None): + def _astype_impl(x, dtype: Optional[DType] = None, method_name=None): # avoids circular imports. if dtype is None: raise ValueError("dtype cannot be None.") @@ -131,9 +132,11 @@ def _generic_method_operator(self, method_name, *args: Any, **kwargs: Any) -> An new_args = [] for a in args: if isinstance(a, np.ndarray): - new_args.append(self.__class__(a.astype(self.dtype))) + new_args.append(self.__class__(a.astype(self.dtype.np_dtype))) elif isinstance(a, (int, float)): - new_args.append(self.__class__(np.array([a]).astype(self.dtype))) + new_args.append( + self.__class__(np.array([a]).astype(self.dtype.np_dtype)) + ) else: new_args.append(a) diff --git a/onnx_array_api/npx/npx_types.py b/onnx_array_api/npx/npx_types.py index a38d53f..52aaed0 100644 --- a/onnx_array_api/npx/npx_types.py +++ b/onnx_array_api/npx/npx_types.py @@ -2,6 +2,7 @@ import numpy as np from onnx import AttributeProto +from onnx.helper import np_dtype_to_tensor_dtype, tensor_dtype_to_np_dtype class WrapperType: @@ -14,9 +15,65 @@ class WrapperType: class DType(WrapperType): """ - Annotated type for dtype. + Type of the element type returned by tensors + following the :epkg:`Array API`. + + :param code: element type based on onnx definition """ + __slots__ = ["code_"] + + def __init__(self, code: int): + self.code_ = code + + def __repr__(self) -> str: + "usual" + return f"DType({self.code_})" + + def __hash__(self) -> int: + return self.code_ + + @property + def code(self) -> int: + return self.code_ + + @property + def np_dtype(self) -> "np.dtype": + return tensor_dtype_to_np_dtype(self.code_) + + def __eq__(self, dt: "DType") -> bool: + "Compares two types." + if dt.__class__ is DType: + return self.code_ == dt.code_ + if isinstance(dt, int): + raise TypeError(f"dt must be DType not {type(dt)}.") + if dt in ElemType.numpy_map: + dti = ElemType.numpy_map[dt] + return self.code_ == dti.code_ + try: + dti = np_dtype_to_tensor_dtype(dt) + except KeyError: + raise TypeError(f"dt must be DType not {type(dt)} - {dt}.") + return self.code_ == dti + + def __lt__(self, dt: "DType") -> bool: + "Compares two types." + if dt.__class__ is DType: + return self.code_ < dt.code_ + if isinstance(dt, int): + raise TypeError(f"dt must be DType not {type(dt)}.") + try: + dti = np_dtype_to_tensor_dtype(dt) + except KeyError: + raise TypeError(f"dt must be DType not {type(dt)} - {dt}.") + return self.code_ < dti + + +class DType2(DType): + pass + + +class _DTypes(DType): pass @@ -27,22 +84,22 @@ class ElemTypeCstInner(WrapperType): __slots__ = [] - undefined = 0 - bool_ = 9 - int8 = 3 - int16 = 5 - int32 = 6 - int64 = 7 - uint8 = 2 - uint16 = 4 - uint32 = 12 - uint64 = 13 - float16 = 10 - float32 = 1 - float64 = 11 - bfloat16 = 16 - complex64 = 14 - complex128 = 15 + undefined = DType(0) + bool_ = DType(9) + int8 = DType(3) + int16 = DType(5) + int32 = DType(6) + int64 = DType(7) + uint8 = DType(2) + uint16 = DType(4) + uint32 = DType(12) + uint64 = DType(13) + float16 = DType(10) + float32 = DType(1) + float64 = DType(11) + bfloat16 = DType(16) + complex64 = DType(14) + complex128 = DType(15) class ElemTypeCstSet(ElemTypeCstInner): @@ -50,7 +107,7 @@ class ElemTypeCstSet(ElemTypeCstInner): Sets of element types. """ - allowed = set(range(1, 17)) + allowed = set(DType(i) for i in range(1, 17)) ints = { ElemTypeCstInner.int8, @@ -90,8 +147,8 @@ def combined(type_set): "Combines all types into a single integer by using power of 2." s = 0 for dt in type_set: - s += 1 << dt - return s + s += 1 << dt.code + return _DTypes(s) class ElemTypeCst(ElemTypeCstSet): @@ -99,22 +156,22 @@ class ElemTypeCst(ElemTypeCstSet): Combination of element types. """ - Undefined = 0 - Bool = 1 << ElemTypeCstInner.bool_ - Int8 = 1 << ElemTypeCstInner.int8 - Int16 = 1 << ElemTypeCstInner.int16 - Int32 = 1 << ElemTypeCstInner.int32 - Int64 = 1 << ElemTypeCstInner.int64 - UInt8 = 1 << ElemTypeCstInner.uint8 - UInt16 = 1 << ElemTypeCstInner.uint16 - UInt32 = 1 << ElemTypeCstInner.uint32 - UInt64 = 1 << ElemTypeCstInner.uint64 - BFloat16 = 1 << ElemTypeCstInner.bfloat16 - Float16 = 1 << ElemTypeCstInner.float16 - Float32 = 1 << ElemTypeCstInner.float32 - Float64 = 1 << ElemTypeCstInner.float64 - Complex64 = 1 << ElemTypeCstInner.complex64 - Complex128 = 1 << ElemTypeCstInner.complex128 + Undefined = DType2(0) + Bool = DType2(1 << ElemTypeCstInner.bool_.code) + Int8 = DType2(1 << ElemTypeCstInner.int8.code) + Int16 = DType2(1 << ElemTypeCstInner.int16.code) + Int32 = DType2(1 << ElemTypeCstInner.int32.code) + Int64 = DType2(1 << ElemTypeCstInner.int64.code) + UInt8 = DType2(1 << ElemTypeCstInner.uint8.code) + UInt16 = DType2(1 << ElemTypeCstInner.uint16.code) + UInt32 = DType2(1 << ElemTypeCstInner.uint32.code) + UInt64 = DType2(1 << ElemTypeCstInner.uint64.code) + BFloat16 = DType2(1 << ElemTypeCstInner.bfloat16.code) + Float16 = DType2(1 << ElemTypeCstInner.float16.code) + Float32 = DType2(1 << ElemTypeCstInner.float32.code) + Float64 = DType2(1 << ElemTypeCstInner.float64.code) + Complex64 = DType2(1 << ElemTypeCstInner.complex64.code) + Complex128 = DType2(1 << ElemTypeCstInner.complex128.code) Numerics = ElemTypeCstSet.combined(ElemTypeCstSet.numerics) Floats = ElemTypeCstSet.combined(ElemTypeCstSet.floats) @@ -131,13 +188,13 @@ class ElemType(ElemTypeCst): names_int = { att: getattr(ElemTypeCstInner, att) for att in dir(ElemTypeCstInner) - if isinstance(getattr(ElemTypeCstInner, att), int) + if isinstance(getattr(ElemTypeCstInner, att), DType) } int_names = { getattr(ElemTypeCstInner, att): att for att in dir(ElemTypeCstInner) - if isinstance(getattr(ElemTypeCstInner, att), int) + if isinstance(getattr(ElemTypeCstInner, att), DType) } set_names = { @@ -150,12 +207,12 @@ class ElemType(ElemTypeCst): **{ getattr(np, att): getattr(ElemTypeCst, att) for att in dir(ElemTypeCst) - if isinstance(getattr(ElemTypeCst, att), int) and hasattr(np, att) + if isinstance(getattr(ElemTypeCst, att), DType) and hasattr(np, att) }, **{ np.dtype(att): getattr(ElemTypeCst, att) for att in dir(ElemTypeCst) - if isinstance(getattr(ElemTypeCst, att), int) and hasattr(np, att) + if isinstance(getattr(ElemTypeCst, att), DType) and hasattr(np, att) }, } @@ -167,7 +224,7 @@ def __class_getitem__(cls, dtype: Union[str, int]): dtype = ElemType.names_int[dtype] elif dtype in ElemType.numpy_map: dtype = ElemType.numpy_map[dtype] - elif dtype == 0: + elif dtype == DType(0): pass elif dtype not in ElemType.allowed: raise ValueError(f"Unexpected dtype {dtype} not in {ElemType.allowed}.") @@ -197,7 +254,10 @@ def get_set_name(cls, dtypes): tt.append(dt.dtype) dtypes = set(tt) for d in dir(cls): - if dtypes == getattr(cls, d): + att = getattr(cls, d) + if not isinstance(att, set): + continue + if dtypes == att: return d return None @@ -333,7 +393,7 @@ def __class_getitem__(cls, *args): if isinstance(a, tuple): shape = a continue - if isinstance(a, int): + if isinstance(a, DType): if dtypes is not None: raise TypeError(f"Unexpected type {type(a)} in {args}.") dtypes = (a,) @@ -363,7 +423,7 @@ def __class_getitem__(cls, *args): check.append(dt) elif dt in ElemType.allowed: check.append(ElemType[dt]) - elif isinstance(dt, int): + elif isinstance(dt, DType): check.append(ElemType[dt]) else: raise TypeError(f"Unexpected type {type(dt)} in {dtypes}, args={args}.") diff --git a/onnx_array_api/npx/npx_var.py b/onnx_array_api/npx/npx_var.py index c67e0ff..28c6e56 100644 --- a/onnx_array_api/npx/npx_var.py +++ b/onnx_array_api/npx/npx_var.py @@ -6,7 +6,7 @@ from .npx_array_api import BaseArrayApi, ArrayApiError from .npx_constants import DEFAULT_OPSETS, ONNX_DOMAIN -from .npx_types import ElemType, OptParType, ParType, TensorType, TupleType +from .npx_types import DType, ElemType, OptParType, ParType, TensorType, TupleType class Par: @@ -300,7 +300,7 @@ def __init__( self._prefix = None if hasattr(dtype, "type_name"): self.dtype = dtype - elif isinstance(dtype, int): + elif isinstance(dtype, DType): # regular parameter self.onnx_op_kwargs["dtype"] = dtype elif dtype is None: diff --git a/onnx_array_api/ort/ort_tensors.py b/onnx_array_api/ort/ort_tensors.py index 82d8f92..ead834d 100644 --- a/onnx_array_api/ort/ort_tensors.py +++ b/onnx_array_api/ort/ort_tensors.py @@ -3,7 +3,6 @@ import numpy as np from onnx import ModelProto, TensorProto from onnx.defs import onnx_opset_version -from onnx.helper import tensor_dtype_to_np_dtype from onnxruntime import InferenceSession, RunOptions, get_available_providers from onnxruntime.capi._pybind_state import OrtDevice as C_OrtDevice from onnxruntime.capi._pybind_state import OrtMemType @@ -11,7 +10,7 @@ from onnxruntime.capi.onnxruntime_pybind11_state import InvalidArgument from ..npx.npx_tensors import EagerTensor, JitTensor -from ..npx.npx_types import TensorType +from ..npx.npx_types import DType, TensorType class OrtTensor: @@ -152,9 +151,9 @@ def shape(self) -> Tuple[int, ...]: return self._tensor.shape() @property - def dtype(self) -> Any: + def dtype(self) -> DType: "Returns the element type of this tensor." - return tensor_dtype_to_np_dtype(self._tensor.element_type()) + return DType(self._tensor.element_type()) @property def key(self) -> Any: @@ -239,9 +238,9 @@ def __array_namespace__(self, api_version: Optional[str] = None): Returns the module holding all the available functions. """ if api_version is None or api_version == "2022.12": - from onnx_array_api.array_api_onnx_ort import onnx_ort_array_api + from onnx_array_api.array_api import onnx_ort - return onnx_ort_array_api + return onnx_ort raise ValueError( f"Unable to return an implementation for api_version={api_version!r}." ) diff --git a/onnx_array_api/plotting/_helper.py b/onnx_array_api/plotting/_helper.py index 69ea987..48e65d9 100644 --- a/onnx_array_api/plotting/_helper.py +++ b/onnx_array_api/plotting/_helper.py @@ -11,6 +11,7 @@ ) from onnx.helper import tensor_dtype_to_np_dtype from onnx.numpy_helper import to_array +from ..npx.npx_types import DType class Graph: @@ -44,7 +45,7 @@ def __init__( self.shape = shape @property - def dtype(self) -> Any: + def dtype(self) -> DType: return self.values.dtype From c600890d51b86609947dbc3e704fab5bc104670e Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 16:38:37 +0200 Subject: [PATCH 12/26] more changes --- _unittests/ut_array_api/test_onnx_numpy.py | 17 ++++++++ onnx_array_api/array_api/__init__.py | 29 ++++++------- onnx_array_api/array_api/onnx_numpy.py | 20 ++++++++- onnx_array_api/npx/npx_core_api.py | 4 +- onnx_array_api/npx/npx_functions.py | 24 +++++++++-- onnx_array_api/npx/npx_jit_eager.py | 4 +- onnx_array_api/npx/npx_types.py | 47 +++++++++++++--------- pyproject.toml | 5 ++- 8 files changed, 107 insertions(+), 43 deletions(-) create mode 100644 _unittests/ut_array_api/test_onnx_numpy.py diff --git a/_unittests/ut_array_api/test_onnx_numpy.py b/_unittests/ut_array_api/test_onnx_numpy.py new file mode 100644 index 0000000..2304ffe --- /dev/null +++ b/_unittests/ut_array_api/test_onnx_numpy.py @@ -0,0 +1,17 @@ +import unittest +import numpy as np +from onnx_array_api.ext_test_case import ExtTestCase +from onnx_array_api.array_api import onnx_numpy as xp +from onnx_array_api.npx.npx_numpy_tensors import EagerNumpyTensor + + +class TestOnnxNumpy(ExtTestCase): + def test_abs(self): + c = EagerNumpyTensor(np.array([4, 5], dtype=np.int64)) + mat = xp.zeros(c, dtype=xp.int64) + a = xp.absolute(mat) + self.assertEqualArray(np.absolute(mat.numpy()), a.numpy()) + + +if __name__ == "__main__": + unittest.main(verbosity=2) diff --git a/onnx_array_api/array_api/__init__.py b/onnx_array_api/array_api/__init__.py index 66a6f7f..e13b184 100644 --- a/onnx_array_api/array_api/__init__.py +++ b/onnx_array_api/array_api/__init__.py @@ -1,18 +1,19 @@ from onnx import TensorProto +from ..npx.npx_types import DType def _finalize_array_api(module): - module.float16 = TensorProto.FLOAT16 - module.float32 = TensorProto.FLOAT - module.float64 = TensorProto.DOUBLE - module.int8 = TensorProto.INT8 - module.int16 = TensorProto.INT16 - module.int32 = TensorProto.INT32 - module.int64 = TensorProto.INT64 - module.uint8 = TensorProto.UINT8 - module.uint16 = TensorProto.UINT16 - module.uint32 = TensorProto.UINT32 - module.uint64 = TensorProto.UINT64 - module.bfloat16 = TensorProto.BFLOAT16 - setattr(module, "bool", TensorProto.BOOL) - # setattr(module, "str", TensorProto.STRING) + module.float16 = DType(TensorProto.FLOAT16) + module.float32 = DType(TensorProto.FLOAT) + module.float64 = DType(TensorProto.DOUBLE) + module.int8 = DType(TensorProto.INT8) + module.int16 = DType(TensorProto.INT16) + module.int32 = DType(TensorProto.INT32) + module.int64 = DType(TensorProto.INT64) + module.uint8 = DType(TensorProto.UINT8) + module.uint16 = DType(TensorProto.UINT16) + module.uint32 = DType(TensorProto.UINT32) + module.uint64 = DType(TensorProto.UINT64) + module.bfloat16 = DType(TensorProto.BFLOAT16) + setattr(module, "bool", DType(TensorProto.BOOL)) + setattr(module, "str", DType(TensorProto.STRING)) diff --git a/onnx_array_api/array_api/onnx_numpy.py b/onnx_array_api/array_api/onnx_numpy.py index f40b686..edcfe36 100644 --- a/onnx_array_api/array_api/onnx_numpy.py +++ b/onnx_array_api/array_api/onnx_numpy.py @@ -3,6 +3,7 @@ """ from typing import Any, Optional import numpy as np +from onnx import TensorProto from ..npx.npx_array_api import BaseArrayApi from ..npx.npx_functions import ( abs, @@ -13,7 +14,9 @@ take, ) from ..npx.npx_functions import asarray as generic_asarray +from ..npx.npx_functions import zeros as generic_zeros from ..npx.npx_numpy_tensors import EagerNumpyTensor +from ..npx.npx_types import DType, ElemType, TensorType, OptParType from . import _finalize_array_api __all__ = [ @@ -24,16 +27,17 @@ "isdtype", "reshape", "take", + "zeros", ] def asarray( a: Any, - dtype: Any = None, + dtype: Optional[DType] = None, order: Optional[str] = None, like: Any = None, copy: bool = False, -): +) -> EagerNumpyTensor: """ Converts anything into an array. """ @@ -46,6 +50,18 @@ def asarray( raise NotImplementedError(f"asarray not implemented for type {type(a)}.") +def zeros( + shape: TensorType[ElemType.int64, "I", (None,)], + dtype: OptParType[DType] = DType(TensorProto.FLOAT), + order: OptParType[str] = "C", +) -> TensorType[ElemType.numerics, "T"]: + if isinstance(shape, tuple): + return generic_zeros( + EagerNumpyTensor(np.array(shape, dtype=np.int64)), dtype=dtype, order=order + ) + return generic_zeros(shape, dtype=dtype, order=order) + + def _finalize(): from . import onnx_numpy diff --git a/onnx_array_api/npx/npx_core_api.py b/onnx_array_api/npx/npx_core_api.py index cc3802a..05cb0bb 100644 --- a/onnx_array_api/npx/npx_core_api.py +++ b/onnx_array_api/npx/npx_core_api.py @@ -5,7 +5,7 @@ from onnx import FunctionProto, ModelProto, NodeProto from .npx_tensors import EagerTensor -from .npx_types import ElemType, OptParType, ParType, TupleType +from .npx_types import DType, ElemType, OptParType, ParType, TupleType from .npx_var import Cst, Input, ManyIdentity, Par, Var @@ -74,7 +74,7 @@ def _process_parameter(fn, sig, k, v, new_pars, inline): parent_op=(fn.__module__, fn.__name__, 0), ) return - if isinstance(v, (int, float, str, tuple)): + if isinstance(v, (int, float, str, tuple, DType)): if inline: new_pars[k] = v else: diff --git a/onnx_array_api/npx/npx_functions.py b/onnx_array_api/npx/npx_functions.py index 9001ddb..3e3d213 100644 --- a/onnx_array_api/npx/npx_functions.py +++ b/onnx_array_api/npx/npx_functions.py @@ -3,7 +3,7 @@ import array_api_compat.numpy as np_array_api import numpy as np from onnx import FunctionProto, ModelProto, NodeProto, TensorProto -from onnx.helper import np_dtype_to_tensor_dtype, tensor_dtype_to_np_dtype +from onnx.helper import make_tensor, np_dtype_to_tensor_dtype, tensor_dtype_to_np_dtype from onnx.numpy_helper import from_array from .npx_constants import FUNCTION_DOMAIN @@ -182,7 +182,7 @@ def asarray( @npxapi_inline def astype( - a: TensorType[ElemType.numerics, "T1"], dtype: OptParType[int] = 1 + a: TensorType[ElemType.numerics, "T1"], dtype: OptParType[DType] = 1 ) -> TensorType[ElemType.numerics, "T2"]: """ Cast an array. @@ -401,7 +401,7 @@ def identity(n: ParType[int], dtype=None) -> TensorType[ElemType.numerics, "T"]: @npxapi_no_inline def isdtype( - dtype: DType, kind: Union[DType, str, Tuple[Union[DType, str], ...]] + dtype: ParType[DType], kind: Union[DType, str, Tuple[Union[DType, str], ...]] ) -> bool: """ See :epkg:`BaseArrayAPI:isdtype`. @@ -630,3 +630,21 @@ def where( ) -> TensorType[ElemType.numerics, "T"]: "See :func:`numpy.where`." return var(cond, x, y, op="Where") + + +@npxapi_inline +def zeros( + shape: TensorType[ElemType.int64, "I", (None,)], + dtype: OptParType[DType] = DType(TensorProto.FLOAT), + order: OptParType[str] = "C", +) -> TensorType[ElemType.numerics, "T"]: + """ + Implements :func:`numpy.zeros`. + """ + if order != "C": + raise RuntimeError(f"order={order!r} != 'C' not supported.") + return var( + shape, + value=make_tensor(name="zero", data_type=dtype.code, dims=[1], vals=[0]), + op="ConstantOfShape", + ) diff --git a/onnx_array_api/npx/npx_jit_eager.py b/onnx_array_api/npx/npx_jit_eager.py index 6b6bfca..33c5685 100644 --- a/onnx_array_api/npx/npx_jit_eager.py +++ b/onnx_array_api/npx/npx_jit_eager.py @@ -5,7 +5,7 @@ import numpy as np from .npx_tensors import EagerTensor, JitTensor -from .npx_types import TensorType +from .npx_types import DType, TensorType from .npx_var import Cst, Input, Var logger = getLogger("onnx-array-api") @@ -153,7 +153,7 @@ def make_key(*values, **kwargs): ) if kwargs: for k, v in sorted(kwargs.items()): - if isinstance(v, (int, float, str, type)): + if isinstance(v, (int, float, str, type, DType)): res.append(k) res.append(v) elif isinstance(v, tuple): diff --git a/onnx_array_api/npx/npx_types.py b/onnx_array_api/npx/npx_types.py index 52aaed0..981a894 100644 --- a/onnx_array_api/npx/npx_types.py +++ b/onnx_array_api/npx/npx_types.py @@ -47,13 +47,15 @@ def __eq__(self, dt: "DType") -> bool: return self.code_ == dt.code_ if isinstance(dt, int): raise TypeError(f"dt must be DType not {type(dt)}.") + if isinstance(dt, str): + return False if dt in ElemType.numpy_map: dti = ElemType.numpy_map[dt] return self.code_ == dti.code_ try: dti = np_dtype_to_tensor_dtype(dt) except KeyError: - raise TypeError(f"dt must be DType not {type(dt)} - {dt}.") + raise TypeError(f"dt must be DType not {type(dt)} - {dt!r}.") return self.code_ == dti def __lt__(self, dt: "DType") -> bool: @@ -69,11 +71,13 @@ def __lt__(self, dt: "DType") -> bool: return self.code_ < dti -class DType2(DType): +class _DType2(DType): + "Wraps an into a different type." pass class _DTypes(DType): + "Wraps an into a different type." pass @@ -100,6 +104,7 @@ class ElemTypeCstInner(WrapperType): bfloat16 = DType(16) complex64 = DType(14) complex128 = DType(15) + str_ = DType(8) class ElemTypeCstSet(ElemTypeCstInner): @@ -142,6 +147,8 @@ class ElemTypeCstSet(ElemTypeCstInner): ElemTypeCstInner.float64, } + strings = {ElemTypeCstInner.str_} + @staticmethod def combined(type_set): "Combines all types into a single integer by using power of 2." @@ -156,26 +163,28 @@ class ElemTypeCst(ElemTypeCstSet): Combination of element types. """ - Undefined = DType2(0) - Bool = DType2(1 << ElemTypeCstInner.bool_.code) - Int8 = DType2(1 << ElemTypeCstInner.int8.code) - Int16 = DType2(1 << ElemTypeCstInner.int16.code) - Int32 = DType2(1 << ElemTypeCstInner.int32.code) - Int64 = DType2(1 << ElemTypeCstInner.int64.code) - UInt8 = DType2(1 << ElemTypeCstInner.uint8.code) - UInt16 = DType2(1 << ElemTypeCstInner.uint16.code) - UInt32 = DType2(1 << ElemTypeCstInner.uint32.code) - UInt64 = DType2(1 << ElemTypeCstInner.uint64.code) - BFloat16 = DType2(1 << ElemTypeCstInner.bfloat16.code) - Float16 = DType2(1 << ElemTypeCstInner.float16.code) - Float32 = DType2(1 << ElemTypeCstInner.float32.code) - Float64 = DType2(1 << ElemTypeCstInner.float64.code) - Complex64 = DType2(1 << ElemTypeCstInner.complex64.code) - Complex128 = DType2(1 << ElemTypeCstInner.complex128.code) + Undefined = _DType2(0) + Bool = _DType2(1 << ElemTypeCstInner.bool_.code) + Int8 = _DType2(1 << ElemTypeCstInner.int8.code) + Int16 = _DType2(1 << ElemTypeCstInner.int16.code) + Int32 = _DType2(1 << ElemTypeCstInner.int32.code) + Int64 = _DType2(1 << ElemTypeCstInner.int64.code) + UInt8 = _DType2(1 << ElemTypeCstInner.uint8.code) + UInt16 = _DType2(1 << ElemTypeCstInner.uint16.code) + UInt32 = _DType2(1 << ElemTypeCstInner.uint32.code) + UInt64 = _DType2(1 << ElemTypeCstInner.uint64.code) + BFloat16 = _DType2(1 << ElemTypeCstInner.bfloat16.code) + Float16 = _DType2(1 << ElemTypeCstInner.float16.code) + Float32 = _DType2(1 << ElemTypeCstInner.float32.code) + Float64 = _DType2(1 << ElemTypeCstInner.float64.code) + Complex64 = _DType2(1 << ElemTypeCstInner.complex64.code) + Complex128 = _DType2(1 << ElemTypeCstInner.complex128.code) + String = _DType2(1 << ElemTypeCstInner.str_.code) Numerics = ElemTypeCstSet.combined(ElemTypeCstSet.numerics) Floats = ElemTypeCstSet.combined(ElemTypeCstSet.floats) Ints = ElemTypeCstSet.combined(ElemTypeCstSet.ints) + Strings = ElemTypeCstSet.combined(ElemTypeCstSet.strings) class ElemType(ElemTypeCst): @@ -270,7 +279,7 @@ class ParType(WrapperType): :param optional: is optional or not """ - map_names = {int: "int", float: "float", str: "str"} + map_names = {int: "int", float: "float", str: "str", DType: "DType"} @classmethod def __class_getitem__(cls, dtype): diff --git a/pyproject.toml b/pyproject.toml index 832a027..7c3e233 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,6 +3,7 @@ report_level = "INFO" ignore_directives = [ "autoclass", "autofunction", + "automodule", "gdot", "image-sg", "runpython", @@ -29,10 +30,12 @@ max-complexity = 10 [tool.ruff.per-file-ignores] "_doc/examples/plot_first_example.py" = ["E402", "F811"] "_doc/examples/plot_onnxruntime.py" = ["E402", "F811"] -"onnx_array_api/profiling.py" = ["E731"] +"onnx_array_api/array_api/onnx_numpy.py" = ["F821"] +"onnx_array_api/array_api/onnx_ort.py" = ["F821"] "onnx_array_api/npx/__init__.py" = ["F401", "F403"] "onnx_array_api/npx/npx_functions.py" = ["F821"] "onnx_array_api/npx/npx_functions_test.py" = ["F821"] "onnx_array_api/npx/npx_var.py" = ["F821"] +"onnx_array_api/profiling.py" = ["E731"] "_unittests/ut_npx/test_npx.py" = ["F821"] From 535cc4a3da8db0d3c0e0e035685480303e9e6fdb Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 17:06:15 +0200 Subject: [PATCH 13/26] fix unit test --- _doc/api/npx_annot.rst | 29 +++++++++++++++++++++++++++-- _unittests/ut_npx/test_npx.py | 9 ++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/_doc/api/npx_annot.rst b/_doc/api/npx_annot.rst index d7e46e3..43de2d7 100644 --- a/_doc/api/npx_annot.rst +++ b/_doc/api/npx_annot.rst @@ -1,29 +1,54 @@ +============= npx.npx_types ============= +DType +===== + +.. autoclass:: onnx_array_api.npx.npx_types.DType + :members: + Annotations -+++++++++++ +=========== + +ElemType +++++++++ .. autoclass:: onnx_array_api.npx.npx_types.ElemType :members: +ParType ++++++++ + .. autoclass:: onnx_array_api.npx.npx_types.ParType :members: +OptParType +++++++++++ + .. autoclass:: onnx_array_api.npx.npx_types.OptParType :members: +TensorType +++++++++++ + .. autoclass:: onnx_array_api.npx.npx_types.TensorType :members: +SequenceType +++++++++++++ + .. autoclass:: onnx_array_api.npx.npx_types.SequenceType :members: +TupleType ++++++++++ + .. autoclass:: onnx_array_api.npx.npx_types.TupleType :members: Shortcuts -+++++++++ +========= .. autoclass:: onnx_array_api.npx.npx_types.Bool diff --git a/_unittests/ut_npx/test_npx.py b/_unittests/ut_npx/test_npx.py index d515738..95209f3 100644 --- a/_unittests/ut_npx/test_npx.py +++ b/_unittests/ut_npx/test_npx.py @@ -128,18 +128,25 @@ def test_tensor(self): self.assertEqual(dt.dtypes[0].dtype, ElemType.float32) self.assertEmpty(dt.shape) self.assertEqual(dt.type_name(), "TensorType['float32']") + dt = TensorType["float32"] self.assertEqual(len(dt.dtypes), 1) self.assertEqual(dt.dtypes[0].dtype, ElemType.float32) self.assertEqual(dt.type_name(), "TensorType['float32']") + dt = TensorType[np.float32] self.assertEqual(len(dt.dtypes), 1) self.assertEqual(dt.dtypes[0].dtype, ElemType.float32) self.assertEqual(dt.type_name(), "TensorType['float32']") self.assertEmpty(dt.shape) + dt = TensorType[np.str_] + self.assertEqual(len(dt.dtypes), 1) + self.assertEqual(dt.dtypes[0].dtype, ElemType.str_) + self.assertEqual(dt.type_name(), "TensorType[strings]") + self.assertEmpty(dt.shape) + self.assertRaise(lambda: TensorType[None], TypeError) - self.assertRaise(lambda: TensorType[np.str_], TypeError) self.assertRaise(lambda: TensorType[{np.float32, np.str_}], TypeError) def test_superset(self): From 5a079119c885fe57e16388b9ddf44156ed10f6f5 Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 17:09:39 +0200 Subject: [PATCH 14/26] fix ci --- azure-pipelines.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6a84f8c..4d03851 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -114,20 +114,25 @@ jobs: displayName: 'Install onnx_array_api' - script: | git clone https://github.com/data-apis/array-api-tests.git - git submodule update --init + git submodule update --init --recusrive displayName: 'clone array-api-tests' + - script: | + cd array-api-tests + git submodule update --init --recursive + cd .. + displayName: 'get submodules for array-api-tests' - script: pip install -r array-api-tests/requirements.txt displayName: 'Install Requirements dev' - script: | export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api cd array-api-tests - pytest array_api_tests/test_creation_functions.py::test_zeros + python -m pytest array_api_tests/test_creation_functions.py::test_zeros cd .. displayName: "test_creation_functions.py::test_zeros" - script: | export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api cd array-api-tests - pytest array_api_tests + python -m pytest array_api_tests cd .. displayName: "all tests" From 3481d27afdaa49e036bd4fa1d5fd744a8014f2ca Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 17:13:46 +0200 Subject: [PATCH 15/26] ci --- azure-pipelines.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4d03851..a77f77a 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -114,7 +114,6 @@ jobs: displayName: 'Install onnx_array_api' - script: | git clone https://github.com/data-apis/array-api-tests.git - git submodule update --init --recusrive displayName: 'clone array-api-tests' - script: | cd array-api-tests From 1990fe8ed6c739822a9fcf4079e402a598f429f5 Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 17:20:44 +0200 Subject: [PATCH 16/26] api --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a77f77a..8bf03f8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -123,13 +123,13 @@ jobs: - script: pip install -r array-api-tests/requirements.txt displayName: 'Install Requirements dev' - script: | - export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api + export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy cd array-api-tests python -m pytest array_api_tests/test_creation_functions.py::test_zeros cd .. displayName: "test_creation_functions.py::test_zeros" - script: | - export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api_onnx_numpy.onnx_numpy_array_api + export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy cd array-api-tests python -m pytest array_api_tests cd .. From e0ca8c475a908ecbb27f29cb7453ccb09e09661f Mon Sep 17 00:00:00 2001 From: xadupre Date: Mon, 5 Jun 2023 17:50:33 +0200 Subject: [PATCH 17/26] ci --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8bf03f8..b03871d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -125,13 +125,13 @@ jobs: - script: | export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy cd array-api-tests - python -m pytest array_api_tests/test_creation_functions.py::test_zeros + python -m pytest -xv array_api_tests/test_creation_functions.py::test_zeros cd .. displayName: "test_creation_functions.py::test_zeros" - script: | export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy cd array-api-tests - python -m pytest array_api_tests + python -m pytest -x array_api_tests cd .. displayName: "all tests" From 1e218b5a3e84d3db8b44f8ef3b1db248a660bc21 Mon Sep 17 00:00:00 2001 From: xadupre Date: Tue, 6 Jun 2023 01:15:42 +0200 Subject: [PATCH 18/26] improvments --- _unittests/test_array_api.sh | 2 + _unittests/ut_npx/test_npx.py | 31 ++++++++++- azure-pipelines.yml | 6 +-- onnx_array_api/array_api/onnx_numpy.py | 39 +++++++++++--- onnx_array_api/array_api/onnx_ort.py | 29 ++--------- onnx_array_api/npx/npx_functions.py | 69 ++++++++++++++----------- onnx_array_api/npx/npx_jit_eager.py | 16 +++++- onnx_array_api/npx/npx_numpy_tensors.py | 13 ++--- onnx_array_api/npx/npx_tensors.py | 2 +- onnx_array_api/npx/npx_types.py | 9 ++++ 10 files changed, 142 insertions(+), 74 deletions(-) create mode 100644 _unittests/test_array_api.sh diff --git a/_unittests/test_array_api.sh b/_unittests/test_array_api.sh new file mode 100644 index 0000000..b32ee41 --- /dev/null +++ b/_unittests/test_array_api.sh @@ -0,0 +1,2 @@ +export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy +pytest ../array-api-tests/array_api_tests/test_creation_functions.py::test_zeros \ No newline at end of file diff --git a/_unittests/ut_npx/test_npx.py b/_unittests/ut_npx/test_npx.py index 95209f3..83473ba 100644 --- a/_unittests/ut_npx/test_npx.py +++ b/_unittests/ut_npx/test_npx.py @@ -29,6 +29,7 @@ npxapi_inline, ) from onnx_array_api.npx.npx_functions import absolute as absolute_inline +from onnx_array_api.npx.npx_functions import all as all_inline from onnx_array_api.npx.npx_functions import arange as arange_inline from onnx_array_api.npx.npx_functions import arccos as arccos_inline from onnx_array_api.npx.npx_functions import arccosh as arccosh_inline @@ -50,6 +51,7 @@ from onnx_array_api.npx.npx_functions import det as det_inline from onnx_array_api.npx.npx_functions import dot as dot_inline from onnx_array_api.npx.npx_functions import einsum as einsum_inline +from onnx_array_api.npx.npx_functions import equal as equal_inline from onnx_array_api.npx.npx_functions import erf as erf_inline from onnx_array_api.npx.npx_functions import exp as exp_inline from onnx_array_api.npx.npx_functions import expand_dims as expand_dims_inline @@ -1163,6 +1165,16 @@ def test_astype(self): got = ref.run(None, {"A": x}) self.assertEqualArray(z, got[0]) + def test_astype_dtype(self): + f = absolute_inline(copy_inline(Input("A")).astype(DType(7))) + self.assertIsInstance(f, Var) + onx = f.to_onnx(constraints={"A": Float64[None]}) + x = np.array([[-5.4, 6.6]], dtype=np.float64) + z = np.abs(x.astype(np.int64)) + ref = ReferenceEvaluator(onx) + got = ref.run(None, {"A": x}) + self.assertEqualArray(z, got[0]) + def test_astype_int(self): f = absolute_inline(copy_inline(Input("A")).astype(1)) self.assertIsInstance(f, Var) @@ -1421,6 +1433,9 @@ def test_einsum(self): lambda x, y: np.einsum(equation, x, y), ) + def test_equal(self): + self.common_test_inline_bin(equal_inline, np.equal) + @unittest.skipIf(scipy is None, reason="scipy is not installed.") def test_erf(self): self.common_test_inline(erf_inline, scipy.special.erf) @@ -1468,7 +1483,8 @@ def test_hstack(self): def test_identity(self): f = identity_inline(2, dtype=np.float64) onx = f.to_onnx(constraints={(0, False): Float64[None]}) - z = np.identity(2) + self.assertIn("dtype:", str(onx)) + z = np.identity(2).astype(np.float64) ref = ReferenceEvaluator(onx) got = ref.run(None, {}) self.assertEqualArray(z, got[0]) @@ -2465,7 +2481,18 @@ def test_take(self): got = ref.run(None, {"A": data, "B": indices}) self.assertEqualArray(y, got[0]) + def test_numpy_all(self): + data = np.array([[1, 0], [1, 1]]).astype(np.bool_) + y = np.all(data, axis=1) + + f = all_inline(Input("A"), axis=1) + self.assertIsInstance(f, Var) + onx = f.to_onnx(constraints={"A": Bool[None]}) + ref = ReferenceEvaluator(onx) + got = ref.run(None, {"A": data}) + self.assertEqualArray(y, got[0]) + if __name__ == "__main__": - TestNpx().test_take() + TestNpx().test_identity() unittest.main(verbosity=2) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b03871d..4ac1f0f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -125,14 +125,12 @@ jobs: - script: | export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy cd array-api-tests + displayName: 'Set API' + - script: | python -m pytest -xv array_api_tests/test_creation_functions.py::test_zeros - cd .. displayName: "test_creation_functions.py::test_zeros" - script: | - export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy - cd array-api-tests python -m pytest -x array_api_tests - cd .. displayName: "all tests" - job: 'TestLinux' diff --git a/onnx_array_api/array_api/onnx_numpy.py b/onnx_array_api/array_api/onnx_numpy.py index edcfe36..7633525 100644 --- a/onnx_array_api/array_api/onnx_numpy.py +++ b/onnx_array_api/array_api/onnx_numpy.py @@ -6,14 +6,16 @@ from onnx import TensorProto from ..npx.npx_array_api import BaseArrayApi from ..npx.npx_functions import ( + all, abs, absolute, astype, + copy as copy_inline, + equal, isdtype, reshape, take, ) -from ..npx.npx_functions import asarray as generic_asarray from ..npx.npx_functions import zeros as generic_zeros from ..npx.npx_numpy_tensors import EagerNumpyTensor from ..npx.npx_types import DType, ElemType, TensorType, OptParType @@ -22,8 +24,10 @@ __all__ = [ "abs", "absolute", + "all", "asarray", "astype", + "equal", "isdtype", "reshape", "take", @@ -41,13 +45,36 @@ def asarray( """ Converts anything into an array. """ + if order not in ("C", None): + raise NotImplementedError(f"asarray is not implemented for order={order!r}.") + if like is not None: + raise NotImplementedError( + f"asarray is not implemented for like != None (type={type(like)})." + ) if isinstance(a, BaseArrayApi): - return generic_asarray(a, dtype=dtype, order=order, like=like, copy=copy) + if copy: + if dtype is None: + return copy_inline(a) + return copy_inline(a).astype(dtype=dtype) + if dtype is None: + return a + return a.astype(dtype=dtype) + if isinstance(a, int): - return EagerNumpyTensor(np.array(a, dtype=np.int64)) - if isinstance(a, float): - return EagerNumpyTensor(np.array(a, dtype=np.float32)) - raise NotImplementedError(f"asarray not implemented for type {type(a)}.") + v = EagerNumpyTensor(np.array(a, dtype=np.int64)) + elif isinstance(a, float): + v = EagerNumpyTensor(np.array(a, dtype=np.float32)) + elif isinstance(a, bool): + v = EagerNumpyTensor(np.array(a, dtype=np.bool_)) + elif isinstance(a, str): + v = EagerNumpyTensor(np.array(a, dtype=np.str_)) + else: + raise RuntimeError(f"Unexpected type {type(a)} for the first input.") + if dtype is not None: + vt = v.astype(dtype=dtype) + else: + vt = v + return vt def zeros( diff --git a/onnx_array_api/array_api/onnx_ort.py b/onnx_array_api/array_api/onnx_ort.py index 621823e..fa8c2af 100644 --- a/onnx_array_api/array_api/onnx_ort.py +++ b/onnx_array_api/array_api/onnx_ort.py @@ -1,51 +1,30 @@ """ Array API valid for an :class:`EagerOrtTensor`. """ -from typing import Any, Optional -import numpy as np -from ..npx.npx_array_api import BaseArrayApi from ..npx.npx_functions import ( + all, abs, absolute, astype, + equal, isdtype, reshape, take, ) -from ..npx.npx_functions import asarray as generic_asarray -from ..ort.ort_tensors import EagerOrtTensor from . import _finalize_array_api __all__ = [ + "all", "abs", "absolute", - "asarray", "astype", + "equal", "isdtype", "reshape", "take", ] -def asarray( - a: Any, - dtype: Any = None, - order: Optional[str] = None, - like: Any = None, - copy: bool = False, -): - """ - Converts anything into an array. - """ - if isinstance(a, BaseArrayApi): - return generic_asarray(a, dtype=dtype, order=order, like=like, copy=copy) - if isinstance(a, int): - return EagerOrtTensor(np.array(a, dtype=np.int64)) - if isinstance(a, float): - return EagerOrtTensor(np.array(a, dtype=np.float32)) - raise NotImplementedError(f"asarray not implemented for type {type(a)}.") - - def _finalize(): from . import onnx_ort diff --git a/onnx_array_api/npx/npx_functions.py b/onnx_array_api/npx/npx_functions.py index 3e3d213..98cdf4e 100644 --- a/onnx_array_api/npx/npx_functions.py +++ b/onnx_array_api/npx/npx_functions.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Tuple, Union +from typing import Optional, Tuple, Union import array_api_compat.numpy as np_array_api import numpy as np @@ -8,7 +8,6 @@ from .npx_constants import FUNCTION_DOMAIN from .npx_core_api import cst, make_tuple, npxapi_inline, npxapi_no_inline, var -from .npx_tensors import BaseArrayApi from .npx_types import ( DType, ElemType, @@ -43,6 +42,27 @@ def absolute( return var(x, op="Abs") +@npxapi_inline +def all( + x: TensorType[ElemType.bool_, "T"], + axis: Optional[TensorType[ElemType.int64, "I"]] = None, + keepdims: ParType[int] = 0, +) -> TensorType[ElemType.bool_, "T"]: + "See :func:`numpy.all`." + + xi = var(x, op="Cast", to=TensorProto.INT64) + + if axis is None: + red = xi.min(keepdims=keepdims) + else: + if isinstance(axis, int): + axis = [axis] + if isinstance(axis, (tuple, list)): + axis = cst(np.array(axis, dtype=np.int64)) + red = xi.min(axis, keepdims=keepdims) + return var(red, cst(1), op="Equal") + + @npxapi_inline def arccos(x: TensorType[ElemType.numerics, "T"]) -> TensorType[ElemType.numerics, "T"]: "See :func:`numpy.arccos`." @@ -159,27 +179,6 @@ def arctanh( return var(x, op="Atanh") -def asarray( - a: Any, - dtype: Any = None, - order: Optional[str] = None, - like: Any = None, - copy: bool = False, -): - """ - Converts anything into an array. - """ - if dtype is not None: - raise RuntimeError("Method 'astype' should be used to change the type.") - if order is not None: - raise NotImplementedError(f"order={order!r} not implemented.") - if isinstance(a, BaseArrayApi): - if copy: - return a.__class__(a, copy=copy) - return a - raise NotImplementedError(f"asarray not implemented for type {type(a)}.") - - @npxapi_inline def astype( a: TensorType[ElemType.numerics, "T1"], dtype: OptParType[DType] = 1 @@ -335,6 +334,14 @@ def einsum( return var(*x, op="Einsum", equation=equation) +@npxapi_inline +def equal( + x: TensorType[ElemType.allowed, "T"], y: TensorType[ElemType.allowed, "T"] +) -> TensorType[ElemType.bool_, "T1"]: + "See :func:`numpy.isnan`." + return var(x, y, op="Equal") + + @npxapi_inline def erf(x: TensorType[ElemType.numerics, "T"]) -> TensorType[ElemType.numerics, "T"]: "See :func:`scipy.special.erf`." @@ -382,18 +389,20 @@ def hstack( @npxapi_inline -def copy(x: TensorType[ElemType.numerics, "T"]) -> TensorType[ElemType.numerics, "T"]: +def copy(x: TensorType[ElemType.allowed, "T"]) -> TensorType[ElemType.allowed, "T"]: "Makes a copy." return var(x, op="Identity") @npxapi_inline -def identity(n: ParType[int], dtype=None) -> TensorType[ElemType.numerics, "T"]: +def identity( + n: ParType[int], dtype: OptParType[DType] = None +) -> TensorType[ElemType.numerics, "T"]: "Makes a copy." - val = np.array([n, n], dtype=np.int64) - shape = cst(val) model = var( - shape, op="ConstantOfShape", value=from_array(np.array([0], dtype=np.int64)) + cst(np.array([n, n], dtype=np.int64)), + op="ConstantOfShape", + value=from_array(np.array([0], dtype=np.int64)), ) v = var(model, dtype=dtype, op="EyeLike") return v @@ -416,7 +425,7 @@ def isdtype( @npxapi_inline -def isnan(x: TensorType[ElemType.numerics, "T"]) -> TensorType[ElemType.bool_, "T"]: +def isnan(x: TensorType[ElemType.numerics, "T"]) -> TensorType[ElemType.bool_, "T1"]: "See :func:`numpy.isnan`." return var(x, op="IsNaN") @@ -643,6 +652,8 @@ def zeros( """ if order != "C": raise RuntimeError(f"order={order!r} != 'C' not supported.") + if dtype is None: + dtype = DType(TensorProto.FLOAT) return var( shape, value=make_tensor(name="zero", data_type=dtype.code, dims=[1], vals=[0]), diff --git a/onnx_array_api/npx/npx_jit_eager.py b/onnx_array_api/npx/npx_jit_eager.py index 33c5685..755c42e 100644 --- a/onnx_array_api/npx/npx_jit_eager.py +++ b/onnx_array_api/npx/npx_jit_eager.py @@ -168,6 +168,8 @@ def make_key(*values, **kwargs): else: newv.append(t) res.append(tuple(newv)) + elif v is None and k in {"dtype"}: + continue else: raise TypeError( f"Type {type(v)} is not yet supported, " @@ -520,6 +522,8 @@ def _preprocess_constants(self, *args): elif isinstance(n, (int, float)): new_args.append(self.tensor_class(np.array(n))) modified = True + elif isinstance(n, DType): + new_args.append(n) elif n in (int, float): # usually used to cast new_args.append(n) @@ -554,7 +558,17 @@ def __call__(self, *args, already_eager=False, **kwargs): lambda t: t is not None and not isinstance( t, - (EagerTensor, Cst, int, float, tuple, slice, type, np.ndarray), + ( + EagerTensor, + Cst, + int, + float, + tuple, + slice, + type, + np.ndarray, + DType, + ), ), args, ) diff --git a/onnx_array_api/npx/npx_numpy_tensors.py b/onnx_array_api/npx/npx_numpy_tensors.py index 8e0d368..e6353ea 100644 --- a/onnx_array_api/npx/npx_numpy_tensors.py +++ b/onnx_array_api/npx/npx_numpy_tensors.py @@ -55,17 +55,18 @@ def __init__(self, tensor: np.ndarray): elif isinstance( tensor, ( - np.int64, + np.float16, np.float32, np.float64, + np.int64, np.int32, - np.float16, - np.int8, np.int16, - np.uint8, - np.uint16, - np.uint32, + np.int8, np.uint64, + np.uint32, + np.uint16, + np.uint8, + np.bool_, ), ): self._tensor = np.array(tensor) diff --git a/onnx_array_api/npx/npx_tensors.py b/onnx_array_api/npx/npx_tensors.py index 094ea70..b1e42de 100644 --- a/onnx_array_api/npx/npx_tensors.py +++ b/onnx_array_api/npx/npx_tensors.py @@ -192,7 +192,7 @@ def _generic_method_astype(self, method_name, *args: Any, **kwargs: Any) -> Any: dtype = ( args[0] - if isinstance(args[0], (int, Var)) + if isinstance(args[0], (DType, Var)) else self._np_dtype_to_tensor_dtype(args[0]) ) eag = eager_onnx(EagerTensor._astype_impl, self.__class__, bypass_eager=True) diff --git a/onnx_array_api/npx/npx_types.py b/onnx_array_api/npx/npx_types.py index 981a894..2c25bee 100644 --- a/onnx_array_api/npx/npx_types.py +++ b/onnx_array_api/npx/npx_types.py @@ -30,6 +30,10 @@ def __repr__(self) -> str: "usual" return f"DType({self.code_})" + def __str__(self) -> str: + "usual" + return f"DT{self.code_}" + def __hash__(self) -> int: return self.code_ @@ -70,6 +74,11 @@ def __lt__(self, dt: "DType") -> bool: raise TypeError(f"dt must be DType not {type(dt)} - {dt}.") return self.code_ < dti + @classmethod + def type_name(cls) -> str: + "Returns its full name." + raise NotImplementedError() + class _DType2(DType): "Wraps an into a different type." From e3440114b1095cbce2d4e070d211c80d0a1ed9b6 Mon Sep 17 00:00:00 2001 From: xadupre Date: Tue, 6 Jun 2023 02:06:22 +0200 Subject: [PATCH 19/26] refactorign --- _unittests/ut_npx/test_npx.py | 2 +- onnx_array_api/npx/npx_array_api.py | 2 +- onnx_array_api/npx/npx_jit_eager.py | 6 ++++++ onnx_array_api/npx/npx_tensors.py | 6 +++--- onnx_array_api/npx/npx_types.py | 4 ++-- onnx_array_api/npx/npx_var.py | 8 ++++---- 6 files changed, 17 insertions(+), 11 deletions(-) diff --git a/_unittests/ut_npx/test_npx.py b/_unittests/ut_npx/test_npx.py index 83473ba..d12820c 100644 --- a/_unittests/ut_npx/test_npx.py +++ b/_unittests/ut_npx/test_npx.py @@ -1483,7 +1483,7 @@ def test_hstack(self): def test_identity(self): f = identity_inline(2, dtype=np.float64) onx = f.to_onnx(constraints={(0, False): Float64[None]}) - self.assertIn("dtype:", str(onx)) + self.assertIn('name: "dtype"', str(onx)) z = np.identity(2).astype(np.float64) ref = ReferenceEvaluator(onx) got = ref.run(None, {}) diff --git a/onnx_array_api/npx/npx_array_api.py b/onnx_array_api/npx/npx_array_api.py index 6816745..58968ae 100644 --- a/onnx_array_api/npx/npx_array_api.py +++ b/onnx_array_api/npx/npx_array_api.py @@ -128,7 +128,7 @@ def T(self) -> "BaseArrayApi": return self.generic_method("T") def astype(self, dtype: Any) -> "BaseArrayApi": - return self.generic_method("astype", dtype) + return self.generic_method("astype", dtype=dtype) @property def shape(self) -> "BaseArrayApi": diff --git a/onnx_array_api/npx/npx_jit_eager.py b/onnx_array_api/npx/npx_jit_eager.py index 755c42e..a4c4435 100644 --- a/onnx_array_api/npx/npx_jit_eager.py +++ b/onnx_array_api/npx/npx_jit_eager.py @@ -195,6 +195,12 @@ def to_jit(self, *values, **kwargs): constraints = {} new_kwargs = {} for i, (v, iname) in enumerate(zip(values, names)): + if i < len(annot_values) and not isinstance(annot_values[i], type): + raise TypeError( + f"annotation {i} is not a type but is {annot_values[i]!r}." + f"for function {self.f} " + f"from module {self.f.__module__!r}." + ) if isinstance(v, (EagerTensor, JitTensor)) and ( i >= len(annot_values) or issubclass(annot_values[i], TensorType) ): diff --git a/onnx_array_api/npx/npx_tensors.py b/onnx_array_api/npx/npx_tensors.py index b1e42de..a3aa136 100644 --- a/onnx_array_api/npx/npx_tensors.py +++ b/onnx_array_api/npx/npx_tensors.py @@ -1,9 +1,9 @@ -from typing import Any, Optional +from typing import Any import numpy as np from onnx.helper import np_dtype_to_tensor_dtype -from .npx_types import DType +from .npx_types import DType, OptParType from .npx_array_api import BaseArrayApi, ArrayApiError @@ -74,7 +74,7 @@ def _getitem_impl_var(obj, index, method_name=None): return meth(obj, index) @staticmethod - def _astype_impl(x, dtype: Optional[DType] = None, method_name=None): + def _astype_impl(x, dtype: OptParType[DType] = None, method_name=None): # avoids circular imports. if dtype is None: raise ValueError("dtype cannot be None.") diff --git a/onnx_array_api/npx/npx_types.py b/onnx_array_api/npx/npx_types.py index 2c25bee..7a96124 100644 --- a/onnx_array_api/npx/npx_types.py +++ b/onnx_array_api/npx/npx_types.py @@ -200,7 +200,7 @@ class ElemType(ElemTypeCst): """ Allowed element type based on numpy dtypes. - :param dtype: integer or a string + :param dtype: DType or a string """ names_int = { @@ -237,7 +237,7 @@ class ElemType(ElemTypeCst): __slots__ = ["dtype"] @classmethod - def __class_getitem__(cls, dtype: Union[str, int]): + def __class_getitem__(cls, dtype: Union[str, DType]): if isinstance(dtype, str): dtype = ElemType.names_int[dtype] elif dtype in ElemType.numpy_map: diff --git a/onnx_array_api/npx/npx_var.py b/onnx_array_api/npx/npx_var.py index 28c6e56..ae5b732 100644 --- a/onnx_array_api/npx/npx_var.py +++ b/onnx_array_api/npx/npx_var.py @@ -276,7 +276,7 @@ def __init__( op: Union[ Callable, str, Tuple[str, str], FunctionProto, ModelProto, NodeProto ] = None, - dtype: type = None, + dtype: Union[type, DType] = None, inline: bool = False, n_var_outputs: Optional[int] = 1, input_indices: Optional[List[int]] = None, @@ -298,11 +298,11 @@ def __init__( self.onnx_op_kwargs = kwargs self._prefix = None - if hasattr(dtype, "type_name"): - self.dtype = dtype - elif isinstance(dtype, DType): + if isinstance(dtype, DType): # regular parameter self.onnx_op_kwargs["dtype"] = dtype + elif hasattr(dtype, "type_name"): + self.dtype = dtype elif dtype is None: self.dtype = None else: From 178e3e933dbfc64cc6b4a18cd087ad6d1f06d59a Mon Sep 17 00:00:00 2001 From: xadupre Date: Tue, 6 Jun 2023 12:50:24 +0200 Subject: [PATCH 20/26] fix asarray --- azure-pipelines.yml | 4 ++ onnx_array_api/array_api/_onnx_common.py | 50 ++++++++++++++++++++++++ onnx_array_api/array_api/onnx_numpy.py | 36 ++--------------- onnx_array_api/array_api/onnx_ort.py | 20 ++++++++++ 4 files changed, 78 insertions(+), 32 deletions(-) create mode 100644 onnx_array_api/array_api/_onnx_common.py diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4ac1f0f..74e6ef8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -127,9 +127,13 @@ jobs: cd array-api-tests displayName: 'Set API' - script: | + export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy + cd array-api-tests python -m pytest -xv array_api_tests/test_creation_functions.py::test_zeros displayName: "test_creation_functions.py::test_zeros" - script: | + export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy + cd array-api-tests python -m pytest -x array_api_tests displayName: "all tests" diff --git a/onnx_array_api/array_api/_onnx_common.py b/onnx_array_api/array_api/_onnx_common.py new file mode 100644 index 0000000..51ed3ec --- /dev/null +++ b/onnx_array_api/array_api/_onnx_common.py @@ -0,0 +1,50 @@ +from typing import Any, Optional +import numpy as np +from ..npx.npx_types import DType +from ..npx.npx_array_api import BaseArrayApi +from ..npx.npx_functions import ( + copy as copy_inline, +) + + +def template_asarray( + TEagerTensor: type, + a: Any, + dtype: Optional[DType] = None, + order: Optional[str] = None, + like: Any = None, + copy: bool = False, +) -> Any: + """ + Converts anything into an array. + """ + if order not in ("C", None): + raise NotImplementedError(f"asarray is not implemented for order={order!r}.") + if like is not None: + raise NotImplementedError( + f"asarray is not implemented for like != None (type={type(like)})." + ) + if isinstance(a, BaseArrayApi): + if copy: + if dtype is None: + return copy_inline(a) + return copy_inline(a).astype(dtype=dtype) + if dtype is None: + return a + return a.astype(dtype=dtype) + + if isinstance(a, int): + v = TEagerTensor(np.array(a, dtype=np.int64)) + elif isinstance(a, float): + v = TEagerTensor(np.array(a, dtype=np.float32)) + elif isinstance(a, bool): + v = TEagerTensor(np.array(a, dtype=np.bool_)) + elif isinstance(a, str): + v = TEagerTensor(np.array(a, dtype=np.str_)) + else: + raise RuntimeError(f"Unexpected type {type(a)} for the first input.") + if dtype is not None: + vt = v.astype(dtype=dtype) + else: + vt = v + return vt diff --git a/onnx_array_api/array_api/onnx_numpy.py b/onnx_array_api/array_api/onnx_numpy.py index 7633525..79b339d 100644 --- a/onnx_array_api/array_api/onnx_numpy.py +++ b/onnx_array_api/array_api/onnx_numpy.py @@ -4,13 +4,11 @@ from typing import Any, Optional import numpy as np from onnx import TensorProto -from ..npx.npx_array_api import BaseArrayApi from ..npx.npx_functions import ( all, abs, absolute, astype, - copy as copy_inline, equal, isdtype, reshape, @@ -19,6 +17,7 @@ from ..npx.npx_functions import zeros as generic_zeros from ..npx.npx_numpy_tensors import EagerNumpyTensor from ..npx.npx_types import DType, ElemType, TensorType, OptParType +from ._onnx_common import template_asarray from . import _finalize_array_api __all__ = [ @@ -45,36 +44,9 @@ def asarray( """ Converts anything into an array. """ - if order not in ("C", None): - raise NotImplementedError(f"asarray is not implemented for order={order!r}.") - if like is not None: - raise NotImplementedError( - f"asarray is not implemented for like != None (type={type(like)})." - ) - if isinstance(a, BaseArrayApi): - if copy: - if dtype is None: - return copy_inline(a) - return copy_inline(a).astype(dtype=dtype) - if dtype is None: - return a - return a.astype(dtype=dtype) - - if isinstance(a, int): - v = EagerNumpyTensor(np.array(a, dtype=np.int64)) - elif isinstance(a, float): - v = EagerNumpyTensor(np.array(a, dtype=np.float32)) - elif isinstance(a, bool): - v = EagerNumpyTensor(np.array(a, dtype=np.bool_)) - elif isinstance(a, str): - v = EagerNumpyTensor(np.array(a, dtype=np.str_)) - else: - raise RuntimeError(f"Unexpected type {type(a)} for the first input.") - if dtype is not None: - vt = v.astype(dtype=dtype) - else: - vt = v - return vt + return template_asarray( + EagerNumpyTensor, a, dtype=dtype, order=order, like=like, copy=copy + ) def zeros( diff --git a/onnx_array_api/array_api/onnx_ort.py b/onnx_array_api/array_api/onnx_ort.py index fa8c2af..505efdf 100644 --- a/onnx_array_api/array_api/onnx_ort.py +++ b/onnx_array_api/array_api/onnx_ort.py @@ -1,6 +1,9 @@ """ Array API valid for an :class:`EagerOrtTensor`. """ +from typing import Optional, Any +from ..ort.ort_tensors import EagerOrtTensor +from ..npx.npx_types import DType from ..npx.npx_functions import ( all, abs, @@ -11,12 +14,14 @@ reshape, take, ) +from ._onnx_common import template_asarray from . import _finalize_array_api __all__ = [ "all", "abs", "absolute", + "asarray", "astype", "equal", "isdtype", @@ -25,6 +30,21 @@ ] +def asarray( + a: Any, + dtype: Optional[DType] = None, + order: Optional[str] = None, + like: Any = None, + copy: bool = False, +) -> EagerOrtTensor: + """ + Converts anything into an array. + """ + return template_asarray( + EagerOrtTensor, a, dtype=dtype, order=order, like=like, copy=copy + ) + + def _finalize(): from . import onnx_ort From 550d0dc6fe061d7075293afd7aae18542c2f4f40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?xavier=20dupr=C3=A9?= Date: Thu, 8 Jun 2023 11:54:04 +0200 Subject: [PATCH 21/26] new udpates --- _unittests/ut_npx/test_sklearn_array_api.py | 2 ++ onnx_array_api/array_api/_onnx_common.py | 2 +- onnx_array_api/npx/npx_jit_eager.py | 8 ++++++-- onnx_array_api/npx/npx_tensors.py | 17 +++++++---------- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/_unittests/ut_npx/test_sklearn_array_api.py b/_unittests/ut_npx/test_sklearn_array_api.py index 5694f83..79120a9 100644 --- a/_unittests/ut_npx/test_sklearn_array_api.py +++ b/_unittests/ut_npx/test_sklearn_array_api.py @@ -27,6 +27,8 @@ def test_sklearn_array_api_linear_discriminant(self): new_x = EagerNumpyTensor(X) self.assertStartsWith("EagerNumpyTensor(array([[", repr(new_x)) with config_context(array_api_dispatch=True): + # It fails if scikit-learn <= 1.2.2 because the ArrayAPI + # is not strictly applied. got = ana.predict(new_x) self.assertEqualArray(expected, got.numpy()) diff --git a/onnx_array_api/array_api/_onnx_common.py b/onnx_array_api/array_api/_onnx_common.py index 51ed3ec..8d136c4 100644 --- a/onnx_array_api/array_api/_onnx_common.py +++ b/onnx_array_api/array_api/_onnx_common.py @@ -44,7 +44,7 @@ def template_asarray( else: raise RuntimeError(f"Unexpected type {type(a)} for the first input.") if dtype is not None: - vt = v.astype(dtype=dtype) + vt = v.astype(dtype) else: vt = v return vt diff --git a/onnx_array_api/npx/npx_jit_eager.py b/onnx_array_api/npx/npx_jit_eager.py index a4c4435..b868602 100644 --- a/onnx_array_api/npx/npx_jit_eager.py +++ b/onnx_array_api/npx/npx_jit_eager.py @@ -131,7 +131,7 @@ def make_key(*values, **kwargs): for iv, v in enumerate(values): if isinstance(v, (Var, EagerTensor, JitTensor)): res.append(v.key) - elif isinstance(v, (int, float)): + elif isinstance(v, (int, float, DType)): res.append(v) elif isinstance(v, slice): res.append(("slice", v.start, v.stop, v.step)) @@ -344,7 +344,11 @@ def jit_call(self, *values, **kwargs): self.info("+", "jit_call") if self.input_to_kwargs_ is None: # No jitting was ever called. - onx, fct = self.to_jit(*values, **kwargs) + try: + onx, fct = self.to_jit(*values, **kwargs) + except Exception as e: + raise RuntimeError(f"ERROR with self.f={self.f}, " + f"values={values!r}, kwargs={kwargs!r}") from e if self.input_to_kwargs_ is None: raise RuntimeError( f"Attribute 'input_to_kwargs_' should be set for " diff --git a/onnx_array_api/npx/npx_tensors.py b/onnx_array_api/npx/npx_tensors.py index a3aa136..64904ed 100644 --- a/onnx_array_api/npx/npx_tensors.py +++ b/onnx_array_api/npx/npx_tensors.py @@ -1,9 +1,9 @@ -from typing import Any +from typing import Any, Union import numpy as np from onnx.helper import np_dtype_to_tensor_dtype -from .npx_types import DType, OptParType +from .npx_types import DType, ParType from .npx_array_api import BaseArrayApi, ArrayApiError @@ -74,7 +74,7 @@ def _getitem_impl_var(obj, index, method_name=None): return meth(obj, index) @staticmethod - def _astype_impl(x, dtype: OptParType[DType] = None, method_name=None): + def _astype_impl(x, dtype: ParType[DType], method_name=None): # avoids circular imports. if dtype is None: raise ValueError("dtype cannot be None.") @@ -182,18 +182,15 @@ def _np_dtype_to_tensor_dtype(dtype): dtype = np.dtype("float64") return np_dtype_to_tensor_dtype(dtype) - def _generic_method_astype(self, method_name, *args: Any, **kwargs: Any) -> Any: + def _generic_method_astype(self, method_name, dtype: Union[DType, "Var"], **kwargs: Any) -> Any: # avoids circular imports. from .npx_jit_eager import eager_onnx from .npx_var import Var - if len(args) != 1: - raise ValueError(f"astype takes only one argument not {len(args)}.") - dtype = ( - args[0] - if isinstance(args[0], (DType, Var)) - else self._np_dtype_to_tensor_dtype(args[0]) + dtype + if isinstance(dtype, (DType, Var)) + else self._np_dtype_to_tensor_dtype(dtype) ) eag = eager_onnx(EagerTensor._astype_impl, self.__class__, bypass_eager=True) res = eag(self, dtype, method_name=method_name, already_eager=True, **kwargs) From 0169b85c6d603b08da824c4f03c851c5e7d42f34 Mon Sep 17 00:00:00 2001 From: Xavier Dupre Date: Fri, 9 Jun 2023 01:47:29 +0200 Subject: [PATCH 22/26] fix two bugs --- onnx_array_api/npx/npx_jit_eager.py | 8 +++++--- onnx_array_api/npx/npx_tensors.py | 10 +++++++--- pyproject.toml | 1 + 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/onnx_array_api/npx/npx_jit_eager.py b/onnx_array_api/npx/npx_jit_eager.py index b868602..85b52d4 100644 --- a/onnx_array_api/npx/npx_jit_eager.py +++ b/onnx_array_api/npx/npx_jit_eager.py @@ -258,7 +258,7 @@ def to_jit(self, *values, **kwargs): kwargs = new_kwargs else: kwargs = kwargs.copy() - kwargs.update(kwargs) + kwargs.update(new_kwargs) var = self.f(*inputs, **kwargs) @@ -347,8 +347,10 @@ def jit_call(self, *values, **kwargs): try: onx, fct = self.to_jit(*values, **kwargs) except Exception as e: - raise RuntimeError(f"ERROR with self.f={self.f}, " - f"values={values!r}, kwargs={kwargs!r}") from e + raise RuntimeError( + f"ERROR with self.f={self.f}, " + f"values={values!r}, kwargs={kwargs!r}" + ) from e if self.input_to_kwargs_ is None: raise RuntimeError( f"Attribute 'input_to_kwargs_' should be set for " diff --git a/onnx_array_api/npx/npx_tensors.py b/onnx_array_api/npx/npx_tensors.py index 64904ed..e1e4b21 100644 --- a/onnx_array_api/npx/npx_tensors.py +++ b/onnx_array_api/npx/npx_tensors.py @@ -3,7 +3,7 @@ import numpy as np from onnx.helper import np_dtype_to_tensor_dtype -from .npx_types import DType, ParType +from .npx_types import DType, ElemType, ParType, TensorType from .npx_array_api import BaseArrayApi, ArrayApiError @@ -74,7 +74,9 @@ def _getitem_impl_var(obj, index, method_name=None): return meth(obj, index) @staticmethod - def _astype_impl(x, dtype: ParType[DType], method_name=None): + def _astype_impl( + x: TensorType[ElemType.allowed, "T1"], dtype: ParType[DType], method_name=None + ) -> TensorType[ElemType.allowed, "T2"]: # avoids circular imports. if dtype is None: raise ValueError("dtype cannot be None.") @@ -182,7 +184,9 @@ def _np_dtype_to_tensor_dtype(dtype): dtype = np.dtype("float64") return np_dtype_to_tensor_dtype(dtype) - def _generic_method_astype(self, method_name, dtype: Union[DType, "Var"], **kwargs: Any) -> Any: + def _generic_method_astype( + self, method_name, dtype: Union[DType, "Var"], **kwargs: Any + ) -> Any: # avoids circular imports. from .npx_jit_eager import eager_onnx from .npx_var import Var diff --git a/pyproject.toml b/pyproject.toml index 7c3e233..9ef84cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,7 @@ max-complexity = 10 "onnx_array_api/npx/__init__.py" = ["F401", "F403"] "onnx_array_api/npx/npx_functions.py" = ["F821"] "onnx_array_api/npx/npx_functions_test.py" = ["F821"] +"onnx_array_api/npx/npx_tensors.py" = ["F821"] "onnx_array_api/npx/npx_var.py" = ["F821"] "onnx_array_api/profiling.py" = ["E731"] "_unittests/ut_npx/test_npx.py" = ["F821"] From cb11e55e305145bd109570e28d69c11d858be33a Mon Sep 17 00:00:00 2001 From: Xavier Dupre Date: Fri, 9 Jun 2023 11:09:14 +0200 Subject: [PATCH 23/26] Add one unit test for empty input --- _unittests/ut_npx/test_npx.py | 13 ++++++++++++- onnx_array_api/npx/npx_functions.py | 10 +++++++++- onnx_array_api/npx/npx_types.py | 10 ++++++---- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/_unittests/ut_npx/test_npx.py b/_unittests/ut_npx/test_npx.py index d12820c..6069acc 100644 --- a/_unittests/ut_npx/test_npx.py +++ b/_unittests/ut_npx/test_npx.py @@ -2492,7 +2492,18 @@ def test_numpy_all(self): got = ref.run(None, {"A": data}) self.assertEqualArray(y, got[0]) + def test_numpy_all_empty(self): + data = np.zeros((0, 1), dtype=np.bool_) + y = np.all(data) + + f = all_inline(Input("A"), axis=1) + self.assertIsInstance(f, Var) + onx = f.to_onnx(constraints={"A": Bool[None]}) + ref = ReferenceEvaluator(onx) + got = ref.run(None, {"A": data}) + self.assertEqualArray(y, got[0]) + if __name__ == "__main__": - TestNpx().test_identity() + TestNpx().test_numpy_all_empty() unittest.main(verbosity=2) diff --git a/onnx_array_api/npx/npx_functions.py b/onnx_array_api/npx/npx_functions.py index 98cdf4e..d0fd271 100644 --- a/onnx_array_api/npx/npx_functions.py +++ b/onnx_array_api/npx/npx_functions.py @@ -48,7 +48,15 @@ def all( axis: Optional[TensorType[ElemType.int64, "I"]] = None, keepdims: ParType[int] = 0, ) -> TensorType[ElemType.bool_, "T"]: - "See :func:`numpy.all`." + """ + See :func:`numpy.all`. + If input x is empty, the answer is True. + """ + # size = var(x, op="Size") + # empty = var(size, cst(np.array(0, dtype=np.int64)), op="Equal") + + # z = make_tensor_value_info("Z", TensorProto.BOOL, [1]) + # g1 = make_graph([make_node("Constant", [], ["Z"], value_bool=[True])], [], [z]) xi = var(x, op="Cast", to=TensorProto.INT64) diff --git a/onnx_array_api/npx/npx_types.py b/onnx_array_api/npx/npx_types.py index 7a96124..aa335bd 100644 --- a/onnx_array_api/npx/npx_types.py +++ b/onnx_array_api/npx/npx_types.py @@ -1,7 +1,7 @@ from typing import Any, Tuple, Union import numpy as np -from onnx import AttributeProto +from onnx import AttributeProto, TensorProto from onnx.helper import np_dtype_to_tensor_dtype, tensor_dtype_to_np_dtype @@ -49,10 +49,12 @@ def __eq__(self, dt: "DType") -> bool: "Compares two types." if dt.__class__ is DType: return self.code_ == dt.code_ - if isinstance(dt, int): - raise TypeError(f"dt must be DType not {type(dt)}.") - if isinstance(dt, str): + if isinstance(dt, (int, bool, str)): return False + if dt is str: + return self.code_ == TensorProto.STRING + if dt is bool: + return self.code_ == TensorProto.BOOL if dt in ElemType.numpy_map: dti = ElemType.numpy_map[dt] return self.code_ == dti.code_ From caa99a7328745029463e7720515333ca64e24e9c Mon Sep 17 00:00:00 2001 From: Xavier Dupre Date: Sat, 10 Jun 2023 10:22:49 +0200 Subject: [PATCH 24/26] fix all when shape is empty and has one dimension --- _unittests/ut_npx/test_npx.py | 36 ++++++++++++++++- onnx_array_api/npx/npx_functions.py | 7 +++- onnx_array_api/npx/npx_numpy_tensors.py | 3 +- onnx_array_api/npx/npx_numpy_tensors_ops.py | 45 +++++++++++++++++++++ 4 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 onnx_array_api/npx/npx_numpy_tensors_ops.py diff --git a/_unittests/ut_npx/test_npx.py b/_unittests/ut_npx/test_npx.py index 6069acc..c9ee35f 100644 --- a/_unittests/ut_npx/test_npx.py +++ b/_unittests/ut_npx/test_npx.py @@ -1489,6 +1489,15 @@ def test_identity(self): got = ref.run(None, {}) self.assertEqualArray(z, got[0]) + def test_identity_uint8(self): + f = identity_inline(2, dtype=np.uint8) + onx = f.to_onnx(constraints={(0, False): Float64[None]}) + self.assertIn('name: "dtype"', str(onx)) + z = np.identity(2).astype(np.uint8) + ref = ReferenceEvaluator(onx) + got = ref.run(None, {}) + self.assertEqualArray(z, got[0]) + def test_isnan(self): self.common_test_inline(isnan_inline, np.isnan) @@ -2493,9 +2502,32 @@ def test_numpy_all(self): self.assertEqualArray(y, got[0]) def test_numpy_all_empty(self): - data = np.zeros((0, 1), dtype=np.bool_) + data = np.zeros((0,), dtype=np.bool_) y = np.all(data) + f = all_inline(Input("A")) + self.assertIsInstance(f, Var) + onx = f.to_onnx(constraints={"A": Bool[None]}) + ref = ReferenceEvaluator(onx) + got = ref.run(None, {"A": data}) + self.assertEqualArray(y, got[0]) + + @unittest.skipIf(True, reason="ReduceMin does not support shape[axis] == 0") + def test_numpy_all_empty_axis_0(self): + data = np.zeros((0, 1), dtype=np.bool_) + y = np.all(data, axis=0) + + f = all_inline(Input("A"), axis=0) + self.assertIsInstance(f, Var) + onx = f.to_onnx(constraints={"A": Bool[None]}) + ref = ReferenceEvaluator(onx) + got = ref.run(None, {"A": data}) + self.assertEqualArray(y, got[0]) + + def test_numpy_all_empty_axis_1(self): + data = np.zeros((0, 1), dtype=np.bool_) + y = np.all(data, axis=1) + f = all_inline(Input("A"), axis=1) self.assertIsInstance(f, Var) onx = f.to_onnx(constraints={"A": Bool[None]}) @@ -2505,5 +2537,5 @@ def test_numpy_all_empty(self): if __name__ == "__main__": - TestNpx().test_numpy_all_empty() + # TestNpx().test_numpy_all_empty_axis_0() unittest.main(verbosity=2) diff --git a/onnx_array_api/npx/npx_functions.py b/onnx_array_api/npx/npx_functions.py index d0fd271..b55cf4d 100644 --- a/onnx_array_api/npx/npx_functions.py +++ b/onnx_array_api/npx/npx_functions.py @@ -61,7 +61,12 @@ def all( xi = var(x, op="Cast", to=TensorProto.INT64) if axis is None: - red = xi.min(keepdims=keepdims) + new_shape = cst(np.array([-1], dtype=np.int64)) + xifl = var(xi, new_shape, op="Reshape") + # in case xifl is empty, we need to add one element + one = cst(np.array([1], dtype=np.int64)) + xifl1 = var(xifl, one, op="Concat", axis=0) + red = xifl1.min(keepdims=keepdims) else: if isinstance(axis, int): axis = [axis] diff --git a/onnx_array_api/npx/npx_numpy_tensors.py b/onnx_array_api/npx/npx_numpy_tensors.py index e6353ea..e1a0c10 100644 --- a/onnx_array_api/npx/npx_numpy_tensors.py +++ b/onnx_array_api/npx/npx_numpy_tensors.py @@ -5,6 +5,7 @@ from onnx.helper import np_dtype_to_tensor_dtype from onnx.reference import ReferenceEvaluator +from .npx_numpy_tensors_ops import ConstantOfShape from .npx_tensors import EagerTensor, JitTensor from .npx_types import DType, TensorType @@ -25,7 +26,7 @@ class Evaluator: """ def __init__(self, tensor_class: type, input_names: List[str], onx: ModelProto): - self.ref = ReferenceEvaluator(onx) + self.ref = ReferenceEvaluator(onx, new_ops=[ConstantOfShape]) self.input_names = input_names self.tensor_class = tensor_class diff --git a/onnx_array_api/npx/npx_numpy_tensors_ops.py b/onnx_array_api/npx/npx_numpy_tensors_ops.py new file mode 100644 index 0000000..dbd5241 --- /dev/null +++ b/onnx_array_api/npx/npx_numpy_tensors_ops.py @@ -0,0 +1,45 @@ +import numpy as np + +from onnx.reference.op_run import OpRun + + +class ConstantOfShape(OpRun): + @staticmethod + def _process(value): + cst = value[0] if isinstance(value, np.ndarray) else value + if isinstance(cst, int): + cst = np.int64(cst) + elif isinstance(cst, float): + cst = np.float64(cst) + elif cst is None: + cst = np.float32(0) + if not isinstance( + cst, + ( + np.float16, + np.float32, + np.float64, + np.int64, + np.int32, + np.int16, + np.int8, + np.uint64, + np.uint32, + np.uint16, + np.uint8, + np.bool_, + ), + ): + raise TypeError(f"value must be a real not {type(cst)}") + + def _run(self, data, value=None): + cst = self._process(value) + try: + res = np.full(tuple(data), cst) + except TypeError as e: + raise RuntimeError( + f"Unable to create a constant of shape " + f"{data!r} with value {cst!r} " + f"(raw value={value!r})." + ) from e + return (res,) From 8945d16f3189c39e3e34d720e778c037fd1a23e7 Mon Sep 17 00:00:00 2001 From: Xavier Dupre Date: Sat, 10 Jun 2023 10:53:24 +0200 Subject: [PATCH 25/26] fix missing return --- _unittests/ut_array_api/test_onnx_numpy.py | 3 +++ azure-pipelines.yml | 12 ++++++------ onnx_array_api/npx/npx_numpy_tensors_ops.py | 1 + 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/_unittests/ut_array_api/test_onnx_numpy.py b/_unittests/ut_array_api/test_onnx_numpy.py index 2304ffe..30e2ca2 100644 --- a/_unittests/ut_array_api/test_onnx_numpy.py +++ b/_unittests/ut_array_api/test_onnx_numpy.py @@ -9,6 +9,9 @@ class TestOnnxNumpy(ExtTestCase): def test_abs(self): c = EagerNumpyTensor(np.array([4, 5], dtype=np.int64)) mat = xp.zeros(c, dtype=xp.int64) + matnp = mat.numpy() + self.assertEqual(matnp.shape, (4, 5)) + self.assertNotEmpty(matnp[0, 0]) a = xp.absolute(mat) self.assertEqualArray(np.absolute(mat.numpy()), a.numpy()) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 74e6ef8..ac11cd8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -36,7 +36,7 @@ jobs: python -m pip install . -v -v -v displayName: 'install wheel' - script: | - python -m pytest -v + python -m pytest displayName: 'Runs Unit Tests' - task: PublishPipelineArtifact@0 inputs: @@ -87,7 +87,7 @@ jobs: black --diff . displayName: 'Black' - script: | - python -m pytest -v + python -m pytest displayName: 'Runs Unit Tests' - job: 'TestLinuxArrayApi' @@ -129,7 +129,7 @@ jobs: - script: | export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy cd array-api-tests - python -m pytest -xv array_api_tests/test_creation_functions.py::test_zeros + python -m pytest -x array_api_tests/test_creation_functions.py::test_zeros displayName: "test_creation_functions.py::test_zeros" - script: | export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy @@ -177,7 +177,7 @@ jobs: black --diff . displayName: 'Black' - script: | - python -m pytest -v + python -m pytest displayName: 'Runs Unit Tests' - script: | python -u setup.py bdist_wheel @@ -213,7 +213,7 @@ jobs: - script: pip install onnxmltools --no-deps displayName: 'Install onnxmltools' - script: | - python -m pytest -v + python -m pytest displayName: 'Runs Unit Tests' - script: | python -u setup.py bdist_wheel @@ -263,7 +263,7 @@ jobs: - script: pip install onnxmltools --no-deps displayName: 'Install onnxmltools' - script: | - python -m pytest -v -v + python -m pytest displayName: 'Runs Unit Tests' - script: | python -u setup.py bdist_wheel diff --git a/onnx_array_api/npx/npx_numpy_tensors_ops.py b/onnx_array_api/npx/npx_numpy_tensors_ops.py index dbd5241..5278019 100644 --- a/onnx_array_api/npx/npx_numpy_tensors_ops.py +++ b/onnx_array_api/npx/npx_numpy_tensors_ops.py @@ -31,6 +31,7 @@ def _process(value): ), ): raise TypeError(f"value must be a real not {type(cst)}") + return cst def _run(self, data, value=None): cst = self._process(value) From 7a979a2ad24093755fa0ee1aae213bef339dd120 Mon Sep 17 00:00:00 2001 From: Xavier Dupre Date: Sat, 10 Jun 2023 10:56:57 +0200 Subject: [PATCH 26/26] remove the full tests --- azure-pipelines.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ac11cd8..defe983 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -131,11 +131,11 @@ jobs: cd array-api-tests python -m pytest -x array_api_tests/test_creation_functions.py::test_zeros displayName: "test_creation_functions.py::test_zeros" - - script: | - export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy - cd array-api-tests - python -m pytest -x array_api_tests - displayName: "all tests" + #- script: | + # export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy + # cd array-api-tests + # python -m pytest -x array_api_tests + # displayName: "all tests" - job: 'TestLinux' pool: