From b21576eeb0d8ca59b70ba39dee93eb6149f84195 Mon Sep 17 00:00:00 2001 From: Hariharan Seshadri Date: Mon, 20 Jan 2020 05:15:10 +0530 Subject: [PATCH] Support non-sequence tensor fed through as a python list (#2782) * Support list feeds in Python --- .../python/onnxruntime_pybind_mlvalue.cc | 48 ++++++++++++++++--- .../test/python/onnxruntime_test_python.py | 17 +++++++ 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/onnxruntime/python/onnxruntime_pybind_mlvalue.cc b/onnxruntime/python/onnxruntime_pybind_mlvalue.cc index e475d32376..ce139e4d71 100644 --- a/onnxruntime/python/onnxruntime_pybind_mlvalue.cc +++ b/onnxruntime/python/onnxruntime_pybind_mlvalue.cc @@ -203,8 +203,9 @@ std::unique_ptr CreateTensor(AllocatorPtr alloc, const std::string& name return p_tensor; } -void CreateSequenceOfTensors(AllocatorPtr alloc, const std::string& name_input, - const InputDefList* input_def_list, PyObject* pylist_obj, OrtValue* p_mlvalue) { +bool CheckIfInputIsSequenceType(const std::string& name_input, + const InputDefList* input_def_list, + /*out*/ onnx::TypeProto& type_proto) { // get sequence type from the model const auto& def_list = *input_def_list; auto ret_it = std::find_if(std::begin(def_list), std::end(def_list), @@ -212,9 +213,21 @@ void CreateSequenceOfTensors(AllocatorPtr alloc, const std::string& name_input, if (ret_it == std::end(def_list)) { throw std::runtime_error("Failed to find input with name: " + name_input + " in the model input def list"); } - const auto* type_proto = (*ret_it)->TypeAsProto(); - if (!type_proto || !(type_proto->has_sequence_type())) { - throw std::runtime_error("Either type_proto was null or it was not of sequence type"); + const auto* temp = (*ret_it)->TypeAsProto(); + if (!temp) { + throw std::runtime_error("Corresponding type_proto is null"); + } else { + type_proto = *temp; + } + + return type_proto.has_sequence_type(); +} + +void CreateSequenceOfTensors(AllocatorPtr alloc, const std::string& name_input, + const InputDefList* input_def_list, PyObject* pylist_obj, OrtValue* p_mlvalue) { + onnx::TypeProto type_proto; + if (!CheckIfInputIsSequenceType(name_input, input_def_list, type_proto)) { + throw std::runtime_error("Input is not of sequence type"); } // populate the seq @@ -232,9 +245,9 @@ void CreateSequenceOfTensors(AllocatorPtr alloc, const std::string& name_input, } } - // set the seq type + // set the seq type MLDataType seq_dtype = OrtTypeInfo::ElementTypeFromProto( - static_cast(type_proto->sequence_type().elem_type().tensor_type().elem_type())); + static_cast(type_proto.sequence_type().elem_type().tensor_type().elem_type())); auto p_seq_tensors = onnxruntime::make_unique(seq_dtype); p_seq_tensors->SetElements(std::move(tensors)); auto ml_tensor_sequence = DataTypeImpl::GetType(); @@ -460,9 +473,30 @@ void CreateGenericIterableMLValue(PyObject* iterator, AllocatorPtr alloc, const void CreateGenericMLValue(const onnxruntime::InputDefList* input_def_list, AllocatorPtr alloc, const std::string& name_input, py::object& value, OrtValue* p_mlvalue) { + onnx::TypeProto type_proto; if (PyObjectCheck_Array(value.ptr())) { // The most frequent case: input comes as an array. PyArrayObject* arr = reinterpret_cast(value.ptr()); + CreateTensorMLValue(alloc, name_input, arr, p_mlvalue); + } else if (PyList_Check(value.ptr()) && + !CheckIfInputIsSequenceType(name_input, input_def_list, type_proto)) { + // This is not a sequence tensor. This is just a regular tensor fed through as a list. + if (!type_proto.tensor_type().has_elem_type()) { + std::runtime_error("The graph is missing type information needed to construct the ORT tensor"); + } + + MLDataType dtype = OrtTypeInfo::ElementTypeFromProto( + static_cast(type_proto.tensor_type().elem_type())); + + int numpy_dtype = OnnxRuntimeTensorToNumpyType(dtype); + + PyArrayObject* arr = reinterpret_cast( + PyArray_FromAny(value.ptr(), PyArray_DescrFromType(numpy_dtype), 0, 0, 0, nullptr)); + + if (!arr) { + throw std::runtime_error("Could not create tensor from given input list"); + } + CreateTensorMLValue(alloc, name_input, arr, p_mlvalue); } else if (PyList_Check(value.ptr())) { auto* seq_tensors = reinterpret_cast(value.ptr()); diff --git a/onnxruntime/test/python/onnxruntime_test_python.py b/onnxruntime/test/python/onnxruntime_test_python.py index 21b0fc8a8c..6c36aa8c9c 100644 --- a/onnxruntime/test/python/onnxruntime_test_python.py +++ b/onnxruntime/test/python/onnxruntime_test_python.py @@ -162,6 +162,23 @@ class TestInferenceSession(unittest.TestCase): t1.join() t2.join() + def testListAsInput(self): + sess = onnxrt.InferenceSession(self.get_name("mul_1.onnx")) + x = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]], dtype=np.float32) + input_name = sess.get_inputs()[0].name + res = sess.run([], {input_name: x.tolist()}) + output_expected = np.array( + [[1.0, 4.0], [9.0, 16.0], [25.0, 36.0]], dtype=np.float32) + np.testing.assert_allclose( + output_expected, res[0], rtol=1e-05, atol=1e-08) + + def testStringListAsInput(self): + sess = onnxrt.InferenceSession(self.get_name("identity_string.onnx")) + x = np.array(['this', 'is', 'identity', 'test'], dtype=np.str).reshape((2, 2)) + x_name = sess.get_inputs()[0].name + res = sess.run([], {x_name: x.tolist()}) + np.testing.assert_equal(x, res[0]) + def testRunDevice(self): device = onnxrt.get_device() self.assertTrue('CPU' in device or 'GPU' in device)