FixPyOpSegFault&MakeItStaticLib (#4600)

* remove pyop wrapper

* add py threading logic

* fix doc

* fix doc

* fix doc

* format doc

* format doc

* format doc

* reenable test

Co-authored-by: RandySheriffH <rashuai@microsoft.com>
This commit is contained in:
RandySheriffH 2020-07-28 11:45:25 -07:00 committed by GitHub
parent 6c2bd127ba
commit 948a33bdfc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 245 additions and 279 deletions

View file

@ -1,17 +1,11 @@
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License.
file(GLOB onnxruntime_pywrapper_srcs "${ONNXRUNTIME_ROOT}/core/language_interop_ops/pyop/pywrapper.cc")
add_library(onnxruntime_pywrapper SHARED ${onnxruntime_pywrapper_srcs})
if (WIN32)
set_target_properties(onnxruntime_pywrapper PROPERTIES LINK_FLAGS "/ignore:4199")
endif()
target_include_directories(onnxruntime_pywrapper PRIVATE ${PYTHON_INCLUDE_DIR} ${NUMPY_INCLUDE_DIR})
target_link_libraries(onnxruntime_pywrapper PRIVATE ${PYTHON_LIBRARIES})
file(GLOB onnxruntime_pyop_srcs "${ONNXRUNTIME_ROOT}/core/language_interop_ops/pyop/pyop.cc")
add_library(onnxruntime_pyop ${onnxruntime_pyop_srcs})
add_dependencies(onnxruntime_pyop onnxruntime_graph)
onnxruntime_add_include_to_target(onnxruntime_pyop onnxruntime_common onnxruntime_graph onnxruntime_framework onnx onnx_proto protobuf::libprotobuf)
target_include_directories(onnxruntime_pyop PRIVATE ${ONNXRUNTIME_ROOT} ${eigen_INCLUDE_DIRS})
target_include_directories(onnxruntime_pyop PRIVATE ${PYTHON_INCLUDE_DIR} ${NUMPY_INCLUDE_DIR})
target_link_libraries(onnxruntime_pyop PRIVATE ${PYTHON_LIBRARIES})

View file

@ -330,10 +330,4 @@ endif()
if (onnxruntime_ENABLE_LANGUAGE_INTEROP_OPS)
include(onnxruntime_language_interop_ops.cmake)
add_custom_command(
TARGET onnxruntime_pybind11_state POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
$<TARGET_FILE:onnxruntime_pywrapper>
$<TARGET_FILE_DIR:${test_data_target}>/onnxruntime/capi/
)
endif()

View file

@ -1,13 +1,11 @@
# Python Operator
The Python Operator provides the capability to easily invoke any custom Python code within a single node of an ONNX graph using ONNX Runtime. This can be useful for quicker experimentation when a model requires operators that are not officially supported in ONNX and ONNX Runtime, particularly if there is already a Python implementation for the required functionality. This should be used with discretion in production scenarios, and all security or other risks should be considered.
The Python Operator provides the capability to easily invoke any custom Python code within a single node of an ONNX graph using ONNX Runtime. This can be useful for quicker experimentation when a model requires operators that are not officially supported in ONNX and ONNX Runtime, particularly if there is already a Python implementation for the required functionality. This should be used with discretion in production scenarios, and all security or other risks should be considered beforehand.
## Design Overview
The feature can be found under [onnxruntime/core/language_interop_ops](../onnxruntime/core/language_interop_ops).
All Python C API dependent code are compiled into a dynamic linked library named pywrapper.
Before calling into Python script, pywrapper will convert onnxruntime tensor(s) to numpy(s), which is converted back when completed.
<p>Here is a chart illustrating the calling sequence:
Here is a chart of calling sequence:
<pre>
onnxruntime pywrapper script
onnxruntime python capi script
| | |
| ------------------------------> | |
| call with tensor(s) | ------------------------------> |
@ -20,10 +18,7 @@ onnxruntime pywrapper script
## How to Use
### Step 1
Build onnxruntime with `--config Release --enable_language_interop_ops --build_shared_lib` and override the existing onnxruntime binary with the latest. Then, copy onnxruntime_pywrapper.dll, libonnxruntime_pywrapper.so, or libonnxruntime_pywrapper.dylib to the path where the onnxruntime binary is located.
**Notes:**
* It is recommended to compile within the Python environment where inferencing will happen. For example, if inferencing will happen in a conda env named myconda1, please compile the binary within that environment as well
* If `--numpy_version=...` is specified, the Python operator will build with that version.
Build onnxruntime with `--config Release --enable_language_interop_ops --build_wheel` and pip install the latest wheel file.
### Step 2
Create an onnx model containing Python operator nodes:
@ -67,7 +62,7 @@ class Multi_2:
return r1, r2
```
### Step 4
Copy mymodule.py into Python sys.path, then reference with onnxruntime. On Windows, please set PYTHONHOME beforehand. It should point to directory where the python is installed, such as C:\Python37 or C:\ProgramData\Anaconda3\envs\myconda1 if it is in conda.
Copy mymodule.py into Python sys.path, then run the model with onnxruntime python API. On Windows, please set PYTHONHOME beforehand. It should point to directory where the python is installed, such as C:\Python37 or C:\ProgramData\Anaconda3\envs\myconda1 if it is in conda.
## Supported Data Types
* TensorProto.BOOL
@ -80,6 +75,7 @@ Copy mymodule.py into Python sys.path, then reference with onnxruntime. On Windo
* TensorProto.DOUBLE
## Limitations
* Inferencing and compiling environments must be installed with same version of python.
* On Windows, `--config Debug` has known issues. Please build with `--config RelWithDebInfo` if debugging symbols are needed.
* Due to Python C API restrictions, multi-threading is disabled so Python operators will run sequentially.

View file

@ -16,30 +16,222 @@
#endif
#include "core/framework/tensorprotoutils.h"
#include "core/platform/env.h"
// #define LOAD_PYOP_SYM(n, v, m) ORT_ENFORCE(Env::Default().GetSymbolFromLibrary(handle_, n, reinterpret_cast<void**>(&v)) == Status::OK(), m)
#ifdef _DEBUG
#undef _DEBUG
#include <Python.h>
#define _DEBUG
#else
#include <Python.h>
#endif
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include "numpy/arrayobject.h"
#include <functional>
#include <iostream>
#include <sstream>
#include <numeric>
#include <vector>
#include <memory>
#include <mutex>
#include <functional>
#include <unordered_map>
using namespace std;
namespace onnxruntime {
const PyOpLibProxy& PyOpLibProxy::GetInstance() {
PyOpLibProxy& PyOpLibProxy::GetInstance() {
static PyOpLibProxy proxy;
return proxy;
}
class Scope
{
public:
Scope(const vector<PyObject*>& objs = {}): objs_(objs) {
mtx_.lock();
}
~Scope() {
for (auto obj: objs_) {
Py_XDECREF(obj);
}
mtx_.unlock();
}
void Add(PyObject* obj) {
objs_.push_back(obj);
}
private:
static std::mutex mtx_;
vector<PyObject*> objs_;
};
PyOpLibProxy::PyOpLibProxy() {
std::string err;
LOAD_PYOP_LIB(LIB_PYOP, handle_, "Failed to load pyop library");
LOAD_PYOP_SYM("Initialize", initialize_, "Failed to import function: Initialize");
LOAD_PYOP_SYM("NewInstance", new_instance_, "Failed to import function: NewInstance");
LOAD_PYOP_SYM("InvokePythonFunc", invoke_python_func_, "Failed to import function: InvokePythonFunc");
LOAD_PYOP_SYM("ReleaseInstance", release_instance_, "Failed to import function: ReleaseInstance");
LOAD_PYOP_SYM("GetLastErrorMessage", get_last_error_message_, "Failed to import function: GetLastErrorMessage");
ORT_ENFORCE(initialize_(), get_last_error_message_(err));
Scope scope;
Py_Initialize();
if (_import_array() < 0) {
return;
}
auto path_list = PySys_GetObject("path");//do not release it
if (nullptr == path_list || !PyList_Check(path_list) ||
PyList_Append(path_list, PyUnicode_FromString(".")) != 0) {
return;
}
initialized_ = true;
}
PyOpLibProxy::~PyOpLibProxy() {
Env::Default().UnloadDynamicLibrary(handle_);
if (initialized_) {
Py_Finalize();
}
}
std::mutex Scope::mtx_;
const char* PyOpLibProxy::GetLastErrorMessage(std::string& err) {
Scope scope;
if (PyErr_Occurred()) {
PyObject *type, *value, *trace;
PyErr_Fetch(&type, &value, &trace);
if (nullptr != value) {
auto pyVal = PyObject_Repr(value);
scope.Add(pyVal);
auto pyStr = PyUnicode_AsEncodedString(pyVal, "utf-8", "Error ~");
scope.Add(pyStr);
err = PyBytes_AS_STRING(pyStr);
}
PyErr_Restore(type, value, trace);
}
return err.c_str();
}
int32_t PyOpLibProxy::GetGil() const {
return PyGILState_Ensure();
}
void PyOpLibProxy::PutGil(int32_t state) const {
PyGILState_Release((PyGILState_STATE)state);
}
PyObject* MakePyObj(const void* data, int32_t type, const vector<int64_t>& dim) {
std::vector<npy_intp> np_dim;
for (auto d: dim) {
np_dim.push_back(static_cast<npy_intp>(d));
}
auto pyObj = static_cast<PyObject*>(PyArray_EMPTY(static_cast<int>(np_dim.size()), np_dim.data(), type, 0));
auto data_len = std::accumulate(begin(np_dim), end(np_dim),
static_cast<int64_t>(PyArray_DescrFromType(type)->elsize),
std::multiplies<int64_t>());
auto np_array = reinterpret_cast<PyArrayObject*>(pyObj);
memcpy(PyArray_DATA(np_array), data, data_len);
return pyObj;
}
bool ExtractOutput(PyObject* pyObj,
vector<unique_ptr<char[]>>& outputs,
vector<int32_t>& outputs_elem_size,
vector<vector<int64_t>>& outputs_dim) {
if (!PyArray_Check(pyObj)) {
return false;
}
outputs_dim.push_back({});
auto np_array = reinterpret_cast<PyArrayObject*>(pyObj);
outputs_elem_size.push_back(static_cast<int32_t>(PyArray_ITEMSIZE(np_array)));
for (int i = 0; i < PyArray_NDIM(np_array); ++i) {
outputs_dim.back().push_back(PyArray_SHAPE(np_array)[i]);
}
auto data_len = std::accumulate(begin(outputs_dim.back()),
end(outputs_dim.back()),
static_cast<int64_t>(outputs_elem_size.back()),
std::multiplies<int64_t>());
outputs.push_back(unique_ptr<char[]>(new char[data_len]));
memcpy(static_cast<void*>(outputs.back().get()), PyArray_DATA(np_array), data_len);
return true;
}
void* PyOpLibProxy::NewInstance(const char* module, const char* class_name, const unordered_map<string, string>& args) {
Scope scope;
auto pyModule = PyImport_ImportModule(module);
if (nullptr == pyModule) {
return nullptr;
}
scope.Add(pyModule);
auto pyClass = PyObject_GetAttrString(pyModule, class_name);
if (nullptr == pyClass) {
return nullptr;
}
scope.Add(pyClass);
auto empty_args = PyTuple_New(0);
scope.Add(empty_args);
auto named_args = PyDict_New();
scope.Add(named_args);
for (const auto& iter: args) {
PyDict_SetItemString(named_args, iter.first.c_str(), PyUnicode_FromString(iter.second.c_str()));
}
return PyObject_Call(pyClass, empty_args, named_args);
}
void PyOpLibProxy::ReleaseInstance(void* instance) {
Scope scope({static_cast<PyObject*>(instance)});
}
bool PyOpLibProxy::InvokePythonFunc(void* raw_inst,
const char* function,
const vector<const void*>& inputs,
const vector<int32_t>& inputs_type,
const vector<vector<int64_t>>& inputs_dim,
vector<unique_ptr<char[]>>& outputs,
vector<int32_t>& outputs_elem_size,
vector<vector<int64_t>>& outputs_dim,
std::function<void(const char*)> logging_func) {
Scope scope;
auto instance = static_cast<PyObject*>(raw_inst);
if (nullptr == instance || nullptr == function) {
logging_func("InvokePythonFunc: found invalid instance or function");
return false;
}
auto pyFunc = PyObject_GetAttrString(instance, function);
if (nullptr == pyFunc) {
logging_func("InvokePythonFunc: failed to create function object");
return false;
}
scope.Add(pyFunc);
auto pyArgs = PyTuple_New(inputs.size());
for (size_t i = 0; i < inputs.size(); ++i) {
PyTuple_SetItem(pyArgs, i, MakePyObj(inputs[i], inputs_type[i], inputs_dim[i]));
}
scope.Add(pyArgs);
auto pyResult = PyEval_CallObject(pyFunc, pyArgs);
if (nullptr == pyResult) {
logging_func("InvokePythonFunc: no result");
return false;
}
scope.Add(pyResult);
if (PyArray_Check(pyResult)) {
ExtractOutput(pyResult, outputs, outputs_elem_size, outputs_dim);
} else if (PyTuple_Check(pyResult)) {
for (int32_t i = 0; i < PyTuple_Size(pyResult); ++i) {
if (!ExtractOutput(PyTuple_GetItem(pyResult, i), outputs, outputs_elem_size, outputs_dim)) {
logging_func("InvokePythonFunc: failed to extract output");
return false;
}
}
} else {
logging_func("InvokePythonFunc: returned value must be numpy(s)");
return false;
}
return true;
}//bool InvokePythonFunc
PyCustomKernel::PyCustomKernel(Ort::CustomOpApi ort,
const OnnxAttrs& attrs,
const std::string& module,
@ -47,13 +239,18 @@ PyCustomKernel::PyCustomKernel(Ort::CustomOpApi ort,
const std::string& compute,
PyOpLogFunc logging_func) : ort_(ort), attrs_(attrs), module_(module), class_name_(class_name), compute_(compute), logging_func_(logging_func) {
std::string err;
instance_ = PyOpLibProxy::GetInstance().new_instance_(module.c_str(), class_name_.c_str(), attrs_);
ORT_ENFORCE(nullptr != instance_, PyOpLibProxy::GetInstance().get_last_error_message_(err));
auto state = PyOpLibProxy::GetInstance().GetGil();
ORT_ENFORCE(PyOpLibProxy::GetInstance().Initialized(), "Py library not properly initialized.");
instance_ = PyOpLibProxy::GetInstance().NewInstance(module.c_str(), class_name_.c_str(), attrs_);
PyOpLibProxy::GetInstance().PutGil(state);
ORT_ENFORCE(nullptr != instance_, PyOpLibProxy::GetInstance().GetLastErrorMessage(err));
}
PyCustomKernel::~PyCustomKernel() {
if (nullptr != instance_) {
PyOpLibProxy::GetInstance().release_instance_(instance_);
auto state = PyOpLibProxy::GetInstance().GetGil();
PyOpLibProxy::GetInstance().ReleaseInstance(instance_);
PyOpLibProxy::GetInstance().PutGil(state);
instance_ = nullptr;
}
}
@ -77,10 +274,12 @@ void PyCustomKernel::Compute(OrtKernelContext* context) {
}
std::string err;
ORT_ENFORCE(PyOpLibProxy::GetInstance().invoke_python_func_(instance_, compute_.c_str(), inputs, inputs_type,
inputs_dim, outputs, outputs_elem_size,
outputs_dim, logging_func_),
PyOpLibProxy::GetInstance().get_last_error_message_(err)); //ORT_ENFORCE
auto state = PyOpLibProxy::GetInstance().GetGil();
ORT_ENFORCE(PyOpLibProxy::GetInstance().InvokePythonFunc(instance_, compute_.c_str(), inputs, inputs_type,
inputs_dim, outputs, outputs_elem_size,
outputs_dim, logging_func_),
PyOpLibProxy::GetInstance().GetLastErrorMessage(err)); //ORT_ENFORCE
PyOpLibProxy::GetInstance().PutGil(state);
for (size_t i = 0; i < outputs.size(); ++i) {
auto ort_output = ort_.KernelContext_GetOutput(context, i, outputs_dim[i].data(), outputs_dim[i].size());

View file

@ -19,33 +19,29 @@ using OnnxTypes = std::vector<ONNXTensorElementDataType>;
using OnnxAttrs = std::unordered_map<std::string, std::string>;
using PyOpLogFunc = std::function<void(const char*)>;
typedef bool Initialize();
typedef void ReleaseInstance(void*);
typedef bool InvokePythonFunc(void*,
const char*,
const std::vector<const void*>&,
const std::vector<int32_t>&,
const std::vector<std::vector<int64_t>>&,
std::vector<std::unique_ptr<char[]>>&,
std::vector<int32_t>&,
std::vector<std::vector<int64_t>>&,
std::function<void(const char*)>);
typedef const char* GetLastErrorMessage(std::string&);
typedef void* NewInstance(const char*, const char*, const OnnxAttrs&);
class PyOpLibProxy {
public:
static const PyOpLibProxy& GetInstance();
HMODULE handle_ = nullptr;
Initialize* initialize_ = nullptr;
NewInstance* new_instance_ = nullptr;
InvokePythonFunc* invoke_python_func_ = nullptr;
ReleaseInstance* release_instance_ = nullptr;
GetLastErrorMessage* get_last_error_message_ = nullptr;
static PyOpLibProxy& GetInstance();
void ReleaseInstance(void*);
bool InvokePythonFunc(void*,
const char*,
const std::vector<const void*>&,
const std::vector<int32_t>&,
const std::vector<std::vector<int64_t>>&,
std::vector<std::unique_ptr<char[]>>&,
std::vector<int32_t>&,
std::vector<std::vector<int64_t>>&,
std::function<void(const char*)>);
const char* GetLastErrorMessage(std::string&);
void* NewInstance(const char*, const char*, const OnnxAttrs&);
bool Initialized() const { return initialized_; }
int32_t GetGil() const;
void PutGil(int32_t) const;
private:
PyOpLibProxy();
~PyOpLibProxy();
bool initialized_ = false;
};
struct PyCustomKernel {

View file

@ -1,213 +0,0 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#ifdef _DEBUG
#undef _DEBUG
#include <Python.h>
#define _DEBUG
#else
#include <Python.h>
#endif
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include "numpy/arrayobject.h"
#include <functional>
#include <iostream>
#include <sstream>
#include <numeric>
#include <vector>
#include <memory>
#include <mutex>
#include <functional>
#include <unordered_map>
using namespace std;
namespace onnxruntime {
#ifdef _WIN32
#define PYOP_EXPORT extern "C" __declspec(dllexport)
#else
#define PYOP_EXPORT extern "C"
#endif
struct Finalizer
{
~Finalizer() {
Py_Finalize();
}
};
class Scope
{
public:
Scope(const vector<PyObject*>& objs = {}): objs_(objs) {
mtx_.lock();
}
~Scope() {
for (auto obj: objs_) {
Py_XDECREF(obj);
}
mtx_.unlock();
}
void Add(PyObject* obj) {
objs_.push_back(obj);
}
private:
static std::mutex mtx_;
vector<PyObject*> objs_;
};
std::mutex Scope::mtx_;
PYOP_EXPORT bool Initialize() {
Scope scope;
Py_Initialize();
if (_import_array() < 0) {
return false;
}
auto path_list = PySys_GetObject("path");//do not release it
if (nullptr == path_list || !PyList_Check(path_list) ||
PyList_Append(path_list, PyUnicode_FromString(".")) != 0) {
return false;
}
static Finalizer finalizer;
return true;
}
PYOP_EXPORT const char* GetLastErrorMessage(std::string& err) {
Scope scope;
if (PyErr_Occurred()) {
PyObject *type, *value, *trace;
PyErr_Fetch(&type, &value, &trace);
if (nullptr != value) {
auto pyVal = PyObject_Repr(value);
scope.Add(pyVal);
auto pyStr = PyUnicode_AsEncodedString(pyVal, "utf-8", "Error ~");
scope.Add(pyStr);
err = PyBytes_AS_STRING(pyStr);
}
PyErr_Restore(type, value, trace);
}
return err.c_str();
}
PyObject* MakePyObj(const void* data, int32_t type, const vector<int64_t>& dim) {
std::vector<npy_intp> np_dim;
for (auto d: dim) {
np_dim.push_back(static_cast<npy_intp>(d));
}
auto pyObj = static_cast<PyObject*>(PyArray_EMPTY(static_cast<int>(np_dim.size()), np_dim.data(), type, 0));
auto data_len = std::accumulate(begin(np_dim), end(np_dim),
static_cast<int64_t>(PyArray_DescrFromType(type)->elsize),
std::multiplies<int64_t>());
auto np_array = reinterpret_cast<PyArrayObject*>(pyObj);
memcpy(PyArray_DATA(np_array), data, data_len);
return pyObj;
}
bool ExtractOutput(PyObject* pyObj,
vector<unique_ptr<char[]>>& outputs,
vector<int32_t>& outputs_elem_size,
vector<vector<int64_t>>& outputs_dim) {
if (!PyArray_Check(pyObj)) {
return false;
}
outputs_dim.push_back({});
auto np_array = reinterpret_cast<PyArrayObject*>(pyObj);
outputs_elem_size.push_back(static_cast<int32_t>(PyArray_ITEMSIZE(np_array)));
for (int i = 0; i < PyArray_NDIM(np_array); ++i) {
outputs_dim.back().push_back(PyArray_SHAPE(np_array)[i]);
}
auto data_len = std::accumulate(begin(outputs_dim.back()),
end(outputs_dim.back()),
static_cast<int64_t>(outputs_elem_size.back()),
std::multiplies<int64_t>());
outputs.push_back(unique_ptr<char[]>(new char[data_len]));
memcpy(static_cast<void*>(outputs.back().get()), PyArray_DATA(np_array), data_len);
return true;
}
PYOP_EXPORT void* NewInstance(const char* module, const char* class_name, const unordered_map<string, string>& args) {
Scope scope;
auto pyModule = PyImport_ImportModule(module);
if (nullptr == pyModule) {
return nullptr;
}
scope.Add(pyModule);
auto pyClass = PyObject_GetAttrString(pyModule, class_name);
if (nullptr == pyClass) {
return nullptr;
}
scope.Add(pyClass);
auto empty_args = PyTuple_New(0);
scope.Add(empty_args);
auto named_args = PyDict_New();
scope.Add(named_args);
for (const auto& iter: args) {
PyDict_SetItemString(named_args, iter.first.c_str(), PyUnicode_FromString(iter.second.c_str()));
}
return PyObject_Call(pyClass, empty_args, named_args);
}
PYOP_EXPORT void ReleaseInstance(void* instance) {
Scope scope({static_cast<PyObject*>(instance)});
}
PYOP_EXPORT bool InvokePythonFunc(void* raw_inst,
const char* function,
const vector<const void*>& inputs,
const vector<int32_t>& inputs_type,
const vector<vector<int64_t>>& inputs_dim,
vector<unique_ptr<char[]>>& outputs,
vector<int32_t>& outputs_elem_size,
vector<vector<int64_t>>& outputs_dim,
std::function<void(const char*)> logging_func) {
Scope scope;
auto instance = static_cast<PyObject*>(raw_inst);
if (nullptr == instance || nullptr == function) {
logging_func("InvokePythonFunc: found invalid instance or function");
return false;
}
auto pyFunc = PyObject_GetAttrString(instance, function);
if (nullptr == pyFunc) {
logging_func("InvokePythonFunc: failed to create function object");
return false;
}
scope.Add(pyFunc);
auto pyArgs = PyTuple_New(inputs.size());
for (size_t i = 0; i < inputs.size(); ++i) {
PyTuple_SetItem(pyArgs, i, MakePyObj(inputs[i], inputs_type[i], inputs_dim[i]));
}
scope.Add(pyArgs);
auto pyResult = PyEval_CallObject(pyFunc, pyArgs);
if (nullptr == pyResult) {
logging_func("InvokePythonFunc: no result");
return false;
}
scope.Add(pyResult);
if (PyArray_Check(pyResult)) {
ExtractOutput(pyResult, outputs, outputs_elem_size, outputs_dim);
} else if (PyTuple_Check(pyResult)) {
for (int32_t i = 0; i < PyTuple_Size(pyResult); ++i) {
if (!ExtractOutput(PyTuple_GetItem(pyResult, i), outputs, outputs_elem_size, outputs_dim)) {
logging_func("InvokePythonFunc: failed to extract output");
return false;
}
}
} else {
logging_func("InvokePythonFunc: returned value must be numpy(s)");
return false;
}
return true;
}//bool InvokePythonFunc
}//namespace onnxruntime

View file

@ -358,8 +358,8 @@ TEST(CApiTest, test_custom_op_library) {
TestInference<PATH_TYPE, int32_t>(*ort_env, CUSTOM_OP_LIBRARY_TEST_MODEL_URI, inputs, "output", expected_dims_y, expected_values_y, 0, nullptr, lib_name.c_str());
}
#if defined(ENABLE_LANGUAGE_INTEROP_OPS) && !defined(_WIN32) // on windows, PYTHONHOME must be set explicitly
TEST(CApiTest, DISABLED_test_pyop) {
#if defined(ENABLE_LANGUAGE_INTEROP_OPS)
TEST(CApiTest, test_pyop) {
std::cout << "Test model with pyop" << std::endl;
std::ofstream module("mymodule.py");
module << "class MyKernel:" << std::endl;