mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-05-17 21:10:43 +00:00
### Description
Deprecate CustomOpApi and refactor dependencies for exception safety and
eliminate memory leaks.
Refactor API classes for clear ownership and semantics.
Introduce `InitProviderOrtApi()`
### Motivation and Context
Make public API better and safer.
Special note about `Ort::Unowned`. The class suffers from the following
problems:
1. It is not able to hold const pointers to the underlying C objects.
This forces users to `const_cast` and circumvent constness of the
returned object. The user is now able to call mutating interfaces on the
object which violates invariants and may be a thread-safety issue. It
also enables to take ownership of the pointer and destroy it
unintentionally (see examples below).
2. The objects that are unowned cannot be copied and that makes coding
inconvenient and at times unsafe.
3. It directly inherits from the type it `unowns`.
All of the above creates great conditions for inadvertent unowned object
mutations and destructions. Consider the following examples of object
slicing, one of them is from a real customer issue and the other one I
accidentally coded myself (and I am supposed to know how this works).
None of the below can be solved by aftermarket patches and can be hard
to diagnose.
#### Example 1 slicing of argument
```cpp
void SlicingOnArgument(Ort::Value& value) {
// This will take possession of the input and if the argument
// is Ort::Unowned<Ort::Value> it would again double free the ptr
// regardless if it was const or not since we cast it away.
Ort::Value output_values[] = {std::move(value)};
}
void main() {
const OrtValue* ptr = nullptr; // some value does not matter
Ort::Unowned<Ort::Value> unowned{const_cast<OrtValue*>(ptr)};
// onowned is destroyed when the call returns.
SlicingOnArgument(unowned);
}
```
#### Example 2 slicing of return value
```cpp
// The return will be sliced to Ort::Value that would own and relase (double free the ptr)
Ort::Value SlicingOnReturn() {
const OrtValue* ptr = nullptr; // some value does not matter
Ort::Unowned<Ort::Value> unowned{const_cast<OrtValue*>(ptr)};
return unowned;
}
```
262 lines
14 KiB
C++
262 lines
14 KiB
C++
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// Licensed under the MIT License.
|
|
|
|
#include <cmath>
|
|
#include <memory>
|
|
#include <sstream>
|
|
#include <unordered_map>
|
|
|
|
#include "common.h"
|
|
#include "tensor_helper.h"
|
|
|
|
// make sure consistent with origin definition
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED == 0, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT == 1, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8 == 2, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8 == 3, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16 == 4, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16 == 5, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32 == 6, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64 == 7, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING == 8, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL == 9, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16 == 10, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE == 11, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32 == 12, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64 == 13, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX64 == 14, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX128 == 15, "definition not consistent with OnnxRuntime");
|
|
static_assert(ONNX_TENSOR_ELEMENT_DATA_TYPE_BFLOAT16 == 16, "definition not consistent with OnnxRuntime");
|
|
constexpr size_t ONNX_TENSOR_ELEMENT_DATA_TYPE_COUNT = 17;
|
|
|
|
// size of element in bytes for each data type. 0 indicates not supported.
|
|
constexpr size_t DATA_TYPE_ELEMENT_SIZE_MAP[] = {
|
|
0, // ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED not supported
|
|
4, // ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT
|
|
1, // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8
|
|
1, // ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8
|
|
2, // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16
|
|
2, // ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16
|
|
4, // ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32
|
|
8, // ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64 INT64 not working in Javascript
|
|
0, // ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING N/A
|
|
1, // ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL
|
|
0, // ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16 FLOAT16 not working in Javascript
|
|
8, // ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE
|
|
4, // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32
|
|
8, // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64 UINT64 not working in Javascript
|
|
0, // ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX64 not supported
|
|
0, // ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX128 not supported
|
|
0 // ONNX_TENSOR_ELEMENT_DATA_TYPE_BFLOAT16 not supported
|
|
};
|
|
static_assert(sizeof(DATA_TYPE_ELEMENT_SIZE_MAP) == sizeof(size_t) * ONNX_TENSOR_ELEMENT_DATA_TYPE_COUNT,
|
|
"definition not matching");
|
|
|
|
constexpr napi_typedarray_type DATA_TYPE_TYPEDARRAY_MAP[] = {
|
|
(napi_typedarray_type)(-1), // ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED not supported
|
|
napi_float32_array, // ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT
|
|
napi_uint8_array, // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8
|
|
napi_int8_array, // ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8
|
|
napi_uint16_array, // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16
|
|
napi_int16_array, // ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16
|
|
napi_int32_array, // ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32
|
|
napi_bigint64_array, // ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64 INT64 not working i
|
|
(napi_typedarray_type)(-1), // ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING not supported
|
|
napi_uint8_array, // ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL
|
|
(napi_typedarray_type)(-1), // ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16 FLOAT16 not working
|
|
napi_float64_array, // ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE
|
|
napi_uint32_array, // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32
|
|
napi_biguint64_array, // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64 UINT64 not working
|
|
(napi_typedarray_type)(-1), // ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX64 not supported
|
|
(napi_typedarray_type)(-1), // ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX128 not supported
|
|
(napi_typedarray_type)(-1) // ONNX_TENSOR_ELEMENT_DATA_TYPE_BFLOAT16 not supported
|
|
};
|
|
static_assert(sizeof(DATA_TYPE_TYPEDARRAY_MAP) == sizeof(napi_typedarray_type) * ONNX_TENSOR_ELEMENT_DATA_TYPE_COUNT,
|
|
"definition not matching");
|
|
|
|
constexpr const char *DATA_TYPE_ID_TO_NAME_MAP[] = {
|
|
nullptr, // ONNX_TENSOR_ELEMENT_DATA_TYPE_UNDEFINED
|
|
"float32", // ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT
|
|
"uint8", // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8
|
|
"int8", // ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8
|
|
"uint16", // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16
|
|
"int16", // ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16
|
|
"int32", // ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32
|
|
"int64", // ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64
|
|
"string", // ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING
|
|
"bool", // ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL
|
|
"float16", // ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16
|
|
"float64", // ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE
|
|
"uint32", // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32
|
|
"uint64", // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64
|
|
nullptr, // ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX64
|
|
nullptr, // ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX128
|
|
nullptr // ONNX_TENSOR_ELEMENT_DATA_TYPE_BFLOAT16
|
|
};
|
|
static_assert(sizeof(DATA_TYPE_ID_TO_NAME_MAP) == sizeof(const char *) * ONNX_TENSOR_ELEMENT_DATA_TYPE_COUNT,
|
|
"definition not matching");
|
|
|
|
const std::unordered_map<std::string, ONNXTensorElementDataType> DATA_TYPE_NAME_TO_ID_MAP = {
|
|
{"float32", ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT}, {"uint8", ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT8},
|
|
{"int8", ONNX_TENSOR_ELEMENT_DATA_TYPE_INT8}, {"uint16", ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT16},
|
|
{"int16", ONNX_TENSOR_ELEMENT_DATA_TYPE_INT16}, {"int32", ONNX_TENSOR_ELEMENT_DATA_TYPE_INT32},
|
|
{"int64", ONNX_TENSOR_ELEMENT_DATA_TYPE_INT64}, {"string", ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING},
|
|
{"bool", ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL}, {"float16", ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16},
|
|
{"float64", ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE}, {"uint32", ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32},
|
|
{"uint64", ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64}};
|
|
|
|
// currently only support tensor
|
|
Ort::Value NapiValueToOrtValue(Napi::Env env, Napi::Value value) {
|
|
ORT_NAPI_THROW_TYPEERROR_IF(!value.IsObject(), env, "Tensor must be an object.");
|
|
|
|
// check 'dims'
|
|
auto tensorObject = value.As<Napi::Object>();
|
|
auto dimsValue = tensorObject.Get("dims");
|
|
ORT_NAPI_THROW_TYPEERROR_IF(!dimsValue.IsArray(), env, "Tensor.dims must be an array.");
|
|
|
|
auto dimsArray = dimsValue.As<Napi::Array>();
|
|
auto len = dimsArray.Length();
|
|
std::vector<int64_t> dims;
|
|
if (len > 0) {
|
|
dims.reserve(len);
|
|
for (uint32_t i = 0; i < len; i++) {
|
|
Napi::Value dimValue = dimsArray[i];
|
|
ORT_NAPI_THROW_TYPEERROR_IF(!dimValue.IsNumber(), env, "Tensor.dims[", i, "] is not a number.");
|
|
auto dimNumber = dimValue.As<Napi::Number>();
|
|
double dimDouble = dimNumber.DoubleValue();
|
|
ORT_NAPI_THROW_RANGEERROR_IF(std::floor(dimDouble) != dimDouble || dimDouble < 0 || dimDouble > 4294967295, env,
|
|
"Tensor.dims[", i, "] is invalid: ", dimDouble);
|
|
int64_t dim = static_cast<int64_t>(dimDouble);
|
|
dims.push_back(dim);
|
|
}
|
|
}
|
|
|
|
// check 'data' and 'type'
|
|
auto tensorDataValue = tensorObject.Get("data");
|
|
auto tensorTypeValue = tensorObject.Get("type");
|
|
ORT_NAPI_THROW_TYPEERROR_IF(!tensorTypeValue.IsString(), env, "Tensor.type must be a string.");
|
|
|
|
auto tensorTypeString = tensorTypeValue.As<Napi::String>().Utf8Value();
|
|
|
|
if (tensorTypeString == "string") {
|
|
ORT_NAPI_THROW_TYPEERROR_IF(!tensorDataValue.IsArray(), env, "Tensor.data must be an array for string tensors.");
|
|
|
|
auto tensorDataArray = tensorDataValue.As<Napi::Array>();
|
|
auto tensorDataSize = tensorDataArray.Length();
|
|
std::vector<std::string> stringData;
|
|
std::vector<const char *> stringDataCStr;
|
|
stringData.reserve(tensorDataSize);
|
|
stringDataCStr.reserve(tensorDataSize);
|
|
for (uint32_t i = 0; i < tensorDataSize; i++) {
|
|
auto currentData = tensorDataArray.Get(i);
|
|
ORT_NAPI_THROW_TYPEERROR_IF(!currentData.IsString(), env, "Tensor.data[", i, "] must be a string.");
|
|
|
|
auto currentString = currentData.As<Napi::String>();
|
|
stringData.emplace_back(currentString.Utf8Value());
|
|
stringDataCStr.emplace_back(stringData[i].c_str());
|
|
}
|
|
|
|
Ort::AllocatorWithDefaultOptions allocator;
|
|
auto tensor = Ort::Value::CreateTensor(allocator, dims.empty() ? nullptr : &dims[0], dims.size(),
|
|
ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING);
|
|
if (stringDataCStr.size() > 0) {
|
|
Ort::ThrowOnError(Ort::GetApi().FillStringTensor(tensor, &stringDataCStr[0], stringDataCStr.size()));
|
|
}
|
|
return tensor;
|
|
} else {
|
|
// lookup numeric tensor types
|
|
auto v = DATA_TYPE_NAME_TO_ID_MAP.find(tensorTypeString);
|
|
ORT_NAPI_THROW_TYPEERROR_IF(v == DATA_TYPE_NAME_TO_ID_MAP.end(), env,
|
|
"Tensor.type is not supported: ", tensorTypeString);
|
|
|
|
ONNXTensorElementDataType elemType = v->second;
|
|
|
|
ORT_NAPI_THROW_TYPEERROR_IF(!tensorDataValue.IsTypedArray(), env,
|
|
"Tensor.data must be a typed array for numeric tensor.");
|
|
|
|
auto tensorDataTypedArray = tensorDataValue.As<Napi::TypedArray>();
|
|
auto typedArrayType = tensorDataValue.As<Napi::TypedArray>().TypedArrayType();
|
|
ORT_NAPI_THROW_TYPEERROR_IF(DATA_TYPE_TYPEDARRAY_MAP[elemType] != typedArrayType, env,
|
|
"Tensor.data must be a typed array (", DATA_TYPE_TYPEDARRAY_MAP[elemType], ") for ",
|
|
tensorTypeString, " tensors, but got typed array (", typedArrayType, ").");
|
|
|
|
auto memory_info = Ort::MemoryInfo::CreateCpu(OrtArenaAllocator, OrtMemTypeDefault);
|
|
char *buffer = reinterpret_cast<char *>(tensorDataTypedArray.ArrayBuffer().Data());
|
|
size_t bufferByteOffset = tensorDataTypedArray.ByteOffset();
|
|
// there is a bug in TypedArray::ElementSize(): https://github.com/nodejs/node-addon-api/pull/705
|
|
// TODO: change to TypedArray::ByteLength() in next node-addon-api release.
|
|
size_t bufferByteLength = tensorDataTypedArray.ElementLength() * DATA_TYPE_ELEMENT_SIZE_MAP[elemType];
|
|
return Ort::Value::CreateTensor(memory_info, buffer + bufferByteOffset, bufferByteLength,
|
|
dims.empty() ? nullptr : &dims[0], dims.size(), elemType);
|
|
}
|
|
}
|
|
|
|
Napi::Value OrtValueToNapiValue(Napi::Env env, Ort::Value &value) {
|
|
Napi::EscapableHandleScope scope(env);
|
|
auto returnValue = Napi::Object::New(env);
|
|
|
|
auto typeInfo = value.GetTypeInfo();
|
|
auto onnxType = typeInfo.GetONNXType();
|
|
|
|
ORT_NAPI_THROW_ERROR_IF(onnxType != ONNX_TYPE_TENSOR, env, "Non tensor type is temporarily not supported.");
|
|
|
|
auto tensorTypeAndShapeInfo = typeInfo.GetTensorTypeAndShapeInfo();
|
|
auto elemType = tensorTypeAndShapeInfo.GetElementType();
|
|
|
|
// type
|
|
auto typeCstr = DATA_TYPE_ID_TO_NAME_MAP[elemType];
|
|
ORT_NAPI_THROW_ERROR_IF(typeCstr == nullptr, env, "Tensor type (", elemType, ") is not supported.");
|
|
|
|
returnValue.Set("type", Napi::String::New(env, typeCstr));
|
|
|
|
// dims
|
|
const size_t dimsCount = tensorTypeAndShapeInfo.GetDimensionsCount();
|
|
std::vector<int64_t> dims;
|
|
if (dimsCount > 0) {
|
|
dims = tensorTypeAndShapeInfo.GetShape();
|
|
}
|
|
auto dimsArray = Napi::Array::New(env, dimsCount);
|
|
for (uint32_t i = 0; i < dimsCount; i++) {
|
|
dimsArray[i] = dims[i];
|
|
}
|
|
returnValue.Set("dims", dimsArray);
|
|
|
|
// size
|
|
auto size = tensorTypeAndShapeInfo.GetElementCount();
|
|
returnValue.Set("size", Napi::Number::From(env, size));
|
|
|
|
// data
|
|
if (elemType == ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING) {
|
|
// string data
|
|
auto stringArray = Napi::Array::New(env, size);
|
|
if (size > 0) {
|
|
auto tempBufferLength = value.GetStringTensorDataLength();
|
|
// create buffer of length (tempBufferLength + 1) to make sure `&tempBuffer[0]` is always valid
|
|
std::vector<char> tempBuffer(tempBufferLength + 1);
|
|
std::vector<size_t> tempOffsets;
|
|
tempOffsets.resize(size);
|
|
value.GetStringTensorContent(&tempBuffer[0], tempBufferLength, &tempOffsets[0], size);
|
|
|
|
for (uint32_t i = 0; i < size; i++) {
|
|
stringArray[i] =
|
|
Napi::String::New(env, &tempBuffer[0] + tempOffsets[i],
|
|
i == size - 1 ? tempBufferLength - tempOffsets[i] : tempOffsets[i + 1] - tempOffsets[i]);
|
|
}
|
|
}
|
|
returnValue.Set("data", Napi::Value(env, stringArray));
|
|
} else {
|
|
// number data
|
|
// TODO: optimize memory
|
|
auto arrayBuffer = Napi::ArrayBuffer::New(env, size * DATA_TYPE_ELEMENT_SIZE_MAP[elemType]);
|
|
if (size > 0) {
|
|
memcpy(arrayBuffer.Data(), value.GetTensorRawData(), size * DATA_TYPE_ELEMENT_SIZE_MAP[elemType]);
|
|
}
|
|
napi_value typedArrayData;
|
|
napi_status status =
|
|
napi_create_typedarray(env, DATA_TYPE_TYPEDARRAY_MAP[elemType], size, arrayBuffer, 0, &typedArrayData);
|
|
NAPI_THROW_IF_FAILED(env, status, Napi::Value);
|
|
returnValue.Set("data", Napi::Value(env, typedArrayData));
|
|
}
|
|
|
|
return scope.Escape(returnValue);
|
|
}
|