Skip to content

Commit

Permalink
[JS API] Add setter for Tensor.data (openvinotoolkit#23949)
Browse files Browse the repository at this point in the history
### Details:
 - `set_data` only accepts TypedArrays.  
- Each entry is a value in one of the supported formats, from 8-bit
integers to 64-bit floating-point numbers.
- Before copying data to Tensor, there is a check if buffers have the
same length and if the element types are the same
- Add helper function `const std::unordered_map<napi_typedarray_type,
ov::element::Type>& get_ov_type()` equivalent to Python APIs `const
std::map<std::string, ov::element::Type>& dtype_to_ov_type();`
 

### Tickets:
 - *128274*
  • Loading branch information
almilosz authored Apr 23, 2024
1 parent da517f5 commit 92b5d6a
Show file tree
Hide file tree
Showing 6 changed files with 80 additions and 3 deletions.
6 changes: 6 additions & 0 deletions src/bindings/js/node/include/helper.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ const std::vector<std::string>& get_supported_types();

typedef std::variant<napi_valuetype, napi_typedarray_type, js_type> napi_types;

/**
* @brief Gets corresponding ov::element::Type_t to the passed TypedArray type
* @throw Exception if there is no corresponing or supported ov::element::Type_t
*/
const ov::element::Type_t& get_ov_type(napi_typedarray_type type);

/**
* @brief Template function to convert Javascript data types into C++ data types
* @tparam TargetType destinated C++ data type
Expand Down
8 changes: 7 additions & 1 deletion src/bindings/js/node/include/tensor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,17 @@ class TensorWrap : public Napi::ObjectWrap<TensorWrap> {
*/
Napi::Value get_data(const Napi::CallbackInfo& info);

/**
* @brief Setter that fills underlaying Tensor's memory by copying data from TypedArray.
* @throw Exception if data's size from TypedArray does not match the size of the tensor's data.
*/
void set_data(const Napi::CallbackInfo& info, const Napi::Value& value);

/** @return Napi::Array containing a tensor shape. */
Napi::Value get_shape(const Napi::CallbackInfo& info);
/** @return Napi::String containing ov::element type. */
Napi::Value get_element_type(const Napi::CallbackInfo& info);
/**@return Napi::Number containing tensor size as total number of elements.*/
/** @return Napi::Number containing tensor size as total number of elements. */
Napi::Value get_size(const Napi::CallbackInfo& info);

private:
Expand Down
2 changes: 1 addition & 1 deletion src/bindings/js/node/lib/addon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ interface CompiledModel {
}

interface Tensor {
data: number[];
data: SupportedTypedArray;
getElementType(): element;
getShape(): number[];
getData(): number[];
Expand Down
15 changes: 15 additions & 0 deletions src/bindings/js/node/src/helper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,21 @@ const std::vector<std::string>& get_supported_types() {
return supported_element_types;
}

const ov::element::Type_t& get_ov_type(napi_typedarray_type type) {
static const std::unordered_map<napi_typedarray_type, ov::element::Type_t> typedarray_to_ov_type{
{napi_int8_array, ov::element::Type_t::i8},
{napi_uint8_array, ov::element::Type_t::u8},
{napi_int16_array, ov::element::Type_t::i16},
{napi_uint16_array, ov::element::Type_t::u16},
{napi_int32_array, ov::element::Type_t::i32},
{napi_uint32_array, ov::element::Type_t::u32},
{napi_float32_array, ov::element::Type_t::f32},
{napi_float64_array, ov::element::Type_t::f64},
{napi_bigint64_array, ov::element::Type_t::i64},
{napi_biguint64_array, ov::element::Type_t::u64}};
return typedarray_to_ov_type.at(type);
}

napi_types napiType(const Napi::Value& val) {
if (val.IsTypedArray())
return val.As<Napi::TypedArray>().TypedArrayType();
Expand Down
19 changes: 18 additions & 1 deletion src/bindings/js/node/src/tensor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ TensorWrap::TensorWrap(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Tensor
Napi::Function TensorWrap::get_class(Napi::Env env) {
return DefineClass(env,
"TensorWrap",
{InstanceAccessor<&TensorWrap::get_data>("data"),
{InstanceAccessor<&TensorWrap::get_data, &TensorWrap::set_data>("data"),
InstanceMethod("getData", &TensorWrap::get_data),
InstanceMethod("getShape", &TensorWrap::get_shape),
InstanceMethod("getElementType", &TensorWrap::get_element_type),
Expand Down Expand Up @@ -132,6 +132,23 @@ Napi::Value TensorWrap::get_data(const Napi::CallbackInfo& info) {
}
}

void TensorWrap::set_data(const Napi::CallbackInfo& info, const Napi::Value& value) {
try {
if (!value.IsTypedArray()) {
OPENVINO_THROW("Passed argument must be a TypedArray.");
}
const auto buf = value.As<Napi::TypedArray>();

if (_tensor.get_byte_size() != buf.ByteLength()) {
OPENVINO_THROW("Passed array must have the same size as the Tensor!");
}
const auto napi_type = buf.TypedArrayType();
std::memcpy(_tensor.data(get_ov_type(napi_type)), buf.ArrayBuffer().Data(), _tensor.get_byte_size());
} catch (std::exception& e) {
reportError(info.Env(), e.what());
}
}

Napi::Value TensorWrap::get_shape(const Napi::CallbackInfo& info) {
if (info.Length() > 0) {
reportError(info.Env(), "No parameters are allowed for the getShape() method.");
Expand Down
33 changes: 33 additions & 0 deletions src/bindings/js/node/tests/tensor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,39 @@ describe('Tensor data', () => {
assert.deepStrictEqual(tensor.getData(), data);
});

it('Test tensor.data setter - different element type throws', () => {
const float64_data = Float64Array.from([1, 2, 3] );
const tensor = new ov.Tensor(ov.element.f32, [1, 3]);
assert.throws(() => {
tensor.data = float64_data;},
/Passed array must have the same size as the Tensor!/
);
});

it('Test tensor.data setter - different element length throws', () => {
const float64_data = Float64Array.from([1, 2, 3] );
const tensor = new ov.Tensor(ov.element.f64, [1, 2]);
assert.throws(() => {
tensor.data = float64_data;},
/Passed array must have the same size as the Tensor!/
);
});

it('Test tensor.data setter - not TypedArray arg throws', () => {
const testString = 'test';
const tensor = new ov.Tensor(ov.element.f64, [1, 2]);
assert.throws(() => {
tensor.data = testString;},
/Passed argument must be a TypedArray./
);
});

it('Test tensor.data setter', () => {
const tensor = new ov.Tensor(ov.element.f32, shape);
tensor.data = data;
assert.deepStrictEqual(tensor.getData(), data);
});

it('Set tensor data with Float32Array created from ArrayBuffer', () => {
const size = elemNum * 4;
const buffer = new ArrayBuffer(size);
Expand Down

0 comments on commit 92b5d6a

Please sign in to comment.