Skip to content

Commit 6efb844

Browse files
authored
[web] support JSPI build (#26095)
### Description This PR introduces the support for building WebGPU EP with JSPI support. When build with `--enable_wasm_jspi`, it does the following: - Use `JSPI` instead of `ASYNCIFY` - Use WebAssembly EH (-fwasm-exceptions) instead of Emscripten exceptions ### Motivation and Context Using JSPI with wasm exceptions help to have: - better performance (reduced CPU overhead) - smaller binary size (no extra generated ASYNCIFY code) - faster build time
1 parent ed7847c commit 6efb844

21 files changed

+211
-56
lines changed

.github/workflows/linux-wasm-ci-build-and-test-workflow.yml

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,15 @@ jobs:
3737
runs-on: ["self-hosted", "1ES.Pool=onnxruntime-github-Ubuntu2204-AMD-CPU"]
3838
env:
3939
buildArch: x64
40-
common_build_args: --parallel ${{ inputs.use_vcpkg == true && '--use_vcpkg --use_vcpkg_ms_internal_asset_cache' || '' }} --config ${{ inputs.build_config }} --skip_submodule_sync --build_wasm --enable_wasm_simd ${{ inputs.enable_wasm_threads == true && '--enable_wasm_threads' || '' }} ${{ inputs.extra_build_args }}
40+
common_build_args: >-
41+
--parallel
42+
${{ inputs.use_vcpkg == true && '--use_vcpkg --use_vcpkg_ms_internal_asset_cache' || '' }}
43+
--config ${{ inputs.build_config }}
44+
--skip_submodule_sync
45+
--build_wasm
46+
--enable_wasm_simd
47+
${{ inputs.enable_wasm_threads == true && '--enable_wasm_threads' || '' }}
48+
${{ inputs.extra_build_args }}
4149
4250
steps:
4351
- name: Checkout code
@@ -70,6 +78,7 @@ jobs:
7078
python ./tools/ci_build/build.py \
7179
${{ env.common_build_args }} \
7280
--build_dir ${{ github.workspace }}/build/wasm_inferencing \
81+
${{ inputs.build_config == 'Release' && '--enable_wasm_api_exception_catching' || '' }} \
7382
--skip_tests
7483
working-directory: ${{ github.workspace }}
7584

@@ -82,6 +91,7 @@ jobs:
8291
--use_jsep \
8392
--use_webnn \
8493
--target onnxruntime_webassembly \
94+
${{ inputs.build_config == 'Release' && '--enable_wasm_api_exception_catching' || '' }} \
8595
--skip_tests
8696
working-directory: ${{ github.workspace }}
8797

@@ -94,6 +104,20 @@ jobs:
94104
--use_webgpu \
95105
--use_webnn \
96106
--target onnxruntime_webassembly \
107+
${{ inputs.build_config == 'Release' && '--enable_wasm_api_exception_catching' || '' }} \
108+
--skip_tests
109+
working-directory: ${{ github.workspace }}
110+
111+
- name: Build (simd + threads + WebGPU experimental, JSPI)
112+
if: ${{ inputs.build_webgpu == true }}
113+
run: |
114+
python ./tools/ci_build/build.py \
115+
${{ env.common_build_args }} \
116+
--build_dir ${{ github.workspace }}/build/wasm_inferencing_webgpu_jspi \
117+
--use_webgpu \
118+
--use_webnn \
119+
--enable_wasm_jspi \
120+
--target onnxruntime_webassembly \
97121
--skip_tests
98122
working-directory: ${{ github.workspace }}
99123

@@ -111,6 +135,10 @@ jobs:
111135
cp ${{ github.workspace }}/build/wasm_inferencing_webgpu/${{ inputs.build_config }}/ort-wasm-simd-threaded.asyncify.wasm ${{ github.workspace }}/artifacts/wasm/
112136
cp ${{ github.workspace }}/build/wasm_inferencing_webgpu/${{ inputs.build_config }}/ort-wasm-simd-threaded.asyncify.mjs ${{ github.workspace }}/artifacts/wasm/
113137
fi
138+
if [ -d ${{ github.workspace }}/build/wasm_inferencing_webgpu_jspi ]; then
139+
cp ${{ github.workspace }}/build/wasm_inferencing_webgpu_jspi/${{ inputs.build_config }}/ort-wasm-simd-threaded.jspi.wasm ${{ github.workspace }}/artifacts/wasm/
140+
cp ${{ github.workspace }}/build/wasm_inferencing_webgpu_jspi/${{ inputs.build_config }}/ort-wasm-simd-threaded.jspi.mjs ${{ github.workspace }}/artifacts/wasm/
141+
fi
114142
115143
- name: Upload WASM artifacts
116144
if: ${{ inputs.skip_publish != true }}

.github/workflows/web.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ jobs:
4848
uses: ./.github/workflows/linux-wasm-ci-build-and-test-workflow.yml
4949
with:
5050
build_config: Release
51-
extra_build_args: "--target onnxruntime_webassembly --skip_tests --enable_wasm_api_exception_catching --disable_rtti"
51+
extra_build_args: "--target onnxruntime_webassembly --skip_tests --disable_rtti"
5252
build_jsep: true
5353
build_webgpu: true
5454

@@ -57,7 +57,7 @@ jobs:
5757
uses: ./.github/workflows/linux-wasm-ci-build-and-test-workflow.yml
5858
with:
5959
build_config: Release
60-
extra_build_args: "--skip_tests --enable_wasm_api_exception_catching --disable_rtti --build_wasm_static_lib"
60+
extra_build_args: "--skip_tests --disable_rtti --build_wasm_static_lib"
6161
use_vcpkg: false
6262
enable_wasm_threads: false
6363
skip_publish: true

cmake/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ option(onnxruntime_USE_NCCL "Build with NCCL support" OFF)
194194

195195
# WebAssembly options
196196
option(onnxruntime_BUILD_WEBASSEMBLY_STATIC_LIB "Enable this option to create WebAssembly static library" OFF)
197+
option(onnxruntime_ENABLE_WEBASSEMBLY_JSPI "Enable WebAssembly JavaScript Promise Integration" OFF)
197198
option(onnxruntime_ENABLE_WEBASSEMBLY_THREADS "Enable this option to create WebAssembly byte codes with multi-threads support" OFF)
198199
option(onnxruntime_ENABLE_WEBASSEMBLY_EXCEPTION_CATCHING "Enable this option to turn on exception catching" OFF)
199200
option(onnxruntime_ENABLE_WEBASSEMBLY_API_EXCEPTION_CATCHING "Enable this option to turn on api exception catching" OFF)

cmake/adjust_global_compile_flags.cmake

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
4545
string(APPEND CMAKE_CXX_FLAGS " -msimd128")
4646
endif()
4747

48-
if (onnxruntime_ENABLE_WEBASSEMBLY_EXCEPTION_CATCHING)
48+
# Enable WebAssembly exception catching.
49+
if (onnxruntime_ENABLE_WEBASSEMBLY_JSPI)
50+
string(APPEND CMAKE_C_FLAGS " -fwasm-exceptions -s WASM_LEGACY_EXCEPTIONS=0")
51+
string(APPEND CMAKE_CXX_FLAGS " -fwasm-exceptions -s WASM_LEGACY_EXCEPTIONS=0")
52+
elseif (onnxruntime_ENABLE_WEBASSEMBLY_EXCEPTION_CATCHING)
4953
string(APPEND CMAKE_C_FLAGS " -s DISABLE_EXCEPTION_CATCHING=0")
5054
string(APPEND CMAKE_CXX_FLAGS " -s DISABLE_EXCEPTION_CATCHING=0")
5155
endif()

cmake/external/onnxruntime_external_deps.cmake

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -314,18 +314,18 @@ if (onnxruntime_ENABLE_CPUINFO)
314314
# Adding pytorch CPU info library
315315
# TODO!! need a better way to find out the supported architectures
316316
set(CPUINFO_SUPPORTED FALSE)
317-
if (APPLE)
317+
if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
318+
# if xnnpack is enabled in a wasm build it needs clog from cpuinfo, but we won't internally use cpuinfo.
319+
if (onnxruntime_USE_XNNPACK)
320+
set(CPUINFO_SUPPORTED TRUE)
321+
endif()
322+
elseif (APPLE)
318323
list(LENGTH CMAKE_OSX_ARCHITECTURES CMAKE_OSX_ARCHITECTURES_LEN)
319324
if (CMAKE_OSX_ARCHITECTURES_LEN LESS_EQUAL 1)
320325
set(CPUINFO_SUPPORTED TRUE)
321326
else()
322327
message(WARNING "cpuinfo is not supported when CMAKE_OSX_ARCHITECTURES has more than one value.")
323328
endif()
324-
elseif (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
325-
# if xnnpack is enabled in a wasm build it needs clog from cpuinfo, but we won't internally use cpuinfo.
326-
if (onnxruntime_USE_XNNPACK)
327-
set(CPUINFO_SUPPORTED TRUE)
328-
endif()
329329
elseif (WIN32)
330330
set(CPUINFO_SUPPORTED TRUE)
331331
else()

cmake/onnxruntime_providers_webgpu.cmake

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,28 @@
4747
list(REMOVE_ITEM EM_DAWN_WEBGPU_C_COMPILE_OPTIONS "-fno-exceptions")
4848
set_property(TARGET emdawnwebgpu_c PROPERTY COMPILE_OPTIONS ${EM_DAWN_WEBGPU_C_COMPILE_OPTIONS})
4949
endif()
50+
if (CMAKE_CXX_FLAGS MATCHES "-fwasm-exceptions")
51+
get_property(EM_DAWN_WEBGPU_C_COMPILE_OPTIONS TARGET emdawnwebgpu_c PROPERTY COMPILE_OPTIONS)
52+
list(REMOVE_ITEM EM_DAWN_WEBGPU_C_COMPILE_OPTIONS "-fno-exceptions")
53+
set_property(TARGET emdawnwebgpu_c PROPERTY COMPILE_OPTIONS ${EM_DAWN_WEBGPU_C_COMPILE_OPTIONS})
54+
endif()
5055

5156
# target "emdawnwebgpu_cpp" is created by Dawn. When it is linked to onnxruntime_providers_webgpu as "PUBLIC"
5257
# dependency, a few build/link flags will be set automatically to make sure emscripten can generate correct
5358
# WebAssembly/JavaScript code for WebGPU support.
5459
target_link_libraries(onnxruntime_providers_webgpu PUBLIC emdawnwebgpu_cpp)
5560

56-
# ASYNCIFY is required for WGPUFuture support (ie. async functions in WebGPU API)
57-
target_link_options(onnxruntime_providers_webgpu PUBLIC
58-
"SHELL:-s ASYNCIFY=1"
59-
"SHELL:-s ASYNCIFY_STACK_SIZE=65536"
60-
)
61+
if (onnxruntime_ENABLE_WEBASSEMBLY_JSPI)
62+
target_link_options(onnxruntime_providers_webgpu PUBLIC
63+
"SHELL:-s JSPI=1"
64+
)
65+
else()
66+
# ASYNCIFY is required for WGPUFuture support (ie. async functions in WebGPU API)
67+
target_link_options(onnxruntime_providers_webgpu PUBLIC
68+
"SHELL:-s ASYNCIFY=1"
69+
"SHELL:-s ASYNCIFY_STACK_SIZE=65536"
70+
)
71+
endif()
6172
else()
6273
onnxruntime_add_include_to_target(onnxruntime_providers_webgpu dawn::dawncpp_headers dawn::dawn_headers)
6374

cmake/onnxruntime_webassembly.cmake

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -244,10 +244,6 @@ else()
244244
)
245245

246246
if (onnxruntime_USE_JSEP)
247-
# NOTE: "-s ASYNCIFY=1" is required for JSEP to work with WebGPU
248-
# This flag allows async functions to be called from sync functions, in the cost of binary size and
249-
# build time. See https://emscripten.org/docs/porting/asyncify.html for more details.
250-
251247
target_compile_definitions(onnxruntime_webassembly PRIVATE USE_JSEP=1)
252248
target_link_options(onnxruntime_webassembly PRIVATE
253249
"SHELL:--pre-js \"${ONNXRUNTIME_ROOT}/wasm/pre-jsep.js\""
@@ -275,13 +271,24 @@ else()
275271
endif()
276272

277273
if (onnxruntime_USE_JSEP OR onnxruntime_USE_WEBGPU OR onnxruntime_USE_WEBNN)
278-
# if any of the above is enabled, we need to use the asyncify library
279-
target_link_options(onnxruntime_webassembly PRIVATE
280-
"SHELL:--pre-js \"${ONNXRUNTIME_ROOT}/wasm/pre-async.js\""
281-
"SHELL:-s ASYNCIFY=1"
282-
"SHELL:-s ASYNCIFY_STACK_SIZE=65536"
283-
)
284-
list(APPEND onnxruntime_webassembly_script_deps "${ONNXRUNTIME_ROOT}/wasm/pre-async.js")
274+
if (onnxruntime_ENABLE_WEBASSEMBLY_JSPI)
275+
target_link_options(onnxruntime_webassembly PRIVATE
276+
"SHELL:-s JSPI=1"
277+
"SHELL:-s JSPI_EXPORTS=[OrtAppendExecutionProvider,OrtCreateSession,OrtRun,OrtRunWithBinding,OrtBindInput]"
278+
)
279+
else()
280+
# NOTE: "-s ASYNCIFY=1" is required for JSEP to work with WebGPU
281+
# This flag allows async functions to be called from sync functions, in the cost of binary size and
282+
# build time. See https://emscripten.org/docs/porting/asyncify.html for more details.
283+
#
284+
# if any of the above is enabled, we need to use the asyncify library
285+
target_link_options(onnxruntime_webassembly PRIVATE
286+
"SHELL:--pre-js \"${ONNXRUNTIME_ROOT}/wasm/pre-async.js\""
287+
"SHELL:-s ASYNCIFY=1"
288+
"SHELL:-s ASYNCIFY_STACK_SIZE=65536"
289+
)
290+
list(APPEND onnxruntime_webassembly_script_deps "${ONNXRUNTIME_ROOT}/wasm/pre-async.js")
291+
endif()
285292
endif()
286293

287294
if (onnxruntime_EMSCRIPTEN_SETTINGS)
@@ -322,8 +329,12 @@ else()
322329
endif()
323330
endif()
324331

325-
# Set link flag to enable exceptions support, this will override default disabling exception throwing behavior when disable exceptions.
326-
target_link_options(onnxruntime_webassembly PRIVATE "SHELL:-s DISABLE_EXCEPTION_THROWING=0")
332+
if (NOT onnxruntime_ENABLE_WEBASSEMBLY_JSPI)
333+
# Set link flag to enable exceptions support, this will override default disabling exception throwing behavior when disable exceptions.
334+
target_link_options(onnxruntime_webassembly PRIVATE
335+
"SHELL:-s DISABLE_EXCEPTION_THROWING=0"
336+
)
337+
endif()
327338

328339
if (onnxruntime_ENABLE_WEBASSEMBLY_PROFILING)
329340
target_link_options(onnxruntime_webassembly PRIVATE --profiling --profiling-funcs)
@@ -379,8 +390,11 @@ else()
379390
if (onnxruntime_USE_JSEP)
380391
string(APPEND target_name ".jsep")
381392
elseif (onnxruntime_USE_WEBGPU OR onnxruntime_USE_WEBNN)
382-
string(APPEND target_name ".asyncify")
383-
# TODO: support JSPI and add ".jspi" once JSPI build is supported
393+
if (onnxruntime_ENABLE_WEBASSEMBLY_JSPI)
394+
string(APPEND target_name ".jspi")
395+
else()
396+
string(APPEND target_name ".asyncify")
397+
endif()
384398
endif()
385399

386400
set_target_properties(onnxruntime_webassembly PROPERTIES OUTPUT_NAME ${target_name} SUFFIX ".mjs")

js/build_webgpu.bat

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@ popd
6969
set PATH=C:\Program Files\Git\usr\bin;%PATH%
7070

7171
call %ROOT%build.bat --config %CONFIG% %CONFIG_EXTRA_FLAG% --skip_submodule_sync --build_wasm --target onnxruntime_webassembly --skip_tests^
72-
--enable_wasm_simd --enable_wasm_threads --use_webnn --use_webgpu --build_dir %BUILD_DIR%
72+
--enable_wasm_simd --enable_wasm_threads --use_webnn --use_webgpu --enable_wasm_jspi --build_dir %BUILD_DIR%
7373

7474
IF NOT "%ERRORLEVEL%" == "0" (
7575
exit /b %ERRORLEVEL%
7676
)
7777

78-
copy /Y %BUILD_DIR%\%CONFIG%\ort-wasm-simd-threaded.asyncify.wasm %ROOT%js\web\dist\
79-
copy /Y %BUILD_DIR%\%CONFIG%\ort-wasm-simd-threaded.asyncify.mjs %ROOT%js\web\dist\
78+
copy /Y %BUILD_DIR%\%CONFIG%\ort-wasm-simd-threaded.jspi.wasm %ROOT%js\web\dist\
79+
copy /Y %BUILD_DIR%\%CONFIG%\ort-wasm-simd-threaded.jspi.mjs %ROOT%js\web\dist\

js/build_webgpu.sh

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ if [ "$1" = "d" ]; then
2424
CONFIG_EXTRA_FLAG="--enable_wasm_profiling --wasm_run_tests_in_browser --cmake_extra_defines onnxruntime_ENABLE_WEBASSEMBLY_OUTPUT_OPTIMIZED_MODEL=1 --enable_wasm_debug_info"
2525
elif [ "$1" = "r" ]; then
2626
CONFIG="Release"
27-
CONFIG_EXTRA_FLAG="--enable_wasm_api_exception_catching --disable_rtti"
27+
# CONFIG_EXTRA_FLAG="--enable_wasm_api_exception_catching --disable_rtti"
28+
CONFIG_EXTRA_FLAG="--disable_rtti"
2829
else
2930
echo "Error: Invalid configuration \"$1\"."
3031
echo "Configuration must be 'd' (Debug) or 'r' (Release)."
@@ -99,6 +100,7 @@ echo "Calling $ROOT_DIR/build.sh to build WebAssembly..."
99100
--enable_wasm_threads \
100101
--use_webnn \
101102
--use_webgpu \
103+
--enable_wasm_jspi \
102104
--build_dir "$BUILD_DIR"
103105

104106
# The 'set -e' command at the beginning of the script ensures that the script will exit
@@ -108,10 +110,10 @@ echo "--- Copying build artifacts ---"
108110
# Ensure the dist directory exists before copying files
109111
mkdir -p "$ROOT_DIR/js/web/dist"
110112

111-
echo "Copying ort-wasm-simd-threaded.asyncify.wasm to $ROOT_DIR/js/web/dist/"
112-
cp -f "$BUILD_DIR/$CONFIG/ort-wasm-simd-threaded.asyncify.wasm" "$ROOT_DIR/js/web/dist/"
113+
echo "Copying ort-wasm-simd-threaded.jspi.wasm to $ROOT_DIR/js/web/dist/"
114+
cp -f "$BUILD_DIR/$CONFIG/ort-wasm-simd-threaded.jspi.wasm" "$ROOT_DIR/js/web/dist/"
113115

114-
echo "Copying ort-wasm-simd-threaded.asyncify.mjs to $ROOT_DIR/js/web/dist/"
115-
cp -f "$BUILD_DIR/$CONFIG/ort-wasm-simd-threaded.asyncify.mjs" "$ROOT_DIR/js/web/dist/"
116+
echo "Copying ort-wasm-simd-threaded.jspi.mjs to $ROOT_DIR/js/web/dist/"
117+
cp -f "$BUILD_DIR/$CONFIG/ort-wasm-simd-threaded.jspi.mjs" "$ROOT_DIR/js/web/dist/"
116118

117119
echo "--- WebGPU build process completed successfully ---"

js/common/lib/env.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export declare namespace Env {
1616
* - `ort-wasm-simd-threaded.wasm` for default build
1717
* - `ort-wasm-simd-threaded.jsep.wasm` for JSEP build (with WebGPU and WebNN)
1818
* - `ort-wasm-simd-threaded.asyncify.wasm` for WebGPU build with Asyncify (with WebNN)
19+
* - `ort-wasm-simd-threaded.jspi.wasm` for WebGPU build with JSPI support (with WebNN)
1920
*/
2021
wasm?: URL | string;
2122
/**
@@ -27,6 +28,7 @@ export declare namespace Env {
2728
* - `ort-wasm-simd-threaded.mjs` for default build
2829
* - `ort-wasm-simd-threaded.jsep.mjs` for JSEP build (with WebGPU and WebNN)
2930
* - `ort-wasm-simd-threaded.asyncify.mjs` for WebGPU build with Asyncify (with WebNN)
31+
* - `ort-wasm-simd-threaded.jspi.mjs` for WebGPU build with JSPI support (with WebNN)
3032
*/
3133
mjs?: URL | string;
3234
}

0 commit comments

Comments
 (0)