Skip to content

Commit 9591978

Browse files
authored
Add example for building executor_runner in Wasm (pytorch#12303)
### Summary Since we want to have ExecuTorch on web builds, we first need to be able to compile it to Wasm. An example was added that gives instructions on how to build executor_runner with Emscripten, along with some modifications to CMakeLists.txt while Emscripten is used while building. ### Test plan The test script can be run with `bash examples/wasm/test_build_wasm.sh` from the root directory. This attempts to build executor_runner and run it on the add_mul and mv2 models. Added a CI test-build-wasm-linux to run this script.
1 parent 7c300e7 commit 9591978

File tree

5 files changed

+186
-0
lines changed

5 files changed

+186
-0
lines changed

.ci/scripts/setup-emscripten.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
set -ex
3+
4+
install_emscripten() {
5+
git clone https://github.com/emscripten-core/emsdk.git
6+
pushd emsdk || return
7+
./emsdk install 4.0.10
8+
./emsdk activate 4.0.10
9+
source ./emsdk_env.sh
10+
popd || return
11+
}
12+
13+
install_emscripten

.github/workflows/pull.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,3 +734,31 @@ jobs:
734734
735735
PYTHON_EXECUTABLE=python bash .ci/scripts/setup-openvino.sh
736736
PYTHON_EXECUTABLE=python bash .ci/scripts/test_openvino.sh
737+
738+
test-build-wasm-linux:
739+
name: test-build-wasm-linux
740+
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
741+
permissions:
742+
id-token: write
743+
contents: read
744+
strategy:
745+
fail-fast: false
746+
with:
747+
runner: linux.2xlarge
748+
docker-image: executorch-ubuntu-22.04-clang12
749+
submodules: 'recursive'
750+
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
751+
timeout: 90
752+
script: |
753+
# The generic Linux job chooses to use base env, not the one setup by the image
754+
CONDA_ENV=$(conda env list --json | jq -r ".envs | .[-1]")
755+
conda activate "${CONDA_ENV}"
756+
757+
BUILD_TOOL="cmake"
758+
PYTHON_EXECUTABLE=python bash .ci/scripts/setup-linux.sh --build-tool "${BUILD_TOOL}"
759+
760+
# Install Node.js and Emscripten
761+
source .ci/scripts/setup-emscripten.sh
762+
763+
# Test selective build
764+
PYTHON_EXECUTABLE=python bash examples/wasm/test_build_wasm.sh

CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,6 +726,17 @@ if(EXECUTORCH_BUILD_EXECUTOR_RUNNER)
726726
endif()
727727
target_link_libraries(executor_runner ${_executor_runner_libs})
728728
target_compile_options(executor_runner PUBLIC ${_common_compile_options})
729+
730+
# Automatically set when using `emcmake cmake` for Wasm build.
731+
if(EMSCRIPTEN)
732+
# Directory of model pte files to embed in the wasm binary.
733+
if(NOT DEFINED WASM_MODEL_DIR)
734+
set(WASM_MODEL_DIR "${CMAKE_SOURCE_DIR}/models/")
735+
endif()
736+
737+
set(CMAKE_EXECUTABLE_SUFFIX ".html")
738+
target_link_options(executor_runner PUBLIC -sALLOW_MEMORY_GROWTH --embed-file "${WASM_MODEL_DIR}@/")
739+
endif()
729740
endif()
730741

731742
if(EXECUTORCH_BUILD_VULKAN)

examples/wasm/README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# ExecuTorch Wasm Build
2+
3+
This guide describes how to build ExecuTorch for WebAssembly (Wasm).
4+
5+
## Quick Start
6+
7+
To quickly test the build, you can run the following commands
8+
9+
```bash
10+
cd executorch # To the top level dir
11+
12+
source .ci/scripts/setup-emscripten.sh # Install Emscripten and set up the environment variables
13+
14+
bash examples/wasm/test_build_wasm.sh # Run the test build script
15+
```
16+
17+
## Prerequisites
18+
19+
- [Emscripten](https://emscripten.org/docs/getting_started/Tutorial.html)
20+
21+
## Generate Models
22+
23+
JavaScript does not have direct access to the host file system. To load a model, it needs to be preloaded or embedded into the virtual file system. In this example, models in the `./models/` directory are embedded by default. We will then build `executorch_runner` in Wasm.
24+
25+
1. Following the setup guide in [Setting up ExecuTorch](https://pytorch.org/executorch/main/getting-started-setup)
26+
you should be able to get the basic development environment for ExecuTorch working.
27+
28+
2. Using the script `portable/scripts/export.py` generate a model binary file by selecting a
29+
model name from the list of available models in the `examples/models` dir.
30+
31+
```bash
32+
cd executorch # To the top level dir
33+
34+
mkdir models
35+
36+
# To get a list of example models
37+
python3 -m examples.portable.script.export -h
38+
39+
# To generate a specific pte model into the models/ directory
40+
python3 -m examples.portable.scripts.export --model_name="mv2" --output_dir="models/" # for MobileNetv2
41+
42+
# This should generate ./models/mv2.pte file, if successful.
43+
```
44+
45+
Use -h (or --help) to see all the supported models. For the browser example, make sure you have a model with the file name `model.pte` in the `./models/` directory.
46+
47+
3. Once we have the model binaries (.pte) in `./models/`, we can build `executor_runner` in Wasm with Emscripten. When calling `emcmake cmake`, you can pass the `-DWASM_MODEL_DIR=<path>` option to specify the directory containing the model files instead of `./models/`.
48+
49+
```bash
50+
./install_executorch.sh --clean
51+
(mkdir cmake-out-wasm \
52+
&& cd cmake-out-wasm \
53+
&& emcmake cmake -DEXECUTORCH_PAL_DEFAULT=posix ..) \
54+
&& cmake --build cmake-out-wasm -j32 --target executor_runner
55+
```
56+
57+
If you need to rebuild `executor_runner` after modifying the contents of `./models/`, you can run the following command
58+
59+
```bash
60+
cmake --build cmake-out-wasm -j32 --target executor_runner --clean-first
61+
```
62+
63+
4. Run the model with Node.js. Emscripten should come preinstalled with a compatible version of Node.js. If you have an incompatible version of Node.js installed, you can use the Emscripten-provided version by running `$EMSDK_NODE` instead of `node`.
64+
65+
```bash
66+
# Run the tool on the generated model.
67+
node cmake-out-wasm/executor_runner.js --model_path mv2.pte
68+
```
69+
70+
5. You can also run the model in the browser. Note that you cannot pass command line arguments to the browser version of the tool. By default, the program will load the model `model.pte` and run it. Several browsers do not support `file://` XHR requests to load the Wasm file. To get around this, you can use a local web server. For example, with Python:
71+
72+
```bash
73+
python3 -m http.server --directory cmake-out-wasm
74+
```
75+
76+
The page will be available at http://localhost:8000/executor_runner.html.
77+
78+
## Common Issues
79+
80+
### CompileError: WebAssembly.instantiate() [...] failed: expected table index 0...
81+
82+
This seems to be an issue with Node.js v16. Emscripten should come preinstalled with a compatible version of Node.js. You can use the Emscripten-provided version by running `$EMSDK_NODE` instead of `node`.
83+
84+
```bash
85+
echo $EMSDK_NODE
86+
.../emsdk/node/22.16.0_64bit/bin/node # example output
87+
```
88+
89+
### Failed to open [...]: No such file or directory (44)
90+
91+
The file may not have been present while building the Wasm binary. You can rebuild with the following command
92+
93+
```bash
94+
cmake --build cmake-out-wasm -j32 --target executor_runner --clean-first
95+
```
96+
97+
The path may also be incorrect. The files in the `WASM_MODEL_DIR` are placed into the root directory of the virtual file system, so you would use `--model_path mv2.pte` instead of `--model_path models/mv2.pte`, for example.

examples/wasm/test_build_wasm.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
2+
set -e
3+
4+
source "$(dirname "${BASH_SOURCE[0]}")/../../.ci/scripts/utils.sh"
5+
6+
test_build_wasm() {
7+
local model_name=$1
8+
local model_export_name="${model_name}.pte"
9+
local model_dir_name="./models_test/"
10+
echo "Exporting ${model_name}"
11+
mkdir -p "${model_dir_name}"
12+
${PYTHON_EXECUTABLE} -m examples.portable.scripts.export --model_name="${model_name}" --output_dir="$model_dir_name"
13+
14+
local example_dir=examples/wasm
15+
local build_dir=cmake-out/${example_dir}
16+
rm -rf ${build_dir}
17+
retry emcmake cmake -DWASM_MODEL_DIR="$(realpath "${model_dir_name}")" -B${build_dir} .
18+
19+
echo "Building ${example_dir}"
20+
cmake --build ${build_dir} -j9 --target executor_runner
21+
22+
echo "Removing ${model_dir_name}"
23+
rm -rf "${model_dir_name}"
24+
25+
echo 'Running wasm build test'
26+
$EMSDK_NODE ${build_dir}/executor_runner.js --model_path="${model_export_name}"
27+
}
28+
29+
if [[ -z $PYTHON_EXECUTABLE ]];
30+
then
31+
PYTHON_EXECUTABLE=python3
32+
fi
33+
34+
cmake_install_executorch_lib
35+
36+
test_build_wasm add_mul
37+
test_build_wasm mv2

0 commit comments

Comments
 (0)