[JS API] Add config param to Core.import_model() (#24023)

### Details:
 - add `Core.import_model(stream, device_name, config[optional})
- add ` ov::AnyMap to_anyMap(const Napi::Env&, const Napi::Value&)`
helper for conversion from Napi::Value to ov::AnyMap and reuse it in
other methods

### Tickets:
 - *136460*
This commit is contained in:
Alicja Miloszewska 2024-04-18 11:59:03 +02:00 committed by GitHub
parent 6029d1012d
commit cac2777c5e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 120 additions and 53 deletions

View File

@ -172,6 +172,8 @@ bool acceptableType(const Napi::Value& val, const std::vector<napi_types>& accep
Napi::Value any_to_js(const Napi::CallbackInfo& info, ov::Any value);
ov::Any js_to_any(const Napi::CallbackInfo& info, Napi::Value value);
ov::Any js_to_any(const Napi::Env& env, const Napi::Value& value);
bool is_napi_value_int(const Napi::CallbackInfo& info, Napi::Value& num);
bool is_napi_value_int(const Napi::Env& env, const Napi::Value& num);
ov::AnyMap to_anyMap(const Napi::Env&, const Napi::Value&);

View File

@ -37,6 +37,11 @@ interface Core {
readModelSync(modelPath: string, weightsPath?: string): Model;
readModelSync(modelBuffer: Uint8Array, weightsBuffer?: Uint8Array): Model;
importModelSync(modelStream: Buffer, device: string): CompiledModel;
importModelSync(
modelStream: Buffer,
device: string,
props: { [key: string]: string | number | boolean }
): CompiledModel;
getAvailableDevices(): string[];
getVersions(deviceName: string): {
[deviceName: string]: {

View File

@ -27,7 +27,6 @@ std::tuple<ov::AnyMap, std::string> try_get_set_property_parameters(const Napi::
validate_set_property_args(info);
std::string device_name;
ov::AnyMap properties;
const size_t args_length = info.Length();
@ -35,16 +34,7 @@ std::tuple<ov::AnyMap, std::string> try_get_set_property_parameters(const Napi::
device_name = info[0].ToString();
const size_t parameters_position_index = device_name.empty() ? 0 : 1;
Napi::Object parameters = info[parameters_position_index].ToObject();
const auto& keys = parameters.GetPropertyNames();
for (uint32_t i = 0; i < keys.Length(); ++i) {
auto property_name = static_cast<Napi::Value>(keys[i]).ToString().Utf8Value();
ov::Any any_value = js_to_any(info, parameters.Get(property_name));
properties.insert(std::make_pair(property_name, any_value));
}
const auto& properties = to_anyMap(info.Env(), info[parameters_position_index]);
return std::make_tuple(properties, device_name);
}
@ -301,25 +291,38 @@ Napi::Value CoreWrap::get_versions(const Napi::CallbackInfo& info) {
}
Napi::Value CoreWrap::import_model(const Napi::CallbackInfo& info) {
if (info.Length() != 2) {
reportError(info.Env(), "Invalid number of arguments -> " + std::to_string(info.Length()));
return info.Env().Undefined();
}
if (!info[0].IsBuffer()) {
reportError(info.Env(), "The first argument must be of type Buffer.");
return info.Env().Undefined();
}
if (!info[1].IsString()) {
reportError(info.Env(), "The second argument must be of type String.");
return info.Env().Undefined();
}
const auto& model_data = info[0].As<Napi::Buffer<uint8_t>>();
const auto model_stream = std::string(reinterpret_cast<char*>(model_data.Data()), model_data.Length());
std::stringstream _stream;
_stream << model_stream;
try {
if (!info[0].IsBuffer()) {
OPENVINO_THROW("The first argument must be of type Buffer.");
}
if (!info[1].IsString()) {
OPENVINO_THROW("The second argument must be of type String.");
}
const auto& model_data = info[0].As<Napi::Buffer<uint8_t>>();
const auto model_stream = std::string(reinterpret_cast<char*>(model_data.Data()), model_data.Length());
std::stringstream _stream;
_stream << model_stream;
const auto& compiled = _core.import_model(_stream, std::string(info[1].ToString()));
return CompiledModelWrap::wrap(info.Env(), compiled);
ov::CompiledModel compiled;
switch (info.Length()) {
case 2: {
compiled = _core.import_model(_stream, std::string(info[1].ToString()));
break;
}
case 3: {
compiled = _core.import_model(_stream, std::string(info[1].ToString()), to_anyMap(info.Env(), info[2]));
break;
}
default: {
OPENVINO_THROW("Invalid number of arguments -> " + std::to_string(info.Length()));
}
}
return CompiledModelWrap::wrap(info.Env(), compiled);
} catch (std::exception& e) {
reportError(info.Env(), e.what());
return info.Env().Undefined();
}
}
Napi::Value CoreWrap::set_property(const Napi::CallbackInfo& info) {

View File

@ -510,7 +510,7 @@ Napi::Value any_to_js(const Napi::CallbackInfo& info, ov::Any value) {
return info.Env().Undefined();
}
ov::Any js_to_any(const Napi::CallbackInfo& info, Napi::Value value) {
ov::Any js_to_any(const Napi::Env& env, const Napi::Value& value) {
if (value.IsString()) {
return ov::Any(value.ToString().Utf8Value());
} else if (value.IsBigInt()) {
@ -526,7 +526,7 @@ ov::Any js_to_any(const Napi::CallbackInfo& info, Napi::Value value) {
} else if (value.IsNumber()) {
Napi::Number num = value.ToNumber();
if (is_napi_value_int(info, value)) {
if (is_napi_value_int(env, value)) {
return ov::Any(num.Int32Value());
} else {
return ov::Any(num.DoubleValue());
@ -538,14 +538,25 @@ ov::Any js_to_any(const Napi::CallbackInfo& info, Napi::Value value) {
}
}
bool is_napi_value_int(const Napi::CallbackInfo& info, Napi::Value& num) {
return info.Env()
.Global()
.Get("Number")
.ToObject()
.Get("isInteger")
.As<Napi::Function>()
.Call({num})
.ToBoolean()
.Value();
bool is_napi_value_int(const Napi::Env& env, const Napi::Value& num) {
return env.Global().Get("Number").ToObject().Get("isInteger").As<Napi::Function>().Call({num}).ToBoolean().Value();
}
ov::AnyMap to_anyMap(const Napi::Env& env, const Napi::Value& val) {
ov::AnyMap properties;
if (!val.IsObject()) {
OPENVINO_THROW("Passed Napi::Value must be an object.");
}
const auto& parameters = val.ToObject();
const auto& keys = parameters.GetPropertyNames();
for (uint32_t i = 0; i < keys.Length(); ++i) {
const auto& property_name = static_cast<Napi::Value>(keys[i]).ToString().Utf8Value();
ov::Any any_value = js_to_any(env, parameters.Get(property_name));
properties.insert(std::make_pair(property_name, any_value));
}
return properties;
}

View File

@ -14,10 +14,10 @@ const compiledModel = core.compileModelSync(model, 'CPU');
const modelLike = [[model],
[compiledModel]];
it('Core.getAvailableDevices()', () => {
const devices = core.getAvailableDevices();
assert.ok(devices.includes('CPU'));
it('Core.getAvailableDevices()', () => {
const devices = core.getAvailableDevices();
assert.ok(devices.includes('CPU'));
});
describe('Core.getVersions()', () => {
@ -214,16 +214,62 @@ describe('Input class for ov::Input<const ov::Node>', () => {
});
it('Test exportModel()/importModel()', () => {
describe('Test exportModel()/importModel()', () => {
const userStream = compiledModel.exportModelSync();
const newCompiled = core.importModelSync(userStream, 'CPU');
const epsilon = 0.5;
const tensor = Float32Array.from({ length: 3072 }, () => (Math.random() + epsilon));
const inferRequest = compiledModel.createInferRequest();
const res1 = inferRequest.infer([tensor]);
const newInferRequest = newCompiled.createInferRequest();
const res2 = newInferRequest.infer([tensor]);
assert.deepStrictEqual(res1['fc_out'].data[0], res2['fc_out'].data[0]);
it('Test importModel(stream, device)', () => {
const newCompiled = core.importModelSync(userStream, 'CPU');
const newInferRequest = newCompiled.createInferRequest();
const res2 = newInferRequest.infer([tensor]);
assert.deepStrictEqual(res1['fc_out'].data[0], res2['fc_out'].data[0]);
});
it('Test importModel(stream, device, config)', () => {
const newCompiled = core.importModelSync(userStream, 'CPU', { 'NUM_STREAMS': 1 });
const newInferRequest = newCompiled.createInferRequest();
const res2 = newInferRequest.infer([tensor]);
assert.deepStrictEqual(res1['fc_out'].data[0], res2['fc_out'].data[0]);
});
it('Test importModel(stream, device) throws', () => {
assert.throws(
() => core.importModelSync(epsilon, 'CPU'),
/The first argument must be of type Buffer./
);
});
it('Test importModel(stream, device) throws', () => {
assert.throws(
() => core.importModelSync(userStream, tensor),
/The second argument must be of type String./
);
});
it('Test importModel(stream, device, config: tensor) throws', () => {
assert.throws(
() => core.importModelSync(userStream, 'CPU', tensor),
/NotFound: Unsupported property 0 by CPU plugin./
);
});
it('Test importModel(stream, device, config: string) throws', () => {
const testString = 'test';
assert.throws(
() => core.importModelSync(userStream, 'CPU', testString),
/Passed Napi::Value must be an object./
);
});
it('Test importModel(stream, device, config: unsupported property) throws', () => {
const tmpDir = '/tmp';
assert.throws(
() => core.importModelSync(userStream, 'CPU', {'CACHE_DIR': tmpDir}),
/Unsupported property CACHE_DIR by CPU plugin./
);
});
});