diff --git a/cmake/external/onnxruntime_external_deps.cmake b/cmake/external/onnxruntime_external_deps.cmake index 387f88bbfa..bc2f7a11ff 100644 --- a/cmake/external/onnxruntime_external_deps.cmake +++ b/cmake/external/onnxruntime_external_deps.cmake @@ -644,10 +644,8 @@ if (onnxruntime_USE_WEBGPU) # file "${DAWN_EMSCRIPTEN_TOOLCHAIN}/tools/maint/gen_struct_info.py" is missing from emsdk installation. # we should copy if from ${PROJECT_SOURCE_DIR}/patches/ - file(COPY_FILE - "${PROJECT_SOURCE_DIR}/patches/emscripten/tools/maint/gen_struct_info.py" - "${DAWN_EMSCRIPTEN_TOOLCHAIN}/tools/maint/gen_struct_info.py" - ONLY_IF_DIFFERENT) + file(COPY "${PROJECT_SOURCE_DIR}/patches/emscripten/tools/maint/gen_struct_info.py" + DESTINATION "${DAWN_EMSCRIPTEN_TOOLCHAIN}/tools/maint/") else() if (onnxruntime_BUILD_DAWN_MONOLITHIC_LIBRARY) set(DAWN_BUILD_MONOLITHIC_LIBRARY ON CACHE BOOL "" FORCE) diff --git a/cmake/onnxruntime_providers_webgpu.cmake b/cmake/onnxruntime_providers_webgpu.cmake index 40729e6009..a69c6ecffe 100644 --- a/cmake/onnxruntime_providers_webgpu.cmake +++ b/cmake/onnxruntime_providers_webgpu.cmake @@ -32,6 +32,7 @@ endif() target_link_libraries(onnxruntime_providers_webgpu PUBLIC emdawnwebgpu_cpp) + target_link_options(onnxruntime_providers_webgpu PUBLIC "SHELL:-s ASYNCIFY=1") else() onnxruntime_add_include_to_target(onnxruntime_providers_webgpu dawn::dawncpp_headers dawn::dawn_headers) diff --git a/cmake/onnxruntime_unittests.cmake b/cmake/onnxruntime_unittests.cmake index 9e3ab4d41f..714c05a41d 100644 --- a/cmake/onnxruntime_unittests.cmake +++ b/cmake/onnxruntime_unittests.cmake @@ -984,12 +984,15 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten") set_target_properties(onnxruntime_test_all PROPERTIES LINK_DEPENDS ${ONNXRUNTIME_ROOT}/wasm/pre.js) set_target_properties(onnxruntime_test_all PROPERTIES LINK_FLAGS "-s STACK_SIZE=5242880 -s INITIAL_MEMORY=536870912 -s ALLOW_MEMORY_GROWTH=1 -s MAXIMUM_MEMORY=4294967296 -s INCOMING_MODULE_JS_API=[preRun,locateFile,arguments,onExit,wasmMemory,buffer,instantiateWasm] --pre-js \"${TEST_SRC_DIR}/wasm/onnxruntime_test_all_adapter.js\" --pre-js \"${ONNXRUNTIME_ROOT}/wasm/pre.js\" -s \"EXPORTED_RUNTIME_METHODS=['FS']\" --preload-file ${CMAKE_CURRENT_BINARY_DIR}/testdata@/testdata -s EXIT_RUNTIME=1") if (onnxruntime_ENABLE_WEBASSEMBLY_THREADS) - set_property(TARGET onnxruntime_test_all APPEND_STRING PROPERTY LINK_FLAGS " -s DEFAULT_PTHREAD_STACK_SIZE=131072 -s PROXY_TO_PTHREAD=1") + set_property(TARGET onnxruntime_test_all APPEND_STRING PROPERTY LINK_FLAGS " -s DEFAULT_PTHREAD_STACK_SIZE=131072 -s PROXY_TO_PTHREAD=1 -s PTHREAD_POOL_SIZE=globalThis.PTHREAD_POOL_SIZE") endif() if (onnxruntime_USE_JSEP) set_target_properties(onnxruntime_test_all PROPERTIES LINK_DEPENDS ${ONNXRUNTIME_ROOT}/wasm/pre-jsep.js) set_property(TARGET onnxruntime_test_all APPEND_STRING PROPERTY LINK_FLAGS " --pre-js \"${ONNXRUNTIME_ROOT}/wasm/pre-jsep.js\"") endif() + if (onnxruntime_USE_WEBGPU) + set_property(TARGET onnxruntime_test_all APPEND_STRING PROPERTY LINK_FLAGS " -s ASYNCIFY_STACK_SIZE=131072") + endif() ### ### if you want to investigate or debug a test failure in onnxruntime_test_all, replace the following line. diff --git a/cmake/patches/dawn/dawn.patch b/cmake/patches/dawn/dawn.patch index 9897dbcec6..868db0c43e 100644 --- a/cmake/patches/dawn/dawn.patch +++ b/cmake/patches/dawn/dawn.patch @@ -10,3 +10,109 @@ index de673537d3..c98dc46de7 100644 "${DAWN_EMSCRIPTEN_TOOLCHAIN}/tools/maint/gen_struct_info.py" -q "${EM_BUILD_GEN_DIR}/struct_info_webgpu.json" +diff --git a/third_party/emdawnwebgpu/library_webgpu.js b/third_party/emdawnwebgpu/library_webgpu.js +index d1835cb090..df03ea2f94 100644 +--- a/third_party/emdawnwebgpu/library_webgpu.js ++++ b/third_party/emdawnwebgpu/library_webgpu.js +@@ -16,10 +16,19 @@ + throw new Error("To use Dawn's library_webgpu.js, disable -sUSE_WEBGPU and first include Dawn's library_webgpu_struct_info.js and library_webgpu_enum_tables.js (before library_webgpu.js)"); + } + ++ if (MEMORY64) { ++ throw new Error("The current implementation of Dawn's library_webgpu.js does not support MEMORY64 yet"); ++ } ++ + // Helper functions for code generation + globalThis.gpu = { +- convertSentinelToUndefined: function(name) { +- return `if (${name} == -1) ${name} = undefined;`; ++ convertSentinelToUndefined: function(name, isPtr = false) { ++ // When `CAN_ADDRESS_2GB` is true, value `-1` is normalized to `0xFFFFFFFF` for pointer. ++ if (CAN_ADDRESS_2GB && isPtr) { ++ return `if (${name} == 0xFFFFFFFF) ${name} = undefined;`; ++ } else { ++ return `if (${name} == -1) ${name} = undefined;`; ++ } + }, + + makeGetBool: function(struct, offset) { +@@ -700,6 +709,7 @@ var LibraryWebGPU = { + {{{ makeSetValue('info', C_STRUCTS.WGPUAdapterInfo.adapterType, 'adapterType', 'i32') }}}; + {{{ makeSetValue('info', C_STRUCTS.WGPUAdapterInfo.vendorID, '0', 'i32') }}}; + {{{ makeSetValue('info', C_STRUCTS.WGPUAdapterInfo.deviceID, '0', 'i32') }}}; ++ return 1; + }, + + wgpuAdapterGetLimits: (adapterPtr, limitsOutPtr) => { +@@ -882,7 +892,7 @@ var LibraryWebGPU = { + + if (size === 0) warnOnce('getMappedRange size=0 no longer means WGPU_WHOLE_MAP_SIZE'); + +- {{{ gpu.convertSentinelToUndefined('size') }}} ++ {{{ gpu.convertSentinelToUndefined('size', true) }}} + + var mapped; + try { +@@ -909,7 +919,7 @@ var LibraryWebGPU = { + + if (size === 0) warnOnce('getMappedRange size=0 no longer means WGPU_WHOLE_MAP_SIZE'); + +- {{{ gpu.convertSentinelToUndefined('size') }}} ++ {{{ gpu.convertSentinelToUndefined('size', true) }}} + + var mapped; + try { +@@ -950,7 +960,7 @@ var LibraryWebGPU = { + var buffer = WebGPU.getJsObject(bufferPtr); + WebGPU.Internals.bufferOnUnmaps[bufferPtr] = []; + +- {{{ gpu.convertSentinelToUndefined('size') }}} ++ {{{ gpu.convertSentinelToUndefined('size', true) }}} + + {{{ runtimeKeepalivePush() }}} + WebGPU.Internals.futureInsert(futureId, buffer.mapAsync(mode, offset, size).then(() => { +@@ -1145,7 +1155,7 @@ var LibraryWebGPU = { + + wgpuCommandEncoderClearBuffer: (encoderPtr, bufferPtr, offset, size) => { + var commandEncoder = WebGPU.getJsObject(encoderPtr); +- {{{ gpu.convertSentinelToUndefined('size') }}} ++ {{{ gpu.convertSentinelToUndefined('size', true) }}} + + var buffer = WebGPU.getJsObject(bufferPtr); + commandEncoder.clearBuffer(buffer, offset, size); +@@ -2103,7 +2113,7 @@ var LibraryWebGPU = { + wgpuRenderBundleEncoderSetIndexBuffer: (passPtr, bufferPtr, format, offset, size) => { + var pass = WebGPU.getJsObject(passPtr); + var buffer = WebGPU.getJsObject(bufferPtr); +- {{{ gpu.convertSentinelToUndefined('size') }}} ++ {{{ gpu.convertSentinelToUndefined('size', true) }}} + pass.setIndexBuffer(buffer, WebGPU.IndexFormat[format], offset, size); + }, + +@@ -2116,7 +2126,7 @@ var LibraryWebGPU = { + wgpuRenderBundleEncoderSetVertexBuffer: (passPtr, slot, bufferPtr, offset, size) => { + var pass = WebGPU.getJsObject(passPtr); + var buffer = WebGPU.getJsObject(bufferPtr); +- {{{ gpu.convertSentinelToUndefined('size') }}} ++ {{{ gpu.convertSentinelToUndefined('size', true) }}} + pass.setVertexBuffer(slot, buffer, offset, size); + }, + +@@ -2211,7 +2221,7 @@ var LibraryWebGPU = { + wgpuRenderPassEncoderSetIndexBuffer: (passPtr, bufferPtr, format, offset, size) => { + var pass = WebGPU.getJsObject(passPtr); + var buffer = WebGPU.getJsObject(bufferPtr); +- {{{ gpu.convertSentinelToUndefined('size') }}} ++ {{{ gpu.convertSentinelToUndefined('size', true) }}} + pass.setIndexBuffer(buffer, WebGPU.IndexFormat[format], offset, size); + }, + +@@ -2234,7 +2244,7 @@ var LibraryWebGPU = { + wgpuRenderPassEncoderSetVertexBuffer: (passPtr, slot, bufferPtr, offset, size) => { + var pass = WebGPU.getJsObject(passPtr); + var buffer = WebGPU.getJsObject(bufferPtr); +- {{{ gpu.convertSentinelToUndefined('size') }}} ++ {{{ gpu.convertSentinelToUndefined('size', true) }}} + pass.setVertexBuffer(slot, buffer, offset, size); + }, + diff --git a/js/build_webgpu.bat b/js/build_webgpu.bat index 767f6b3444..a2427e58ac 100644 --- a/js/build_webgpu.bat +++ b/js/build_webgpu.bat @@ -17,7 +17,8 @@ set BUILD_DIR=%ROOT%build_webgpu :arg1 if ["%~1"]==["d"] ( set CONFIG=Debug - set CONFIG_EXTRA_FLAG=--enable_wasm_profiling --cmake_extra_defines onnxruntime_ENABLE_WEBASSEMBLY_OUTPUT_OPTIMIZED_MODEL=1 + set CONFIG_EXTRA_FLAG=--enable_wasm_profiling --wasm_run_tests_in_browser + @rem --cmake_extra_defines onnxruntime_ENABLE_WEBASSEMBLY_OUTPUT_OPTIMIZED_MODEL=1 @rem --enable_wasm_debug_info goto :arg2 ) diff --git a/onnxruntime/core/providers/webgpu/webgpu_context.cc b/onnxruntime/core/providers/webgpu/webgpu_context.cc index c6f84a8099..2418620630 100644 --- a/onnxruntime/core/providers/webgpu/webgpu_context.cc +++ b/onnxruntime/core/providers/webgpu/webgpu_context.cc @@ -696,9 +696,13 @@ WebGpuContext& WebGpuContextFactory::CreateContext(const WebGpuContextConfig& co #endif // Step.2 - Create wgpu::Instance +#if !defined(__wasm__) wgpu::InstanceDescriptor instance_desc{}; instance_desc.features.timedWaitAnyEnable = true; default_instance_ = wgpu::CreateInstance(&instance_desc); +#else + default_instance_ = wgpu::CreateInstance(nullptr); +#endif ORT_ENFORCE(default_instance_ != nullptr, "Failed to create wgpu::Instance."); }); diff --git a/onnxruntime/test/wasm/karma.conf.js b/onnxruntime/test/wasm/karma.conf.js index d6830501d2..723412d770 100644 --- a/onnxruntime/test/wasm/karma.conf.js +++ b/onnxruntime/test/wasm/karma.conf.js @@ -7,7 +7,7 @@ const THREADS = process.argv.includes('--wasm-threads'); const files = []; const proxies = {}; -const chromeFlags = ['--no-sandbox']; +const chromeFlags = ['--no-sandbox', '--js-flags="--max_old_space_size=8192"']; for (const arg of process.argv) { if (arg.startsWith('--entry=')) { const entry = arg.substring('--entry='.length); @@ -38,6 +38,10 @@ const gtestReporter = {'reporter:gtest': ['type', function() { }; }]}; +// In Node.js v16 and below, 'localhost' is using IPv4, so need to listen to '0.0.0.0' +// In Node.js v17+, 'localhost' is using IPv6, so need to listen to '::' +const listenAddress = Number.parseInt(process.versions.node.split('.')[0]) >= 17 ? '::' : '0.0.0.0'; + module.exports = function(config) { config.set({ basePath: '.', @@ -55,11 +59,12 @@ module.exports = function(config) { client: { captureConsole: true, // Pass the gtest flags to the test runner - args: process.argv.filter(arg => arg.startsWith('--gtest_')) + args: process.argv.filter(arg => arg.startsWith('--gtest_') || arg.startsWith('--wasm-threads=')) }, browserDisconnectTimeout: 600000, // allow running tests for 30 minutes browserNoActivityTimeout: 30 * 60 * 1000, + listenAddress, customLaunchers: { ChromeTest: { base: 'ChromeCanary', diff --git a/onnxruntime/test/wasm/onnxruntime_test_all_adapter.js b/onnxruntime/test/wasm/onnxruntime_test_all_adapter.js index 54c4c5b9d7..1aea559612 100644 --- a/onnxruntime/test/wasm/onnxruntime_test_all_adapter.js +++ b/onnxruntime/test/wasm/onnxruntime_test_all_adapter.js @@ -15,6 +15,7 @@ if (typeof process !== 'undefined') { // In Node.js args = process.argv; + globalThis.PTHREAD_POOL_SIZE = Math.min(8, require('os').cpus().length); onTestComplete = function () { if (gtestOutputFilepath) { require('fs').writeFileSync(gtestOutputFilepath, gtestOutputFiledata); @@ -23,6 +24,7 @@ } else if (typeof __karma__ !== 'undefined') { // In browser (launched by karma) args = __karma__.config.args; + globalThis.PTHREAD_POOL_SIZE = Math.min(8, navigator.hardwareConcurrency); onTestComplete = function (exitCode) { __karma__.result({ id: '', @@ -42,10 +44,14 @@ // check for flag "--gtest_output=xml:" const argGtestOutputPrefix = '--gtest_output=xml:'; + // check for flag "--wasm-threads=" + const argWasmThreads = '--wasm-threads='; + for (const arg of args) { if (arg.startsWith(argGtestOutputPrefix)) { gtestOutputFilepath = arg.substring(argGtestOutputPrefix.length); - break; + } else if (arg.startsWith(argWasmThreads)) { + globalThis.PTHREAD_POOL_SIZE = Number.parseInt(arg.substring(argWasmThreads.length)); } }