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: pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy