-
Notifications
You must be signed in to change notification settings - Fork 3k
[OV JS] Improve Core.importModel implementation: SharedStreamBuffer + variant source #33658
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
[OV JS] Improve Core.importModel implementation: SharedStreamBuffer + variant source #33658
Conversation
…or sources - Add required includes: <variant>, <istream>, <memory>, shared_buffer.hpp - Replace std::stringstream with std::variant<monostate, BufferSource, TensorSource> - BufferSource: pins JS Buffer with ObjectReference, stores SharedStreamBuffer - TensorSource: pins JS TensorWrap, stores ov::Tensor directly - Add _error_msg field for proper async error propagation - Use ov::AnyMap instead of std::map<std::string, ov::Any> Part of issue openvinotoolkit#33601: Improve implementation of Node.js API Core.import_model()
- Add required includes: <type_traits>, tensor.hpp, shared_buffer.hpp - Replace direct _stream access with std::visit over source variant - BufferSource: create local std::istream over SharedStreamBuffer - TensorSource: call import_model(tensor, ...) directly - Add try/catch with proper Promise rejection on error - Handle both std::exception and unknown exceptions Part of issue openvinotoolkit#33601: Improve implementation of Node.js API Core.import_model()
…nsor support - Replace std::string + std::stringstream with ov::SharedStreamBuffer (zero-copy) - Add Tensor input support using cast_to_tensor and direct tensor overload - Use ov::js::validate pattern for consistent argument validation - Use ov::AnyMap for config consistency - Proper static_cast for Buffer length to size_t Part of issue openvinotoolkit#33601: Improve implementation of Node.js API Core.import_model()
- Use std::make_unique for RAII safety (no leak if setup throws) - Add Tensor input support: pin TensorWrap with ObjectReference - Buffer input: pin JS Buffer, use SharedStreamBuffer (zero-copy) - Store source in variant, worker thread dispatches via std::visit - Consistent ov::AnyMap usage for config Part of issue openvinotoolkit#33601: Improve implementation of Node.js API Core.import_model()
Auto-format core_wrap.hpp and core_wrap.cpp to match project code style.
- importModelSync now uses ov::js::validate() and reports standardized parameter mismatch errors - Update basic.test.js to assert on the new stable error message instead of legacy per-arg type strings Part of issue openvinotoolkit#33601: Improve implementation of Node.js API Core.import_model()
|
build_jenkins |
almilosz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR! I left a few comments
| auto callback = [](Napi::Env env, Napi::Function, ImportModelContext* context) { | ||
| context->deferred.Resolve(cpp_to_js(env, context->_compiled_model)); | ||
| if (!context->_error_msg.empty()) { | ||
| context->deferred.Reject(Napi::Error::New(env, context->_error_msg).Value()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have a test for such scenario?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I added a test case for this.
There’s a new unit test that passes an invalid/empty buffer and asserts Core.importModel() returns a Promise that rejects.
it("importModel rejects promise on invalid buffer", async () => { const invalid = Buffer.alloc(0); await assert.rejects(core.importModel(invalid, "CPU"), Error); });
| assert.throws( | ||
| () => core.importModelSync(model, "CPU"), | ||
| /'importModelSync' method called with incorrect parameters./, | ||
| /'importModelSync' method called with incorrect parameters\./, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| /'importModelSync' method called with incorrect parameters\./, | |
| /'importModelSync' method called with incorrect parameters./, |
looks unnecessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’ve reverted/simplified those assertions in basic.test.js to drop the \. escape .
I originally escaped the dot to match the trailing period literally when tightening the tests around the validation error output, but it doesn’t really add signal and just makes the check more brittle/noisy. This should be cleaner now.
|
|
||
| context->_compiled_model = std::visit( | ||
| [&](auto& src) -> ov::CompiledModel { | ||
| using T = std::decay_t<decltype(src)>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For better readability/consistency you can use std::visit(overloaded as here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks . Updated to std::visit().
| // Returns a Promise to JS. Method import_model() is performed on additional thread. | ||
| return context_data->deferred.Promise(); | ||
| context_data->nativeThread = std::thread(importModelThread, context_data.get(), std::ref(_mutex)); | ||
| auto* raw = context_data.release(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we need raw pointer anyway, let's go back to auto context_data = new ImportModelContext(env, _core);
Or is there a different reason for this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point — no other reason.
I used the make_unique() + release() approach mainly to avoid leaks if something fails during setup, but since we end up needing a raw pointer anyway, it’s not the clearest pattern here.
I’ve switched it back to new ImportModelContext(...) and kept the same safety. This should be easier to follow and consistent with the rest of the file.
| buf_src.data = reinterpret_cast<const char*>(buf.Data()); | ||
| buf_src.size = static_cast<size_t>(buf.Length()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Those are used only to create SharedStreamBuffer and can be removed from ImportModelContext::BufferSource
| ov::SharedStreamBuffer shared_buffer(reinterpret_cast<const char*>(model_data.Data()), | ||
| static_cast<size_t>(model_data.Length())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| ov::SharedStreamBuffer shared_buffer(reinterpret_cast<const char*>(model_data.Data()), | |
| static_cast<size_t>(model_data.Length())); | |
| ov::SharedStreamBuffer shared_buffer(model_data.Data(), model_data.Length()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes I made those changes.
- Remove explicit casts in SharedStreamBuffer construction - Use overloaded helper for std::visit instead of if constexpr chain - Remove redundant data/size fields from BufferSource struct - Use raw new with try-catch guard instead of make_unique + release - Remove unnecessary regex escape in test assertions - Add test for importModel promise rejection on invalid buffer
Fixes #33601
What / Why
This refactors the Node.js bindings for
Core.importModelSync()andCore.importModel().The async Buffer path was still going through
std::string→std::stringstream, which adds extra copies. Also, the async worker didn’t cleanly separate “import from stream bytes” vs “import from tensor”, so it was easy to end up treating tensor data like a stream. This change switches Buffer imports toov::SharedStreamBufferand makes the async worker explicitly dispatch based on input type.No JS API changes — just internal implementation cleanup.
What changed
ov::SharedStreamBuffer+std::istreaminstead of building astd::stringstream.ImportModelContextnow stores the input asstd::variant<std::monostate, BufferSource, TensorSource>.BufferSourcekeeps a persistent reference to the JS Buffer so it can’t be GC’d while the worker is running, and wraps the underlying bytes withov::SharedStreamBuffer.TensorSourcekeeps a persistent reference to the JS object backingTensorWrap, and storesov::Tensordirectly.importModelThreadusesstd::visitto call the rightov::Core::import_modeloverload (stream vs tensor).Error.Files changed
src/bindings/js/node/include/core_wrap.hppsrc/bindings/js/node/src/core_wrap.cppsrc/bindings/js/node/tests/unit/basic.test.jsChecks
Ran:
npm run lintnpm run formatnpm testCC: @almilosz @Retribution98