onnxruntime/js/node/src/tensor_helper.cc
Yulong Wang abdc31de40
[js] change default formatter for JavaScript/TypeScript from clang-format to Prettier (#21728)
### Description

See
454996d496
for manual changes (excluded auto-generated formatting changes)

### Why

Because the toolsets for old clang-format is out-of-date. This reduces
the development efficiency.

- The NPM package `clang-format` is already in maintenance mode. not
updated since 2 years ago.
- The VSCode extension for clang-format is not maintained for a while,
and a recent Node.js security update made it not working at all in
Windows.

No one in community seems interested in fixing those.

Choose Prettier as it is the most popular TS/JS formatter.

### How to merge

It's easy to break the build:
- Be careful of any new commits on main not included in this PR.
- Be careful that after this PR is merged, other PRs that already passed
CI can merge.

So, make sure there is no new commits before merging this one, and
invalidate js PRs that already passed CI, force them to merge to latest.
2024-08-14 16:51:22 -07:00

253 lines
13 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
0, // ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING N/A
1, // ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL
2, // ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16
8, // ONNX_TENSOR_ELEMENT_DATA_TYPE_DOUBLE
4, // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT32
8, // ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64
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
(napi_typedarray_type)(-1), // ONNX_TENSOR_ELEMENT_DATA_TYPE_STRING not supported
napi_uint8_array, // ONNX_TENSOR_ELEMENT_DATA_TYPE_BOOL
napi_uint16_array, // ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT16 FLOAT16 uses Uint16Array
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
(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, OrtMemoryInfo* memory_info) {
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, ").");
char* buffer = reinterpret_cast<char*>(tensorDataTypedArray.ArrayBuffer().Data());
size_t bufferByteOffset = tensorDataTypedArray.ByteOffset();
size_t bufferByteLength = tensorDataTypedArray.ByteLength();
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);
}