diff --git a/.gitignore b/.gitignore index 15b6d216d..c332ddc0d 100644 --- a/.gitignore +++ b/.gitignore @@ -96,3 +96,7 @@ node/generator/specifications node/PATH_TO_DAWN node/third_party/webnn-polyfill node/node_modules +node/third_party +node/examples/electron/lenet/node_modules +node/examples/electron/image_classification/node_modules +node/examples/console/lenet/node_modules \ No newline at end of file diff --git a/DEPS b/DEPS index 63447f406..7a838e053 100644 --- a/DEPS +++ b/DEPS @@ -18,6 +18,9 @@ deps = { 'node/third_party/webnn-polyfill': { 'url': '{github_git}/webmachinelearning/webnn-polyfill.git@5c5e9f6f5dbbe9dc67c448c8f7a55b6ddf4dee25' }, + 'node/third_party/webnn-samples': { + 'url': '{github_git}/webmachinelearning/webnn-samples.git@65cab3bb9ea626fb5b68c32169e7602bf3796b8f' + }, 'third_party/stb': { 'url': '{github_git}/nothings/stb@b42009b3b9d4ca35bc703f5310eedc74f584be58' }, @@ -29,7 +32,7 @@ deps = { # Dependencies required for backends. 'third_party/DirectML': { - 'url': '{github_git}/microsoft/DirectML.git@e1b29b20a21bd2fb669a0c774f9870f8e9731da6', + 'url': '{github_git}/microsoft/DirectML.git@af53ef743559079bd2ebb1aa83d9f98e87d774e1', 'condition': 'checkout_win', }, 'third_party/oneDNN': { diff --git a/NOTICE b/NOTICE index 683ff4a1c..b146f6a63 100644 --- a/NOTICE +++ b/NOTICE @@ -808,3 +808,215 @@ https://github.com/oneapi-src/oneDNN.git "THIRD-PARTY-PROGRAMS" file. --------------------------------------------------------- + +--------------------------------------------------------- + +https://github.com/openvinotoolkit/openvino_tensorflow.git 37a2e5b6ff1e60217d31340ad3975b41faa39da0 + +==================================== +License for OpenVINO™ integration with TensorFlow +==================================== + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +--------------------------------------------------------- diff --git a/examples/BUILD.gn b/examples/BUILD.gn index 3053c1308..7ed8957c6 100644 --- a/examples/BUILD.gn +++ b/examples/BUILD.gn @@ -57,6 +57,9 @@ static_library("webnn_sample_utils") { } public_configs = [ "${webnn_root}/src/common:dawn_internal" ] + if (is_linux) { + public_configs += [ "//build/config//gcc:rpath_for_built_shared_libraries" ] + } } # Template for samples to avoid listing webnn_sample_utils as a dep every time diff --git a/node/LICENSE b/node/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/node/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/node/README.md b/node/README.md new file mode 100644 index 000000000..3fb453cff --- /dev/null +++ b/node/README.md @@ -0,0 +1,53 @@ +# WebNN-native Binding for Node.js + +*WebNN-native Binding for Node.js is a Node.js [C++ addon](https://nodejs.org/api/addons.html). The implementation is based on WebNN-native C++ API and exposes [WebNN](https://webmachinelearning.github.io/webnn/) JavaScript API. + + +## Prerequisites + +Install [Node.js](https://nodejs.org/). + +For Windows, install [Visual Studio 2019](https://visualstudio.microsoft.com/vs/). + +For Linux, install `build-essential` package. + +**Verified configurations:** + * Node.js 14 LTS + * Windows 10 + * Ubuntu Linux 16.04 + +## Build WebNN-native + +You need to build WebNN native as a C/C++ library by following [WebNN-native README](https://github.com/webmachinelearning/webnn-native#readme), please make sure the end2end tests can work as expected. + +**Note:** In case you have multiple python installations, you might want to use the --script-executable gn flag to instruct gn to use the python 2.x installation. + +## Build and Run + +### Install Node.js modules + +Install node-gyp and other Node.js modules that are required to build the Node.js addon. The `webnn_native_lib_path` must be configured so that WebNN native header files and libraries can be found. The path must be a relative path to the `node` folder. For example, execute the following command: + +```shell script +> npm install --webnn_native_lib_path="../out/Release" +``` + +### Build + +The Node.js addon can also be built after install. For example, execute the following command: + +```shell script +> npm run build --webnn_native_lib_path="../out/Release" +``` + +## Test + +```shell script +> npm test +``` + +## Example + + * [LeNet in Electron.js](examples/electron/lenet/README.md) + * [LeNet in Node.js](examples/console/lenet/README.md) + * [ImageClassification in Electron.js](examples/electron/image_classification/README.md) diff --git a/node/binding.gyp b/node/binding.gyp new file mode 100644 index 000000000..b9cb06380 --- /dev/null +++ b/node/binding.gyp @@ -0,0 +1,83 @@ +{ + 'variables': { + 'WEBNN_NATIVE_DIR': '<@(module_root_dir)/../', + 'WEBNN_NATIVE_LIB_PATH': '<@(module_root_dir)/../<(webnn_native_lib_path)', + }, + 'targets': [ + { + 'target_name': 'webnn_node', + 'sources': [ + "'src/'+f).join(' ')\")", + "'src/ops/'+f).join(' ')\")", + # webnn_cpp.cpp must be relative path. + '../<(webnn_native_lib_path)/gen/src/webnn/webnn_cpp.cpp', + ], + 'cflags!': [ '-fno-exceptions', '-fno-rtti'], + 'cflags_cc!': [ '-fno-exceptions', '-fno-rtti'], + 'default_configuration': 'Release', + 'configurations': { + 'Debug': { + 'msvs_settings': { + 'VCCLCompilerTool': { + 'ExceptionHandling': 1, + 'RuntimeTypeInfo': 'true', + 'RuntimeLibrary': 3 # MultiThreadedDebugDLL (/MDd) + }, + }, + }, + 'Release': { + 'msvs_settings': { + 'VCCLCompilerTool': { + 'ExceptionHandling': 1, + 'RuntimeTypeInfo': 'true', + 'RuntimeLibrary': 2 # MultiThreadedDLL (/MD) + }, + }, + } + }, + 'include_dirs' : [ + ' webnn_node_examples@0.0.1 start +> node main.mjs + +loading elapsed time: 5.00 ms +compilation elapsed time: 33.00 ms +execution elapsed time: 1.00 ms +success for outputs buffer 0,0,0,1,0,0,0,0,0,0 +``` \ No newline at end of file diff --git a/node/examples/console/lenet/main.mjs b/node/examples/console/lenet/main.mjs new file mode 100644 index 000000000..6c2dc7936 --- /dev/null +++ b/node/examples/console/lenet/main.mjs @@ -0,0 +1,41 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +'use strict'; + +import webnn from '../../../node_setup.js'; + +(async function main() { + let module = await import('../../../third_party/webnn-samples/lenet/lenet.js'); + const lenet = new module.LeNet('../../../third_party/webnn-polyfill/test-data/models/lenet_nchw/weights/lenet.bin'); + let start = Date.now(); + const outputOperand = await lenet.load(); + console.log( + `loading elapsed time: ${(Date.now() - start).toFixed(2)} ms`); + + start = Date.now(); + await lenet.build(outputOperand); + const compilationTime = Date.now() - start; + console.log(`compilation elapsed time: ${compilationTime.toFixed(2)} ms`); + + start = Date.now(); + const input = new Float32Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,3,5,4,3,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,47,119,210,164,119,116,10,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,99,233,250,253,252,250,246,72,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,24,224,253,254,253,254,253,230,72,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,105,240,253,226,253,254,250,136,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,8,201,251,213,253,254,248,41,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,86,201,251,254,254,249,48,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,35,207,253,254,252,164,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,7,92,139,249,254,254,250,112,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,43,206,245,251,254,254,254,248,43,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,80,239,253,254,254,254,254,251,143,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,111,239,250,250,250,253,252,168,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,74,90,91,98,234,251,170,5,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,2,2,7,134,250,214,34,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,16,245,253,208,31,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4,2,0,0,0,0,0,4,137,251,250,117,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,25,168,91,4,1,0,1,3,34,233,253,245,40,1,0,0,0,0,0,0,0,0,0,0,0,0,0,4,156,247,210,69,39,7,54,93,201,252,250,138,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,60,195,248,248,218,180,235,249,253,251,205,19,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,133,111,247,249,250,253,254,253,226,11,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,12,115,118,164,245,247,229,57,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,3,3,4,6,6,6,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); + const result = await lenet.predict(input); + const inferenceTime = Date.now() - start.toFixed(2); + console.log(`execution elapsed time: ${inferenceTime.toFixed(2)} ms`); + const expected = [ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0]; + let utils = await import('../../../third_party/webnn-polyfill/test/utils.js'); + utils.checkValue(result, expected); + console.log("success for outputs buffer " + result); +})(); diff --git a/node/examples/console/lenet/package.json b/node/examples/console/lenet/package.json new file mode 100644 index 000000000..f60e39a55 --- /dev/null +++ b/node/examples/console/lenet/package.json @@ -0,0 +1,12 @@ +{ + "name": "lenet", + "version": "0.0.1", + "description": "A Node.js example of LeNet using webnn-native", + "main": "main.mjs", + "author": "WebNN-native Authors", + "license": "Apache-2.0", + "type": "module", + "scripts": { + "start": "node main.mjs" + } +} diff --git a/node/examples/electron/image_classification/README.md b/node/examples/electron/image_classification/README.md new file mode 100644 index 000000000..42d57f646 --- /dev/null +++ b/node/examples/electron/image_classification/README.md @@ -0,0 +1,23 @@ +# An Electron.js example of Image Classification using webnn-native + +### Install + +Firstly, ensure that you have done these steps in [README.md](/node/README.md), then run: +```bash +npm install +``` + +### Run + +```bash +npm start +``` + +You can also run multiple times with "numRuns" to get the median inference time, for example: +```bash +npm start numRuns=100 +``` + +### Screenshot + +![screenshot](screenshot.png) \ No newline at end of file diff --git a/node/examples/electron/image_classification/main.js b/node/examples/electron/image_classification/main.js new file mode 100644 index 000000000..9c7e7a0c8 --- /dev/null +++ b/node/examples/electron/image_classification/main.js @@ -0,0 +1,55 @@ +// Modules to control application life and create native browser window +const {app, BrowserWindow} = require('electron') +const path = require('path') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow = {} + +function createWindow() { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 1220, + height: 840, + webPreferences: { + nodeIntegration: true, + preload: app.getAppPath().replace('image_classification','../../node_setup.js') + } + }) + + // Load the index.html with 'numRunsParm' to run inference multiple times. + let url = `file://${__dirname}/../../../third_party/webnn-samples/image_classification/index.html` + const numRunsParm = '?' + process.argv[2] + mainWindow.loadURL(url + numRunsParm) + + // Emitted when the window is closed. + mainWindow.on('closed', function() { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', function() { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') app.quit() +}) + +app.on( + 'activate', + function() { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) createWindow() + }) + + // In this file you can include the rest of your app's specific main process + // code. You can also put them in separate files and require them here. diff --git a/node/examples/electron/image_classification/package-lock.json b/node/examples/electron/image_classification/package-lock.json new file mode 100644 index 000000000..d7075f807 --- /dev/null +++ b/node/examples/electron/image_classification/package-lock.json @@ -0,0 +1,649 @@ +{ + "name": "image_classification", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@electron/get": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/@electron/get/-/get-1.12.4.tgz", + "integrity": "sha512-6nr9DbJPUR9Xujw6zD3y+rS95TyItEVM0NVjt1EehY2vUWfIgPiIPVHxCvaTS0xr2B+DRxovYVKbuOWqC35kjg==", + "requires": { + "debug": "^4.1.1", + "env-paths": "^2.2.0", + "fs-extra": "^8.1.0", + "global-agent": "^2.0.2", + "global-tunnel-ng": "^2.7.1", + "got": "^9.6.0", + "progress": "^2.0.3", + "semver": "^6.2.0", + "sumchecker": "^3.0.1" + } + }, + "@sindresorhus/is": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", + "integrity": "sha512-9NET910DNaIPngYnLLPeg+Ogzqsi9uM4mSboU5y6p8S5DzMTVEsJZrawi+BoDNUVBa2DhJqQYUFvMDfgU062LQ==" + }, + "@szmarczak/http-timer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-1.1.2.tgz", + "integrity": "sha512-XIB2XbzHTN6ieIjfIMV9hlVcfPU26s2vafYWQcZHWXHOxiaRZYEDKEwdl129Zyg50+foYV2jCgtrqSA6qNuNSA==", + "requires": { + "defer-to-connect": "^1.0.1" + } + }, + "@types/node": { + "version": "12.20.15", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.15.tgz", + "integrity": "sha512-F6S4Chv4JicJmyrwlDkxUdGNSplsQdGwp1A0AJloEVDirWdZOAiRHhovDlsFkKUrquUXhz1imJhXHsf59auyAg==" + }, + "boolean": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.1.0.tgz", + "integrity": "sha512-K6r5tvO1ykeYerI7jIyTvSFw2l6D6DzqkljGj2E2uyYAAdDo2SV4qGJIV75cHIQpTFyb6BB0BEHiDdDrFsNI+g==", + "optional": true + }, + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, + "cacheable-request": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-6.1.0.tgz", + "integrity": "sha512-Oj3cAGPCqOZX7Rz64Uny2GYAZNliQSqfbePrgAQ1wKAihYmCUnraBtJtKcGR4xz7wF+LoJC+ssFZvv5BgF9Igg==", + "requires": { + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^3.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^4.1.0", + "responselike": "^1.0.2" + }, + "dependencies": { + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "requires": { + "pump": "^3.0.0" + } + }, + "lowercase-keys": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", + "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" + } + } + }, + "clone-response": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "config-chain": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", + "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", + "optional": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "core-js": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.14.0.tgz", + "integrity": "sha512-3s+ed8er9ahK+zJpp9ZtuVcDoFzHNiZsPbNAAE4KXgrRHbjSqqNN6xGSXq6bq7TZIbKj4NLrLb6bJ5i+vSVjHA==", + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decompress-response": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", + "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", + "requires": { + "mimic-response": "^1.0.0" + } + }, + "defer-to-connect": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-1.1.3.tgz", + "integrity": "sha512-0ISdNousHvZT2EiFlZeZAHBUvSxmKswVCEf8hW7KWgG4a8MVEu/3Vb6uWYozkjylyCxe0JBIiRB1jV45S70WVQ==" + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "optional": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "detect-node": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", + "optional": true + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "electron": { + "version": "11.4.8", + "resolved": "https://registry.npmjs.org/electron/-/electron-11.4.8.tgz", + "integrity": "sha512-NrxlDZN1sWiDCWWOm5aX+tPGtiLgsCUwNqNFP3eJfY+RPdYLsxYRJDFa1vc4GcuCZEp9kZusINjmpPWsvJdspQ==", + "requires": { + "@electron/get": "^1.0.1", + "@types/node": "^12.0.12", + "extract-zip": "^1.0.3" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "optional": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "requires": { + "once": "^1.4.0" + } + }, + "env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==" + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "optional": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "optional": true + }, + "extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "requires": { + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, + "fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "requires": { + "pend": "~1.2.0" + } + }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "get-stream": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", + "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "requires": { + "pump": "^3.0.0" + } + }, + "global-agent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.2.0.tgz", + "integrity": "sha512-+20KpaW6DDLqhG7JDiJpD1JvNvb8ts+TNl7BPOYcURqCrXqnN1Vf+XVOrkKJAFPqfX+oEhsdzOj1hLWkBTdNJg==", + "optional": true, + "requires": { + "boolean": "^3.0.1", + "core-js": "^3.6.5", + "es6-error": "^4.1.1", + "matcher": "^3.0.0", + "roarr": "^2.15.3", + "semver": "^7.3.2", + "serialize-error": "^7.0.1" + }, + "dependencies": { + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "optional": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "global-tunnel-ng": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/global-tunnel-ng/-/global-tunnel-ng-2.7.1.tgz", + "integrity": "sha512-4s+DyciWBV0eK148wqXxcmVAbFVPqtc3sEtUE/GTQfuU80rySLcMhUmHKSHI7/LDj8q0gDYI1lIhRRB7ieRAqg==", + "optional": true, + "requires": { + "encodeurl": "^1.0.2", + "lodash": "^4.17.10", + "npm-conf": "^1.1.3", + "tunnel": "^0.0.6" + } + }, + "globalthis": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.2.tgz", + "integrity": "sha512-ZQnSFO1la8P7auIOQECnm0sSuoMeaSq0EEdXMBFF2QJO4uNcwbyhSgG3MruWNbFTqCLmxVwGOl7LZ9kASvHdeQ==", + "optional": true, + "requires": { + "define-properties": "^1.1.3" + } + }, + "got": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/got/-/got-9.6.0.tgz", + "integrity": "sha512-R7eWptXuGYxwijs0eV+v3o6+XH1IqVK8dJOEecQfTmkncw9AV4dcw/Dhxi8MdlqPthxxpZyizMzyg8RTmEsG+Q==", + "requires": { + "@sindresorhus/is": "^0.14.0", + "@szmarczak/http-timer": "^1.1.2", + "cacheable-request": "^6.0.0", + "decompress-response": "^3.3.0", + "duplexer3": "^0.1.4", + "get-stream": "^4.1.0", + "lowercase-keys": "^1.0.1", + "mimic-response": "^1.0.1", + "p-cancelable": "^1.0.0", + "to-readable-stream": "^1.0.0", + "url-parse-lax": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==" + }, + "http-cache-semantics": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", + "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "optional": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "json-buffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.0.tgz", + "integrity": "sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "optional": true + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "keyv": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-3.1.0.tgz", + "integrity": "sha512-9ykJ/46SN/9KPM/sichzQ7OvXyGDYKGTaDlKMGCAlg2UK8KRy4jb0d8sFc+0Tt0YYnThq8X2RZgCg74RPxgcVA==", + "requires": { + "json-buffer": "3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "optional": true + }, + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "optional": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "matcher": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz", + "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==", + "optional": true, + "requires": { + "escape-string-regexp": "^4.0.0" + } + }, + "mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "normalize-url": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" + }, + "npm-conf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/npm-conf/-/npm-conf-1.1.3.tgz", + "integrity": "sha512-Yic4bZHJOt9RCFbRP3GgpqhScOY4HH3V2P8yBj6CeYq118Qr+BLXqT2JvpJ00mryLESpgOxf5XlFv4ZjXxLScw==", + "optional": true, + "requires": { + "config-chain": "^1.1.11", + "pify": "^3.0.0" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "optional": true + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + }, + "pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "optional": true + }, + "prepend-http": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-2.0.0.tgz", + "integrity": "sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==" + }, + "proto-list": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", + "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", + "optional": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "responselike": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-1.0.2.tgz", + "integrity": "sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=", + "requires": { + "lowercase-keys": "^1.0.0" + } + }, + "roarr": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz", + "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==", + "optional": true, + "requires": { + "boolean": "^3.0.1", + "detect-node": "^2.0.4", + "globalthis": "^1.0.1", + "json-stringify-safe": "^5.0.1", + "semver-compare": "^1.0.0", + "sprintf-js": "^1.1.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "optional": true + }, + "serialize-error": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz", + "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==", + "optional": true, + "requires": { + "type-fest": "^0.13.1" + } + }, + "sprintf-js": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz", + "integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug==", + "optional": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "sumchecker": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", + "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==", + "requires": { + "debug": "^4.1.0" + } + }, + "to-readable-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-1.0.0.tgz", + "integrity": "sha512-Iq25XBt6zD5npPhlLVXGFN3/gyR2/qODcKNNyTMd4vbm39HUaOiAM4PMq0eMVC/Tkxz+Zjdsc55g9yyz+Yq00Q==" + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "optional": true + }, + "type-fest": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", + "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==", + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "url-parse-lax": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz", + "integrity": "sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=", + "requires": { + "prepend-http": "^2.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "optional": true + }, + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "requires": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + } + } +} diff --git a/node/examples/electron/image_classification/package.json b/node/examples/electron/image_classification/package.json new file mode 100644 index 000000000..87011d592 --- /dev/null +++ b/node/examples/electron/image_classification/package.json @@ -0,0 +1,14 @@ +{ + "name": "image_classification", + "version": "0.0.1", + "description": "An Electon.js example of image_classification using webnn-native", + "main": "main.js", + "author": "WebNN-native Authors", + "license": "Apache-2.0", + "scripts": { + "start": "electron ." + }, + "dependencies": { + "electron": "^11.0.3" + } +} diff --git a/node/examples/electron/image_classification/screenshot.png b/node/examples/electron/image_classification/screenshot.png new file mode 100644 index 000000000..a44bc6569 Binary files /dev/null and b/node/examples/electron/image_classification/screenshot.png differ diff --git a/node/examples/electron/lenet/README.md b/node/examples/electron/lenet/README.md new file mode 100644 index 000000000..9830d11a0 --- /dev/null +++ b/node/examples/electron/lenet/README.md @@ -0,0 +1,23 @@ +# An Electron.js example of LeNet using webnn-native + +### Install + +Firstly, ensure that you have done these steps in [README.md](/node/README.md), then run: +```bash +npm install +``` + +### Run + +```bash +npm start +``` + +You can also run multiple times with "numRuns" to get the median inference time, for example: +```bash +npm start numRuns=100 +``` + +### Screenshot + +![screenshot](screenshot.png) \ No newline at end of file diff --git a/node/examples/electron/lenet/main.js b/node/examples/electron/lenet/main.js new file mode 100644 index 000000000..4e9d5adca --- /dev/null +++ b/node/examples/electron/lenet/main.js @@ -0,0 +1,55 @@ +// Modules to control application life and create native browser window +const {app, BrowserWindow} = require('electron') +const path = require('path') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow + +function createWindow() { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 1220, + height: 840, + webPreferences: { + nodeIntegration: true, + preload: app.getAppPath().replace('lenet','../../node_setup.js') + } + }) + + // Load the index.html with 'numRunsParm' to run inference multiple times. + let url = `file://${__dirname}/../../../third_party/webnn-samples/lenet/index.html` + const numRunsParm = '?' + process.argv[2] + mainWindow.loadURL(url + numRunsParm) + + // Emitted when the window is closed. + mainWindow.on('closed', function() { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.on('ready', createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', function() { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') app.quit() +}) + +app.on( + 'activate', + function() { + // On macOS it's common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) createWindow() + }) + + // In this file you can include the rest of your app's specific main process + // code. You can also put them in separate files and require them here. diff --git a/node/examples/electron/lenet/package.json b/node/examples/electron/lenet/package.json new file mode 100644 index 000000000..7496fc9cb --- /dev/null +++ b/node/examples/electron/lenet/package.json @@ -0,0 +1,14 @@ +{ + "name": "lenet", + "version": "0.0.1", + "description": "An Electon.js example of LeNet using webnn-native", + "main": "main.js", + "author": "WebNN-native Authors", + "license": "Apache-2.0", + "scripts": { + "start": "electron ." + }, + "dependencies": { + "electron": "^11.0.3" + } +} diff --git a/node/examples/electron/lenet/screenshot.png b/node/examples/electron/lenet/screenshot.png new file mode 100644 index 000000000..5756aaf11 Binary files /dev/null and b/node/examples/electron/lenet/screenshot.png differ diff --git a/node/lib/webnn.js b/node/lib/webnn.js new file mode 100644 index 000000000..bc296b2df --- /dev/null +++ b/node/lib/webnn.js @@ -0,0 +1,3 @@ +"use strict"; + +module.exports = require('bindings')(`webnn_node.node`); diff --git a/node/node_setup.js b/node/node_setup.js new file mode 100644 index 000000000..386c16bd2 --- /dev/null +++ b/node/node_setup.js @@ -0,0 +1,7 @@ +global.navigator = require('./lib/webnn'); +global.MLContext = global.navigator.MLContext +global.MLGraphBuilder = global.navigator.MLGraphBuilder +global.MLGraph = global.navigator.MLGraph +global.MLOperand = global.navigator.MLOperand +global.chai = require('chai'); +global.fs = require('fs'); diff --git a/node/package-lock.json b/node/package-lock.json new file mode 100644 index 000000000..751afd2bb --- /dev/null +++ b/node/package-lock.json @@ -0,0 +1,1306 @@ +{ + "name": "webnn-node", + "version": "0.1.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "dev": true, + "requires": { + "lodash": "^4.17.14" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "basic-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", + "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "requires": { + "file-uri-to-path": "1.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chai": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", + "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "corser": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz", + "integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=", + "dev": true + }, + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "ecstatic": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz", + "integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==", + "dev": true, + "requires": { + "he": "^1.1.1", + "mime": "^1.6.0", + "minimist": "^1.1.0", + "url-join": "^2.0.5" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "es-abstract": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.3.tgz", + "integrity": "sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "get-intrinsic": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.2", + "is-callable": "^1.2.3", + "is-negative-zero": "^2.0.1", + "is-regex": "^1.1.3", + "is-string": "^1.0.6", + "object-inspect": "^1.10.3", + "object-keys": "^1.1.1", + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.4", + "string.prototype.trimstart": "^1.0.4", + "unbox-primitive": "^1.0.1" + }, + "dependencies": { + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + } + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "follow-redirects": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", + "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==", + "dev": true + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", + "integrity": "sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "http-proxy": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", + "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "dev": true, + "requires": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + } + }, + "http-server": { + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz", + "integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==", + "dev": true, + "requires": { + "basic-auth": "^1.0.3", + "colors": "^1.4.0", + "corser": "^2.0.1", + "ecstatic": "^3.3.2", + "http-proxy": "^1.18.0", + "minimist": "^1.2.5", + "opener": "^1.5.1", + "portfinder": "^1.0.25", + "secure-compare": "3.0.1", + "union": "~0.5.0" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-bigint": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.2.tgz", + "integrity": "sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.1.tgz", + "integrity": "sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "dev": true + }, + "is-callable": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "dev": true + }, + "is-date-object": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", + "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-negative-zero": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", + "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.5.tgz", + "integrity": "sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw==", + "dev": true + }, + "is-regex": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", + "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-symbols": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.6.tgz", + "integrity": "sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w==", + "dev": true + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + } + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", + "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.5", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "dev": true + }, + "node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", + "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", + "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.18.0-next.2" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "portfinder": { + "version": "1.0.28", + "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", + "integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==", + "dev": true, + "requires": { + "async": "^2.6.2", + "debug": "^3.1.1", + "mkdirp": "^0.5.5" + } + }, + "qs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", + "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", + "dev": true, + "requires": { + "side-channel": "^1.0.4" + } + }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", + "dev": true + }, + "secure-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz", + "integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM=", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz", + "integrity": "sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.4.tgz", + "integrity": "sha512-jh6e984OBfvxS50tdY2nRZnoC5/mLFKOREQfw8t5yytkoUsJRNxvI/E39qu1sD0OtWI3OC0XgKSmcWwziwYuZw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "unbox-primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz", + "integrity": "sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has-bigints": "^1.0.1", + "has-symbols": "^1.0.2", + "which-boxed-primitive": "^1.0.2" + } + }, + "union": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz", + "integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==", + "dev": true, + "requires": { + "qs": "^6.4.0" + } + }, + "url-join": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz", + "integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg=", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + } + } +} diff --git a/node/package.json b/node/package.json new file mode 100644 index 000000000..6f4068741 --- /dev/null +++ b/node/package.json @@ -0,0 +1,41 @@ +{ + "name": "webnn-node", + "version": "0.1.0", + "description": "WebNN-native Binding for Node.js*", + "main": "lib/webnn.js", + "author": "WebNN-native Authors", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/webmachinelearning/webnn-native.git" + }, + "directories": { + "example": "examples", + "lib": "lib", + "test": "third_party/webnn-polyfill/test" + }, + "scripts": { + "build": "node-gyp configure && node-gyp build", + "build-debug": "node-gyp configure --debug && node-gyp build", + "start": "http-server", + "test": "cross-env NODE_ENV=test mocha --require ./node_setup.js third_party/webnn-polyfill/test/*/*.js third_party/webnn-polyfill/test/cts/from_nnapi/tests/cts.js third_party/webnn-polyfill/test/models/**/*.js", + "test-api": "cross-env NODE_ENV=test mocha --require ./node_setup.js third_party/webnn-polyfill/test/api/*.js", + "test-ops": "cross-env NODE_ENV=test mocha --require ./node_setup.js third_party/webnn-polyfill/test/ops/*.js", + "test-cts": "cross-env NODE_ENV=test mocha --require ./node_setup.js third_party/webnn-polyfill/test/cts/from_nnapi/tests/cts.js", + "test-models": "cross-env NODE_ENV=test mocha --require ./node_setup.js third_party/webnn-polyfill/test/models/**/*.js" + }, + "dependencies": { + "bindings": "^1.5.0" + }, + "gypfile": true, + "engines": { + "node": ">= 12.20.1" + }, + "devDependencies": { + "chai": "^4.2.0", + "cross-env": "^7.0.3", + "http-server": "^0.12.3", + "mocha": "^7.1.1", + "node-addon-api": "^3.0.2" + } +} diff --git a/node/src/Context.cpp b/node/src/Context.cpp new file mode 100644 index 000000000..d21fce754 --- /dev/null +++ b/node/src/Context.cpp @@ -0,0 +1,55 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Context.h" + +#include +#include +#include + +Napi::FunctionReference node::Context::constructor; + +namespace node { + + Context::Context(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + WebnnProcTable backendProcs = webnn_native::GetProcs(); + webnnProcSetProcs(&backendProcs); + mImpl = ml::Context::Acquire(webnn_native::CreateContext()); + if (!mImpl) { + Napi::Error::New(info.Env(), "Failed to create Context").ThrowAsJavaScriptException(); + return; + } + mImpl.SetUncapturedErrorCallback( + [](MLErrorType type, char const* message, void* userData) { + if (type != MLErrorType_NoError) { + std::cout << "Uncaptured Error type is " << type << ", message is " << message + << std::endl; + } + }, + this); + } + + ml::Context Context::GetImpl() { + return mImpl; + } + + Napi::Object Context::Initialize(Napi::Env env, Napi::Object exports) { + Napi::HandleScope scope(env); + Napi::Function func = DefineClass(env, "MLContext", {}); + constructor = Napi::Persistent(func); + constructor.SuppressDestruct(); + exports.Set("MLContext", func); + return exports; + } +} // namespace node diff --git a/node/src/Context.h b/node/src/Context.h new file mode 100644 index 000000000..1dfbfef04 --- /dev/null +++ b/node/src/Context.h @@ -0,0 +1,39 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_ML_CONTEXT_H_ +#define NODE_ML_CONTEXT_H_ + +#include +#include + +namespace node { + + class Context : public Napi::ObjectWrap { + public: + static Napi::Object Initialize(Napi::Env env, Napi::Object exports); + static Napi::FunctionReference constructor; + + Context(const Napi::CallbackInfo& info); + ~Context() = default; + + ml::Context GetImpl(); + + private: + ml::Context mImpl; + }; + +} // namespace node + +#endif // NODE_ML_CONTEXT_H_ diff --git a/node/src/Graph.cpp b/node/src/Graph.cpp new file mode 100644 index 000000000..356a18e17 --- /dev/null +++ b/node/src/Graph.cpp @@ -0,0 +1,282 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Graph.h" + +#include +#include + +#include "Utils.h" + +namespace node { + + struct Input { + public: + void const* buffer = nullptr; + size_t size; + std::vector dimensions; + + const ml::Input* AsPtr() { + mInput.buffer = buffer; + mInput.size = size; + if (!dimensions.empty()) { + mInput.dimensions = dimensions.data(); + mInput.dimensionsCount = dimensions.size(); + } + return &mInput; + } + + private: + ml::Input mInput; + }; + + struct Output { + public: + void* buffer = nullptr; + size_t size; + std::vector dimensions; + + const ml::Output* AsPtr() { + mOutput.buffer = buffer; + mOutput.size = size; + if (!dimensions.empty()) { + mOutput.dimensions = dimensions.data(); + mOutput.dimensionsCount = dimensions.size(); + } + return &mOutput; + } + + private: + ml::Output mOutput; + }; + + // Hold Promise::Deferred with AsyncWorker. + class ComputeGraphWorker : public Napi::AsyncWorker { + public: + ComputeGraphWorker(Napi::Env& env, + Napi::Promise::Deferred& deferred, + ml::Graph graph, + std::map inputs, + std::map outputs, + std::vector outputNames) + : Napi::AsyncWorker(env), + mEnv(env), + mDeferred(deferred), + mGraph(graph), + mInputs(std::move(inputs)), + mOutputs(std::move(outputs)), + mOutputNames(std::move(outputNames)) { + } + + ~ComputeGraphWorker() = default; + + void Execute() { + ml::NamedInputs namedInputs = ml::CreateNamedInputs(); + for (auto& input : mInputs) { + namedInputs.Set(input.first.data(), input.second.AsPtr()); + } + ml::NamedOutputs namedOutputs = mOutputs.empty() ? nullptr : ml::CreateNamedOutputs(); + for (auto& output : mOutputs) { + namedOutputs.Set(output.first.data(), output.second.AsPtr()); + } + mGraph.Compute( + namedInputs, + [](MLComputeGraphStatus status, MLNamedResults results, char const* message, + void* userdata) { + ComputeGraphWorker* computeWorker = + reinterpret_cast(userdata); + computeWorker->SetResults(status, results, message); + }, + reinterpret_cast(this), namedOutputs); + } + + void OnOK() { + if (mStatus != ml::ComputeGraphStatus::Success) { + return mDeferred.Reject(Napi::Error::New(mEnv, mMessage).Value()); + } + Napi::Object jsResults = Napi::Object::New(mEnv); + for (auto& name : mOutputNames) { + ml::Result* result = new ml::Result(mNamedResults.Get(name.data())); + if (result->GetHandle() == nullptr) { + // specified outputs. + continue; + } + Napi::Object jsOutput = Napi::Object::New(mEnv); + typedef void (*FinalizerCallback)(Napi::Env env, void* buffer, ml::Result* reslut); + Napi::ArrayBuffer arrayBuffer = + Napi::ArrayBuffer::New( + mEnv, const_cast(result->Buffer()), result->BufferSize(), + [](Napi::Env env, void* buffer, ml::Result* reslut) { delete reslut; }, + result); + // FIXME: handle other data types + Napi::Float32Array float32Array = Napi::Float32Array::New( + mEnv, result->BufferSize() / sizeof(float), arrayBuffer, 0); + jsOutput.Set("data", float32Array); + if (result->Dimensions()) { + Napi::Array jsDimensions = Napi::Array::New(mEnv, result->DimensionsSize()); + for (size_t i = 0; i < result->DimensionsSize(); ++i) { + jsDimensions[i] = Napi::Number::New(mEnv, result->Dimensions()[i]); + } + jsOutput.Set("dimensions", jsDimensions); + } + jsResults.Set(name, jsOutput); + } + mDeferred.Resolve(jsResults); + } + + void SetResults(MLComputeGraphStatus status, MLNamedResults results, char const* message) { + mStatus = static_cast(status); + mNamedResults = mNamedResults.Acquire(results); + if (message) { + mMessage = std::string(message); + } + } + + private: + Napi::Env mEnv; + Napi::Promise::Deferred mDeferred; + ml::Graph mGraph; + ml::ComputeGraphStatus mStatus; + std::string mMessage; + std::map mInputs; + std::map mOutputs; + std::vector mOutputNames; + ml::NamedResults mNamedResults; + }; + + template + bool GetNamedResources(const Napi::Value& jsValue, std::map& namedResources) { + if (!jsValue.IsObject()) { + return false; + } + Napi::Object jsResources = jsValue.As(); + Napi::Array names = jsResources.GetPropertyNames(); + if (names.Length() == 0) { + return false; + } + for (size_t i = 0; i < names.Length(); ++i) { + std::string name = names.Get(i).As().Utf8Value(); + Napi::Object jsResource = jsResources.Get(name).As(); + // dictionary Input { + // required ArrayBufferView buffer; + // sequence dimensions; + // }; + // dictionary Output { + // ArrayBufferView buffer; + // sequence dimensions; + // }; + T resource = {}; + if (!jsResource.Has("data")) { + // Input buffer is required. + return false; + } + int jsElementLength = 0; + if (jsResource.Has("data")) { + if (!jsResource.Get("data").IsTypedArray()) { + return false; + } + + // FIXME: validate the type of typed array. + + Napi::TypedArray jsTypedArray = jsResource.Get("data").As(); + resource.buffer = reinterpret_cast( + reinterpret_cast(jsTypedArray.ArrayBuffer().Data()) + + jsTypedArray.ByteOffset()); + resource.size = jsTypedArray.ByteLength(); + jsElementLength = jsTypedArray.ElementSize(); + } + if (HasOptionMember(jsResource, "dimensions")) { + if (!GetInt32Array(jsResource.Get("dimensions"), resource.dimensions)) { + return false; + } + + // Check dimensions. + if (jsElementLength) { + int dimensionSize = 1; + for (auto& dim : resource.dimensions) { + dimensionSize *= dim; + } + if (dimensionSize != jsElementLength) { + return false; + } + } + } + namedResources[name] = resource; + } + return true; + } + + Napi::FunctionReference Graph::constructor; + + Graph::Graph(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + } + + Napi::Value Graph::Compute(const Napi::CallbackInfo& info) { + // Promise compute(NamedInputs inputs, optional NamedOutputs outputs = {}); + WEBNN_NODE_ASSERT(info.Length() == 1 || info.Length() == 2, + "The number of arguments is invalid."); + std::map inputs; + WEBNN_NODE_ASSERT(GetNamedResources(info[0], inputs), + "The inputs parameter is invalid."); + + std::map outputs; + if (info.Length() > 1) { + WEBNN_NODE_ASSERT(GetNamedResources(info[1], outputs), + "The outputs parameter is invalid."); + } + Napi::Env env = info.Env(); + auto deferred = Napi::Promise::Deferred::New(env); + ComputeGraphWorker* worker = new ComputeGraphWorker(env, deferred, mImpl, std::move(inputs), + std::move(outputs), mOutputNames); + worker->Queue(); + return deferred.Promise(); + } + + Napi::Value Graph::ComputeSync(const Napi::CallbackInfo& info) { + // status compute(NamedInputs inputs, NamedOutputs outputs); + WEBNN_NODE_ASSERT(info.Length() == 2, "The number of arguments is invalid."); + std::map inputs; + WEBNN_NODE_ASSERT(GetNamedResources(info[0], inputs), + "The inputs parameter is invalid."); + + std::map outputs; + WEBNN_NODE_ASSERT(GetNamedResources(info[1], outputs), + "The outputs parameter is invalid."); + + ml::NamedInputs namedInputs = ml::CreateNamedInputs(); + for (auto& input : inputs) { + namedInputs.Set(input.first.data(), input.second.AsPtr()); + } + ml::NamedOutputs namedOutputs = ml::CreateNamedOutputs(); + for (auto& output : outputs) { + namedOutputs.Set(output.first.data(), output.second.AsPtr()); + } + ml::ComputeGraphStatus status = mImpl.ComputeSync(namedInputs, namedOutputs); + + return Napi::Number::New(info.Env(), static_cast(status)); + } + + Napi::Object Graph::Initialize(Napi::Env env, Napi::Object exports) { + Napi::HandleScope scope(env); + Napi::Function func = + DefineClass(env, "MLGraph", + {InstanceMethod("compute", &Graph::Compute, napi_enumerable), + InstanceMethod("computeSync", &Graph::ComputeSync, napi_enumerable)}); + constructor = Napi::Persistent(func); + constructor.SuppressDestruct(); + exports.Set("MLGraph", func); + return exports; + } + +} // namespace node diff --git a/node/src/Graph.h b/node/src/Graph.h new file mode 100644 index 000000000..355771efb --- /dev/null +++ b/node/src/Graph.h @@ -0,0 +1,48 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_GRAPH_H_ +#define NODE_GRAPH_H_ + +#include +#include +#include + +namespace node { + + class BuildGraphWorker; + class GraphBuilder; + + class Graph : public Napi::ObjectWrap { + public: + static Napi::Object Initialize(Napi::Env env, Napi::Object exports); + static Napi::FunctionReference constructor; + + explicit Graph(const Napi::CallbackInfo& info); + ~Graph() = default; + + private: + friend BuildGraphWorker; + friend GraphBuilder; + + Napi::Value Compute(const Napi::CallbackInfo& info); + Napi::Value ComputeSync(const Napi::CallbackInfo& info); + + ml::Graph mImpl; + std::vector mOutputNames; + }; + +} // namespace node + +#endif // NODE_GRAPH_H_ diff --git a/node/src/GraphBuilder.cpp b/node/src/GraphBuilder.cpp new file mode 100644 index 000000000..68e89c565 --- /dev/null +++ b/node/src/GraphBuilder.cpp @@ -0,0 +1,254 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "GraphBuilder.h" + +#include +#include + +#include "Context.h" +#include "Graph.h" +#include "Operand.h" +#include "Utils.h" +#include "ops/BatchNorm.h" +#include "ops/Clamp.h" +#include "ops/Concat.h" +#include "ops/Constant.h" +#include "ops/Conv2d.h" +#include "ops/Gemm.h" +#include "ops/Input.h" +#include "ops/LeakyRelu.h" +#include "ops/Pool2d.h" +#include "ops/Reshape.h" +#include "ops/Transpose.h" + +Napi::FunctionReference node::GraphBuilder::constructor; + +#define BUILD_BINARY(op) \ + WEBNN_NODE_ASSERT(info.Length() == 2, "The number of arguments is invalid."); \ + std::vector args; \ + ml::Operand a; \ + WEBNN_NODE_ASSERT(GetOperand(info[0], a, args), "The a parameter is invalid."); \ + ml::Operand b; \ + WEBNN_NODE_ASSERT(GetOperand(info[1], b, args), "The a parameter is invalid."); \ + Napi::Object object = Operand::constructor.New(args); \ + Operand* operand = Napi::ObjectWrap::Unwrap(object); \ + operand->SetImpl(mImpl.op(a, b)); \ + return object; + +#define BUILD_UNARY(op) \ + WEBNN_NODE_ASSERT(info.Length() == 1, "The number of arguments is invalid."); \ + std::vector args; \ + ml::Operand input; \ + WEBNN_NODE_ASSERT(GetOperand(info[0], input, args), "The input parameter is invalid."); \ + Napi::Object object = Operand::constructor.New(args); \ + Operand* operand = Napi::ObjectWrap::Unwrap(object); \ + operand->SetImpl(mImpl.op(input)); \ + return object; + +namespace node { + + class BuildGraphWorker : public Napi::AsyncWorker { + public: + BuildGraphWorker(Napi::Env& env, + Napi::Promise::Deferred& deferred, + ml::GraphBuilder builder, + ml::NamedOperands namedOperands, + std::vector outputNames) + : Napi::AsyncWorker(env), + mEnv(env), + mDeferred(deferred), + mBuilder(builder), + mNamedOperands(std::move(namedOperands)), + mOutputNames(std::move(outputNames)) { + } + + ~BuildGraphWorker() = default; + + void Execute() { + mBuilder.Build( + mNamedOperands, + [](MLBuildGraphStatus status, MLGraph impl, char const* message, void* userData) { + BuildGraphWorker* asyncWorker = reinterpret_cast(userData); + asyncWorker->SetGraph(status, impl, message); + }, + reinterpret_cast(this)); + } + + void OnOK() { + if (mStatus != ml::BuildGraphStatus::Success) { + return mDeferred.Reject(Napi::Value::From(mEnv, mMessage)); + } + Napi::Object object = node::Graph::constructor.New({}); + node::Graph* jsGraph = Napi::ObjectWrap::Unwrap(object); + jsGraph->mImpl = mGraph; + jsGraph->mOutputNames = std::move(mOutputNames); + mDeferred.Resolve(object); + } + + void SetGraph(MLBuildGraphStatus status, MLGraph impl, char const* message) { + mStatus = static_cast(status); + mGraph = mGraph.Acquire(impl); + if (message) { + mMessage = std::string(message); + } + } + + private: + Napi::Env mEnv; + Napi::Promise::Deferred mDeferred; + ml::GraphBuilder mBuilder; + ml::NamedOperands mNamedOperands; + std::vector mOutputNames; + ml::BuildGraphStatus mStatus; + std::string mMessage; + ml::Graph mGraph; + }; + + GraphBuilder::GraphBuilder(const Napi::CallbackInfo& info) + : Napi::ObjectWrap(info) { + Napi::Object object = info[0].As(); + node::Context* context = Napi::ObjectWrap::Unwrap(object); + mImpl = ml::CreateGraphBuilder(context->GetImpl()); + } + + Napi::Value GraphBuilder::Constant(const Napi::CallbackInfo& info) { + return op::Constant::Build(info, mImpl); + } + + Napi::Value GraphBuilder::Input(const Napi::CallbackInfo& info) { + return op::Input::Build(info, mImpl); + } + + Napi::Value GraphBuilder::Add(const Napi::CallbackInfo& info) { + BUILD_BINARY(Add); + } + + Napi::Value GraphBuilder::Mul(const Napi::CallbackInfo& info) { + BUILD_BINARY(Mul); + } + + Napi::Value GraphBuilder::Matmul(const Napi::CallbackInfo& info) { + BUILD_BINARY(Matmul); + } + + Napi::Value GraphBuilder::BatchNorm(const Napi::CallbackInfo& info) { + return op::BatchNorm::Build(info, mImpl); + } + + Napi::Value GraphBuilder::Conv2d(const Napi::CallbackInfo& info) { + return op::Conv2d::Build(info, mImpl); + } + + Napi::Value GraphBuilder::Concat(const Napi::CallbackInfo& info) { + return op::Concat::Build(info, mImpl); + } + + Napi::Value GraphBuilder::Gemm(const Napi::CallbackInfo& info) { + return op::Gemm::Build(info, mImpl); + } + + Napi::Value GraphBuilder::Clamp(const Napi::CallbackInfo& info) { + return op::Clamp::Build(info, mImpl); + } + + Napi::Value GraphBuilder::MaxPool2d(const Napi::CallbackInfo& info) { + return op::Pool2d::Build(info, mImpl, op::Pool2dType::kMaxPool2d); + } + + Napi::Value GraphBuilder::AveragePool2d(const Napi::CallbackInfo& info) { + return op::Pool2d::Build(info, mImpl, op::Pool2dType::kAveragePool2d); + } + + Napi::Value GraphBuilder::Relu(const Napi::CallbackInfo& info) { + BUILD_UNARY(Relu); + } + + Napi::Value GraphBuilder::Softmax(const Napi::CallbackInfo& info) { + BUILD_UNARY(Softmax); + } + + Napi::Value GraphBuilder::LeakyRelu(const Napi::CallbackInfo& info) { + return op::LeakyRelu::Build(info, mImpl); + } + + Napi::Value GraphBuilder::Reshape(const Napi::CallbackInfo& info) { + return op::Reshape::Build(info, mImpl); + } + + Napi::Value GraphBuilder::Transpose(const Napi::CallbackInfo& info) { + return op::Transpose::Build(info, mImpl); + } + + Napi::Value GraphBuilder::Build(const Napi::CallbackInfo& info) { + // Promise Build(NamedOperands outputs); + WEBNN_NODE_ASSERT(info.Length() == 1, "The number of arguments is invalid."); + Napi::Env env = info.Env(); + auto deferred = Napi::Promise::Deferred::New(env); + ml::NamedOperands namedOperands; + std::vector names; + WEBNN_NODE_ASSERT(GetNamedOperands(info[0], namedOperands, names), + "The outputs parameter is invalid."); + BuildGraphWorker* worker = + new BuildGraphWorker(env, deferred, mImpl, std::move(namedOperands), std::move(names)); + worker->Queue(); + return deferred.Promise(); + } + + Napi::Value GraphBuilder::BuildSync(const Napi::CallbackInfo& info) { + // MLGraph BuildSync(NamedOperands outputs); + WEBNN_NODE_ASSERT(info.Length() == 1, "The number of arguments is invalid."); + ml::NamedOperands namedOperands; + std::vector names; + WEBNN_NODE_ASSERT(GetNamedOperands(info[0], namedOperands, names), + "The outputs parameter is invalid."); + ml::Graph graph = mImpl.BuildSync(namedOperands); + WEBNN_NODE_ASSERT(graph != nullptr, "Failed to build graph."); + Napi::Object object = node::Graph::constructor.New({}); + node::Graph* jsGraph = Napi::ObjectWrap::Unwrap(object); + jsGraph->mImpl = graph; + jsGraph->mOutputNames = names; + return object; + } + + Napi::Object GraphBuilder::Initialize(Napi::Env env, Napi::Object exports) { + Napi::HandleScope scope(env); + Napi::Function func = DefineClass( + env, "MLGraphBuilder", + {InstanceMethod("constant", &GraphBuilder::Constant, napi_enumerable), + InstanceMethod("input", &GraphBuilder::Input, napi_enumerable), + InstanceMethod("add", &GraphBuilder::Add, napi_enumerable), + InstanceMethod("batchNormalization", &GraphBuilder::BatchNorm, napi_enumerable), + InstanceMethod("mul", &GraphBuilder::Mul, napi_enumerable), + InstanceMethod("matmul", &GraphBuilder::Matmul, napi_enumerable), + InstanceMethod("concat", &GraphBuilder::Concat, napi_enumerable), + InstanceMethod("conv2d", &GraphBuilder::Conv2d, napi_enumerable), + InstanceMethod("clamp", &GraphBuilder::Clamp, napi_enumerable), + InstanceMethod("gemm", &GraphBuilder::Gemm, napi_enumerable), + InstanceMethod("maxPool2d", &GraphBuilder::MaxPool2d, napi_enumerable), + InstanceMethod("averagePool2d", &GraphBuilder::AveragePool2d, napi_enumerable), + InstanceMethod("relu", &GraphBuilder::Relu, napi_enumerable), + InstanceMethod("leakyRelu", &GraphBuilder::LeakyRelu, napi_enumerable), + InstanceMethod("reshape", &GraphBuilder::Reshape, napi_enumerable), + InstanceMethod("softmax", &GraphBuilder::Softmax, napi_enumerable), + InstanceMethod("transpose", &GraphBuilder::Transpose, napi_enumerable), + InstanceMethod("build", &GraphBuilder::Build, napi_enumerable), + InstanceMethod("buildSync", &GraphBuilder::BuildSync, napi_enumerable)}); + constructor = Napi::Persistent(func); + constructor.SuppressDestruct(); + exports.Set("MLGraphBuilder", func); + return exports; + } + +} // namespace node diff --git a/node/src/GraphBuilder.h b/node/src/GraphBuilder.h new file mode 100644 index 000000000..5745aca83 --- /dev/null +++ b/node/src/GraphBuilder.h @@ -0,0 +1,57 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_MODEL_BUILDER_H_ +#define NODE_MODEL_BUILDER_H_ + +#include +#include + +namespace node { + + class GraphBuilder : public Napi::ObjectWrap { + public: + static Napi::Object Initialize(Napi::Env env, Napi::Object exports); + static Napi::FunctionReference constructor; + + explicit GraphBuilder(const Napi::CallbackInfo& info); + ~GraphBuilder() = default; + + private: + Napi::Value Constant(const Napi::CallbackInfo& info); + Napi::Value Input(const Napi::CallbackInfo& info); + Napi::Value Add(const Napi::CallbackInfo& info); + Napi::Value BatchNorm(const Napi::CallbackInfo& info); + Napi::Value Mul(const Napi::CallbackInfo& info); + Napi::Value Matmul(const Napi::CallbackInfo& info); + Napi::Value Conv2d(const Napi::CallbackInfo& info); + Napi::Value Concat(const Napi::CallbackInfo& info); + Napi::Value Clamp(const Napi::CallbackInfo& info); + Napi::Value Gemm(const Napi::CallbackInfo& info); + Napi::Value MaxPool2d(const Napi::CallbackInfo& info); + Napi::Value AveragePool2d(const Napi::CallbackInfo& info); + Napi::Value Relu(const Napi::CallbackInfo& info); + Napi::Value LeakyRelu(const Napi::CallbackInfo& info); + Napi::Value Reshape(const Napi::CallbackInfo& info); + Napi::Value Softmax(const Napi::CallbackInfo& info); + Napi::Value Transpose(const Napi::CallbackInfo& info); + Napi::Value Build(const Napi::CallbackInfo& info); + Napi::Value BuildSync(const Napi::CallbackInfo& info); + + ml::GraphBuilder mImpl; + }; + +} // namespace node + +#endif // NODE_MODEL_BUILDER_H_ diff --git a/node/src/Index.cpp b/node/src/Index.cpp new file mode 100644 index 000000000..9236d771b --- /dev/null +++ b/node/src/Index.cpp @@ -0,0 +1,33 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Index.h" + +#include "Context.h" +#include "Graph.h" +#include "GraphBuilder.h" +#include "ML.h" +#include "Operand.h" + +Napi::Object Init(Napi::Env env, Napi::Object exports) { + node::ML::Initialize(env, exports); + node::Context::Initialize(env, exports); + node::GraphBuilder::Initialize(env, exports); + node::Graph::Initialize(env, exports); + node::Operand::Initialize(env, exports); + + return exports; +} + +NODE_API_MODULE(addon, Init) diff --git a/node/src/Index.h b/node/src/Index.h new file mode 100644 index 000000000..b7e9e9da8 --- /dev/null +++ b/node/src/Index.h @@ -0,0 +1,26 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef __BASE__ +#define __BASE__ + +#include + +#include +#include +#include +#include +#include + +#endif diff --git a/node/src/ML.cpp b/node/src/ML.cpp new file mode 100644 index 000000000..4accb77a8 --- /dev/null +++ b/node/src/ML.cpp @@ -0,0 +1,41 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ML.h" + +#include "Context.h" + +Napi::FunctionReference node::ML::constructor; + +namespace node { + + ML::ML(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + } + + Napi::Value ML::CreateContext(const Napi::CallbackInfo& info) { + Napi::Object context = Context::constructor.New({}); + return context; + } + + Napi::Object ML::Initialize(Napi::Env env, Napi::Object exports) { + Napi::HandleScope scope(env); + Napi::Function func = DefineClass( + env, "ml", {StaticMethod("createContext", &ML::CreateContext, napi_enumerable)}); + constructor = Napi::Persistent(func); + constructor.SuppressDestruct(); + exports.Set("ml", func); + return exports; + } + +} // namespace node diff --git a/node/src/ML.h b/node/src/ML.h new file mode 100644 index 000000000..85c129ad2 --- /dev/null +++ b/node/src/ML.h @@ -0,0 +1,36 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_ML_H__ +#define NODE_ML_H__ + +#include + +namespace node { + + class ML : public Napi::ObjectWrap { + public: + static Napi::Object Initialize(Napi::Env env, Napi::Object exports); + static Napi::FunctionReference constructor; + + ML(const Napi::CallbackInfo& info); + ~ML() = default; + + private: + static Napi::Value CreateContext(const Napi::CallbackInfo& info); + }; + +} // namespace node + +#endif // NODE_ML_H__ diff --git a/node/src/Operand.cpp b/node/src/Operand.cpp new file mode 100644 index 000000000..e68891c8e --- /dev/null +++ b/node/src/Operand.cpp @@ -0,0 +1,41 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "Operand.h" + +#include "Utils.h" + +Napi::FunctionReference node::Operand::constructor; + +namespace node { + + Operand::Operand(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info) { + for (size_t i = 0; i < info.Length(); ++i) { + Napi::Object operand = info[i].As(); + WEBNN_NODE_ASSERT_AND_RETURN(operand.InstanceOf(Operand::constructor.Value()), + "The input must be Operand object."); + mInputs.push_back(Napi::Persistent(operand)); + } + } + + Napi::Object Operand::Initialize(Napi::Env env, Napi::Object exports) { + Napi::HandleScope scope(env); + Napi::Function func = DefineClass(env, "MLOperand", {}); + constructor = Napi::Persistent(func); + constructor.SuppressDestruct(); + exports.Set("MLOperand", func); + return exports; + } + +} // namespace node diff --git a/node/src/Operand.h b/node/src/Operand.h new file mode 100644 index 000000000..e7597814a --- /dev/null +++ b/node/src/Operand.h @@ -0,0 +1,47 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_ML_OPERAND_H_ +#define NODE_ML_OPERAND_H_ + +#include +#include + +namespace node { + + class ModelBuilder; + + class Operand : public Napi::ObjectWrap { + public: + static Napi::Object Initialize(Napi::Env env, Napi::Object exports); + static Napi::FunctionReference constructor; + + explicit Operand(const Napi::CallbackInfo& info); + ~Operand() = default; + + ml::Operand GetImpl() const { + return mImpl; + }; + void SetImpl(const ml::Operand& operand) { + mImpl = operand; + }; + + private: + ml::Operand mImpl; + std::vector mInputs; + }; + +} // namespace node + +#endif // NODE_ML_OPERAND_H_ diff --git a/node/src/Utils.h b/node/src/Utils.h new file mode 100644 index 000000000..e6414c472 --- /dev/null +++ b/node/src/Utils.h @@ -0,0 +1,341 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_UTILS_H_ +#define NODE_UTILS_H_ + +#define NAPI_EXPERIMENTAL +#include +#include +#include + +#include "Operand.h" + +#define WEBNN_NODE_THROW(message) \ + Napi::Error::New(info.Env(), message).ThrowAsJavaScriptException(); + +#define WEBNN_NODE_THROW_AND_RETURN(message) \ + WEBNN_NODE_THROW(message); \ + return info.Env().Undefined(); + +#define WEBNN_NODE_ASSERT(condition, message) \ + if (!(condition)) { \ + WEBNN_NODE_THROW_AND_RETURN(message); \ + } + +#define WEBNN_NODE_ASSERT_AND_RETURN(condition, message) \ + if (!(condition)) { \ + WEBNN_NODE_THROW(message); \ + return; \ + } + +namespace node { + + template + bool GetMappedValue(const std::unordered_map map, + const std::string name, + T& value) { + if (map.find(name) == map.end()) { + return false; + } + value = map.at(name); + return true; + } + + inline bool GetOperandType(const Napi::Value& jsValue, ml::OperandType& value) { + const std::unordered_map operandTypeMap = { + {"float32", ml::OperandType::Float32}, {"float16", ml::OperandType::Float16}, + {"int32", ml::OperandType::Int32}, {"uint32", ml::OperandType::Uint32}, + {"int8", ml::OperandType::Int8}, {"uint8", ml::OperandType::Uint8}, + }; + if (!jsValue.IsString()) { + return false; + } + return GetMappedValue(operandTypeMap, jsValue.As().Utf8Value(), value); + } + + inline bool GetInputOperandLayout(const Napi::Value& jsValue, ml::InputOperandLayout& value) { + const std::unordered_map inputOperandLayoutMap = { + {"nchw", ml::InputOperandLayout::Nchw}, + {"nhwc", ml::InputOperandLayout::Nhwc}, + }; + if (!jsValue.IsString()) { + return false; + } + return GetMappedValue(inputOperandLayoutMap, jsValue.As().Utf8Value(), value); + }; + + inline bool GetFilterOperandLayout(const Napi::Value& jsValue, ml::FilterOperandLayout& value) { + const std::unordered_map filterOperandLayoutMap = { + {"oihw", ml::FilterOperandLayout::Oihw}, + {"hwio", ml::FilterOperandLayout::Hwio}, + {"ohwi", ml::FilterOperandLayout::Ohwi}, + {"ihwo", ml::FilterOperandLayout::Ihwo}, + }; + if (!jsValue.IsString()) { + return false; + } + return GetMappedValue(filterOperandLayoutMap, jsValue.As().Utf8Value(), + value); + }; + + inline bool GetAutopad(const Napi::Value& jsValue, ml::AutoPad& value) { + const std::unordered_map AutoPadMap = { + {"explicit", ml::AutoPad::Explicit}, + {"same-upper", ml::AutoPad::SameUpper}, + {"same-lower", ml::AutoPad::SameLower}, + }; + if (!jsValue.IsString()) { + return false; + } + return GetMappedValue(AutoPadMap, jsValue.As().Utf8Value(), value); + }; + + inline bool GetInt32(const Napi::Value& jsValue, int32_t& value) { + if (!jsValue.IsNumber()) { + return false; + } + + // Here is a workaround to check int32 following + // https://github.com/nodejs/node-addon-api/issues/57. + float floatValue = jsValue.As().FloatValue(); + int32_t intValue = jsValue.As().Int32Value(); + if (std::fabs(floatValue - intValue) > 1e-6) { + // It's not integer type. + return false; + } + value = jsValue.As().Int32Value(); + return true; + } + + inline bool GetUint32(const Napi::Value& jsValue, uint32_t& value) { + if (!jsValue.IsNumber()) { + return false; + } + value = jsValue.As().Uint32Value(); + return true; + } + + inline bool GetFloat(const Napi::Value& jsValue, float& value) { + if (!jsValue.IsNumber()) { + return false; + } + value = jsValue.As().FloatValue(); + return true; + } + + inline bool GetBoolean(const Napi::Value& jsValue, bool& value) { + if (!jsValue.IsBoolean()) { + return false; + } + value = jsValue.As().Value(); + return true; + } + + inline bool GetString(const Napi::Value& jsValue, std::string& value) { + if (!jsValue.IsString()) { + return false; + } + value = jsValue.As().Utf8Value(); + return true; + } + + inline bool GetInt32Array(const Napi::Value& jsValue, + std::vector& array, + const size_t size = std::numeric_limits::max()) { + if (!jsValue.IsArray()) { + return false; + } + Napi::Array jsArray = jsValue.As(); + if (size != std::numeric_limits::max() && size != jsArray.Length()) { + return false; + } + std::vector int32Array; + for (uint32_t i = 0; i < jsArray.Length(); ++i) { + Napi::Value jsItem = static_cast(jsArray[i]); + int32_t value; + if (!GetInt32(jsItem, value)) { + return false; + } + int32Array.push_back(value); + } + array = int32Array; + return true; + } + + inline bool GetOperand(const Napi::Value& jsValue, + ml::Operand& operand, + std::vector& args) { + if (!jsValue.IsObject()) { + return false; + } + Napi::Object jsObject = jsValue.As(); + if (!jsObject.InstanceOf(Operand::constructor.Value())) { + return false; + } + operand = Napi::ObjectWrap::Unwrap(jsObject)->GetImpl(); + args.push_back(jsObject.As()); + return true; + } + + inline bool GetOperandArray(const Napi::Value& jsValue, + std::vector& array, + std::vector& args) { + if (!jsValue.IsArray()) { + return false; + } + Napi::Array jsArray = jsValue.As(); + for (size_t j = 0; j < jsArray.Length(); j++) { + if (!jsArray.Get(j).IsObject()) { + return false; + } + Napi::Object object = jsArray.Get(j).As(); + if (!object.InstanceOf(Operand::constructor.Value())) { + return false; + } + Operand* operand = Napi::ObjectWrap::Unwrap(object); + array.push_back(operand->GetImpl()); + args.push_back(object.As()); + } + return true; + } + + struct OperandDescriptor { + public: + ml::OperandType type; + std::vector dimensions; + + const ml::OperandDescriptor* AsPtr() { + if (!dimensions.empty()) { + mDesc.dimensions = dimensions.data(); + mDesc.dimensionsCount = dimensions.size(); + } + mDesc.type = type; + return &mDesc; + } + + private: + ml::OperandDescriptor mDesc; + }; + + inline bool GetOperandDescriptor(const Napi::Value& jsValue, OperandDescriptor& desc) { + if (!jsValue.IsObject()) { + return false; + } + Napi::Object jsDesc = jsValue.As(); + if (!jsDesc.Has("type")) { + return false; + } + if (!GetOperandType(jsDesc.Get("type"), desc.type)) { + return false; + } + if (jsDesc.Has("dimensions")) { + if (!GetInt32Array(jsDesc.Get("dimensions"), desc.dimensions)) { + return false; + } + } + return true; + } + + inline bool GetBufferView(const Napi::Value& jsValue, + const ml::OperandType type, + const std::vector& dimensions, + void*& value, + size_t& size) { + const std::unordered_map arrayTypeMap = { + {ml::OperandType::Float32, napi_float32_array}, + {ml::OperandType::Float16, napi_uint16_array}, + {ml::OperandType::Int32, napi_int32_array}, + {ml::OperandType::Uint32, napi_uint32_array}, + {ml::OperandType::Int8, napi_int8_array}, + {ml::OperandType::Uint8, napi_uint8_array}, + }; + if (!jsValue.IsTypedArray()) { + return false; + } + Napi::TypedArray jsTypedArray = jsValue.As(); + if (arrayTypeMap.find(type) == arrayTypeMap.end()) { + return false; + } + if (arrayTypeMap.at(type) != jsTypedArray.TypedArrayType()) { + return false; + } + value = + reinterpret_cast(reinterpret_cast(jsTypedArray.ArrayBuffer().Data()) + + jsTypedArray.ByteOffset()); + size = jsTypedArray.ByteLength(); + size_t expectedSize; + switch (type) { + case ml::OperandType::Float32: + expectedSize = sizeof(float); + break; + case ml::OperandType::Float16: + expectedSize = sizeof(uint16_t); + break; + case ml::OperandType::Int32: + expectedSize = sizeof(int32_t); + break; + case ml::OperandType::Uint32: + expectedSize = sizeof(uint32_t); + break; + case ml::OperandType::Int8: + expectedSize = sizeof(int8_t); + break; + case ml::OperandType::Uint8: + expectedSize = sizeof(uint8_t); + break; + default: + return false; + } + for (auto dim : dimensions) { + expectedSize *= dim; + } + if (expectedSize != size) { + return false; + } + return true; + } + + inline bool GetNamedOperands(const Napi::Value& jsValue, + ml::NamedOperands& namedOperands, + std::vector& names) { + if (!jsValue.IsObject()) { + return false; + } + Napi::Object jsOutputs = jsValue.As(); + namedOperands = ml::CreateNamedOperands(); + Napi::Array outputNames = jsOutputs.GetPropertyNames(); + if (outputNames.Length() == 0) { + return false; + } + for (size_t j = 0; j < outputNames.Length(); ++j) { + std::string name = outputNames.Get(j).As().Utf8Value(); + Napi::Object output = jsOutputs.Get(name).As(); + if (!output.InstanceOf(Operand::constructor.Value())) { + return false; + } + ml::Operand operand = Napi::ObjectWrap::Unwrap(output)->GetImpl(); + namedOperands.Set(name.data(), operand); + names.push_back(name); + } + return true; + } + + inline bool HasOptionMember(const Napi::Object& jsOptions, const std::string& name) { + return jsOptions.Has(name) && !jsOptions.Get(name).IsUndefined(); + } + +} // namespace node + +#endif // NODE_UTILS_H_ diff --git a/node/src/ops/BatchNorm.cpp b/node/src/ops/BatchNorm.cpp new file mode 100644 index 000000000..d86b93244 --- /dev/null +++ b/node/src/ops/BatchNorm.cpp @@ -0,0 +1,70 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ops/BatchNorm.h" + +#include "Operand.h" +#include "Utils.h" + +namespace node { namespace op { + + Napi::Value BatchNorm::Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder) { + // Operand batchNormalization(Operand input, Operand mean, Operand variance, + // optional BatchNormalizationOptions options = {}); + WEBNN_NODE_ASSERT(info.Length() == 3 || info.Length() == 4, + "The number of arguments is invalid."); + + std::vector args; + ml::Operand input; + WEBNN_NODE_ASSERT(GetOperand(info[0], input, args), "The input parameter is invalid."); + ml::Operand mean; + WEBNN_NODE_ASSERT(GetOperand(info[1], mean, args), "The mean parameter is invalid."); + ml::Operand variance; + WEBNN_NODE_ASSERT(GetOperand(info[2], variance, args), + "The variance parameter is invalid."); + + // dictionary BatchNormalizationOptions { + // Operand scale; + // Operand bias; + // long axis = 1; + // float epsilon = 1e-5; + // }; + ml::BatchNormOptions options; + if (info.Length() == 4 && !info[3].IsUndefined()) { + WEBNN_NODE_ASSERT(info[3].IsObject(), "The options must be an object.") + Napi::Object jsOptions = info[3].As(); + if (HasOptionMember(jsOptions, "scale")) { + WEBNN_NODE_ASSERT(GetOperand(jsOptions.Get("scale"), options.scale, args), + "The scale parameter is invalid."); + } + if (HasOptionMember(jsOptions, "bias")) { + WEBNN_NODE_ASSERT(GetOperand(jsOptions.Get("bias"), options.bias, args), + "The bias parameter is invalid."); + } + if (HasOptionMember(jsOptions, "axis")) { + WEBNN_NODE_ASSERT(GetUint32(jsOptions.Get("axis"), options.axis), + "The axis parameter is invalid."); + } + if (HasOptionMember(jsOptions, "epsilon")) { + WEBNN_NODE_ASSERT(GetFloat(jsOptions.Get("epsilon"), options.epsilon), + "The epsilon parameter is invalid."); + } + } + + Napi::Object object = Operand::constructor.New(args); + Operand* operand = Napi::ObjectWrap::Unwrap(object); + operand->SetImpl(builder.BatchNorm(input, mean, variance, &options)); + return object; + } +}} // namespace node::op diff --git a/node/src/ops/BatchNorm.h b/node/src/ops/BatchNorm.h new file mode 100644 index 000000000..a7c045362 --- /dev/null +++ b/node/src/ops/BatchNorm.h @@ -0,0 +1,29 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_OPS_BATCHNORM_H__ +#define NODE_OPS_BATCHNORM_H__ + +#include +#include + +namespace node { namespace op { + + struct BatchNorm { + static Napi::Value Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder); + }; + +}} // namespace node::op + +#endif // ___OPS_BATCHNORM_H__ diff --git a/node/src/ops/Clamp.cpp b/node/src/ops/Clamp.cpp new file mode 100644 index 000000000..16a55a515 --- /dev/null +++ b/node/src/ops/Clamp.cpp @@ -0,0 +1,55 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ops/Clamp.h" + +#include "Operand.h" +#include "Utils.h" + +namespace node { namespace op { + + Napi::Value Clamp::Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder) { + // Operand clamp(Operand x, optional ClampOptions options = {}); + WEBNN_NODE_ASSERT(info.Length() == 1 || info.Length() == 2, + "The number of arguments is invalid."); + + std::vector args; + ml::Operand input; + WEBNN_NODE_ASSERT(GetOperand(info[0], input, args), "The input parameter is invalid."); + + // dictionary ClampOptions { + // Operand minValue; + // Operand maxValue; + // }; + ml::ClampOptions options; + if (info.Length() == 2 && !info[1].IsUndefined()) { + WEBNN_NODE_ASSERT(info[1].IsObject(), "The options must be an object."); + Napi::Object jsOptions = info[1].As(); + if (HasOptionMember(jsOptions, "minValue")) { + WEBNN_NODE_ASSERT(GetOperand(jsOptions.Get("minValue"), options.minValue, args), + "The minValue parameter is invalid."); + } + if (HasOptionMember(jsOptions, "maxValue")) { + WEBNN_NODE_ASSERT(GetOperand(jsOptions.Get("maxValue"), options.maxValue, args), + "The maxValue parameter is invalid."); + } + } + + Napi::Object object = Operand::constructor.New(args); + Operand* operand = Napi::ObjectWrap::Unwrap(object); + operand->SetImpl(builder.Clamp(input, &options)); + return object; + } + +}} // namespace node::op diff --git a/node/src/ops/Clamp.h b/node/src/ops/Clamp.h new file mode 100644 index 000000000..204fe19c0 --- /dev/null +++ b/node/src/ops/Clamp.h @@ -0,0 +1,29 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_OPS_CLAMP_H_ +#define NODE_OPS_CLAMP_H_ + +#include +#include + +namespace node { namespace op { + + struct Clamp { + static Napi::Value Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder); + }; + +}} // namespace node::op + +#endif // NODE_OPS_CLAMP_H_ diff --git a/node/src/ops/Concat.cpp b/node/src/ops/Concat.cpp new file mode 100644 index 000000000..0d9c4d5b7 --- /dev/null +++ b/node/src/ops/Concat.cpp @@ -0,0 +1,37 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ops/Concat.h" + +#include "Operand.h" +#include "Utils.h" + +namespace node { namespace op { + + Napi::Value Concat::Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder) { + // Operand concat(sequence inputs, long axis); + WEBNN_NODE_ASSERT(info.Length() == 2, "The number of arguments is invalid."); + + std::vector args; + std::vector inputs; + WEBNN_NODE_ASSERT(GetOperandArray(info[0], inputs, args), + "The input operands are invalid."); + int32_t axis; + WEBNN_NODE_ASSERT(GetInt32(info[1], axis), "The axis parameter is invalid."); + Napi::Object object = Operand::constructor.New(args); + Operand* operand = Napi::ObjectWrap::Unwrap(object); + operand->SetImpl(builder.Concat(inputs.size(), inputs.data(), axis)); + return object; + } +}} // namespace node::op diff --git a/node/src/ops/Concat.h b/node/src/ops/Concat.h new file mode 100644 index 000000000..4e3e98a8c --- /dev/null +++ b/node/src/ops/Concat.h @@ -0,0 +1,29 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_OPS_CONCAT_H_ +#define NODE_OPS_CONCAT_H_ + +#include +#include + +namespace node { namespace op { + + struct Concat { + static Napi::Value Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder); + }; + +}} // namespace node::op + +#endif // NODE_OPS_CONCAT_H_ diff --git a/node/src/ops/Constant.cpp b/node/src/ops/Constant.cpp new file mode 100644 index 000000000..dcb6d0b15 --- /dev/null +++ b/node/src/ops/Constant.cpp @@ -0,0 +1,102 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ops/Constant.h" + +#include "Operand.h" +#include "Utils.h" + +namespace node { namespace op { + + namespace { + union Scalar { + float floatValue; + uint16_t uint16Value; + int32_t int32Value; + uint32_t uint32Value; + int8_t int8Value; + uint8_t uint8Value; + }; + } // namespace + + Napi::Value Constant::Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder) { + // Operand constant(OperandDescriptor desc, ArrayBufferView value); + // Operand constant(double value, optional OperandType type = "float32"); + WEBNN_NODE_ASSERT(info.Length() == 1 || info.Length() == 2, + "The number of arguments is invalid."); + + Napi::Object object = Operand::constructor.New({}); + OperandDescriptor desc; + void* value; + size_t size; + Scalar scalar; + if (info[0].IsNumber()) { + // Operand constant(double value, optional OperandType type = "float32"); + if (info.Length() == 1) { + desc.type = ml::OperandType::Float32; + } else { + WEBNN_NODE_ASSERT(GetOperandType(info[1], desc.type), + "The type parameter is invalid."); + } + Napi::Number jsValue = info[0].As(); + if (desc.type == ml::OperandType::Float32) { + scalar.floatValue = jsValue.FloatValue(); + value = &scalar.floatValue; + size = sizeof(float); + } else if (desc.type == ml::OperandType::Float16) { + scalar.uint16Value = static_cast(jsValue.Uint32Value()); + value = &scalar.uint16Value; + size = sizeof(uint16_t); + } else if (desc.type == ml::OperandType::Int32) { + WEBNN_NODE_ASSERT(GetInt32(info[0], scalar.int32Value), + "Invalid value according to int32 type."); + value = &scalar.int32Value; + size = sizeof(int32_t); + } else if (desc.type == ml::OperandType::Uint32) { + scalar.uint32Value = jsValue.Uint32Value(); + value = &scalar.uint32Value; + size = sizeof(uint32_t); + } else if (desc.type == ml::OperandType::Int8) { + scalar.int8Value = static_cast(jsValue.Int32Value()); + value = &scalar.int8Value; + size = sizeof(int8_t); + } else if (desc.type == ml::OperandType::Uint8) { + scalar.uint8Value = static_cast(jsValue.Uint32Value()); + value = &scalar.uint8Value; + size = sizeof(uint8_t); + } else { + WEBNN_NODE_THROW_AND_RETURN("The operand type is not supported."); + } + desc.dimensions = {1}; + + Napi::ArrayBuffer arrayBuffer = Napi::ArrayBuffer::New(info.Env(), size); + memcpy(arrayBuffer.Data(), value, size); + value = arrayBuffer.Data(); + // Keep a reference of value. + object.Set("value", arrayBuffer); + } else { + WEBNN_NODE_ASSERT(GetOperandDescriptor(info[0], desc), + "The desc parameter is invalid."); + WEBNN_NODE_ASSERT(GetBufferView(info[1], desc.type, desc.dimensions, value, size), + "The value parameter is invalid."); + // Keep a reference of value. + object.Set("value", info[1]); + } + + Operand* operand = Napi::ObjectWrap::Unwrap(object); + operand->SetImpl(builder.Constant(desc.AsPtr(), value, size)); + return object; + } + +}} // namespace node::op diff --git a/node/src/ops/Constant.h b/node/src/ops/Constant.h new file mode 100644 index 000000000..2ad6ffef5 --- /dev/null +++ b/node/src/ops/Constant.h @@ -0,0 +1,29 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_OPS_CONSTANT_H_ +#define NODE_OPS_CONSTANT_H_ + +#include +#include + +namespace node { namespace op { + + struct Constant { + static Napi::Value Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder); + }; + +}} // namespace node::op + +#endif // NODE_OPS_CONSTANT_H_ diff --git a/node/src/ops/Conv2d.cpp b/node/src/ops/Conv2d.cpp new file mode 100644 index 000000000..cafb1e160 --- /dev/null +++ b/node/src/ops/Conv2d.cpp @@ -0,0 +1,122 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ops/Conv2d.h" + +#include "Operand.h" +#include "Utils.h" + +namespace node { namespace op { + + struct Conv2dOptions { + public: + std::vector padding; + std::vector strides; + std::vector dilations; + int32_t groups = 1; + ml::AutoPad autoPad = ml::AutoPad::Explicit; + ml::InputOperandLayout inputLayout = ml::InputOperandLayout::Nchw; + ml::FilterOperandLayout filterLayout = ml::FilterOperandLayout::Oihw; + + const ml::Conv2dOptions* AsPtr() { + if (!padding.empty()) { + mOptions.paddingCount = padding.size(); + mOptions.padding = padding.data(); + } + if (!strides.empty()) { + mOptions.stridesCount = strides.size(); + mOptions.strides = strides.data(); + } + if (!dilations.empty()) { + mOptions.dilationsCount = dilations.size(); + mOptions.dilations = dilations.data(); + } + mOptions.groups = groups; + mOptions.autoPad = autoPad; + mOptions.inputLayout = inputLayout; + mOptions.filterLayout = filterLayout; + return &mOptions; + } + + private: + ml::Conv2dOptions mOptions; + }; + + Napi::Value Conv2d::Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder) { + // Operand conv2d(Operand input, Operand filter, optional Conv2dOptions options = {}); + WEBNN_NODE_ASSERT(info.Length() == 2 || info.Length() == 3, + "The number of arguments is invalid."); + + std::vector args; + ml::Operand input; + WEBNN_NODE_ASSERT(GetOperand(info[0], input, args), "The input parameter is invalid."); + ml::Operand filter; + WEBNN_NODE_ASSERT(GetOperand(info[1], filter, args), "The filter parameter is invalid."); + + // dictionary Conv2dOptions { + // sequence padding; + // sequence strides; + // sequence dilations; + // AutoPad autoPad = "explicit"; + // long groups = 1; + // InputOperandLayout inputLayout = "nchw"; + // FilterOperandLayout filterLayout = "oihw"; + // }; + Conv2dOptions options; + if (info.Length() == 3 && !info[2].IsUndefined()) { + WEBNN_NODE_ASSERT(info[2].IsObject(), "The options must be an object."); + Napi::Object jsOptions = info[2].As(); + if (HasOptionMember(jsOptions, "padding")) { + WEBNN_NODE_ASSERT(GetInt32Array(jsOptions.Get("padding"), options.padding, 4), + "The padding parameter is invalid."); + } + if (HasOptionMember(jsOptions, "strides")) { + WEBNN_NODE_ASSERT(GetInt32Array(jsOptions.Get("strides"), options.strides, 2), + "The strides parameter is invalid."); + } + if (HasOptionMember(jsOptions, "dilations")) { + WEBNN_NODE_ASSERT(GetInt32Array(jsOptions.Get("dilations"), options.dilations, 2), + "The dilations parameter is invalid."); + } + if (HasOptionMember(jsOptions, "groups")) { + WEBNN_NODE_ASSERT(GetInt32(jsOptions.Get("groups"), options.groups), + "The groups parameter is invalid."); + } + if (HasOptionMember(jsOptions, "inputLayout")) { + WEBNN_NODE_ASSERT( + GetInputOperandLayout(jsOptions.Get("inputLayout"), options.inputLayout), + "The inputLayout parameter is invalid."); + } + if (HasOptionMember(jsOptions, "filterLayout")) { + WEBNN_NODE_ASSERT( + GetFilterOperandLayout(jsOptions.Get("filterLayout"), options.filterLayout), + "The filterLayout parameter is invalid."); + } + if (HasOptionMember(jsOptions, "autoPad")) { + WEBNN_NODE_ASSERT(GetAutopad(jsOptions.Get("autoPad"), options.autoPad), + "The autoPad parameter is invalid."); + } + if (HasOptionMember(jsOptions, "transpose")) { + WEBNN_NODE_THROW_AND_RETURN( + "The transpose parameter isn't supported in webNN-native."); + } + } + + Napi::Object object = Operand::constructor.New(args); + Operand* operand = Napi::ObjectWrap::Unwrap(object); + operand->SetImpl(builder.Conv2d(input, filter, options.AsPtr())); + return object; + } + +}} // namespace node::op diff --git a/node/src/ops/Conv2d.h b/node/src/ops/Conv2d.h new file mode 100644 index 000000000..4efda12e0 --- /dev/null +++ b/node/src/ops/Conv2d.h @@ -0,0 +1,29 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_OPS_CONV2D_H_ +#define NODE_OPS_CONV2D_H_ + +#include +#include + +namespace node { namespace op { + + struct Conv2d { + static Napi::Value Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder); + }; + +}} // namespace node::op + +#endif // NODE_OPS_CONV2D_H_ diff --git a/node/src/ops/Gemm.cpp b/node/src/ops/Gemm.cpp new file mode 100644 index 000000000..b824d7ed9 --- /dev/null +++ b/node/src/ops/Gemm.cpp @@ -0,0 +1,70 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ops/Gemm.h" + +#include "Operand.h" +#include "Utils.h" + +namespace node { namespace op { + + Napi::Value Gemm::Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder) { + // Operand gemm(Operand a, Operand b, optional GemmOptions options = {}); + WEBNN_NODE_ASSERT(info.Length() == 2 || info.Length() == 3, + "The number of arguments is invalid."); + + std::vector args; + ml::Operand a; + WEBNN_NODE_ASSERT(GetOperand(info[0], a, args), "The a parameter is invalid."); + ml::Operand b; + WEBNN_NODE_ASSERT(GetOperand(info[1], b, args), "The b parameter is invalid."); + + // dictionary GemmOptions { + // Operand c; + // float alpha = 1.0; + // float beta = 1.0; + // boolean aTranspose = false; + // boolean bTranspose = false; + // }; + ml::GemmOptions options; + if (info.Length() == 3 && !info[2].IsUndefined()) { + WEBNN_NODE_ASSERT(info[2].IsObject(), "The options must be an object."); + Napi::Object jsOptions = info[2].As(); + if (HasOptionMember(jsOptions, "c")) { + WEBNN_NODE_ASSERT(GetOperand(jsOptions.Get("c"), options.c, args), + "The c parameter is invalid."); + } + if (HasOptionMember(jsOptions, "alpha")) { + WEBNN_NODE_ASSERT(GetFloat(jsOptions.Get("alpha"), options.alpha), + "The alpha parameter is invalid."); + } + if (HasOptionMember(jsOptions, "beta")) { + WEBNN_NODE_ASSERT(GetFloat(jsOptions.Get("beta"), options.beta), + "The beta parameter is invalid."); + } + if (HasOptionMember(jsOptions, "aTranspose")) { + WEBNN_NODE_ASSERT(GetBoolean(jsOptions.Get("aTranspose"), options.aTranspose), + "The aTranspose parameter is invalid."); + } + if (HasOptionMember(jsOptions, "bTranspose")) { + WEBNN_NODE_ASSERT(GetBoolean(jsOptions.Get("bTranspose"), options.bTranspose), + "The bTranspose parameter is invalid."); + } + } + Napi::Object object = Operand::constructor.New(args); + Operand* operand = Napi::ObjectWrap::Unwrap(object); + operand->SetImpl(builder.Gemm(a, b, &options)); + return object; + } +}} // namespace node::op diff --git a/node/src/ops/Gemm.h b/node/src/ops/Gemm.h new file mode 100644 index 000000000..142b1b07f --- /dev/null +++ b/node/src/ops/Gemm.h @@ -0,0 +1,29 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_OPS_GEMM_H_ +#define NODE_OPS_GEMM_H_ + +#include +#include + +namespace node { namespace op { + + struct Gemm { + static Napi::Value Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder); + }; + +}} // namespace node::op + +#endif // NODE_OPS_GEMM_H_ diff --git a/node/src/ops/Input.cpp b/node/src/ops/Input.cpp new file mode 100644 index 000000000..55fb599ec --- /dev/null +++ b/node/src/ops/Input.cpp @@ -0,0 +1,36 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ops/Input.h" + +#include "Utils.h" + +namespace node { namespace op { + + Napi::Value Input::Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder) { + // Operand input(DOMString name, OperandDescriptor desc); + WEBNN_NODE_ASSERT(info.Length() == 2, "The number of arguments is invalid."); + std::string name; + WEBNN_NODE_ASSERT(GetString(info[0], name), "The name must be a string."); + + OperandDescriptor desc; + WEBNN_NODE_ASSERT(GetOperandDescriptor(info[1], desc), "The desc parameter is invalid."); + + Napi::Object object = Operand::constructor.New({}); + Operand* operand = Napi::ObjectWrap::Unwrap(object); + operand->SetImpl(builder.Input(name.c_str(), desc.AsPtr())); + return object; + } + +}} // namespace node::op diff --git a/node/src/ops/Input.h b/node/src/ops/Input.h new file mode 100644 index 000000000..6736e9aca --- /dev/null +++ b/node/src/ops/Input.h @@ -0,0 +1,29 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_OPS_INPUT_H_ +#define NODE_OPS_INPUT_H_ + +#include +#include + +namespace node { namespace op { + + struct Input { + static Napi::Value Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder); + }; + +}} // namespace node::op + +#endif // NODE_OPS_INPUT_H_ diff --git a/node/src/ops/LeakyRelu.cpp b/node/src/ops/LeakyRelu.cpp new file mode 100644 index 000000000..4d84df35f --- /dev/null +++ b/node/src/ops/LeakyRelu.cpp @@ -0,0 +1,50 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ops/LeakyRelu.h" + +#include "Utils.h" + +namespace node { namespace op { + + Napi::Value LeakyRelu::Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder) { + // partial interface ModelBuilder { + // Operand leakyRelu(Operand x, optional LeakyReluOptions options = {}); + // }; + WEBNN_NODE_ASSERT(info.Length() == 1 || info.Length() == 2, + "The number of arguments is invalid."); + + std::vector args; + ml::Operand input; + WEBNN_NODE_ASSERT(GetOperand(info[0], input, args), "The input parameter is invalid."); + + // dictionary LeakyReluOptions { + // float alpha = 0.01; + // }; + ml::LeakyReluOptions options; + if (info.Length() == 2 && !info[1].IsUndefined()) { + WEBNN_NODE_ASSERT(info[1].IsObject(), "The options must be an object."); + Napi::Object jsOptions = info[1].As(); + if (HasOptionMember(jsOptions, "alpha")) { + WEBNN_NODE_ASSERT(GetFloat(jsOptions.Get("alpha"), options.alpha), + "The alpha parameter is invalid."); + } + } + + Napi::Object object = Operand::constructor.New(args); + Operand* operand = Napi::ObjectWrap::Unwrap(object); + operand->SetImpl(builder.LeakyRelu(input, &options)); + return object; + } +}} // namespace node::op \ No newline at end of file diff --git a/node/src/ops/LeakyRelu.h b/node/src/ops/LeakyRelu.h new file mode 100644 index 000000000..ebcf5a2d3 --- /dev/null +++ b/node/src/ops/LeakyRelu.h @@ -0,0 +1,29 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_OPS_LEAKYRELU_H_ +#define NODE_OPS_LEAKYRELU_H_ + +#include +#include + +namespace node { namespace op { + + struct LeakyRelu { + static Napi::Value Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder); + }; + +}} // namespace node::op + +#endif // NODE_OPS_LEAKYRELU_H_ diff --git a/node/src/ops/Pool2d.cpp b/node/src/ops/Pool2d.cpp new file mode 100644 index 000000000..a30d1cdb1 --- /dev/null +++ b/node/src/ops/Pool2d.cpp @@ -0,0 +1,127 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ops/Pool2d.h" + +#include "Operand.h" +#include "Utils.h" + +namespace node { namespace op { + + struct Pool2dOptions { + public: + std::vector windowDimensions; + std::vector padding; + std::vector strides; + std::vector dilations; + ml::AutoPad autoPad = ml::AutoPad::Explicit; + ml::InputOperandLayout layout = ml::InputOperandLayout::Nchw; + + const ml::Pool2dOptions* AsPtr() { + if (!windowDimensions.empty()) { + mOptions.windowDimensionsCount = windowDimensions.size(); + mOptions.windowDimensions = windowDimensions.data(); + } + if (!padding.empty()) { + mOptions.paddingCount = padding.size(); + mOptions.padding = padding.data(); + } + if (!strides.empty()) { + mOptions.stridesCount = strides.size(); + mOptions.strides = strides.data(); + } + if (!dilations.empty()) { + mOptions.dilationsCount = dilations.size(); + mOptions.dilations = dilations.data(); + } + mOptions.autoPad = autoPad; + mOptions.layout = layout; + return &mOptions; + } + + private: + ml::Pool2dOptions mOptions; + }; + + Napi::Value Pool2d::Build(const Napi::CallbackInfo& info, + ml::GraphBuilder builder, + Pool2dType type) { + // Operand averagePool2d(Operand input, optional Pool2dOptions options = {}); + // Operand l2Pool2d(Operand input, optional Pool2dOptions options = {}); + // Operand maxPool2d(Operand input, optional Pool2dOptions options = {}); + WEBNN_NODE_ASSERT(info.Length() == 1 || info.Length() == 2, + "The number of arguments is invalid."); + + std::vector args; + ml::Operand input; + WEBNN_NODE_ASSERT(GetOperand(info[0], input, args), "The input parameter is invalid."); + + // dictionary Pool2dOptions { + // sequence windowDimensions; + // sequence padding; + // sequence strides; + // sequence dilations; + // AutoPad autoPad = "explicit"; + // InputOperandLayout layout = "nchw"; + // }; + Pool2dOptions options; + if (info.Length() == 2 && !info[1].IsUndefined()) { + WEBNN_NODE_ASSERT(info[1].IsObject(), "The options must be an object."); + Napi::Object jsOptions = info[1].As(); + if (HasOptionMember(jsOptions, "windowDimensions")) { + WEBNN_NODE_ASSERT( + GetInt32Array(jsOptions.Get("windowDimensions"), options.windowDimensions, 2), + "The windowDimensions parameter is invalid."); + } + if (HasOptionMember(jsOptions, "padding")) { + WEBNN_NODE_ASSERT(GetInt32Array(jsOptions.Get("padding"), options.padding, 4), + "The padding parameter is invalid."); + } + if (HasOptionMember(jsOptions, "strides")) { + WEBNN_NODE_ASSERT(GetInt32Array(jsOptions.Get("strides"), options.strides, 2), + "The strides parameter is invalid."); + } + if (HasOptionMember(jsOptions, "dilations")) { + WEBNN_NODE_ASSERT(GetInt32Array(jsOptions.Get("dilations"), options.dilations, 2), + "The dilations parameter is invalid."); + } + if (HasOptionMember(jsOptions, "autoPad")) { + WEBNN_NODE_ASSERT(GetAutopad(jsOptions.Get("autoPad"), options.autoPad), + "The autoPad parameter is invalid."); + } + if (HasOptionMember(jsOptions, "layout")) { + WEBNN_NODE_ASSERT(GetInputOperandLayout(jsOptions.Get("layout"), options.layout), + "The layout parameter is invalid."); + } + } + + ml::Operand pool2d; + switch (type) { + case Pool2dType::kAveragePool2d: + pool2d = builder.AveragePool2d(input, options.AsPtr()); + break; + case Pool2dType::kMaxPool2d: + pool2d = builder.MaxPool2d(input, options.AsPtr()); + break; + default: + WEBNN_NODE_THROW_AND_RETURN("The type of pool2d is not supported."); + } + + Napi::Object object = Operand::constructor.New(args); + Operand* operand = Napi::ObjectWrap::Unwrap(object); + operand->SetImpl(pool2d); + return object; + } + +}} // namespace node::op diff --git a/node/src/ops/Pool2d.h b/node/src/ops/Pool2d.h new file mode 100644 index 000000000..c44e8686c --- /dev/null +++ b/node/src/ops/Pool2d.h @@ -0,0 +1,37 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_OPS_POOL2D_H_ +#define NODE_OPS_POOL2D_H_ + +#include +#include + +namespace node { namespace op { + + enum Pool2dType { + kAveragePool2d = 0, + kL2Pool2d, + kMaxPool2d, + }; + + struct Pool2d { + static Napi::Value Build(const Napi::CallbackInfo& info, + ml::GraphBuilder builder, + Pool2dType type); + }; + +}} // namespace node::op + +#endif // NODE_OPS_POOL2D_H_ diff --git a/node/src/ops/Reshape.cpp b/node/src/ops/Reshape.cpp new file mode 100644 index 000000000..93be7eab7 --- /dev/null +++ b/node/src/ops/Reshape.cpp @@ -0,0 +1,40 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ops/Reshape.h" + +#include "Utils.h" + +namespace node { namespace op { + + Napi::Value Reshape::Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder) { + // Operand reshape(Operand input, sequence newShape); + WEBNN_NODE_ASSERT(info.Length() == 2, "The number of arguments is invalid."); + + std::vector args; + ml::Operand input; + WEBNN_NODE_ASSERT(GetOperand(info[0], input, args), "The input parameter is invalid."); + + std::vector newShape; + WEBNN_NODE_ASSERT(GetInt32Array(info[1], newShape), "The newShape parameter is invalid."); + WEBNN_NODE_ASSERT(newShape.empty() == false, "The newShape is empty."); + + ml::Operand reshape = builder.Reshape(input, newShape.data(), newShape.size()); + Napi::Object object = Operand::constructor.New(args); + Operand* operand = Napi::ObjectWrap::Unwrap(object); + operand->SetImpl(reshape); + return object; + } + +}} // namespace node::op diff --git a/node/src/ops/Reshape.h b/node/src/ops/Reshape.h new file mode 100644 index 000000000..6cbe88347 --- /dev/null +++ b/node/src/ops/Reshape.h @@ -0,0 +1,29 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_OPS_RESHAPE_H_ +#define NODE_OPS_RESHAPE_H_ + +#include +#include + +namespace node { namespace op { + + struct Reshape { + static Napi::Value Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder); + }; + +}} // namespace node::op + +#endif // NODE_OPS_RESHAPE_H_ diff --git a/node/src/ops/Transpose.cpp b/node/src/ops/Transpose.cpp new file mode 100644 index 000000000..7540a55c3 --- /dev/null +++ b/node/src/ops/Transpose.cpp @@ -0,0 +1,52 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "ops/Transpose.h" + +#include "Utils.h" + +namespace node { namespace op { + + Napi::Value Transpose::Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder) { + // Operand transpose(Operand input, optional TransposeOptions options = {}); + WEBNN_NODE_ASSERT(info.Length() == 1 || info.Length() == 2, + "The number of arguments is invalid."); + + std::vector args; + ml::Operand input; + WEBNN_NODE_ASSERT(GetOperand(info[0], input, args), "The input parameter is invalid."); + + // dictionary TransposeOptions { + // sequence permutation; + // }; + ml::TransposeOptions options; + std::vector permutation; + if (info.Length() == 2 && !info[1].IsUndefined()) { + WEBNN_NODE_ASSERT(info[1].IsObject(), "The options must be an object."); + Napi::Object jsOptions = info[1].As(); + if (HasOptionMember(jsOptions, "permutation")) { + WEBNN_NODE_ASSERT(GetInt32Array(jsOptions.Get("permutation"), permutation), + "The permutation parameter is invalid."); + WEBNN_NODE_ASSERT(permutation.empty() == false, "The newShape is empty."); + options.permutation = permutation.data(); + options.permutationCount = permutation.size(); + } + } + Napi::Object object = Operand::constructor.New(args); + Operand* operand = Napi::ObjectWrap::Unwrap(object); + operand->SetImpl(builder.Transpose(input, &options)); + return object; + } + +}} // namespace node::op diff --git a/node/src/ops/Transpose.h b/node/src/ops/Transpose.h new file mode 100644 index 000000000..7c75cfe86 --- /dev/null +++ b/node/src/ops/Transpose.h @@ -0,0 +1,29 @@ +// Copyright 2021 The WebNN-native Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef NODE_OPS_TRANSPOSE_H_ +#define NODE_OPS_TRANSPOSE_H_ + +#include +#include + +namespace node { namespace op { + + struct Transpose { + static Napi::Value Build(const Napi::CallbackInfo& info, ml::GraphBuilder builder); + }; + +}} // namespace node::op + +#endif // NODE_OPS_TRANSPOSE_H_ diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn index ae4003a19..f39973e51 100644 --- a/src/tests/BUILD.gn +++ b/src/tests/BUILD.gn @@ -105,6 +105,9 @@ webnn_json_generator("mock_webnn_gen") { test("webnn_unittests") { configs += [ "${webnn_root}/src/common:dawn_internal" ] + if (is_linux) { + configs += [ "//build/config//gcc:rpath_for_built_shared_libraries" ] + } deps = [ ":gmock_and_gtest", @@ -200,6 +203,9 @@ source_set("webnn_end2end_tests_sources") { test("webnn_end2end_tests") { configs += [ "${webnn_root}/src/common:dawn_internal" ] + if (is_linux) { + configs += [ "//build/config//gcc:rpath_for_built_shared_libraries" ] + } deps = [ ":gmock_and_gtest", diff --git a/src/webnn_native/BUILD.gn b/src/webnn_native/BUILD.gn index 79b991395..a32ab7a0a 100644 --- a/src/webnn_native/BUILD.gn +++ b/src/webnn_native/BUILD.gn @@ -159,13 +159,17 @@ source_set("webnn_native_sources") { "openvino/GraphIE.h", ] - sources += [ - "openvino/ienn_symbol_table/ienn_symbol_table.cc", - "openvino/ienn_symbol_table/ienn_symbol_table.h", - "openvino/ienn_symbol_table/late_binding_symbol_table.cc", - "openvino/ienn_symbol_table/late_binding_symbol_table.h", - ] + include_dirs = [ "openvino/ienn/src" ] + + if (is_win) { + lib_dirs = [ "openvino/ienn/lib/Windows64" ] + libs = [ "ie_nn_c_api.lib" ] + } + if (is_linux) { + lib_dirs = [ "openvino/ienn/lib/Linux64" ] + libs = [ "ie_nn_c_api" ] + assert( is_component_build == false, "`is_component_build=false` must be set for OpenVINO backend on Linux.") @@ -192,10 +196,10 @@ source_set("webnn_native_sources") { include_dirs = [ "//third_party/DirectML/Libraries", - "//third_party/microsoft.ai.directml.1.4.1/include", + "//third_party/microsoft.ai.directml.1.5.1/include", ] - lib_dirs = [ "//third_party/microsoft.ai.directml.1.4.1/bin/x64-win" ] + lib_dirs = [ "//third_party/microsoft.ai.directml.1.5.1/bin/x64-win" ] libs = [ "dxgi.lib", @@ -320,7 +324,7 @@ if (webnn_enable_openvino) { if (webnn_enable_dml) { dml_dll = "DirectML.dll" os_folder = "x64-win" - dml_dll_path = "//third_party/microsoft.ai.directml.1.4.1/bin/${os_folder}" + dml_dll_path = "//third_party/microsoft.ai.directml.1.5.1/bin/${os_folder}" copy("copy_dml_dll") { sources = [ "${dml_dll_path}/${dml_dll}" ] outputs = [ "$root_out_dir/{{source_file_part}}" ] diff --git a/src/webnn_native/dml/ContextDML.cpp b/src/webnn_native/dml/ContextDML.cpp index 5bf0ea58f..d5046a420 100644 --- a/src/webnn_native/dml/ContextDML.cpp +++ b/src/webnn_native/dml/ContextDML.cpp @@ -20,13 +20,7 @@ namespace webnn_native { namespace dml { ContextBase* Create(MLContextOptions const* options) { - Ref context = - AcquireRef(new Context(reinterpret_cast(options))); - if (FAILED(reinterpret_cast(context.Get())->CreateDevice())) { - dawn::ErrorLog() << "Failed to create DirectML device."; - return nullptr; - } - return context.Detach(); + return new Context(reinterpret_cast(options)); } Context::Context(ContextOptions const* options) { @@ -36,15 +30,6 @@ namespace webnn_native { namespace dml { mOptions = *options; } - HRESULT Context::CreateDevice() { -#if defined(_DEBUG) - mDevice.reset(new ::pydml::Device(true, true)); -#else - mDevice.reset(new ::pydml::Device(true, false)); -#endif - return mDevice->Init(); - } - GraphBase* Context::CreateGraphImpl() { return new Graph(this); } diff --git a/src/webnn_native/dml/ContextDML.h b/src/webnn_native/dml/ContextDML.h index cad5a1c89..19e145402 100644 --- a/src/webnn_native/dml/ContextDML.h +++ b/src/webnn_native/dml/ContextDML.h @@ -26,16 +26,9 @@ namespace webnn_native { namespace dml { Context(ContextOptions const* options); ~Context() override = default; - HRESULT CreateDevice(); - - std::shared_ptr<::pydml::Device> GetDevice() { - return mDevice; - } - private: GraphBase* CreateGraphImpl() override; - std::shared_ptr<::pydml::Device> mDevice; ContextOptions mOptions; }; diff --git a/src/webnn_native/dml/GraphDML.cpp b/src/webnn_native/dml/GraphDML.cpp index ea14a2da1..1ce73d72b 100644 --- a/src/webnn_native/dml/GraphDML.cpp +++ b/src/webnn_native/dml/GraphDML.cpp @@ -26,6 +26,18 @@ #include "webnn_native/dml/deps/src/precomp.h" #include "webnn_native/ops/LeakyRelu.h" +#define COMPUTE_DML_ERROR_CALLBACK(messages) \ + { \ + if (callback) { \ + callback(MLComputeGraphStatus_Error, nullptr, messages, userdata); \ + } else { \ + dawn::ErrorLog() << messages; \ + } \ + return MLComputeGraphStatus_Error; \ + } \ + for (;;) \ + break + namespace webnn_native { namespace dml { class Result : public ResultBase { public: @@ -447,7 +459,12 @@ namespace webnn_native { namespace dml { } Graph::Graph(Context* context) : GraphBase(context) { - mDevice = context->GetDevice(); +#if defined(_DEBUG) + mDevice.reset(new ::pydml::Device(true, true)); +#else + mDevice.reset(new ::pydml::Device(true, false)); +#endif + mDevice->Init(); mGraph.reset(new ::dml::Graph(mDevice->GetDevice())); } @@ -1148,6 +1165,7 @@ namespace webnn_native { namespace dml { for (auto& binding : mBindings) { inputBindings.push_back(binding.get()); } + std::lock_guard lock(mMutex); return FAILED(mDevice->InitializeOperator(mCompiledModel->op.Get(), inputBindings)) ? MLBuildGraphStatus_Error : MLBuildGraphStatus_Success; @@ -1169,10 +1187,16 @@ namespace webnn_native { namespace dml { NamedOutputsBase* outputs, MLComputeGraphCallback callback, void* userdata) { - for (auto& input : inputs->GetRecords()) { - ::pydml::Binding* inputBinding = mInputs.at(input.first); - inputBinding->data.buffer = const_cast(input.second->buffer); - inputBinding->data.size = input.second->size; + auto namedInputs = inputs->GetRecords(); + for (auto& input : mInputs) { + // All the inputs must be set. + if (namedInputs.find(input.first) == namedInputs.end()) { + COMPUTE_DML_ERROR_CALLBACK("The input must be set."); + } + + ::pydml::Binding* inputBinding = input.second; + inputBinding->data.buffer = const_cast(namedInputs[input.first]->buffer); + inputBinding->data.size = namedInputs[input.first]->size; } std::vector inputBindings; for (auto& binding : mBindings) { @@ -1182,6 +1206,9 @@ namespace webnn_native { namespace dml { std::vector outputNames; if (outputs != nullptr) { for (auto& output : outputs->GetRecords()) { + if (mOutputs.find(output.first) == mOutputs.end()) { + COMPUTE_DML_ERROR_CALLBACK("The output name is invalid."); + } outputNames.push_back(output.first); outputExpressions.push_back(&(mOutputs.at(output.first))); } @@ -1192,6 +1219,7 @@ namespace webnn_native { namespace dml { } } std::vector outputTensors; + std::lock_guard lock(mMutex); if (FAILED(mDevice->DispatchOperator(mCompiledModel->op.Get(), inputBindings, outputExpressions, outputTensors))) { if (callback) { diff --git a/src/webnn_native/dml/GraphDML.h b/src/webnn_native/dml/GraphDML.h index 68e1bb21d..7ce3db718 100644 --- a/src/webnn_native/dml/GraphDML.h +++ b/src/webnn_native/dml/GraphDML.h @@ -16,6 +16,7 @@ #define WEBNN_NATIVE_DML_MODEL_DML_H_ #include +#include #include #include "webnn_native/Graph.h" @@ -83,6 +84,8 @@ namespace webnn_native { namespace dml { size_t size); std::shared_ptr<::pydml::Device> mDevice; + // The mutex is used to lock mDevice. + std::mutex mMutex; std::unique_ptr<::dml::Graph> mGraph; std::map mExpression; std::vector> mBindings; diff --git a/src/webnn_native/dml/deps/script/download_dml.py b/src/webnn_native/dml/deps/script/download_dml.py index 8ced9a8df..c95e81402 100644 --- a/src/webnn_native/dml/deps/script/download_dml.py +++ b/src/webnn_native/dml/deps/script/download_dml.py @@ -23,7 +23,7 @@ dml_feed_url = 'https://api.nuget.org/v3/index.json' dml_resource_id = 'microsoft.ai.directml' -dml_resource_version = '1.4.1' +dml_resource_version = '1.5.1' dependency_dir = '../../../../../third_party' dml_bin_path = f'{dependency_dir}/{dml_resource_id}.{dml_resource_version}/bin/x64-win/' diff --git a/src/webnn_native/openvino/ContextIE.cpp b/src/webnn_native/openvino/ContextIE.cpp index f165e220a..4ad6a6606 100644 --- a/src/webnn_native/openvino/ContextIE.cpp +++ b/src/webnn_native/openvino/ContextIE.cpp @@ -17,17 +17,10 @@ #include "common/Log.h" #include "common/RefCounted.h" #include "webnn_native/openvino/GraphIE.h" -#include "webnn_native/openvino/ienn_symbol_table/ienn_symbol_table.h" namespace webnn_native { namespace ie { ContextBase* Create(MLContextOptions const* options) { - // Load ienn_c_api library. - if (!GetIESymbolTable()->Load()) { - dawn::ErrorLog() << "Failed to load the OpenVINO libraries, please make sure the " - "OpenVINO environment variables are set."; - return nullptr; - } return new Context(reinterpret_cast(options)); } diff --git a/src/webnn_native/openvino/GraphIE.cpp b/src/webnn_native/openvino/GraphIE.cpp index 4f6d7758d..1c9ca49a3 100644 --- a/src/webnn_native/openvino/GraphIE.cpp +++ b/src/webnn_native/openvino/GraphIE.cpp @@ -25,8 +25,8 @@ #include "webnn_native/NamedResults.h" #include "webnn_native/Result.h" #include "webnn_native/openvino/ErrorIE.h" -#include "webnn_native/openvino/ienn_symbol_table/ienn_symbol_table.h" +#define IE(sym) sym #define COMPUTE_ERROR_CALLBACK(code, messages) \ { \ MaybeError maybeError = CheckStatusCode(code, messages); \ diff --git a/src/webnn_native/openvino/ienn/build_ienn_msvc.bat b/src/webnn_native/openvino/ienn/build_ienn_msvc.bat index 9a1d7d2ee..fcdb59d38 100644 --- a/src/webnn_native/openvino/ienn/build_ienn_msvc.bat +++ b/src/webnn_native/openvino/ienn/build_ienn_msvc.bat @@ -151,6 +151,7 @@ if ERRORLEVEL 1 GOTO errorHandling :: copy the ie_nn_c_api.dll to ienn\lib\Windows64 if exist "%ROOT_DIR%\lib\Windows64\ie_nn_c_api.dll" del "%ROOT_DIR%\lib\Windows64\ie_nn_c_api.dll" xcopy "%ROOT_DIR%\build\intel64\Release\ie_nn_c_api.dll" "%ROOT_DIR%\lib\Windows64" +xcopy "%ROOT_DIR%\build\intel64\Release\ie_nn_c_api.lib" "%ROOT_DIR%\lib\Windows64" echo Done. goto :eof diff --git a/src/webnn_native/openvino/ienn/lib/Linux64/libie_nn_c_api.so b/src/webnn_native/openvino/ienn/lib/Linux64/libie_nn_c_api.so index 6a0e02a5e..54824a035 100755 Binary files a/src/webnn_native/openvino/ienn/lib/Linux64/libie_nn_c_api.so and b/src/webnn_native/openvino/ienn/lib/Linux64/libie_nn_c_api.so differ diff --git a/src/webnn_native/openvino/ienn/lib/Windows64/ie_nn_c_api.dll b/src/webnn_native/openvino/ienn/lib/Windows64/ie_nn_c_api.dll index 086252163..cb799092a 100644 Binary files a/src/webnn_native/openvino/ienn/lib/Windows64/ie_nn_c_api.dll and b/src/webnn_native/openvino/ienn/lib/Windows64/ie_nn_c_api.dll differ diff --git a/src/webnn_native/openvino/ienn/lib/Windows64/ie_nn_c_api.lib b/src/webnn_native/openvino/ienn/lib/Windows64/ie_nn_c_api.lib new file mode 100644 index 000000000..9630128e7 Binary files /dev/null and b/src/webnn_native/openvino/ienn/lib/Windows64/ie_nn_c_api.lib differ diff --git a/src/webnn_native/openvino/ienn/src/default_opset.h b/src/webnn_native/openvino/ienn/src/default_opset.h new file mode 100644 index 000000000..ffb39688c --- /dev/null +++ b/src/webnn_native/openvino/ienn/src/default_opset.h @@ -0,0 +1,22 @@ +/******************************************************************************* + * Copyright (C) 2021 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + *******************************************************************************/ + +#ifndef OPENVINO_TF_BRIDGE_DEFAULT_OPSET_H_ +#define OPENVINO_TF_BRIDGE_DEFAULT_OPSET_H_ +#pragma once + +#include "ngraph/opsets/opset5.hpp" + +namespace tensorflow { +namespace openvino_tensorflow { + +namespace opset = ngraph::opset5; +namespace default_opset = ngraph::opset5; + +} // namespace openvino_tensorflow +} // namespace tensorflow + +#endif // OPENVINO_TF_BRIDGE_DEFAULT_OPSET_H_ \ No newline at end of file diff --git a/src/webnn_native/openvino/ienn/src/ie_compilation.cpp b/src/webnn_native/openvino/ienn/src/ie_compilation.cpp index 0969acc6a..3c14a1878 100644 --- a/src/webnn_native/openvino/ienn/src/ie_compilation.cpp +++ b/src/webnn_native/openvino/ienn/src/ie_compilation.cpp @@ -51,6 +51,7 @@ Compilation::Compilation(std::shared_ptr model) ie_core_->LoadNetwork(*(model->network_), device_name, plugin_Config)))); infer_request_.reset(new InferRequest( static_cast(execution_->CreateInferRequest()))); + output_node_map_ = std::move(model->output_node_map_); } Compilation::~Compilation() { @@ -95,6 +96,11 @@ StatusCode Compilation::GetBuffer(const char* name, if (infer_request_ == nullptr) { return StatusCode::NETWORK_NOT_LOADED; } + + if (output_node_map_.find(name) != output_node_map_.end()) { + name = output_node_map_.find(name)->second.data(); + } + Blob::Ptr output_blob = infer_request_->GetBlob(name); *byte_length = output_blob->byteSize(); *buffer = malloc(*byte_length); @@ -108,6 +114,10 @@ StatusCode Compilation::GetDimensions(const char* name, if (infer_request_ == nullptr) { return StatusCode::NETWORK_NOT_LOADED; } + if (output_node_map_.find(name) != output_node_map_.end()) { + name = output_node_map_.find(name)->second.data(); + } + Blob::Ptr output_blob = infer_request_->GetBlob(name); InferenceEngine::SizeVector dims = output_blob->getTensorDesc().getDims(); dimensions->ranks = dims.size(); diff --git a/src/webnn_native/openvino/ienn/src/ie_compilation.h b/src/webnn_native/openvino/ienn/src/ie_compilation.h index d097a26a0..8b09dc316 100644 --- a/src/webnn_native/openvino/ienn/src/ie_compilation.h +++ b/src/webnn_native/openvino/ienn/src/ie_compilation.h @@ -48,6 +48,7 @@ class Compilation { std::unique_ptr infer_request_; std::unique_ptr execution_; std::unique_ptr ie_core_; + std::map output_node_map_; DISALLOW_COPY_AND_ASSIGN(Compilation); }; diff --git a/src/webnn_native/openvino/ienn/src/ie_model.cpp b/src/webnn_native/openvino/ienn/src/ie_model.cpp index bcdf849bf..23ea8af26 100644 --- a/src/webnn_native/openvino/ienn/src/ie_model.cpp +++ b/src/webnn_native/openvino/ienn/src/ie_model.cpp @@ -13,6 +13,7 @@ // limitations under the License. #include "ie_model.h" +#include "transpose_sinking.h" #include #include @@ -20,6 +21,8 @@ #include "ngraph/ngraph.hpp" #include "ngraph/node.hpp" +#include "ngraph/pass/manager.hpp" +#include "ngraph/pass/pass.hpp" #include "utils.h" namespace InferenceEngine { @@ -516,8 +519,15 @@ ie_operand_t* Model::AddGemm(const ie_operand_t* inputs, // operations as "alpha * A * B + beta * C". Transpose if it's necessary. auto a_node = name_node_map_[inputs[0].name]; auto b_node = name_node_map_[inputs[1].name]; + auto size = a_node.get_shape().size(); + SizeVector input_order; + input_order.reserve(size); + for (uint32_t i = 0; i < size; ++i) { + input_order.push_back(size - i - 1); + } + const auto order_node = - op::Constant::create(element::i64, Shape{0}, SizeVector()); + op::Constant::create(element::i64, {size}, input_order); if (options->aTranspose) { a_node = std::make_shared(a_node, order_node)->output(0); } @@ -556,13 +566,33 @@ void Model::Finish() { auto ngraph_function = std::make_shared(ngraph_outputs_, ngraph_inputs_); network_ = std::make_unique(ngraph_function); + // collect the OutputDataMap before passes + OutputsDataMap output_info_previous(network_->getOutputsInfo()); + std::vector output_node_names_previous; + output_node_names_previous.resize(output_info_previous.size()); + for (auto itr : output_info_previous) { + output_node_names_previous.push_back(itr.first); + } + ngraph::pass::Manager passes; + passes + .register_pass(); + passes.run_passes(ngraph_function); + network_ = std::make_unique(ngraph_function); InputsDataMap input_info(network_->getInputsInfo()); for (auto itr : input_info) { itr.second->setPrecision(Precision::FP32); } - OutputsDataMap output_info(network_->getOutputsInfo()); - for (auto itr : output_info) { + OutputsDataMap output_info_after(network_->getOutputsInfo()); + std::vector output_node_names_after; + output_node_names_after.resize(output_info_after.size()); + for (auto itr : output_info_after) { itr.second->setPrecision(Precision::FP32); + output_node_names_after.push_back(itr.first); + } + + for (size_t i = 0; i < output_node_names_after.size(); i++) { + output_node_map_[output_node_names_previous[i]] = + output_node_names_after[i]; } } diff --git a/src/webnn_native/openvino/ienn/src/ie_model.h b/src/webnn_native/openvino/ienn/src/ie_model.h index e1e9e46f6..db6d2ff1e 100644 --- a/src/webnn_native/openvino/ienn/src/ie_model.h +++ b/src/webnn_native/openvino/ienn/src/ie_model.h @@ -78,6 +78,7 @@ class Model { std::vector> ngraph_inputs_; std::vector> ngraph_outputs_; std::unique_ptr network_; + std::map output_node_map_; DISALLOW_COPY_AND_ASSIGN(Model); }; diff --git a/src/webnn_native/openvino/ienn/src/transpose_sinking.cpp b/src/webnn_native/openvino/ienn/src/transpose_sinking.cpp new file mode 100644 index 000000000..038ad8b2f --- /dev/null +++ b/src/webnn_native/openvino/ienn/src/transpose_sinking.cpp @@ -0,0 +1,447 @@ +/***************************************************************************** + * Copyright (C) 2021 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + *****************************************************************************/ + +#include +#include +#include +#include +#include + +#include "ngraph/ngraph.hpp" +#include "ngraph/pattern/op/label.hpp" +#include "ngraph/util.hpp" + +#include "default_opset.h" +#include "transpose_sinking.h" + +/* + * This file is originated from this + * repo-https://github.com/openvinotoolkit/openvino_tensorflow/blob/master/openvino_tensorflow/pass/transpose_sinking.cc + * commit id: 5c8f0130d3bbac4485476b612b26edec4e81f339 + */ +using namespace std; + +namespace tensorflow { +namespace openvino_tensorflow { +namespace pass { + +using TransposeMap = unordered_map>; + +static ngraph::CoordinateDiff apply_permutation(ngraph::CoordinateDiff input, + ngraph::AxisVector order) { + ngraph::CoordinateDiff output(input.size()); + for (size_t i = 0; i < order.size(); i++) { + output[i] = input.at(order.at(i)); + } + return output; +} + +static ngraph::AxisVector permutation_to_default_order( + const ngraph::AxisVector& axis_order) { + ngraph::AxisVector out(axis_order.size()); + for (size_t i = 0; i < axis_order.size(); i++) { + out.at(axis_order[i]) = i; + } + return out; +} + +template +static string describe(shared_ptr node) { + // ensure that it's either a reshape or a transpose + // TODO: use static_assert + if (!(std::is_base_of::value || + std::is_base_of::value)) { + throw runtime_error( + "describe template specialization has to be either reshape or " + "transpose"); + } + stringstream ss; + auto transpose = ngraph::as_type_ptr(node); + auto const1 = ngraph::as_type_ptr( + transpose->get_input_node_shared_ptr(1)); + ss << transpose->get_name() << " ( axis order = " + << ngraph::vector_to_string(const1->get_axis_vector_val()) + << " , shape = " << ngraph::vector_to_string(transpose->get_shape()) + << " ) " + << " , input = " << transpose->input_value(0).get_node()->get_name(); + return ss.str(); +} + +static shared_ptr make_transpose( + ngraph::Output arg, + const ngraph::AxisVector& input_order) { + auto order = std::make_shared( + ngraph::element::u64, ngraph::Shape{input_order.size()}, input_order); + auto transpose = make_shared(arg, order); + return transpose; +} + +static shared_ptr make_reshape( + ngraph::Output arg, + const ngraph::AxisVector& input_order) { + auto order = std::make_shared( + ngraph::element::u64, ngraph::Shape{input_order.size()}, input_order); + auto transpose = make_shared(arg, order, false); + return transpose; +} + +static void write_transposemap(TransposeMap& reorders, + ngraph::Output target, + shared_ptr transpose) { + auto name = + target.get_node()->get_name() + "." + to_string(target.get_index()); + reorders[name] = transpose; +} + +static shared_ptr read_transposemap( + TransposeMap& reorders, + ngraph::Output target) { + auto name = + target.get_node()->get_name() + "." + to_string(target.get_index()); + auto transpose = reorders[name]; + return transpose; +} + +static shared_ptr combine_transposes( + shared_ptr t1, + shared_ptr t2) { + auto default_order = ngraph::get_default_order(t1->get_shape()); + auto t1_const = ngraph::as_type_ptr( + t1->input_value(1).get_node_shared_ptr()); + auto t2_const = ngraph::as_type_ptr( + t2->input_value(1).get_node_shared_ptr()); + auto perm_t1 = + ngraph::apply_permutation(default_order, t1_const->get_axis_vector_val()); + auto perm_t2 = + ngraph::apply_permutation(perm_t1, t2_const->get_axis_vector_val()); + auto combined = make_transpose(t2->input_value(0), perm_t2); + return combined; +} + +static void insert_transpose(shared_ptr target, + shared_ptr transpose, + size_t input_index) { + auto arg = target->input(input_index).get_source_output(); + auto new_order = ngraph::as_type_ptr( + transpose->input_value(1).get_node_shared_ptr()); + auto new_transpose = make_transpose(arg.get_node_shared_ptr(), + new_order->get_axis_vector_val()); + target->input(input_index).replace_source_output(new_transpose->output(0)); +} + +static void delete_transpose(shared_ptr transpose) { + if (!transpose->get_users().empty()) { + ngraph::Output output = transpose->output(0); + for (auto input : output.get_target_inputs()) { + input.replace_source_output(transpose->input_value(0)); + } + } +} + +static void mark_transpose_for_deletion( + shared_ptr transpose, + set>& transposes_to_delete) { + transposes_to_delete.insert(transpose); +} + +static shared_ptr create_default_transpose( + ngraph::Output n) { + auto default_order = ngraph::get_default_order(n.get_shape()); + auto order = std::make_shared( + ngraph::element::u64, ngraph::Shape{default_order.size()}, default_order); + return make_shared(n, order); +} + +// convert_binary_to_default_order is used when one of the arguments +// of a binary op isn't in the default format (i.e. nhwc instead of nchw) +// We normalize the "left" argument to match the order of the "right" argument +// by either inserting a transpose or a reshape, depending on the shape of the +// "left" argument. +static void convert_binary_to_default_order( + shared_ptr binary, + const ngraph::Input& input, + ngraph::Output right, + TransposeMap& reorders, + set>& transposes_to_delete) { + auto left = input.get_source_output(); + auto right_t = read_transposemap(reorders, right); + auto right_const = ngraph::as_type_ptr( + right_t->input_value(1).get_node_shared_ptr()); + + auto perm_to_def = + permutation_to_default_order(right_const->get_axis_vector_val()); + + // if right input is being implicitly broadcasted, insert a reshape + // instead of a transpose + shared_ptr new_node; + auto left_shape = left.get_shape(); + if (left_shape.size() < perm_to_def.size()) { + left_shape.insert(left_shape.begin(), + perm_to_def.size() - left_shape.size(), 1); + auto new_shape = ngraph::apply_permutation(left_shape, perm_to_def); + new_node = make_reshape(left, new_shape); + } else if (left_shape.size() == perm_to_def.size()) { + new_node = make_transpose(left, perm_to_def); + } else { + throw runtime_error( + "case not supported when converting binary to default order"); + } + input.replace_source_output(new_node->output(0)); + + // this should now insert transpose on right + mark_transpose_for_deletion(right_t, transposes_to_delete); + write_transposemap(reorders, binary, right_t); +} + +static void materialize_shapes( + shared_ptr n, + TransposeMap& reorders, + set>& transposes_to_delete) { + // For each node, create a default transpose for + // each of the outputs and store in the map + for (auto& it : n->outputs()) { + write_transposemap(reorders, it, create_default_transpose(it)); + } + + for (size_t i = 0; i < n->input_values().size(); i++) { + // materialize all pending transposes, flush pending transposes + auto arg = n->input_value(i); + auto arg_transpose = read_transposemap(reorders, arg); + mark_transpose_for_deletion(arg_transpose, transposes_to_delete); + auto arg_shape = arg.get_shape(); + auto arg_transpose_order = ngraph::as_type_ptr( + arg_transpose->input_value(1).get_node_shared_ptr()); + if (arg_transpose_order->get_axis_vector_val() != + get_default_order(arg.get_shape())) { + // Insert if arg needs to be transposed. + insert_transpose(n, arg_transpose, i); + } + } +} + +static void sink_transpose( + shared_ptr transpose, + TransposeMap& reorders, + set>& transposes_to_delete) { + auto transpose_in = transpose->input_value(0); + auto orig_transpose = read_transposemap(reorders, transpose_in); + // combine both transposes + auto new_transpose = combine_transposes(orig_transpose, transpose); + // remove original transpose now it's combined with a new one + // should be safe to remove an already detached node + mark_transpose_for_deletion(orig_transpose, transposes_to_delete); + // replace transpose with combined one + ngraph::replace_node(transpose, new_transpose); + mark_transpose_for_deletion(new_transpose, transposes_to_delete); + write_transposemap(reorders, new_transpose, new_transpose); +} + +static void sink_unary( + shared_ptr n, + TransposeMap& reorders, + set>& /* transposes_to_delete */) { + auto arg_transpose = read_transposemap(reorders, n->input_value(0)); + write_transposemap(reorders, n, arg_transpose); +} + +static void sink_binary(shared_ptr binary, + TransposeMap& reorders, + set>& transposes_to_delete) { + auto left = binary->input_value(0); + auto right = binary->input_value(1); + auto left_t = read_transposemap(reorders, left); + auto right_t = read_transposemap(reorders, right); + auto left_const = ngraph::as_type_ptr( + left_t->input_value(1).get_node_shared_ptr()); + auto right_const = ngraph::as_type_ptr( + right_t->input_value(1).get_node_shared_ptr()); + + auto left_order = left_const->get_axis_vector_val(); + auto right_order = right_const->get_axis_vector_val(); + + auto left_mismatch = + left_order != ngraph::get_default_order(left.get_shape()); + auto right_mismatch = + right_order != ngraph::get_default_order(right.get_shape()); + + if ((left_order.size() == right_order.size() && left_order == right_order) || + (!left_mismatch && !right_mismatch)) { + // Propagate the reshape which matches the shape of the binary node + auto new_transpose = + (binary->get_output_shape(0) == left.get_shape()) ? left_t : right_t; + write_transposemap(reorders, binary, new_transpose); + // at this point, both transposes will be eventually removed + mark_transpose_for_deletion(left_t, transposes_to_delete); + mark_transpose_for_deletion(right_t, transposes_to_delete); + } else { + if (right_mismatch) { + convert_binary_to_default_order(binary, binary->input(0), right, reorders, + transposes_to_delete); + } else { + if (left_mismatch) { + convert_binary_to_default_order(binary, binary->input(1), left, + reorders, transposes_to_delete); + } + } + } +} + +static void sink_pad( + shared_ptr n, + TransposeMap& reorders, + set>& /* transposes_to_delete */) { + auto n_in = n->input_value(0); + auto arg_transpose = read_transposemap(reorders, n_in); + describe(arg_transpose); + auto arg_transpose_order = ngraph::as_type_ptr( + arg_transpose->input_value(1).get_node_shared_ptr()); + auto order = arg_transpose_order->get_axis_vector_val(); + // we need the correct input shape to produce the right output shape + // we are going to create a label of the right input shape, + // so a new pad will have the right shape + auto def_order = permutation_to_default_order(order); + auto input_shape = + ngraph::apply_permutation(arg_transpose->get_shape(), def_order); + auto dummy_correct_shape = make_shared( + arg_transpose->get_element_type(), input_shape); + + auto pad_begin = apply_permutation(n->get_pads_begin(), def_order); + auto pad_end = apply_permutation(n->get_pads_end(), def_order); + auto new_begin = make_shared( + ngraph::element::i64, ngraph::Shape{pad_begin.size()}, pad_begin); + auto new_end = make_shared( + ngraph::element::i64, ngraph::Shape{pad_end.size()}, pad_end); + auto new_pad = + make_shared(dummy_correct_shape, new_begin, new_end, + n->input_value(3), n->get_pad_mode()); + ngraph::replace_node(dummy_correct_shape, + n->input_value(0).get_node_shared_ptr()); + ngraph::replace_node(n, new_pad); + auto new_transpose = make_transpose(new_pad, order); + write_transposemap(reorders, new_pad, new_transpose); +} + +static void sink_concat(shared_ptr n, + TransposeMap& reorders, + set>& transposes_to_delete) { + auto n_in = n->input_value(0); + auto arg_transpose = read_transposemap(reorders, n_in); + auto arg_transpose_order = ngraph::as_type_ptr( + arg_transpose->input_value(1).get_node_shared_ptr()); + auto order = arg_transpose_order->get_axis_vector_val(); + // we need the correct input shape to produce the right output shape + // we are going to create a label of the right input shape, + // so a new concat will have the right shape + auto def_order = permutation_to_default_order(order); + auto input_shape = + ngraph::apply_permutation(arg_transpose->get_shape(), def_order); + auto dummy_correct_shape = make_shared( + arg_transpose->get_element_type(), input_shape); + + ngraph::NodeVector new_args; + new_args.push_back(dummy_correct_shape); + + for (size_t i = 1; i < n->get_input_size(); i++) { + auto iarg = n->input_value(i); + auto iarg_transpose = read_transposemap(reorders, iarg); + auto iarg_transpose_order = ngraph::as_type_ptr( + iarg_transpose->input_value(1).get_node_shared_ptr()); + auto iorder = iarg_transpose_order->get_axis_vector_val(); + if (iorder != order) { + materialize_shapes(n, reorders, transposes_to_delete); + return; + } + + auto iinput_shape = + ngraph::apply_permutation(iarg_transpose->get_shape(), def_order); + auto idummy_correct_shape = make_shared( + iarg_transpose->get_element_type(), iinput_shape); + new_args.push_back(idummy_correct_shape); + } + + auto new_axis = order.at(n->get_concatenation_axis()); + auto new_concat = make_shared(new_args, new_axis); + // put back the original arguments + for (size_t i = 0; i < new_concat->get_input_size(); i++) { + new_concat->input(i).replace_source_output(n->input_value(i)); + } + ngraph::replace_node(n, new_concat); + auto new_transpose = make_transpose(new_concat, order); + write_transposemap(reorders, new_concat, new_transpose); +} + +// The goal of TransposeSinking is to remove +// round-trip transposes(i.e. nhwc->nchw(nchw-only-op)->nhwc) +// around nchw-only-op (e.g.Convolution, Batchnorm, Avg/MaxPool) +// This is achieved by both **sinking**, propagating transposes +// through ops towards ngraph::op::Results, +// or **swimming** Transposes up towards ngraph::op::Parameter +// For each op type we support we can either combine +// two transposes by replacing the existing Transpose, +// materialize pending transposes if they can't be propagated through op +bool TransposeSinking::run_on_function(shared_ptr f) { + TransposeMap reorders; + set> transposes_to_delete; + unordered_map orig_result_out_shape; + + // delete + + // STEP 1 : Sink or Swim transposes away for op clusters + for (auto n : f->get_ordered_ops()) { + // collect output shape of all Result nodes for a sanity check + if (ngraph::op::is_output(n)) { + orig_result_out_shape[n->get_name()] = n->get_output_shape(0); + } + if (auto transpose = ngraph::as_type_ptr(n)) { + sink_transpose(transpose, reorders, transposes_to_delete); + } else if (ngraph::op::is_unary_elementwise_arithmetic(n) || + ngraph::as_type_ptr(n)) { + // since clamp op is not identified as unary_element op + // and we use clamp to implement activations, we need to sink Clamp as + // other unary ops + sink_unary(n, reorders, transposes_to_delete); + } else if (ngraph::op::is_binary_elementwise_arithmetic(n)) { + sink_binary(n, reorders, transposes_to_delete); + } else if (auto pad = ngraph::as_type_ptr(n)) { + sink_pad(pad, reorders, transposes_to_delete); + } else if (auto concat = ngraph::as_type_ptr(n)) { + sink_concat(concat, reorders, transposes_to_delete); + } else { + materialize_shapes(n, reorders, transposes_to_delete); + } + } + + // STEP 2: purge all the transposes we either sunk or swam. + for (auto r : transposes_to_delete) { + delete_transpose(r); + } + + // STEP 3: fix wrong shape info wholesale + for (auto n : f->get_ordered_ops()) { + n->revalidate_and_infer_types(); + } + + const ngraph::ResultVector& results = f->get_results(); + for (auto r : results) { + // make sure shapes are always materialized before results + NGRAPH_CHECK( + r->get_shape() == r->get_input_shape(0) && + r->get_element_type() == r->input_value(0).get_element_type(), + " op::Result = ", *r, ", Arg = ", r->input_value(0).get_node()); + + // make sure that after TransposeSinking pass the output_shape for Result + // does not change from the expected output_shape before the pass + NGRAPH_CHECK(r->get_output_shape(0) == orig_result_out_shape[r->get_name()], + " op::Result = ", *r, " expected output shape = ", + orig_result_out_shape[r->get_name()]); + } + + return true; +} + +} // namespace pass +} // namespace openvino_tensorflow +} // namespace tensorflow diff --git a/src/webnn_native/openvino/ienn/src/transpose_sinking.h b/src/webnn_native/openvino/ienn/src/transpose_sinking.h new file mode 100644 index 000000000..f58b2391c --- /dev/null +++ b/src/webnn_native/openvino/ienn/src/transpose_sinking.h @@ -0,0 +1,27 @@ +/***************************************************************************** + * Copyright (C) 2021 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + *****************************************************************************/ + +#pragma once + +#include "ngraph/ngraph.hpp" +#include "ngraph/pass/pass.hpp" +#include "ngraph/util.hpp" + +namespace tensorflow { +namespace openvino_tensorflow { +namespace pass { + +class TransposeSinking : public ngraph::pass::FunctionPass { + public: + TransposeSinking() { + set_property(ngraph::pass::PassProperty::REQUIRE_STATIC_SHAPE, true); + } + bool run_on_function(std::shared_ptr function) override; +}; + +} // namespace pass +} // namespace openvino_tensorflow +} // namespace tensorflow diff --git a/src/webnn_native/openvino/ienn_symbol_table/.clang-format b/src/webnn_native/openvino/ienn_symbol_table/.clang-format deleted file mode 100644 index 0f2062e30..000000000 --- a/src/webnn_native/openvino/ienn_symbol_table/.clang-format +++ /dev/null @@ -1,9 +0,0 @@ - -# Defines the Chromium style for automatic reformatting. -# http://clang.llvm.org/docs/ClangFormatStyleOptions.html -BasedOnStyle: Chromium -# This defaults to 'Auto'. Explicitly set it for a while, so that -# 'vector >' in existing files gets formatted to -# 'vector>'. ('Auto' means that clang-format will only use -# 'int>>' if the file already contains at least one such instance.) -Standard: Cpp11 diff --git a/src/webnn_native/openvino/ienn_symbol_table/ienn_symbol_table.cc b/src/webnn_native/openvino/ienn_symbol_table/ienn_symbol_table.cc deleted file mode 100644 index 013323546..000000000 --- a/src/webnn_native/openvino/ienn_symbol_table/ienn_symbol_table.cc +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2021 The WebNN-native Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "ienn_symbol_table.h" - -#include - -namespace webnn_native { - -// The ie_nn_c_api symbols. -#if defined(__linux__) -LATE_BINDING_SYMBOL_TABLE_DEFINE_BEGIN(IESymbolTable, "libie_nn_c_api.so") -#elif defined(_WIN32) || defined(_WIN64) -LATE_BINDING_SYMBOL_TABLE_DEFINE_BEGIN(IESymbolTable, "ie_nn_c_api.dll") -#endif -#define X(sym) LATE_BINDING_SYMBOL_TABLE_DEFINE_ENTRY(IESymbolTable, sym) -IE_SYMBOLS_LIST -#undef X -LATE_BINDING_SYMBOL_TABLE_DEFINE_END(IESymbolTable) - -IESymbolTable* GetIESymbolTable() { - static std::unique_ptr ienn_symbol_table = - std::make_unique(); - return ienn_symbol_table.get(); -} - -} // namespace webnn_native diff --git a/src/webnn_native/openvino/ienn_symbol_table/ienn_symbol_table.h b/src/webnn_native/openvino/ienn_symbol_table/ienn_symbol_table.h deleted file mode 100644 index 8844834ce..000000000 --- a/src/webnn_native/openvino/ienn_symbol_table/ienn_symbol_table.h +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2021 The WebNN-native Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef SERVICES_ML_IENN_SYMBOL_TABLE_H_ -#define SERVICES_ML_IENN_SYMBOL_TABLE_H_ - -#include "late_binding_symbol_table.h" - -namespace webnn_native { - -// The ienn symbols we need, as an X-Macro list. -#define IE_SYMBOLS_LIST \ - X(ie_create_model) \ - X(ie_model_free) \ - X(ie_model_add_constant) \ - X(ie_model_add_input) \ - X(ie_model_add_output) \ - X(ie_model_add_mat_mul) \ - X(ie_operand_free) \ - X(ie_model_finish) \ - X(ie_create_compilation) \ - X(ie_compilation_free) \ - X(ie_compilation_set_input) \ - X(ie_compilation_compute) \ - X(ie_compilation_get_output) \ - X(ie_model_add_batch_norm) \ - X(ie_model_add_binary) \ - X(ie_model_add_clamp) \ - X(ie_model_add_conv2d) \ - X(ie_model_add_gemm) \ - X(ie_model_add_pool2d) \ - X(ie_model_add_relu) \ - X(ie_model_add_reshape) \ - X(ie_model_add_softmax) \ - X(ie_model_add_transpose) \ - X(ie_model_add_leaky_relu) \ - X(ie_model_add_concat) \ - X(ie_model_get_outputs_number) \ - X(ie_model_get_output_name) \ - X(ie_model_free_name) \ - X(ie_compilation_get_buffer) \ - X(ie_compilation_free_buffer) \ - X(ie_compilation_get_dimensions) \ - X(ie_compilation_free_dimensions) - -LATE_BINDING_SYMBOL_TABLE_DECLARE_BEGIN(IESymbolTable) -#define X(sym) LATE_BINDING_SYMBOL_TABLE_DECLARE_ENTRY(IESymbolTable, sym) -IE_SYMBOLS_LIST -#undef X -LATE_BINDING_SYMBOL_TABLE_DECLARE_END(IESymbolTable) - -IESymbolTable* GetIESymbolTable(); - -#if defined(_WIN32) || defined(_WIN64) || defined(__linux__) -#define IE(sym) LATESYM_GET(IESymbolTable, GetIESymbolTable(), sym) -#else -#define IE(sym) sym -#endif - -} // namespace webnn_native - -#endif // SERVICES_ML_IENN_SYMBOL_TABLE_H_ diff --git a/src/webnn_native/openvino/ienn_symbol_table/late_binding_symbol_table.cc b/src/webnn_native/openvino/ienn_symbol_table/late_binding_symbol_table.cc deleted file mode 100644 index 56e51fa8e..000000000 --- a/src/webnn_native/openvino/ienn_symbol_table/late_binding_symbol_table.cc +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2010 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#include "late_binding_symbol_table.h" - -#if defined(__linux__) || defined(__APPLE__) -#include -#include -#elif defined(_WIN32) || defined(_WIN64) -#include -#endif - -#include "common/Log.h" -#include "common/SystemUtils.h" -#if defined(__APPLE__) -#include "base/mac/bundle_locations.h" -#include "base/mac/foundation_util.h" -#endif - -namespace webnn_native { - -inline static const char* GetDllError() { -#if defined(__linux__) || defined(__APPLE__) - char* err = dlerror(); - if (err) { - return err; - } -#endif - return "No error"; -} - -DllHandle InternalLoadDll(const char dll_name[]) { - DllHandle handle = nullptr; -#if defined(__linux__) - // Use absolute path to open library if file exists, otherwise find it in - // LD_LIBRARY_PATH such as node.js. - std::string dll_path = GetExecutableDirectory().append(dll_name); - if (access(dll_path.data(), F_OK) != 0) { - dll_path = std::string(dll_name); - } - handle = dlopen(dll_path.data(), RTLD_NOW); -#elif defined(__APPLE__) - // base::FilePath base_dir; - // if (base::mac::AmIBundled()) { - // base_dir = base::mac::FrameworkBundlePath().Append("Libraries"); - // } else { - // if (!base::PathService::Get(base::FILE_EXE, &base_dir)) { - // LOG(ERROR) << "PathService::Get failed."; - // return nullptr; - // } - // base_dir = base_dir.DirName(); - // } - // base::FilePath dll_path = base_dir.Append(dll_name); - handle = dlopen(dll_name.MaybeAsASCII().c_str(), RTLD_NOW); -#elif defined(_WIN32) || defined(_WIN64) - handle = LoadLibraryA(dll_name); -#endif - return handle; -} - -void InternalUnloadDll(DllHandle handle) { -#if !defined(ADDRESS_SANITIZER) -#if defined(__linux__) || defined(__APPLE__) - if (dlclose(handle) != 0) { - dawn::ErrorLog() << GetDllError(); - } -#elif defined(_WIN32) || defined(_WIN64) - FreeLibrary(static_cast(handle)); -#endif -#endif // !defined(ADDRESS_SANITIZER) -} - -static bool LoadSymbol(DllHandle handle, - const char* symbol_name, - void** symbol) { -#if defined(__linux__) || defined(__APPLE__) - *symbol = dlsym(handle, symbol_name); - char* err = dlerror(); - if (err) { - dawn::ErrorLog() << "Error loading symbol " << symbol_name << " : " << err; - return false; - } else if (!*symbol) { - dawn::ErrorLog() << "Symbol " << symbol_name << " is NULL"; - return false; - } -#elif defined(_WIN32) || defined(_WIN64) - *symbol = reinterpret_cast( - GetProcAddress(static_cast(handle), symbol_name)); -#endif - return true; -} - -// This routine MUST assign SOME value for every symbol, even if that value is -// NULL, or else some symbols may be left with uninitialized data that the -// caller may later interpret as a valid address. -bool InternalLoadSymbols(DllHandle handle, - int num_symbols, - const char* const symbol_names[], - void* symbols[]) { - // Clear any old errors. - GetDllError(); - for (int i = 0; i < num_symbols; ++i) { - if (!LoadSymbol(handle, symbol_names[i], &symbols[i])) { - return false; - } - } - return true; -} - -} // namespace webnn_native diff --git a/src/webnn_native/openvino/ienn_symbol_table/late_binding_symbol_table.h b/src/webnn_native/openvino/ienn_symbol_table/late_binding_symbol_table.h deleted file mode 100644 index 4cdd1a36f..000000000 --- a/src/webnn_native/openvino/ienn_symbol_table/late_binding_symbol_table.h +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (c) 2010 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. An additional intellectual property rights grant can be found - * in the file PATENTS. All contributing project authors may - * be found in the AUTHORS file in the root of the source tree. - */ - -#ifndef SERVICES_LATE_BINDING_SYMBOL_TABLE_H_ -#define SERVICES_LATE_BINDING_SYMBOL_TABLE_H_ - -#include -#include // for NULL -#include - -// #include "base/macros.h" - -// This file provides macros for creating "symbol table" classes to simplify the -// dynamic loading of symbols from DLLs. Currently the implementation only -// supports pure C symbols. - -namespace webnn_native { - -typedef void* DllHandle; - -const DllHandle kInvalidDllHandle = NULL; - -// These are helpers for use only by the class below. -DllHandle InternalLoadDll(const char dll_name[]); - -void InternalUnloadDll(DllHandle handle); - -bool InternalLoadSymbols(DllHandle handle, - int num_symbols, - const char* const symbol_names[], - void* symbols[]); - -template -class LateBindingSymbolTable { - public: - LateBindingSymbolTable() - : handle_(kInvalidDllHandle), undefined_symbols_(false) { - memset(symbols_, 0, sizeof(symbols_)); - } - - ~LateBindingSymbolTable() { Unload(); } - - static int NumSymbols() { return SYMBOL_TABLE_SIZE; } - - // We do not use this, but we offer it for theoretical convenience. - static const char* GetSymbolName(int index) { - assert(index < NumSymbols()); - return kSymbolNames[index]; - } - - bool IsLoaded() const { return handle_ != kInvalidDllHandle; } - - // Loads the DLL and the symbol table. Returns true iff the DLL and symbol - // table loaded successfully. - bool Load() { - if (IsLoaded()) { - return true; - } - if (undefined_symbols_) { - // We do not attempt to load again because repeated attempts are not - // likely to succeed and DLL loading is costly. - return false; - } - handle_ = InternalLoadDll(kDllName); - if (!IsLoaded()) { - return false; - } - if (!InternalLoadSymbols(handle_, NumSymbols(), kSymbolNames, symbols_)) { - undefined_symbols_ = true; - Unload(); - return false; - } - return true; - } - - void Unload() { - if (!IsLoaded()) { - return; - } - InternalUnloadDll(handle_); - handle_ = kInvalidDllHandle; - memset(symbols_, 0, sizeof(symbols_)); - } - - // Retrieves the given symbol. NOTE: Recommended to use LATESYM_GET below - // instead of this. - void* GetSymbol(int index) const { - assert(IsLoaded()); - assert(index < NumSymbols()); - return symbols_[index]; - } - - private: - DllHandle handle_; - bool undefined_symbols_; - void* symbols_[SYMBOL_TABLE_SIZE]; - - // DISALLOW_COPY_AND_ASSIGN(LateBindingSymbolTable); -}; - -// This macro must be invoked in a header to declare a symbol table class. -#define LATE_BINDING_SYMBOL_TABLE_DECLARE_BEGIN(ClassName) enum { -// This macro must be invoked in the header declaration once for each symbol -// (recommended to use an X-Macro to avoid duplication). -// This macro defines an enum with names built from the symbols, which -// essentially creates a hash table in the compiler from symbol names to their -// indices in the symbol table class. -#define LATE_BINDING_SYMBOL_TABLE_DECLARE_ENTRY(ClassName, sym) \ - ClassName##_SYMBOL_TABLE_INDEX_##sym, - -// This macro completes the header declaration. -#define LATE_BINDING_SYMBOL_TABLE_DECLARE_END(ClassName) \ - ClassName##_SYMBOL_TABLE_SIZE \ - } \ - ; \ - \ - extern const char ClassName##_kDllName[]; \ - extern const char* const \ - ClassName##_kSymbolNames[ClassName##_SYMBOL_TABLE_SIZE]; \ - \ - typedef ::webnn_native::LateBindingSymbolTable< \ - ClassName##_SYMBOL_TABLE_SIZE, ClassName##_kDllName, \ - ClassName##_kSymbolNames> \ - ClassName; - -// This macro must be invoked in a .cc file to define a previously-declared -// symbol table class. -#define LATE_BINDING_SYMBOL_TABLE_DEFINE_BEGIN(ClassName, dllName) \ - const char ClassName##_kDllName[] = dllName; \ - const char* const ClassName##_kSymbolNames[ClassName##_SYMBOL_TABLE_SIZE] = { -// This macro must be invoked in the .cc definition once for each symbol -// (recommended to use an X-Macro to avoid duplication). -// This would have to use the mangled name if we were to ever support C++ -// symbols. -#define LATE_BINDING_SYMBOL_TABLE_DEFINE_ENTRY(ClassName, sym) #sym, - -#define LATE_BINDING_SYMBOL_TABLE_DEFINE_END(ClassName) \ - } \ - ; - -// Index of a given symbol in the given symbol table class. -#define LATESYM_INDEXOF(ClassName, sym) (ClassName##_SYMBOL_TABLE_INDEX_##sym) - -// Returns a reference to the given late-binded symbol, with the correct type. -#define LATESYM_GET(ClassName, inst, sym) \ - (*reinterpret_cast( \ - (inst)->GetSymbol(LATESYM_INDEXOF(ClassName, sym)))) - -} // namespace webnn_native - -#endif // SERVICES_LATE_BINDING_SYMBOL_TABLE_H_