mirror of
https://github.com/saymrwulf/onnxruntime.git
synced 2026-05-25 22:26:24 +00:00
[js/web] fix bundle for multi-thread, add e2e test and support nodejs (#7688)
* fix bundle for multi-thread, add e2e test and support nodejs * add copyright banner * resolve comments * add comments for isMultiThreadSupported()
This commit is contained in:
parent
a74e41e47d
commit
97d9bcd644
26 changed files with 612 additions and 145 deletions
10
js/README.md
10
js/README.md
|
|
@ -127,6 +127,7 @@ Node.js v12+ (recommended v14+)
|
|||
2. ~~Follow [instructions](https://www.onnxruntime.ai/docs/how-to/build.html#apis-and-language-bindings) for building ONNX Runtime WebAssembly. (TODO: document is not ready. we are working on it.)~~
|
||||
|
||||
in `<ORT_ROOT>/`, run either of the following commands to build WebAssembly:
|
||||
|
||||
```sh
|
||||
# In windows, use 'build' to replace './build.sh'
|
||||
|
||||
|
|
@ -136,16 +137,19 @@ Node.js v12+ (recommended v14+)
|
|||
# The following command build release.
|
||||
./build.sh --config Release --build_wasm --skip_tests --disable_wasm_exception_catching --disable_rtti
|
||||
```
|
||||
|
||||
To build with multi-thread support, append flag ` --enable_wasm_threads` to the command.
|
||||
|
||||
3. Copy following files from build output folder to `<ORT_ROOT>/js/web/dist/`:
|
||||
|
||||
- ort-wasm.wasm
|
||||
- ort-wasm-threaded.wasm (if appliable)
|
||||
- ort-wasm-threaded.worker.js (if appliable)
|
||||
- ort-wasm-threaded.wasm (build with flag '--enable_wasm_threads')
|
||||
|
||||
4. Copy following files from build output folder to `<ORT_ROOT>/js/web/lib/wasm/binding/`:
|
||||
|
||||
- ort-wasm.js
|
||||
- ort-wasm-threaded.js (if appliable)
|
||||
- ort-wasm-threaded.js (build with flag '--enable_wasm_threads')
|
||||
- ort-wasm-threaded.worker.js (build with flag '--enable_wasm_threads')
|
||||
|
||||
5. Use following command in folder `<ORT_ROOT>/js/web` to build:
|
||||
```
|
||||
|
|
|
|||
1
js/web/.gitignore
vendored
1
js/web/.gitignore
vendored
|
|
@ -16,4 +16,5 @@ script/**/*.js.map
|
|||
lib/wasm/binding/**/*.wasm
|
||||
!lib/wasm/binding/**/*.d.ts
|
||||
|
||||
test/testdata-config.json
|
||||
test/data/node/
|
||||
|
|
|
|||
|
|
@ -45,10 +45,8 @@ module.exports = function (config) {
|
|||
frameworks: ['mocha'],
|
||||
files: [
|
||||
{ pattern: commonFile },
|
||||
{ pattern: 'test/testdata-config.js' },
|
||||
{ pattern: mainFile },
|
||||
{ pattern: 'test/testdata-file-cache-*.json', included: false },
|
||||
//{ pattern: 'test/onnx-worker.js', included: false },
|
||||
{ pattern: 'test/data/**/*', included: false, nocache: true },
|
||||
{ pattern: 'dist/ort-wasm.wasm', included: false },
|
||||
{ pattern: 'dist/ort-wasm-threaded.wasm', included: false },
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import {readFile} from 'fs';
|
||||
import {Backend, env, InferenceSession, SessionHandler} from 'onnxruntime-common';
|
||||
import {cpus} from 'os';
|
||||
import {promisify} from 'util';
|
||||
|
||||
import {OnnxruntimeWebAssemblySessionHandler} from './wasm/session-handler';
|
||||
import {initializeWebAssembly} from './wasm/wasm-factory';
|
||||
|
|
@ -18,7 +21,8 @@ export const initializeFlags = (): void => {
|
|||
}
|
||||
|
||||
if (typeof env.wasm.numThreads !== 'number' || !Number.isInteger(env.wasm.numThreads) || env.wasm.numThreads < 0) {
|
||||
env.wasm.numThreads = Math.ceil((navigator.hardwareConcurrency || 1) / 2);
|
||||
const numCpuLogicalCores = typeof navigator === 'undefined' ? cpus().length : navigator.hardwareConcurrency;
|
||||
env.wasm.numThreads = Math.ceil((numCpuLogicalCores || 1) / 2);
|
||||
}
|
||||
env.wasm.numThreads = Math.min(4, env.wasm.numThreads);
|
||||
|
||||
|
|
@ -42,9 +46,15 @@ class OnnxruntimeWebAssemblyBackend implements Backend {
|
|||
Promise<SessionHandler> {
|
||||
let buffer: Uint8Array;
|
||||
if (typeof pathOrBuffer === 'string') {
|
||||
const response = await fetch(pathOrBuffer);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
buffer = new Uint8Array(arrayBuffer);
|
||||
if (typeof fetch === 'undefined') {
|
||||
// node
|
||||
buffer = await promisify(readFile)(pathOrBuffer);
|
||||
} else {
|
||||
// browser
|
||||
const response = await fetch(pathOrBuffer);
|
||||
const arrayBuffer = await response.arrayBuffer();
|
||||
buffer = new Uint8Array(arrayBuffer);
|
||||
}
|
||||
} else {
|
||||
buffer = pathOrBuffer;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,5 +3,9 @@
|
|||
|
||||
import {OrtWasmModule} from './ort-wasm';
|
||||
|
||||
declare const moduleFactory: EmscriptenModuleFactory<OrtWasmModule>;
|
||||
export interface OrtWasmThreadedModule extends OrtWasmModule {
|
||||
PThread?: {terminateAllThreads(): void};
|
||||
}
|
||||
|
||||
declare const moduleFactory: EmscriptenModuleFactory<OrtWasmThreadedModule>;
|
||||
export default moduleFactory;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@
|
|||
// Licensed under the MIT License.
|
||||
|
||||
import {env} from 'onnxruntime-common';
|
||||
import * as path from 'path';
|
||||
|
||||
import {OrtWasmModule} from './binding/ort-wasm';
|
||||
import {OrtWasmThreadedModule} from './binding/ort-wasm-threaded';
|
||||
import ortWasmFactoryThreaded from './binding/ort-wasm-threaded.js';
|
||||
import ortWasmFactory from './binding/ort-wasm.js';
|
||||
|
||||
|
|
@ -13,11 +16,14 @@ let aborted = false;
|
|||
|
||||
const isMultiThreadSupported = (): boolean => {
|
||||
try {
|
||||
// Test for transferability of SABs (needed for Firefox)
|
||||
// Test for transferability of SABs (for browsers. needed for Firefox)
|
||||
// https://groups.google.com/forum/#!msg/mozilla.dev.platform/IHkBZlHETpA/dwsMNchWEQAJ
|
||||
new MessageChannel().port1.postMessage(new SharedArrayBuffer(1));
|
||||
// This typed array is a WebAssembly program containing threaded
|
||||
// instructions.
|
||||
if (typeof MessageChannel !== 'undefined') {
|
||||
new MessageChannel().port1.postMessage(new SharedArrayBuffer(1));
|
||||
}
|
||||
|
||||
// Test for WebAssembly threads capability (for both browsers and Node.js)
|
||||
// This typed array is a WebAssembly program containing threaded instructions.
|
||||
return WebAssembly.validate(new Uint8Array([
|
||||
0, 97, 115, 109, 1, 0, 0, 0, 1, 4, 1, 96, 0, 0, 3, 2, 1, 0, 5,
|
||||
4, 1, 3, 1, 1, 10, 11, 1, 9, 0, 65, 0, 254, 16, 2, 0, 26, 11
|
||||
|
|
@ -65,9 +71,25 @@ export const initializeWebAssembly = async(): Promise<void> => {
|
|||
const config: Partial<OrtWasmModule> = {};
|
||||
|
||||
if (useThreads) {
|
||||
config.mainScriptUrlOrBlob = new Blob(
|
||||
[`var ortWasmThreaded=(function(){var _scriptDir;return ${ortWasmFactoryThreaded.toString()}})();`],
|
||||
{type: 'text/javascript'});
|
||||
if (typeof Blob === 'undefined') {
|
||||
config.mainScriptUrlOrBlob = path.join(__dirname, 'ort-wasm-threaded.js');
|
||||
} else {
|
||||
const scriptSourceCode =
|
||||
`var ortWasmThreaded=(function(){var _scriptDir;return ${ortWasmFactoryThreaded.toString()}})();`;
|
||||
config.mainScriptUrlOrBlob = new Blob([scriptSourceCode], {type: 'text/javascript'});
|
||||
config.locateFile = (fileName: string, scriptDirectory: string) => {
|
||||
if (fileName.endsWith('.worker.js')) {
|
||||
return URL.createObjectURL(new Blob(
|
||||
[
|
||||
// This require() function is handled by webpack to load file content of the corresponding .worker.js
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
require('./binding/ort-wasm-threaded.worker.js')
|
||||
],
|
||||
{type: 'text/javascript'}));
|
||||
}
|
||||
return scriptDirectory + fileName;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
factory(config).then(
|
||||
|
|
@ -100,3 +122,15 @@ export const getInstance = (): OrtWasmModule => {
|
|||
|
||||
throw new Error('WebAssembly is not initialized yet.');
|
||||
};
|
||||
|
||||
export const dispose = (): void => {
|
||||
if (initialized && !initializing && !aborted) {
|
||||
initializing = true;
|
||||
|
||||
(wasm as OrtWasmThreadedModule).PThread?.terminateAllThreads();
|
||||
|
||||
initializing = false;
|
||||
initialized = false;
|
||||
aborted = true;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"prepare": "tsc",
|
||||
"build": "node ./script/build",
|
||||
"test": "node ./script/prepare-test-data && node ./script/test-runner-cli",
|
||||
"test:e2e": "node ./test/e2e/run",
|
||||
"prepack": "node ./script/prepack"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@ const args = minimist(process.argv);
|
|||
// --bundle-mode=prod (default)
|
||||
// --bundle-mode=dev
|
||||
// --bundle-mode=perf
|
||||
// --bundle-mode=node
|
||||
const MODE = args['bundle-mode'] || 'prod';
|
||||
if (['prod', 'dev', 'perf'].indexOf(MODE) === -1) {
|
||||
if (['prod', 'dev', 'perf', 'node'].indexOf(MODE) === -1) {
|
||||
throw new Error(`unknown build mode: ${MODE}`);
|
||||
}
|
||||
|
||||
|
|
@ -24,12 +25,16 @@ const WASM = typeof args.wasm === 'undefined' ? true : !!args.wasm;
|
|||
|
||||
// Path variables
|
||||
const WASM_BINDING_FOLDER = path.join(__dirname, '..', 'lib', 'wasm', 'binding');
|
||||
const WASM_JS_PATH = path.join(WASM_BINDING_FOLDER, 'ort-wasm.js');
|
||||
const WASM_THREADED_JS_PATH = path.join(WASM_BINDING_FOLDER, 'ort-wasm-threaded.js');
|
||||
const WASM_BINDING_JS_PATH = path.join(WASM_BINDING_FOLDER, 'ort-wasm.js');
|
||||
const WASM_BINDING_THREADED_JS_PATH = path.join(WASM_BINDING_FOLDER, 'ort-wasm-threaded.js');
|
||||
const WASM_BINDING_THREADED_WORKER_JS_PATH = path.join(WASM_BINDING_FOLDER, 'ort-wasm-threaded.worker.js');
|
||||
const WASM_BINDING_THREADED_MIN_JS_PATH = path.join(WASM_BINDING_FOLDER, 'ort-wasm-threaded.min.js');
|
||||
const WASM_BINDING_THREADED_MIN_WORKER_JS_PATH = path.join(WASM_BINDING_FOLDER, 'ort-wasm-threaded.min.worker.js');
|
||||
const WASM_DIST_FOLDER = path.join(__dirname, '..', 'dist');
|
||||
const WASM_WASM_PATH = path.join(WASM_DIST_FOLDER, 'ort-wasm.wasm');
|
||||
const WASM_THREADED_WASM_PATH = path.join(WASM_DIST_FOLDER, 'ort-wasm-threaded.wasm');
|
||||
const WASM_THREADED_WORKER_JS_PATH = path.join(WASM_DIST_FOLDER, 'ort-wasm-threaded.worker.js');
|
||||
const WASM_THREADED_JS_PATH = path.join(WASM_DIST_FOLDER, 'ort-wasm-threaded.js');
|
||||
|
||||
function validateFile(path: string): void {
|
||||
npmlog.info('Build', `Ensure file: ${path}`);
|
||||
|
|
@ -41,28 +46,87 @@ function validateFile(path: string): void {
|
|||
}
|
||||
}
|
||||
|
||||
npmlog.info('Build.Bundle', 'Retrieving npm bin folder...');
|
||||
const npmBin = execSync('npm bin', {encoding: 'utf8'}).trimRight();
|
||||
npmlog.info('Build.Bundle', `Retrieving npm bin folder... DONE, folder: ${npmBin}`);
|
||||
|
||||
if (WASM) {
|
||||
npmlog.info('Build', 'Validating WebAssembly artifacts...');
|
||||
try {
|
||||
validateFile(WASM_JS_PATH);
|
||||
validateFile(WASM_THREADED_JS_PATH);
|
||||
validateFile(WASM_BINDING_JS_PATH);
|
||||
validateFile(WASM_BINDING_THREADED_JS_PATH);
|
||||
validateFile(WASM_BINDING_THREADED_WORKER_JS_PATH);
|
||||
validateFile(WASM_WASM_PATH);
|
||||
validateFile(WASM_THREADED_WASM_PATH);
|
||||
validateFile(WASM_THREADED_WORKER_JS_PATH);
|
||||
} catch (e) {
|
||||
npmlog.error('Build', `WebAssembly files are not ready. build WASM first. ERR: ${e}`);
|
||||
throw e;
|
||||
}
|
||||
npmlog.info('Build', 'Validating WebAssembly artifacts... DONE');
|
||||
|
||||
const VERSION = require(path.join(__dirname, '../package.json')).version;
|
||||
const COPYRIGHT_BANNER = `/*!
|
||||
* ONNX Runtime Web v${VERSION}
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/
|
||||
`;
|
||||
|
||||
const terserCommand = path.join(npmBin, 'terser');
|
||||
npmlog.info('Build', 'Minimizing file "ort-wasm-threaded.js"...');
|
||||
try {
|
||||
const terser = spawnSync(
|
||||
terserCommand,
|
||||
[
|
||||
WASM_BINDING_THREADED_JS_PATH, '--compress', 'passes=2', '--format', 'comments=false', '--mangle',
|
||||
'reserved=[_scriptDir]', '--module'
|
||||
],
|
||||
{shell: true, encoding: 'utf-8'});
|
||||
if (terser.status !== 0) {
|
||||
console.error(terser.error);
|
||||
process.exit(terser.status === null ? undefined : terser.status);
|
||||
}
|
||||
|
||||
fs.writeFileSync(WASM_BINDING_THREADED_MIN_JS_PATH, terser.stdout);
|
||||
fs.writeFileSync(WASM_THREADED_JS_PATH, COPYRIGHT_BANNER + terser.stdout);
|
||||
|
||||
validateFile(WASM_BINDING_THREADED_MIN_JS_PATH);
|
||||
validateFile(WASM_THREADED_JS_PATH);
|
||||
} catch (e) {
|
||||
npmlog.error('Build', `Failed to run terser on ort-wasm-threaded.js. ERR: ${e}`);
|
||||
throw e;
|
||||
}
|
||||
npmlog.info('Build', 'Minimizing file "ort-wasm-threaded.js"... DONE');
|
||||
|
||||
npmlog.info('Build', 'Minimizing file "ort-wasm-threaded.worker.js"...');
|
||||
try {
|
||||
const terser = spawnSync(
|
||||
terserCommand,
|
||||
[
|
||||
WASM_BINDING_THREADED_WORKER_JS_PATH, '--compress', 'passes=2', '--format', 'comments=false', '--mangle',
|
||||
'reserved=[_scriptDir]', '--toplevel'
|
||||
],
|
||||
{shell: true, encoding: 'utf-8'});
|
||||
if (terser.status !== 0) {
|
||||
console.error(terser.error);
|
||||
process.exit(terser.status === null ? undefined : terser.status);
|
||||
}
|
||||
|
||||
fs.writeFileSync(WASM_BINDING_THREADED_MIN_WORKER_JS_PATH, terser.stdout);
|
||||
fs.writeFileSync(WASM_THREADED_WORKER_JS_PATH, COPYRIGHT_BANNER + terser.stdout);
|
||||
|
||||
validateFile(WASM_BINDING_THREADED_MIN_WORKER_JS_PATH);
|
||||
validateFile(WASM_THREADED_WORKER_JS_PATH);
|
||||
} catch (e) {
|
||||
npmlog.error('Build', `Failed to run terser on ort-wasm-threaded.worker.js. ERR: ${e}`);
|
||||
throw e;
|
||||
}
|
||||
npmlog.info('Build', 'Minimizing file "ort-wasm-threaded.worker.js"... DONE');
|
||||
}
|
||||
|
||||
npmlog.info('Build', 'Building bundle...');
|
||||
{
|
||||
npmlog.info('Build.Bundle', '(1/2) Retrieving npm bin folder...');
|
||||
const npmBin = execSync('npm bin', {encoding: 'utf8'}).trimRight();
|
||||
npmlog.info('Build.Bundle', `(1/2) Retrieving npm bin folder... DONE, folder: ${npmBin}`);
|
||||
|
||||
npmlog.info('Build.Bundle', '(2/2) Running webpack to generate bundles...');
|
||||
npmlog.info('Build.Bundle', 'Running webpack to generate bundles...');
|
||||
const webpackCommand = path.join(npmBin, 'webpack');
|
||||
const webpackArgs = ['--env', `--bundle-mode=${MODE}`];
|
||||
npmlog.info('Build.Bundle', `CMD: ${webpackCommand} ${webpackArgs.join(' ')}`);
|
||||
|
|
@ -71,6 +135,6 @@ npmlog.info('Build', 'Building bundle...');
|
|||
console.error(webpack.error);
|
||||
process.exit(webpack.status === null ? undefined : webpack.status);
|
||||
}
|
||||
npmlog.info('Build.Bundle', '(2/2) Running webpack to generate bundles... DONE');
|
||||
npmlog.info('Build.Bundle', 'Running webpack to generate bundles... DONE');
|
||||
}
|
||||
npmlog.info('Build', 'Building bundle... DONE');
|
||||
|
|
|
|||
|
|
@ -119,11 +119,12 @@ export interface TestRunnerCliArgs {
|
|||
*
|
||||
* For running tests, the default mode is 'dev'. If flag '--perf' is set, the mode will be set to 'perf'.
|
||||
*
|
||||
* Mode | Output File | Main | Source Map | Webpack Config
|
||||
* ------ | ------------------ | -------------------- | ------------------ | --------------
|
||||
* prod | /dist/ort.min.js | /lib/index.ts | source-map | production
|
||||
* dev | /test/ort.dev.js | /test/test-main.ts | inline-source-map | development
|
||||
* perf | /test/ort.perf.js | /test/test-main.ts | (none) | production
|
||||
* Mode | Output File | Main | Source Map | Webpack Config
|
||||
* ------ | --------------------- | -------------------- | ------------------ | --------------
|
||||
* prod | /dist/ort.min.js | /lib/index.ts | source-map | production
|
||||
* node | /dist/ort-web.node.js | /lib/index.ts | source-map | production
|
||||
* dev | /test/ort.dev.js | /test/test-main.ts | inline-source-map | development
|
||||
* perf | /test/ort.perf.js | /test/test-main.ts | (none) | production
|
||||
*/
|
||||
bundleMode: TestRunnerCliArgs.BundleMode;
|
||||
|
||||
|
|
@ -298,24 +299,23 @@ export function parseTestRunnerCliArgs(cmdlineArgs: string[]): TestRunnerCliArgs
|
|||
|
||||
const mode = args._.length === 0 ? 'suite0' : args._[0];
|
||||
|
||||
// Option: -b=<...>, --backend=<...>
|
||||
const backendArgs = args.backend || args.b;
|
||||
const backend = (typeof backendArgs !== 'string') ? ['webgl', 'wasm'] : backendArgs.split(',');
|
||||
for (const b of backend) {
|
||||
if (b !== 'webgl' && b !== 'wasm') {
|
||||
throw new Error(`not supported backend ${b}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Option: -e=<...>, --env=<...>
|
||||
const envArg = args.env || args.e;
|
||||
const env = (typeof envArg !== 'string') ? 'chrome' : envArg;
|
||||
if (['chrome', 'edge', 'firefox', 'electron', 'safari', 'node', 'bs'].indexOf(env) === -1) {
|
||||
throw new Error(`not supported env ${env}`);
|
||||
}
|
||||
if (env === 'node') {
|
||||
// TODO: support node
|
||||
throw new Error('node is currently not supported.');
|
||||
|
||||
// Option: -b=<...>, --backend=<...>
|
||||
const browserBackends = ['webgl', 'wasm'];
|
||||
const nodejsBackends = ['cpu', 'wasm'];
|
||||
const backendArgs = args.backend || args.b;
|
||||
const backend =
|
||||
(typeof backendArgs !== 'string') ? (env === 'node' ? nodejsBackends : browserBackends) : backendArgs.split(',');
|
||||
for (const b of backend) {
|
||||
if ((env !== 'node' && browserBackends.indexOf(b) === -1) || (env === 'node' && nodejsBackends.indexOf(b) === -1)) {
|
||||
throw new Error(`backend ${b} is not supported in env ${env}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Options:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
/* eslint-disable @typescript-eslint/no-use-before-define */
|
||||
|
||||
import {execSync, spawnSync} from 'child_process';
|
||||
import * as fs from 'fs';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as globby from 'globby';
|
||||
import {default as minimatch} from 'minimatch';
|
||||
import npmlog from 'npmlog';
|
||||
|
|
@ -399,31 +399,45 @@ function run(config: Test.Config) {
|
|||
`(1/5) Writing file cache to file: testdata-file-cache-*.json ... ${
|
||||
fileCacheUrls.length > 0 ? `DONE, ${fileCacheUrls.length} file(s) generated` : 'SKIPPED'}`);
|
||||
|
||||
// STEP 2. write the config to testdata-config.js
|
||||
npmlog.info('TestRunnerCli.Run', '(2/5) Writing config to file: testdata-config.js ...');
|
||||
// STEP 2. write the config to testdata-config.json
|
||||
npmlog.info('TestRunnerCli.Run', '(2/5) Writing config to file: testdata-config.json ...');
|
||||
saveConfig(config);
|
||||
npmlog.info('TestRunnerCli.Run', '(2/5) Writing config to file: testdata-config.js ... DONE');
|
||||
npmlog.info('TestRunnerCli.Run', '(2/5) Writing config to file: testdata-config.json ... DONE');
|
||||
|
||||
// STEP 3. get npm bin folder
|
||||
npmlog.info('TestRunnerCli.Run', '(3/5) Retrieving npm bin folder...');
|
||||
const npmBin = execSync('npm bin', {encoding: 'utf8'}).trimRight();
|
||||
npmlog.info('TestRunnerCli.Run', `(3/5) Retrieving npm bin folder... DONE, folder: ${npmBin}`);
|
||||
|
||||
// STEP 4. generate bundle
|
||||
npmlog.info('TestRunnerCli.Run', '(4/5) Running build to generate bundle...');
|
||||
const buildCommand = `node ${path.join(__dirname, 'build')}`;
|
||||
const buildArgs = [`--bundle-mode=${args.env === 'node' ? 'node' : args.bundleMode}`];
|
||||
if (args.backends.indexOf('wasm') === -1) {
|
||||
buildArgs.push('--no-wasm');
|
||||
}
|
||||
npmlog.info('TestRunnerCli.Run', `CMD: ${buildCommand} ${buildArgs.join(' ')}`);
|
||||
const build = spawnSync(buildCommand, buildArgs, {shell: true, stdio: 'inherit'});
|
||||
if (build.status !== 0) {
|
||||
console.error(build.error);
|
||||
process.exit(build.status === null ? undefined : build.status);
|
||||
}
|
||||
npmlog.info('TestRunnerCli.Run', '(4/5) Running build to generate bundle... DONE');
|
||||
|
||||
if (args.env === 'node') {
|
||||
// STEP 4. use tsc to build ONNX Runtime Web
|
||||
npmlog.info('TestRunnerCli.Run', '(4/5) Running tsc...');
|
||||
// STEP 5. run tsc and run mocha
|
||||
npmlog.info('TestRunnerCli.Run', '(5/5) Running tsc...');
|
||||
const tscCommand = path.join(npmBin, 'tsc');
|
||||
const tsc = spawnSync(tscCommand, {shell: true, stdio: 'inherit'});
|
||||
if (tsc.status !== 0) {
|
||||
console.error(tsc.error);
|
||||
process.exit(tsc.status === null ? undefined : tsc.status);
|
||||
}
|
||||
npmlog.info('TestRunnerCli.Run', '(4/5) Running tsc... DONE');
|
||||
npmlog.info('TestRunnerCli.Run', '(5/5) Running tsc... DONE');
|
||||
|
||||
// STEP 5. run mocha
|
||||
npmlog.info('TestRunnerCli.Run', '(5/5) Running mocha...');
|
||||
const mochaCommand = path.join(npmBin, 'mocha');
|
||||
const mochaArgs = [path.join(TEST_ROOT, 'test-main'), '--timeout 60000'];
|
||||
const mochaArgs = [path.join(TEST_ROOT, 'test-main'), `--timeout ${args.debug ? 9999999 : 60000}`];
|
||||
npmlog.info('TestRunnerCli.Run', `CMD: ${mochaCommand} ${mochaArgs.join(' ')}`);
|
||||
const mocha = spawnSync(mochaCommand, mochaArgs, {shell: true, stdio: 'inherit'});
|
||||
if (mocha.status !== 0) {
|
||||
|
|
@ -433,21 +447,6 @@ function run(config: Test.Config) {
|
|||
npmlog.info('TestRunnerCli.Run', '(5/5) Running mocha... DONE');
|
||||
|
||||
} else {
|
||||
// STEP 4. generate bundle
|
||||
npmlog.info('TestRunnerCli.Run', '(4/5) Running build to generate bundle...');
|
||||
const buildCommand = `node ${path.join(__dirname, 'build')}`;
|
||||
const buildArgs = [`--bundle-mode=${args.bundleMode}`];
|
||||
if (args.backends.indexOf('wasm') === -1) {
|
||||
buildArgs.push('--no-wasm');
|
||||
}
|
||||
npmlog.info('TestRunnerCli.Run', `CMD: ${buildCommand} ${buildArgs.join(' ')}`);
|
||||
const build = spawnSync(buildCommand, buildArgs, {shell: true, stdio: 'inherit'});
|
||||
if (build.status !== 0) {
|
||||
console.error(build.error);
|
||||
process.exit(build.status === null ? undefined : build.status);
|
||||
}
|
||||
npmlog.info('TestRunnerCli.Run', '(4/5) Running build to generate bundle... DONE');
|
||||
|
||||
// STEP 5. use Karma to run test
|
||||
npmlog.info('TestRunnerCli.Run', '(5/5) Running karma to start test runner...');
|
||||
const karmaCommand = path.join(npmBin, 'karma');
|
||||
|
|
@ -549,39 +548,7 @@ function saveOneFileCache(index: number, fileCache: Test.FileCache) {
|
|||
}
|
||||
|
||||
function saveConfig(config: Test.Config) {
|
||||
let setOptions = '';
|
||||
if (config.options.debug !== undefined) {
|
||||
setOptions += `ort.env.debug = ${config.options.debug};`;
|
||||
}
|
||||
if (config.options.webglFlags && config.options.webglFlags.contextId !== undefined) {
|
||||
setOptions += `ort.env.webgl.contextId = ${JSON.stringify(config.options.webglFlags.contextId)};`;
|
||||
}
|
||||
if (config.options.webglFlags && config.options.webglFlags.matmulMaxBatchSize !== undefined) {
|
||||
setOptions += `ort.env.webgl.matmulMaxBatchSize = ${config.options.webglFlags.matmulMaxBatchSize};`;
|
||||
}
|
||||
if (config.options.webglFlags && config.options.webglFlags.textureCacheMode !== undefined) {
|
||||
setOptions += `ort.env.webgl.textureCacheMode = ${JSON.stringify(config.options.webglFlags.textureCacheMode)};`;
|
||||
}
|
||||
if (config.options.webglFlags && config.options.webglFlags.pack !== undefined) {
|
||||
setOptions += `ort.env.webgl.pack = ${JSON.stringify(config.options.webglFlags.pack)};`;
|
||||
}
|
||||
if (config.options.wasmFlags && config.options.wasmFlags.numThreads !== undefined) {
|
||||
setOptions += `ort.env.wasm.numThreads = ${JSON.stringify(config.options.wasmFlags.numThreads)};`;
|
||||
}
|
||||
if (config.options.wasmFlags && config.options.wasmFlags.loggingLevel !== undefined) {
|
||||
setOptions += `ort.env.wasm.loggingLevel = ${JSON.stringify(config.options.wasmFlags.loggingLevel)};`;
|
||||
}
|
||||
if (config.options.wasmFlags && config.options.wasmFlags.initTimeout !== undefined) {
|
||||
setOptions += `ort.env.wasm.initTimeout = ${JSON.stringify(config.options.wasmFlags.initTimeout)};`;
|
||||
}
|
||||
// TODO: support onnxruntime nodejs binding
|
||||
// if (config.model.some(testGroup => testGroup.tests.some(test => test.backend === 'cpu'))) {
|
||||
// setOptions += 'require(\'onnxruntime-node\');';
|
||||
// }
|
||||
|
||||
fs.writeFileSync(path.join(TEST_ROOT, './testdata-config.js'), `${setOptions}
|
||||
|
||||
ort.env.ORT_WEB_TEST_DATA=${JSON.stringify(config)};`);
|
||||
fs.writeJSONSync(path.join(TEST_ROOT, './testdata-config.json'), config);
|
||||
}
|
||||
|
||||
function getBrowserNameFromEnv(env: TestRunnerCliArgs['env'], debug?: boolean) {
|
||||
|
|
|
|||
1
js/web/test/e2e/.gitignore
vendored
Normal file
1
js/web/test/e2e/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
!**/*.js
|
||||
7
js/web/test/e2e/browser-test-wasm-no-threads.js
Normal file
7
js/web/test/e2e/browser-test-wasm-no-threads.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
it('Browser E2E testing - WebAssembly backend (no threads)', async function () {
|
||||
ort.env.wasm.numThreads = 1;
|
||||
await testFunction(ort, { executionProviders: ['wasm'] });
|
||||
});
|
||||
6
js/web/test/e2e/browser-test-wasm.js
Normal file
6
js/web/test/e2e/browser-test-wasm.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
it('Browser E2E testing - WebAssembly backend', async function () {
|
||||
await testFunction(ort, { executionProviders: ['wasm'] });
|
||||
});
|
||||
6
js/web/test/e2e/browser-test-webgl.js
Normal file
6
js/web/test/e2e/browser-test-webgl.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
it('Browser E2E testing - WebGL backend', async function () {
|
||||
await testFunction(ort, { executionProviders: ['webgl'] });
|
||||
});
|
||||
36
js/web/test/e2e/common.js
Normal file
36
js/web/test/e2e/common.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
function assert(cond) {
|
||||
if (!cond) throw new Error();
|
||||
}
|
||||
|
||||
var testFunction = async function (ort, options) {
|
||||
const session = await ort.InferenceSession.create('./model.onnx', options || {});
|
||||
|
||||
const dataA = Float32Array.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
|
||||
const dataB = Float32Array.from([10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120]);
|
||||
|
||||
const fetches = await session.run({
|
||||
a: new ort.Tensor('float32', dataA, [3, 4]),
|
||||
b: new ort.Tensor('float32', dataB, [4, 3])
|
||||
});
|
||||
|
||||
const c = fetches.c;
|
||||
|
||||
assert(c instanceof ort.Tensor);
|
||||
assert(c.dims.length === 2 && c.dims[0] === 3 && c.dims[1] === 3);
|
||||
assert(c.data[0] === 700);
|
||||
assert(c.data[1] === 800);
|
||||
assert(c.data[2] === 900);
|
||||
assert(c.data[3] === 1580);
|
||||
assert(c.data[4] === 1840);
|
||||
assert(c.data[5] === 2100);
|
||||
assert(c.data[6] === 2460);
|
||||
assert(c.data[7] === 2880);
|
||||
assert(c.data[8] === 3300);
|
||||
};
|
||||
|
||||
if (typeof module === 'object') {
|
||||
module.exports = testFunction;
|
||||
}
|
||||
51
js/web/test/e2e/karma.conf.js
Normal file
51
js/web/test/e2e/karma.conf.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
const args = require('minimist')(process.argv.slice(2));
|
||||
const SELF_HOST = !!args['self-host'];
|
||||
const TEST_MAIN = args['test-main'];
|
||||
if (typeof TEST_MAIN !== 'string') {
|
||||
throw new Error('flag --test-main=<TEST_MAIN_JS_FILE> is required');
|
||||
}
|
||||
const USER_DATA = args['user-data'];
|
||||
if (typeof USER_DATA !== 'string') {
|
||||
throw new Error('flag --user-data=<CHROME_USER_DATA_FOLDER> is required');
|
||||
}
|
||||
|
||||
module.exports = function (config) {
|
||||
const distPrefix = SELF_HOST ? './node_modules/onnxruntime-web/dist/' : 'http://localhost:8081/dist/';
|
||||
config.set({
|
||||
frameworks: ['mocha'],
|
||||
files: [
|
||||
{ pattern: distPrefix + 'ort.js' },
|
||||
{ pattern: './common.js' },
|
||||
{ pattern: TEST_MAIN },
|
||||
{ pattern: './node_modules/onnxruntime-web/dist/**/*', included: false, nocache: true },
|
||||
{ pattern: './model.onnx', included: false }
|
||||
],
|
||||
proxies: {
|
||||
'/model.onnx': '/base/model.onnx',
|
||||
},
|
||||
client: { captureConsole: true, mocha: { expose: ['body'], timeout: 60000 } },
|
||||
reporters: ['mocha'],
|
||||
captureTimeout: 120000,
|
||||
reportSlowerThan: 100,
|
||||
browserDisconnectTimeout: 600000,
|
||||
browserNoActivityTimeout: 300000,
|
||||
browserDisconnectTolerance: 0,
|
||||
browserSocketTimeout: 60000,
|
||||
hostname: 'localhost',
|
||||
browsers: [],
|
||||
customLaunchers: {
|
||||
Chrome_default: {
|
||||
base: 'Chrome',
|
||||
chromeDataDir: USER_DATA
|
||||
},
|
||||
Chrome_no_threads: {
|
||||
base: 'Chrome',
|
||||
chromeDataDir: USER_DATA,
|
||||
// TODO: no-thread flags
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
16
js/web/test/e2e/model.onnx
Normal file
16
js/web/test/e2e/model.onnx
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
backend-test:b
|
||||
|
||||
a
|
||||
bc"MatMultest_matmul_2dZ
|
||||
a
|
||||
|
||||
|
||||
Z
|
||||
b
|
||||
|
||||
|
||||
b
|
||||
c
|
||||
|
||||
|
||||
B
|
||||
10
js/web/test/e2e/node-test-main-no-threads.js
Normal file
10
js/web/test/e2e/node-test-main-no-threads.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
const ort = require('onnxruntime-web');
|
||||
const testFunction = require('./common');
|
||||
|
||||
it('Browser E2E testing - WebAssembly backend', async function () {
|
||||
ort.env.wasm.numThreads = 1;
|
||||
await testFunction(ort, { executionProviders: ['wasm'] });
|
||||
});
|
||||
11
js/web/test/e2e/node-test-main.js
Normal file
11
js/web/test/e2e/node-test-main.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
const ort = require('onnxruntime-web');
|
||||
const testFunction = require('./common');
|
||||
|
||||
it('Browser E2E testing - WebAssembly backend', async function () {
|
||||
await testFunction(ort, { executionProviders: ['wasm'] });
|
||||
|
||||
process.exit();
|
||||
});
|
||||
13
js/web/test/e2e/package.json
Normal file
13
js/web/test/e2e/package.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"fs-extra": "^9.1.0",
|
||||
"globby": "^11.0.3",
|
||||
"karma": "^6.3.2",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-mocha": "^2.0.1",
|
||||
"karma-mocha-reporter": "^2.2.5",
|
||||
"light-server": "^2.9.1",
|
||||
"minimist": "^1.2.5",
|
||||
"mocha": "^8.3.2"
|
||||
}
|
||||
}
|
||||
109
js/web/test/e2e/run.js
Normal file
109
js/web/test/e2e/run.js
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
const path = require('path');
|
||||
const fs = require('fs-extra');
|
||||
const globby = require('globby');
|
||||
const { spawn } = require('child_process');
|
||||
const startServer = require('./simple-http-server');
|
||||
|
||||
// copy whole folder to out-side of <ORT_ROOT>/js/ because we need to test in a folder that no `package.json` file
|
||||
// exists in its parent folder.
|
||||
// here we use <ORT_ROOT>/build/js/e2e/ for the test
|
||||
|
||||
const TEST_E2E_SRC_FOLDER = __dirname;
|
||||
const JS_ROOT_FOLDER = path.resolve(__dirname, '../../..');
|
||||
const TEST_E2E_RUN_FOLDER = path.resolve(JS_ROOT_FOLDER, '../build/js/e2e');
|
||||
const NPM_CACHE_FOLDER = path.resolve(TEST_E2E_RUN_FOLDER, '../npm_cache');
|
||||
const CHROME_USER_DATA_FOLDER = path.resolve(TEST_E2E_RUN_FOLDER, '../user_data');
|
||||
fs.emptyDirSync(TEST_E2E_RUN_FOLDER);
|
||||
fs.emptyDirSync(NPM_CACHE_FOLDER);
|
||||
fs.emptyDirSync(CHROME_USER_DATA_FOLDER);
|
||||
fs.copySync(TEST_E2E_SRC_FOLDER, TEST_E2E_RUN_FOLDER);
|
||||
|
||||
// find packed package
|
||||
|
||||
const ORT_COMMON_FOLDER = path.resolve(JS_ROOT_FOLDER, 'common');
|
||||
const ORT_COMMON_PACKED_FILEPATH_CANDIDATES = globby.sync('onnxruntime-common-*.tgz', { cwd: ORT_COMMON_FOLDER });
|
||||
if (ORT_COMMON_PACKED_FILEPATH_CANDIDATES.length !== 1) {
|
||||
throw new Error('cannot find exactly single package for onnxruntime-common.');
|
||||
}
|
||||
const ORT_COMMON_PACKED_FILEPATH = path.resolve(ORT_COMMON_FOLDER, ORT_COMMON_PACKED_FILEPATH_CANDIDATES[0]);
|
||||
|
||||
const ORT_WEB_FOLDER = path.resolve(JS_ROOT_FOLDER, 'web');
|
||||
const ORT_WEB_PACKED_FILEPATH_CANDIDATES = globby.sync('onnxruntime-web-*.tgz', { cwd: ORT_WEB_FOLDER });
|
||||
if (ORT_WEB_PACKED_FILEPATH_CANDIDATES.length !== 1) {
|
||||
throw new Error('cannot find exactly single package for onnxruntime-web.');
|
||||
}
|
||||
const ORT_WEB_PACKED_FILEPATH = path.resolve(ORT_WEB_FOLDER, ORT_WEB_PACKED_FILEPATH_CANDIDATES[0]);
|
||||
|
||||
// we start here:
|
||||
|
||||
async function main() {
|
||||
// install dev dependencies
|
||||
await runInShell(`npm install"`);
|
||||
|
||||
// npm install with "--cache" to install packed packages with an empty cache folder
|
||||
await runInShell(`npm install --cache "${NPM_CACHE_FOLDER}" "${ORT_COMMON_PACKED_FILEPATH}" "${ORT_WEB_PACKED_FILEPATH}"`);
|
||||
|
||||
// test case run in Node.js
|
||||
await testAllNodejsCases();
|
||||
|
||||
// test cases with self-host (ort hosted in same origin)
|
||||
await testAllBrowserCases({ hostInKarma: true });
|
||||
|
||||
// test cases without self-host (ort hosted in same origin)
|
||||
startServer(path.resolve(TEST_E2E_RUN_FOLDER, 'node_modules', 'onnxruntime-web'));
|
||||
await testAllBrowserCases({ hostInKarma: false });
|
||||
|
||||
// no error occurs, exit with code 0
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async function testAllNodejsCases() {
|
||||
await runInShell('node ./node_modules/mocha/bin/mocha ./node-test-main-no-threads.js');
|
||||
await runInShell('node ./node_modules/mocha/bin/mocha ./node-test-main.js');
|
||||
await runInShell('node --experimental-wasm-threads --experimental-wasm-bulk-memory ./node_modules/mocha/bin/mocha ./node-test-main-no-threads.js');
|
||||
await runInShell('node --experimental-wasm-threads --experimental-wasm-bulk-memory ./node_modules/mocha/bin/mocha ./node-test-main.js');
|
||||
}
|
||||
|
||||
async function testAllBrowserCases({ hostInKarma }) {
|
||||
await runKarma({ hostInKarma, main: './browser-test-webgl.js', browser: 'Chrome_default' });
|
||||
await runKarma({ hostInKarma, main: './browser-test-wasm.js', browser: 'Chrome_default' });
|
||||
await runKarma({ hostInKarma, main: './browser-test-wasm-no-threads.js', browser: 'Chrome_default' });
|
||||
}
|
||||
|
||||
async function runKarma({ hostInKarma, main, browser }) {
|
||||
const selfHostFlag = hostInKarma ? '--self-host' : '';
|
||||
await runInShell(
|
||||
`npx karma start --single-run --browsers ${browser} ${selfHostFlag} --test-main=${main} --user-data=${CHROME_USER_DATA_FOLDER}`);
|
||||
}
|
||||
|
||||
async function runInShell(cmd) {
|
||||
console.log('===============================================================');
|
||||
console.log(' Running command in shell:');
|
||||
console.log(' > ' + cmd);
|
||||
console.log('===============================================================');
|
||||
let complete = false;
|
||||
const childProcess = spawn(cmd, { shell: true, stdio: 'inherit', cwd: TEST_E2E_RUN_FOLDER });
|
||||
childProcess.on('close', function (code) {
|
||||
if (code !== 0) {
|
||||
process.exit(code);
|
||||
} else {
|
||||
complete = true;
|
||||
}
|
||||
});
|
||||
while (!complete) {
|
||||
await delay(100);
|
||||
}
|
||||
}
|
||||
|
||||
async function delay(ms) {
|
||||
return new Promise(function (resolve) {
|
||||
setTimeout(function () {
|
||||
resolve();
|
||||
}, ms);
|
||||
});
|
||||
}
|
||||
|
||||
main();
|
||||
58
js/web/test/e2e/simple-http-server.js
Normal file
58
js/web/test/e2e/simple-http-server.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
// this is a simple HTTP server that enables CORS.
|
||||
// following code is based on https://developer.mozilla.org/en-US/docs/Learn/Server-side/Node_server_without_framework
|
||||
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
module.exports = function (dir) {
|
||||
http.createServer(function (request, response) {
|
||||
console.log('request ', request.url);
|
||||
|
||||
var filePath = '.' + request.url;
|
||||
|
||||
var extname = String(path.extname(filePath)).toLowerCase();
|
||||
var mimeTypes = {
|
||||
'.html': 'text/html',
|
||||
'.js': 'text/javascript',
|
||||
'.css': 'text/css',
|
||||
'.json': 'application/json',
|
||||
'.png': 'image/png',
|
||||
'.jpg': 'image/jpg',
|
||||
'.gif': 'image/gif',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.wav': 'audio/wav',
|
||||
'.mp4': 'video/mp4',
|
||||
'.woff': 'application/font-woff',
|
||||
'.ttf': 'application/font-ttf',
|
||||
'.eot': 'application/vnd.ms-fontobject',
|
||||
'.otf': 'application/font-otf',
|
||||
'.wasm': 'application/wasm'
|
||||
};
|
||||
|
||||
var contentType = mimeTypes[extname] || 'application/octet-stream';
|
||||
|
||||
fs.readFile(path.resolve(dir, filePath), function (error, content) {
|
||||
if (error) {
|
||||
if (error.code == 'ENOENT') {
|
||||
response.writeHead(404);
|
||||
response.end('404');
|
||||
}
|
||||
else {
|
||||
response.writeHead(500);
|
||||
response.end('500');
|
||||
}
|
||||
}
|
||||
else {
|
||||
response.setHeader('access-control-allow-origin', '*');
|
||||
response.writeHead(200, { 'Content-Type': contentType });
|
||||
response.end(content, 'utf-8');
|
||||
}
|
||||
});
|
||||
|
||||
}).listen(8081);
|
||||
console.log('Server running at http://127.0.0.1:8081/');
|
||||
};
|
||||
|
|
@ -1,16 +1,48 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
import '../lib/index'; // this need to be the first line
|
||||
// Load onnxruntime-web and testdata-config.
|
||||
// NOTE: this need to be called before import any other library.
|
||||
const ort = require('..');
|
||||
const ORT_WEB_TEST_CONFIG = require('./testdata-config.json') as Test.Config;
|
||||
|
||||
import * as ort from 'onnxruntime-common';
|
||||
import * as platform from 'platform';
|
||||
|
||||
import {Logger} from '../lib/onnxjs/instrument';
|
||||
|
||||
import {Test} from './test-types';
|
||||
|
||||
const ORT_WEB_TEST_CONFIG = (ort.env as any).ORT_WEB_TEST_DATA as Test.Config;
|
||||
if (ORT_WEB_TEST_CONFIG.model.some(testGroup => testGroup.tests.some(test => test.backend === 'cpu'))) {
|
||||
// require onnxruntime-node
|
||||
require('../../node');
|
||||
}
|
||||
|
||||
// set flags
|
||||
const options = ORT_WEB_TEST_CONFIG.options;
|
||||
if (options.debug !== undefined) {
|
||||
ort.env.debug = options.debug;
|
||||
}
|
||||
if (ort.env.webgl && options.webglFlags && options.webglFlags.contextId !== undefined) {
|
||||
ort.env.webgl.contextId = options.webglFlags.contextId;
|
||||
}
|
||||
if (ort.env.webgl && options.webglFlags && options.webglFlags.matmulMaxBatchSize !== undefined) {
|
||||
ort.env.webgl.matmulMaxBatchSize = options.webglFlags.matmulMaxBatchSize;
|
||||
}
|
||||
if (ort.env.webgl && options.webglFlags && options.webglFlags.textureCacheMode !== undefined) {
|
||||
ort.env.webgl.textureCacheMode = options.webglFlags.textureCacheMode;
|
||||
}
|
||||
if (ort.env.webgl && options.webglFlags && options.webglFlags.pack !== undefined) {
|
||||
ort.env.webgl.pack = options.webglFlags.pack;
|
||||
}
|
||||
if (ort.env.wasm && options.wasmFlags && options.wasmFlags.numThreads !== undefined) {
|
||||
ort.env.wasm.numThreads = options.wasmFlags.numThreads;
|
||||
}
|
||||
if (ort.env.wasm && options.wasmFlags && options.wasmFlags.loggingLevel !== undefined) {
|
||||
ort.env.wasm.loggingLevel = options.wasmFlags.loggingLevel;
|
||||
}
|
||||
if (ort.env.wasm && options.wasmFlags && options.wasmFlags.initTimeout !== undefined) {
|
||||
ort.env.wasm.initTimeout = options.wasmFlags.initTimeout;
|
||||
}
|
||||
|
||||
// Set logging configuration
|
||||
for (const logConfig of ORT_WEB_TEST_CONFIG.log) {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
{
|
||||
"cpu": {
|
||||
"onnx": [],
|
||||
"node": [],
|
||||
"ops": []
|
||||
},
|
||||
"webgl": {
|
||||
"onnx": ["resnet50", "squeezenet", "tiny_yolov2", "emotion_ferplus"],
|
||||
"node": [
|
||||
|
|
|
|||
|
|
@ -7,38 +7,33 @@ const NodePolyfillPlugin = require('node-polyfill-webpack-plugin');
|
|||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const minimist = require('minimist');
|
||||
|
||||
function addCopyrightBannerPlugin(mode) {
|
||||
const VERSION = require(path.join(__dirname, 'package.json')).version;
|
||||
const COPYRIGHT_BANNER = `/*!
|
||||
* ONNX Runtime Web v${VERSION}
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/`;
|
||||
const VERSION = require(path.join(__dirname, 'package.json')).version;
|
||||
const COPYRIGHT_BANNER = `/*!
|
||||
* ONNX Runtime Web v${VERSION}
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License.
|
||||
*/`;
|
||||
|
||||
if (mode === 'production') {
|
||||
return new TerserPlugin({
|
||||
extractComments: false,
|
||||
terserOptions: {
|
||||
format: {
|
||||
preamble: COPYRIGHT_BANNER,
|
||||
comments: false,
|
||||
},
|
||||
compress: {
|
||||
passes: 2
|
||||
},
|
||||
mangle: {
|
||||
reserved: ["_scriptDir"]
|
||||
}
|
||||
function defaultTerserPluginOptions() {
|
||||
return {
|
||||
extractComments: false,
|
||||
terserOptions: {
|
||||
format: {
|
||||
comments: false,
|
||||
},
|
||||
compress: {
|
||||
passes: 2
|
||||
},
|
||||
mangle: {
|
||||
reserved: ["_scriptDir"]
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return new webpack.BannerPlugin({ banner: COPYRIGHT_BANNER, raw: true });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// common config for release bundle
|
||||
function buildConfig({ filename, format, target, mode, devtool }) {
|
||||
return {
|
||||
const config = {
|
||||
target: [format === 'commonjs' ? 'node' : 'web', target],
|
||||
entry: path.resolve(__dirname, 'lib/index.ts'),
|
||||
output: {
|
||||
|
|
@ -49,10 +44,7 @@ function buildConfig({ filename, format, target, mode, devtool }) {
|
|||
}
|
||||
},
|
||||
resolve: { extensions: ['.ts', '.js'] },
|
||||
plugins: [
|
||||
new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/] }),
|
||||
addCopyrightBannerPlugin(mode),
|
||||
],
|
||||
plugins: [new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/] })],
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.ts$/,
|
||||
|
|
@ -64,11 +56,28 @@ function buildConfig({ filename, format, target, mode, devtool }) {
|
|||
}
|
||||
}
|
||||
]
|
||||
}, {
|
||||
test: /\.worker.js$/,
|
||||
type: 'asset/source'
|
||||
}]
|
||||
},
|
||||
mode,
|
||||
devtool
|
||||
};
|
||||
|
||||
if (mode === 'production') {
|
||||
config.resolve.alias = {
|
||||
'./binding/ort-wasm-threaded.js': './binding/ort-wasm-threaded.min.js',
|
||||
'./binding/ort-wasm-threaded.worker.js': './binding/ort-wasm-threaded.min.worker.js'
|
||||
};
|
||||
const options = defaultTerserPluginOptions();
|
||||
options.terserOptions.format.preamble = COPYRIGHT_BANNER;
|
||||
config.plugins.push(new TerserPlugin(options));
|
||||
} else {
|
||||
config.plugins.push(new webpack.BannerPlugin({ banner: COPYRIGHT_BANNER, raw: true }));
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// "ort{.min}.js" config
|
||||
|
|
@ -108,10 +117,9 @@ function buildOrtWebConfig({
|
|||
config.externals.path = 'path';
|
||||
config.externals.fs = 'fs';
|
||||
config.externals.util = 'util';
|
||||
}
|
||||
// in browser, do not use those node builtin modules
|
||||
if (format === 'umd') {
|
||||
config.resolve.fallback = { path: false, fs: false, util: false };
|
||||
config.externals.worker_threads = 'worker_threads';
|
||||
config.externals.perf_hooks = 'perf_hooks';
|
||||
config.externals.os = 'os';
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
|
@ -123,7 +131,7 @@ function buildTestRunnerConfig({
|
|||
mode = 'production',
|
||||
devtool = 'source-map'
|
||||
}) {
|
||||
return {
|
||||
const config = {
|
||||
target: ['web', target],
|
||||
entry: path.resolve(__dirname, 'test/test-main.ts'),
|
||||
output: {
|
||||
|
|
@ -139,6 +147,7 @@ function buildTestRunnerConfig({
|
|||
'fs': 'fs',
|
||||
'perf_hooks': 'perf_hooks',
|
||||
'worker_threads': 'worker_threads',
|
||||
'../../node': '../../node'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js'],
|
||||
|
|
@ -148,7 +157,6 @@ function buildTestRunnerConfig({
|
|||
plugins: [
|
||||
new webpack.WatchIgnorePlugin({ paths: [/\.js$/, /\.d\.ts$/] }),
|
||||
new NodePolyfillPlugin(),
|
||||
addCopyrightBannerPlugin(mode),
|
||||
],
|
||||
module: {
|
||||
rules: [{
|
||||
|
|
@ -161,16 +169,25 @@ function buildTestRunnerConfig({
|
|||
}
|
||||
}
|
||||
]
|
||||
}, {
|
||||
test: /\.worker\.js$/,
|
||||
type: 'asset/source'
|
||||
}]
|
||||
},
|
||||
mode: mode,
|
||||
devtool: devtool,
|
||||
};
|
||||
|
||||
if (mode === 'production') {
|
||||
config.plugins.push(new TerserPlugin(defaultTerserPluginOptions()));
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
module.exports = () => {
|
||||
const args = minimist(process.argv);
|
||||
const bundleMode = args['bundle-mode'] || 'prod'; // 'prod'|'dev'|'perf'|undefined;
|
||||
const bundleMode = args['bundle-mode'] || 'prod'; // 'prod'|'dev'|'perf'|'node'|undefined;
|
||||
const builds = [];
|
||||
|
||||
switch (bundleMode) {
|
||||
|
|
@ -193,7 +210,10 @@ module.exports = () => {
|
|||
buildOrtWebConfig({ suffix: '.es6.min', target: 'es6' }),
|
||||
// ort-web.es6.js
|
||||
buildOrtWebConfig({ suffix: '.es6', mode: 'development', devtool: 'inline-source-map', target: 'es6' }),
|
||||
);
|
||||
|
||||
case 'node':
|
||||
builds.push(
|
||||
// ort-web.node.js
|
||||
buildOrtWebConfig({ suffix: '.node', format: 'commonjs' }),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -157,7 +157,6 @@ jobs:
|
|||
sourceFolder: $(Pipeline.Workspace)\artifacts
|
||||
contents: |
|
||||
**\*.wasm
|
||||
**\*.worker.js
|
||||
targetFolder: $(Build.SourcesDirectory)\js\web\dist
|
||||
flattenFolders: true
|
||||
displayName: 'Binplace dist files'
|
||||
|
|
@ -166,7 +165,6 @@ jobs:
|
|||
sourceFolder: $(Pipeline.Workspace)\artifacts
|
||||
contents: |
|
||||
**\*.js
|
||||
!**\*.worker.js
|
||||
targetFolder: $(Build.SourcesDirectory)\js\web\lib\wasm\binding
|
||||
flattenFolders: true
|
||||
displayName: 'Binplace js files'
|
||||
|
|
@ -212,6 +210,11 @@ jobs:
|
|||
workingDirectory: '$(Build.SourcesDirectory)\js\web'
|
||||
displayName: 'Generate NPM package (onnxruntime-web)'
|
||||
condition: and(succeeded(), eq(variables['BuildConfig'], 'Release'))
|
||||
- script: |
|
||||
npm run test:e2e
|
||||
workingDirectory: '$(Build.SourcesDirectory)\js\web'
|
||||
displayName: 'E2E package consuming test'
|
||||
condition: and(succeeded(), eq(variables['BuildConfig'], 'Release'))
|
||||
- task: CopyFiles@2
|
||||
inputs:
|
||||
sourceFolder: $(Build.SourcesDirectory)\js\common
|
||||
|
|
|
|||
Loading…
Reference in a new issue