Skip to content

Extends Array API to EagerOrt #18

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 9 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ _cache/*
dist/*
build/*
.eggs/*
.hypothesis/*
*egg-info/*
_doc/auto_examples/*
_doc/examples/_cache/*
Expand Down
13 changes: 13 additions & 0 deletions _unittests/onnx-numpy-skips.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# API failures
array_api_tests/test_creation_functions.py::test_arange
array_api_tests/test_creation_functions.py::test_asarray_scalars
array_api_tests/test_creation_functions.py::test_asarray_arrays
array_api_tests/test_creation_functions.py::test_empty
array_api_tests/test_creation_functions.py::test_empty_like
array_api_tests/test_creation_functions.py::test_eye
array_api_tests/test_creation_functions.py::test_full
array_api_tests/test_creation_functions.py::test_full_like
array_api_tests/test_creation_functions.py::test_linspace
array_api_tests/test_creation_functions.py::test_meshgrid
array_api_tests/test_creation_functions.py::test_ones_like
array_api_tests/test_creation_functions.py::test_zeros_like
15 changes: 15 additions & 0 deletions _unittests/onnx-ort-skips.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Not implementated by onnxruntime
array_api_tests/test_creation_functions.py::test_arange
array_api_tests/test_creation_functions.py::test_asarray_scalars
array_api_tests/test_creation_functions.py::test_asarray_arrays
array_api_tests/test_creation_functions.py::test_empty
array_api_tests/test_creation_functions.py::test_empty_like
array_api_tests/test_creation_functions.py::test_eye
array_api_tests/test_creation_functions.py::test_full
array_api_tests/test_creation_functions.py::test_full_like
array_api_tests/test_creation_functions.py::test_linspace
array_api_tests/test_creation_functions.py::test_meshgrid
array_api_tests/test_creation_functions.py::test_ones
array_api_tests/test_creation_functions.py::test_ones_like
array_api_tests/test_creation_functions.py::test_zeros
array_api_tests/test_creation_functions.py::test_zeros_like
112 changes: 112 additions & 0 deletions _unittests/ut_array_api/test_array_apis.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import unittest
from inspect import isfunction, ismethod
import numpy as np
from onnx_array_api.ext_test_case import ExtTestCase
from onnx_array_api.array_api import onnx_numpy as xpn
from onnx_array_api.array_api import onnx_ort as xpo

# from onnx_array_api.npx.npx_numpy_tensors import EagerNumpyTensor
# from onnx_array_api.ort.ort_tensors import EagerOrtTensor


class TestArraysApis(ExtTestCase):
def test_zeros_numpy_1(self):
c = xpn.zeros(1)
d = c.numpy()
self.assertEqualArray(np.array([0], dtype=np.float32), d)

def test_zeros_ort_1(self):
c = xpo.zeros(1)
d = c.numpy()
self.assertEqualArray(np.array([0], dtype=np.float32), d)

def test_ffinfo(self):
dt = np.float32
fi1 = np.finfo(dt)
fi2 = xpn.finfo(dt)
fi3 = xpo.finfo(dt)
dt1 = fi1.dtype
dt2 = fi2.dtype
dt3 = fi3.dtype
self.assertEqual(dt2, dt3)
self.assertNotEqual(dt1.__class__, dt2.__class__)
mi1 = fi1.min
mi2 = fi2.min
self.assertEqual(mi1, mi2)
mi1 = fi1.smallest_normal
mi2 = fi2.smallest_normal
self.assertEqual(mi1, mi2)
for n in dir(fi1):
if n.startswith("__"):
continue
if n in {"machar"}:
continue
v1 = getattr(fi1, n)
with self.subTest(att=n):
v2 = getattr(fi2, n)
v3 = getattr(fi3, n)
if isfunction(v1) or ismethod(v1):
try:
v1 = v1()
except TypeError:
continue
v2 = v2()
v3 = v3()
if v1 != v2:
raise AssertionError(
f"12: info disagree on name {n!r}: {v1} != {v2}, "
f"type(v1)={type(v1)}, type(v2)={type(v2)}, "
f"ismethod={ismethod(v1)}."
)
if v2 != v3:
raise AssertionError(
f"23: info disagree on name {n!r}: {v2} != {v3}, "
f"type(v1)={type(v1)}, type(v2)={type(v2)}, "
f"ismethod={ismethod(v1)}."
)

def test_iiinfo(self):
dt = np.int64
fi1 = np.iinfo(dt)
fi2 = xpn.iinfo(dt)
fi3 = xpo.iinfo(dt)
dt1 = fi1.dtype
dt2 = fi2.dtype
dt3 = fi3.dtype
self.assertEqual(dt2, dt3)
self.assertNotEqual(dt1.__class__, dt2.__class__)
mi1 = fi1.min
mi2 = fi2.min
self.assertEqual(mi1, mi2)
for n in dir(fi1):
if n.startswith("__"):
continue
if n in {"machar"}:
continue
v1 = getattr(fi1, n)
with self.subTest(att=n):
v2 = getattr(fi2, n)
v3 = getattr(fi3, n)
if isfunction(v1) or ismethod(v1):
try:
v1 = v1()
except TypeError:
continue
v2 = v2()
v3 = v3()
if v1 != v2:
raise AssertionError(
f"12: info disagree on name {n!r}: {v1} != {v2}, "
f"type(v1)={type(v1)}, type(v2)={type(v2)}, "
f"ismethod={ismethod(v1)}."
)
if v2 != v3:
raise AssertionError(
f"23: info disagree on name {n!r}: {v2} != {v3}, "
f"type(v1)={type(v1)}, type(v2)={type(v2)}, "
f"ismethod={ismethod(v1)}."
)


if __name__ == "__main__":
unittest.main(verbosity=2)
4 changes: 2 additions & 2 deletions _unittests/ut_array_api/test_onnx_numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
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
from onnx_array_api.npx.npx_numpy_tensors import EagerNumpyTensor as EagerTensor


class TestOnnxNumpy(ExtTestCase):
def test_abs(self):
c = EagerNumpyTensor(np.array([4, 5], dtype=np.int64))
c = EagerTensor(np.array([4, 5], dtype=np.int64))
mat = xp.zeros(c, dtype=xp.int64)
matnp = mat.numpy()
self.assertEqual(matnp.shape, (4, 5))
Expand Down
20 changes: 20 additions & 0 deletions _unittests/ut_array_api/test_onnx_ort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import unittest
import numpy as np
from onnx_array_api.ext_test_case import ExtTestCase
from onnx_array_api.array_api import onnx_ort as xp
from onnx_array_api.ort.ort_tensors import EagerOrtTensor as EagerTensor


class TestOnnxOrt(ExtTestCase):
def test_abs(self):
c = EagerTensor(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())


if __name__ == "__main__":
unittest.main(verbosity=2)
48 changes: 45 additions & 3 deletions _unittests/ut_ort/test_ort_tensor.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
import unittest
from contextlib import redirect_stdout
from io import StringIO

import numpy as np
from onnx.defs import onnx_opset_version
from onnx.reference import ReferenceEvaluator
from onnxruntime import InferenceSession

from onnx_array_api.ext_test_case import ExtTestCase
from onnx_array_api.npx import eager_onnx, jit_onnx
from onnx_array_api.npx.npx_functions import absolute as absolute_inline
from onnx_array_api.npx.npx_functions import cdist as cdist_inline
from onnx_array_api.npx.npx_functions_test import absolute
from onnx_array_api.npx.npx_types import Float32, Float64
from onnx_array_api.npx.npx_functions import copy as copy_inline
from onnx_array_api.npx.npx_types import Float32, Float64, DType
from onnx_array_api.npx.npx_var import Input
from onnx_array_api.ort.ort_tensors import EagerOrtTensor, JitOrtTensor, OrtTensor

Expand Down Expand Up @@ -193,6 +192,49 @@ def impl(xa, xb):
if len(pieces) > 2:
raise AssertionError(f"Function is not using argument:\n{onx}")

def test_astype(self):
f = absolute_inline(copy_inline(Input("A")).astype(np.float32))
onx = f.to_onnx(constraints={"A": Float64[None]})
x = np.array([[-5, 6]], dtype=np.float64)
z = np.abs(x.astype(np.float32))
ref = InferenceSession(
onx.SerializeToString(), providers=["CPUExecutionProvider"]
)
got = ref.run(None, {"A": x})
self.assertEqualArray(z, got[0])

def test_astype0(self):
f = absolute_inline(copy_inline(Input("A")).astype(np.float32))
onx = f.to_onnx(constraints={"A": Float64[None]})
x = np.array(-5, dtype=np.float64)
z = np.abs(x.astype(np.float32))
ref = InferenceSession(
onx.SerializeToString(), providers=["CPUExecutionProvider"]
)
got = ref.run(None, {"A": x})
self.assertEqualArray(z, got[0])

def test_eager_ort_cast(self):
def impl(A):
return A.astype(DType("FLOAT"))

e = eager_onnx(impl)
self.assertEqual(len(e.versions), 0)

# Float64
x = np.array([0, 1, -2], dtype=np.float64)
z = x.astype(np.float32)
res = e(x)
self.assertEqualArray(z, res)
self.assertEqual(res.dtype, np.float32)

# again
x = np.array(1, dtype=np.float64)
z = x.astype(np.float32)
res = e(x)
self.assertEqualArray(z, res)
self.assertEqual(res.dtype, np.float32)


if __name__ == "__main__":
# TestNpx().test_eager_numpy()
Expand Down
19 changes: 9 additions & 10 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ jobs:
displayName: 'Install tools'
- script: pip install -r requirements.txt
displayName: 'Install Requirements'
- script: pip install onnxruntime
displayName: 'Install onnxruntime'
- script: python setup.py install
displayName: 'Install onnx_array_api'
- script: |
Expand All @@ -129,8 +131,13 @@ jobs:
- script: |
export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy
cd array-api-tests
python -m pytest -x array_api_tests/test_creation_functions.py::test_zeros
displayName: "test_creation_functions.py::test_zeros"
python -m pytest -x array_api_tests/test_creation_functions.py --skips-file=../_unittests/onnx-numpy-skips.txt -v
displayName: "numpy test_creation_functions.py"
- script: |
export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_ort
cd array-api-tests
python -m pytest -x array_api_tests/test_creation_functions.py --skips-file=../_unittests/onnx-ort-skips.txt -v
displayName: "ort test_creation_functions.py"
#- script: |
# export ARRAY_API_TESTS_MODULE=onnx_array_api.array_api.onnx_numpy
# cd array-api-tests
Expand Down Expand Up @@ -246,16 +253,8 @@ jobs:
displayName: 'export'
- script: gcc --version
displayName: 'gcc version'
- script: brew install llvm
displayName: 'install llvm'
- script: brew install libomp
displayName: 'Install omp'
- script: brew install p7zip
displayName: 'Install p7zip'
- script: python -m pip install --upgrade pip setuptools wheel
displayName: 'Install tools'
- script: brew install pybind11
displayName: 'Install pybind11'
- script: pip install -r requirements.txt
displayName: 'Install Requirements'
- script: pip install -r requirements-dev.txt
Expand Down
45 changes: 45 additions & 0 deletions onnx_array_api/_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import numpy as np
from typing import Any
from onnx import helper, TensorProto


def np_dtype_to_tensor_dtype(dtype: Any):
"""
Improves :func:`onnx.helper.np_dtype_to_tensor_dtype`.
"""
try:
dt = helper.np_dtype_to_tensor_dtype(dtype)
except KeyError:
if dtype == np.float32:
dt = TensorProto.FLOAT
elif dtype == np.float64:
dt = TensorProto.DOUBLE
elif dtype == np.int64:
dt = TensorProto.INT64
elif dtype == np.int32:
dt = TensorProto.INT32
elif dtype == np.int16:
dt = TensorProto.INT16
elif dtype == np.int8:
dt = TensorProto.INT8
elif dtype == np.uint64:
dt = TensorProto.UINT64
elif dtype == np.uint32:
dt = TensorProto.UINT32
elif dtype == np.uint16:
dt = TensorProto.UINT16
elif dtype == np.uint8:
dt = TensorProto.UINT8
elif dtype == np.float16:
dt = TensorProto.FLOAT16
elif dtype in (bool, np.bool_):
dt = TensorProto.BOOL
elif dtype in (str, np.str_):
dt = TensorProto.STRING
elif dtype is int:
dt = TensorProto.INT64
elif dtype is float:
dt = TensorProto.FLOAT64
else:
raise KeyError(f"Unable to guess type for dtype={dtype}.")
return dt
36 changes: 36 additions & 0 deletions onnx_array_api/array_api/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,42 @@
import numpy as np
from onnx import TensorProto
from .._helpers import np_dtype_to_tensor_dtype
from ..npx.npx_types import DType


def _finfo(dtype):
"""
Similar to :class:`numpy.finfo`.
"""
dt = dtype.np_dtype if isinstance(dtype, DType) else dtype
res = np.finfo(dt)
d = res.__dict__.copy()
d["dtype"] = DType(np_dtype_to_tensor_dtype(dt))
nres = type("finfo", (res.__class__,), d)
setattr(nres, "smallest_normal", res.smallest_normal)
setattr(nres, "tiny", res.tiny)
return nres


def _iinfo(dtype):
"""
Similar to :class:`numpy.finfo`.
"""
dt = dtype.np_dtype if isinstance(dtype, DType) else dtype
res = np.iinfo(dt)
d = res.__dict__.copy()
d["dtype"] = DType(np_dtype_to_tensor_dtype(dt))
nres = type("finfo", (res.__class__,), d)
setattr(nres, "min", res.min)
setattr(nres, "max", res.max)
return nres


def _finalize_array_api(module):
"""
Adds common attributes to Array API defined in this modules
such as types.
"""
module.float16 = DType(TensorProto.FLOAT16)
module.float32 = DType(TensorProto.FLOAT)
module.float64 = DType(TensorProto.DOUBLE)
Expand All @@ -17,3 +51,5 @@ def _finalize_array_api(module):
module.bfloat16 = DType(TensorProto.BFLOAT16)
setattr(module, "bool", DType(TensorProto.BOOL))
setattr(module, "str", DType(TensorProto.STRING))
setattr(module, "finfo", _finfo)
setattr(module, "iinfo", _iinfo)
Loading