Skip to content

Commit 329489c

Browse files
[JS OV] Extend js bindings supported platforms (openvinotoolkit#21995)
* Fix npm variables list * Fix typo * Provide instructions for js bindings arm compilation * Remove extra spaces * Set optimal scope of flags for both archs * Support windows compilation * Extend .gitignore * Unify compilation instruction * Exclude async infer from windows build * Enable js on windows * Activate js validation in windows workflow * Fix precision
1 parent 7af56f1 commit 329489c

14 files changed

+294
-48
lines changed

.github/workflows/windows.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,7 @@ jobs:
201201
cmake --build ${{ env.BUILD_DIR }} --parallel --config ${{ env.CMAKE_BUILD_TYPE }} --verbose
202202
203203
- name: CMake configure, build and install - OpenVINO JS API
204-
if: ${{ 'false' }} # 128689
205-
# if: fromJSON(needs.smart_ci.outputs.affected_components).JS_API
204+
if: fromJSON(needs.smart_ci.outputs.affected_components).JS_API
206205
run:
207206
cmake -DCPACK_GENERATOR=NPM -DENABLE_SYSTEM_TBB=OFF -UTBB* -S ${{ env.OPENVINO_REPO }} -B ${{ env.BUILD_DIR }}
208207

cmake/features.cmake

+1-2
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,7 @@ ov_dependent_option (ENABLE_SYSTEM_SNAPPY "Enables use of system version of Snap
177177
ov_dependent_option (ENABLE_PYTHON_PACKAGING "Enables packaging of Python API in APT / YUM" OFF
178178
"ENABLE_PYTHON;UNIX" OFF)
179179

180-
ov_dependent_option (ENABLE_JS "Enables JS API building" ON
181-
"NOT WIN32" OFF)
180+
ov_option(ENABLE_JS "Enables JS API building" ON)
182181

183182
ov_option(ENABLE_OPENVINO_DEBUG "Enable output for OPENVINO_DEBUG statements" OFF)
184183

src/bindings/js/node/.gitignore

+4
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ dist
33
build
44
types
55
ov_runtime
6+
7+
8+
*.exp
9+
*.lib

src/bindings/js/node/CMakeLists.txt

+16-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ if(CMAKE_COMPILER_IS_GNUCXX AND LINUX AND CMAKE_CXX_COMPILER_VERSION VERSION_LES
1212
return()
1313
endif()
1414

15+
if(WIN32)
16+
set(CMAKE_JS_LIB ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/node.lib)
17+
set(CMAKE_JS_SRC ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/win_delay_load_hook.cc)
18+
19+
set(CMAKE_JS_NODELIB_DEF ${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/node-lib.def)
20+
set(CMAKE_JS_NODELIB_TARGET ${CMAKE_JS_LIB})
21+
endif()
22+
1523
cmake_minimum_required(VERSION 3.14)
1624

1725
project(ov_node_addon)
@@ -57,6 +65,8 @@ add_library(${PROJECT_NAME} SHARED
5765
${CMAKE_CURRENT_SOURCE_DIR}/src/addon.cpp
5866
${CMAKE_CURRENT_SOURCE_DIR}/src/element_type.cpp
5967
${CMAKE_CURRENT_SOURCE_DIR}/src/partial_shape_wrap.cpp
68+
69+
${CMAKE_JS_SRC}
6070
)
6171

6272
target_include_directories(${PROJECT_NAME} PRIVATE
@@ -65,7 +75,12 @@ target_include_directories(${PROJECT_NAME} PRIVATE
6575
"${CMAKE_CURRENT_SOURCE_DIR}/include"
6676
)
6777

68-
target_link_libraries(${PROJECT_NAME} PRIVATE openvino::runtime)
78+
target_link_libraries(${PROJECT_NAME} PRIVATE openvino::runtime ${CMAKE_JS_LIB})
79+
80+
if(MSVC AND CMAKE_JS_NODELIB_DEF AND CMAKE_JS_NODELIB_TARGET)
81+
# Generate node.lib
82+
execute_process(COMMAND ${CMAKE_AR} /def:${CMAKE_JS_NODELIB_DEF} /out:${CMAKE_JS_NODELIB_TARGET} ${CMAKE_STATIC_LINKER_FLAGS})
83+
endif()
6984

7085
if(CMAKE_COMPILER_IS_GNUCXX OR OV_COMPILER_IS_CLANG)
7186
ov_add_compiler_flags(-Wno-missing-declarations)

src/bindings/js/node/README.md

+19-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,25 @@
1111

1212
- Make sure that all submodules are updated `git submodule update --init --recursive`
1313
- Create build dir `mkdir build && cd build`
14-
- To get binaries for openvinojs-node package run:
15-
`cmake -DCPACK_GENERATOR=NPM -DENABLE_SYSTEM_TBB=OFF -UTBB* -DCMAKE_INSTALL_PREFIX=../src/bindings/js/node/bin ..`
14+
- To configure binaries building run:
15+
16+
### Linux x86, Linux arm, Mac x86, Mac arm
17+
```bash
18+
cmake \
19+
-DCMAKE_BUILD_TYPE=Release \
20+
-DENABLE_FASTER_BUILD=ON \
21+
-DCPACK_GENERATOR=NPM \
22+
-DENABLE_SYSTEM_TBB=OFF -UTBB* \
23+
-DENABLE_TESTS=OFF \
24+
-DENABLE_SAMPLES=OFF \
25+
-DENABLE_WHEEL=OFF \
26+
-DENABLE_PYTHON=OFF \
27+
-DENABLE_INTEL_GPU=OFF \
28+
-DCMAKE_INSTALL_PREFIX=../src/bindings/js/node/bin \
29+
..
30+
```
31+
32+
- To get binaries for openvinojs-node package run compilation:
1633
`make --jobs=$(nproc --all) install`
1734
- Go to npm package folder `cd ../src/bindings/js/node`
1835
- Now you can install dependencies packages and transpile ts to js code. Run `npm install`

src/bindings/js/node/include/infer_request.hpp

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#pragma once
55
#include <napi.h>
6+
67
#include <thread>
78

89
#include "openvino/runtime/infer_request.hpp"
@@ -101,8 +102,11 @@ class InferRequestWrap : public Napi::ObjectWrap<InferRequestWrap> {
101102
/** @brief Checks incoming Napi::Value and calls overloaded infer() method */
102103
Napi::Value infer_dispatch(const Napi::CallbackInfo& info);
103104

105+
// 128760
106+
#ifndef _WIN32
104107
/** @brief Checks incoming Napi::Value and asynchronously returns the result of inference. */
105108
Napi::Value infer_async(const Napi::CallbackInfo& info);
109+
#endif
106110

107111
/** @brief Infers specified inputs in synchronous mode.
108112
* @param inputs An object with a collection of pairs key (input_name) and a value (tensor, tensor's data)

src/bindings/js/node/src/compiled_model.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ Napi::Value CompiledModelWrap::get_outputs(const Napi::CallbackInfo& info) {
7070
auto cm_outputs = _compiled_model.outputs(); // Output<Node>
7171
Napi::Array js_outputs = Napi::Array::New(info.Env(), cm_outputs.size());
7272

73-
size_t i = 0;
73+
uint32_t i = 0;
7474
for (auto& out : cm_outputs)
7575
js_outputs[i++] = Output<const ov::Node>::wrap(info.Env(), out);
7676

@@ -104,7 +104,7 @@ Napi::Value CompiledModelWrap::get_inputs(const Napi::CallbackInfo& info) {
104104
auto cm_inputs = _compiled_model.inputs(); // Output<Node>
105105
Napi::Array js_inputs = Napi::Array::New(info.Env(), cm_inputs.size());
106106

107-
size_t i = 0;
107+
uint32_t i = 0;
108108
for (auto& out : cm_inputs)
109109
js_inputs[i++] = Output<const ov::Node>::wrap(info.Env(), out);
110110

src/bindings/js/node/src/core_wrap.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ Napi::Value CoreWrap::get_available_devices(const Napi::CallbackInfo& info) {
221221
const auto& devices = _core.get_available_devices();
222222
Napi::Array js_devices = Napi::Array::New(info.Env(), devices.size());
223223

224-
size_t i = 0;
224+
uint32_t i = 0;
225225
for (const auto& dev : devices)
226226
js_devices[i++] = dev;
227227

src/bindings/js/node/src/helper.cpp

+8-8
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ std::vector<size_t> js_to_cpp<std::vector<size_t>>(const Napi::CallbackInfo& inf
6767

6868
std::vector<size_t> nativeArray;
6969

70-
for (size_t i = 0; i < arrayLength; ++i) {
70+
for (uint32_t i = 0; i < arrayLength; ++i) {
7171
Napi::Value arrayItem = array[i];
7272
if (!arrayItem.IsNumber()) {
7373
OPENVINO_THROW(std::string("Passed array must contain only numbers."));
@@ -107,7 +107,7 @@ std::unordered_set<std::string> js_to_cpp<std::unordered_set<std::string>>(
107107

108108
std::unordered_set<std::string> nativeArray;
109109

110-
for (size_t i = 0; i < arrayLength; ++i) {
110+
for (uint32_t i = 0; i < arrayLength; ++i) {
111111
Napi::Value arrayItem = array[i];
112112
if (!arrayItem.IsString()) {
113113
OPENVINO_THROW(std::string("Passed array must contain only strings."));
@@ -199,7 +199,7 @@ std::map<std::string, ov::Any> js_to_cpp<std::map<std::string, ov::Any>>(
199199
const auto& config = elem.ToObject();
200200
const auto& keys = config.GetPropertyNames();
201201

202-
for (size_t i = 0; i < keys.Length(); ++i) {
202+
for (uint32_t i = 0; i < keys.Length(); ++i) {
203203
const std::string& option = static_cast<Napi::Value>(keys[i]).ToString();
204204
properties_to_cpp[option] = js_to_cpp<ov::Any>(config.Get(option), {napi_string});
205205
}
@@ -216,7 +216,7 @@ Napi::String cpp_to_js<ov::element::Type_t, Napi::String>(const Napi::CallbackIn
216216
template <>
217217
Napi::Array cpp_to_js<ov::Shape, Napi::Array>(const Napi::CallbackInfo& info, const ov::Shape shape) {
218218
auto arr = Napi::Array::New(info.Env(), shape.size());
219-
for (size_t i = 0; i < shape.size(); ++i)
219+
for (uint32_t i = 0; i < shape.size(); ++i)
220220
arr[i] = shape[i];
221221
return arr;
222222
}
@@ -226,7 +226,7 @@ Napi::Array cpp_to_js<ov::PartialShape, Napi::Array>(const Napi::CallbackInfo& i
226226
size_t size = shape.size();
227227
Napi::Array dimensions = Napi::Array::New(info.Env(), size);
228228

229-
for (size_t i = 0; i < size; i++) {
229+
for (uint32_t i = 0; i < size; i++) {
230230
ov::Dimension dim = shape[i];
231231

232232
if (dim.is_static()) {
@@ -254,7 +254,7 @@ Napi::Array cpp_to_js<ov::Dimension, Napi::Array>(const Napi::CallbackInfo& info
254254

255255
// Indexes looks wierd, but clear assignment,
256256
// like: interval[0] = value doesn't work here
257-
size_t indexes[] = {0, 1};
257+
uint32_t indexes[] = {0, 1};
258258
interval[indexes[0]] = dim.get_min_length();
259259
interval[indexes[1]] = dim.get_max_length();
260260

@@ -270,13 +270,13 @@ ov::TensorVector parse_input_data(const Napi::Value& input) {
270270
ov::TensorVector parsed_input;
271271
if (input.IsArray()) {
272272
auto inputs = input.As<Napi::Array>();
273-
for (size_t i = 0; i < inputs.Length(); ++i) {
273+
for (uint32_t i = 0; i < inputs.Length(); ++i) {
274274
parsed_input.emplace_back(cast_to_tensor(static_cast<Napi::Value>(inputs[i])));
275275
}
276276
} else if (input.IsObject()) {
277277
auto inputs = input.ToObject();
278278
const auto& keys = inputs.GetPropertyNames();
279-
for (size_t i = 0; i < keys.Length(); ++i) {
279+
for (uint32_t i = 0; i < keys.Length(); ++i) {
280280
auto value = inputs.Get(static_cast<Napi::Value>(keys[i]).ToString().Utf8Value());
281281
parsed_input.emplace_back(cast_to_tensor(static_cast<Napi::Value>(value)));
282282
}

src/bindings/js/node/src/infer_request.cpp

+8-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ Napi::Function InferRequestWrap::get_class(Napi::Env env) {
3030
InstanceMethod("getInputTensor", &InferRequestWrap::get_input_tensor),
3131
InstanceMethod("getOutputTensor", &InferRequestWrap::get_output_tensor),
3232
InstanceMethod("infer", &InferRequestWrap::infer_dispatch),
33+
// 128760
34+
#ifndef _WIN32
3335
InstanceMethod("inferAsync", &InferRequestWrap::infer_async),
36+
#endif
3437
InstanceMethod("getCompiledModel", &InferRequestWrap::get_compiled_model),
3538
});
3639
}
@@ -171,7 +174,7 @@ Napi::Value InferRequestWrap::infer_dispatch(const Napi::CallbackInfo& info) {
171174
}
172175

173176
void InferRequestWrap::infer(const Napi::Array& inputs) {
174-
for (size_t i = 0; i < inputs.Length(); ++i) {
177+
for (uint32_t i = 0; i < inputs.Length(); ++i) {
175178
auto tensor = value_to_tensor(inputs[i], _infer_request, i);
176179
_infer_request.set_input_tensor(i, tensor);
177180
}
@@ -181,7 +184,7 @@ void InferRequestWrap::infer(const Napi::Array& inputs) {
181184
void InferRequestWrap::infer(const Napi::Object& inputs) {
182185
const auto& keys = inputs.GetPropertyNames();
183186

184-
for (size_t i = 0; i < keys.Length(); ++i) {
187+
for (uint32_t i = 0; i < keys.Length(); ++i) {
185188
auto input_name = static_cast<Napi::Value>(keys[i]).ToString().Utf8Value();
186189
auto value = inputs.Get(input_name);
187190
auto tensor = value_to_tensor(value, _infer_request, input_name);
@@ -194,7 +197,8 @@ void InferRequestWrap::infer(const Napi::Object& inputs) {
194197
Napi::Value InferRequestWrap::get_compiled_model(const Napi::CallbackInfo& info) {
195198
return CompiledModelWrap::wrap(info.Env(), _infer_request.get_compiled_model());
196199
}
197-
200+
// 128760
201+
#ifndef _WIN32
198202
void FinalizerCallback(Napi::Env env, void* finalizeData, TsfnContext* context) {
199203
context->native_thread.join();
200204
delete context;
@@ -256,3 +260,4 @@ Napi::Value InferRequestWrap::infer_async(const Napi::CallbackInfo& info) {
256260
context->native_thread = std::thread(performInferenceThread, context);
257261
return context->deferred.Promise();
258262
}
263+
#endif

src/bindings/js/node/src/model_wrap.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ Napi::Value ModelWrap::get_inputs(const Napi::CallbackInfo& info) {
9999
auto cm_inputs = _model->inputs(); // Output<Node>
100100
Napi::Array js_inputs = Napi::Array::New(info.Env(), cm_inputs.size());
101101

102-
size_t i = 0;
102+
uint32_t i = 0;
103103
for (auto& input : cm_inputs)
104104
js_inputs[i++] = Output<ov::Node>::wrap(info.Env(), input);
105105

@@ -110,7 +110,7 @@ Napi::Value ModelWrap::get_outputs(const Napi::CallbackInfo& info) {
110110
auto cm_outputs = _model->outputs(); // Output<Node>
111111
Napi::Array js_outputs = Napi::Array::New(info.Env(), cm_outputs.size());
112112

113-
size_t i = 0;
113+
uint32_t i = 0;
114114
for (auto& out : cm_outputs)
115115
js_outputs[i++] = Output<ov::Node>::wrap(info.Env(), out);
116116

src/bindings/js/node/tests/infer_request.test.js

+29-25
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Copyright (C) 2018-2023 Intel Corporation
33
// SPDX-License-Identifier: Apache-2.0
44

5+
const os = require('node:os');
56
const { addon: ov } = require('..');
67
const assert = require('assert');
78
const { describe, it } = require('node:test');
@@ -78,35 +79,38 @@ describe('InferRequest', () => {
7879
});
7980
});
8081

81-
it('Test inferAsync(inputData: { [inputName: string]: Tensor })', () => {
82-
inferRequestAsync.inferAsync({ data: tensor }).then(result => {
83-
assert.ok(result['fc_out'] instanceof ov.Tensor);
84-
assert.deepStrictEqual(Object.keys(result), ['fc_out']);
85-
assert.deepStrictEqual(result['fc_out'].data.length, 10);}
86-
);
87-
});
82+
// 128760
83+
if (os.platform() !== 'win32') {
84+
it('Test inferAsync(inputData: { [inputName: string]: Tensor })', () => {
85+
inferRequestAsync.inferAsync({ data: tensor }).then(result => {
86+
assert.ok(result['fc_out'] instanceof ov.Tensor);
87+
assert.deepStrictEqual(Object.keys(result), ['fc_out']);
88+
assert.deepStrictEqual(result['fc_out'].data.length, 10);}
89+
);
90+
});
8891

89-
it('Test inferAsync(inputData: Tensor[])', () => {
90-
inferRequestAsync.inferAsync([ tensor ]).then(result => {
91-
assert.ok(result['fc_out'] instanceof ov.Tensor);
92-
assert.deepStrictEqual(Object.keys(result), ['fc_out']);
93-
assert.deepStrictEqual(result['fc_out'].data.length, 10);
92+
it('Test inferAsync(inputData: Tensor[])', () => {
93+
inferRequestAsync.inferAsync([ tensor ]).then(result => {
94+
assert.ok(result['fc_out'] instanceof ov.Tensor);
95+
assert.deepStrictEqual(Object.keys(result), ['fc_out']);
96+
assert.deepStrictEqual(result['fc_out'].data.length, 10);
97+
});
9498
});
95-
});
9699

97-
it('Test inferAsync([data]) throws: Cannot create a tensor from the passed Napi::Value.', () => {
98-
assert.throws(
99-
() => inferRequestAsync.inferAsync(['string']).then(),
100-
/Cannot create a tensor from the passed Napi::Value./
101-
);
102-
});
100+
it('Test inferAsync([data]) throws: Cannot create a tensor from the passed Napi::Value.', () => {
101+
assert.throws(
102+
() => inferRequestAsync.inferAsync(['string']).then(),
103+
/Cannot create a tensor from the passed Napi::Value./
104+
);
105+
});
103106

104-
it('Test inferAsync({ data: "string"}) throws: Cannot create a tensor from the passed Napi::Value.', () => {
105-
assert.throws(
106-
() => inferRequestAsync.inferAsync({data: 'string'}).then(),
107-
/Cannot create a tensor from the passed Napi::Value./
108-
);
109-
});
107+
it('Test inferAsync({ data: "string"}) throws: Cannot create a tensor from the passed Napi::Value.', () => {
108+
assert.throws(
109+
() => inferRequestAsync.inferAsync({data: 'string'}).then(),
110+
/Cannot create a tensor from the passed Napi::Value./
111+
);
112+
});
113+
}
110114

111115
it('Test setInputTensor(tensor)', () => {
112116
inferRequest.setInputTensor(tensor);

0 commit comments

Comments
 (0)