mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-05-16 21:00:14 +00:00
* initial commit for Node.js binding * add c++ code * add inference session impl * e2e working * add settings.json * add test data * adjust binding declaration * refine tensor constructor declaration * update tests * enable onnx tests * simply refine readme * refine cpp impl * refine tests * formatting * add linting * move bin folder * fix linux build * manually update test filter list * update C++ API headers: fix crash in release build * make (manually) prebuild work * add test into prepack script * specify prebuild runtime type (N-API) * build.ts: update rebuild and include regex * fix lazy load on electron.js * update dev version, git link and binary host * support session options and run options * bump dev version * update README * add 1 example * move folder * adjust path * update document for examples * rename example 01 * add example 02 * add session option: log severity level * add example 04 * resolve comments * fix typo * remove double guard in header files * add copyright banner * move BUILD outside from README * consume test filter list from onnxruntime
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
|
|
size_t dimsCount = tensorTypeAndShapeInfo.GetDimensionsCount();
|
|
std::vector<int64_t> dims;
|
|
if (dimsCount > 0) {
|
|
dims.resize(dimsCount);
|
|
tensorTypeAndShapeInfo.GetDimensions(&dims[0], dimsCount);
|
|
}
|
|
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();
|
|
auto tempBuffer = std::make_unique<char[]>(tempBufferLength);
|
|
std::vector<size_t> tempOffsets;
|
|
tempOffsets.resize(size);
|
|
value.GetStringTensorContent(tempBuffer.get(), tempBufferLength, &tempOffsets[0], size);
|
|
|
|
for (uint32_t i = 0; i < size; i++) {
|
|
stringArray[i] =
|
|
Napi::String::New(env, tempBuffer.get() + 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.GetTensorMutableData<void>(), 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);
|
|
}
|