Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .ci/scripts/setup-emscripten.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

set -ex

install_nodejs() {
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
\. "$HOME/.nvm/nvm.sh"
nvm install 22
}

install_emscripten() {
git clone https://github.com/emscripten-core/emsdk.git
pushd emsdk || return
./emsdk install 4.0.10
./emsdk activate 4.0.10
source ./emsdk_env.sh
popd || return
}

install_nodejs
install_emscripten
28 changes: 28 additions & 0 deletions .github/workflows/pull.yml
Original file line number Diff line number Diff line change
Expand Up @@ -734,3 +734,31 @@ jobs:

PYTHON_EXECUTABLE=python bash .ci/scripts/setup-openvino.sh
PYTHON_EXECUTABLE=python bash .ci/scripts/test_openvino.sh

test-build-wasm-linux:
name: test-build-wasm-linux
uses: pytorch/test-infra/.github/workflows/linux_job_v2.yml@main
permissions:
id-token: write
contents: read
strategy:
fail-fast: false
with:
runner: linux.2xlarge
docker-image: executorch-ubuntu-22.04-clang12
submodules: 'recursive'
ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}
timeout: 90
script: |
# The generic Linux job chooses to use base env, not the one setup by the image
CONDA_ENV=$(conda env list --json | jq -r ".envs | .[-1]")
conda activate "${CONDA_ENV}"

BUILD_TOOL="cmake"
PYTHON_EXECUTABLE=python bash .ci/scripts/setup-linux.sh --build-tool "${BUILD_TOOL}"

# Install Node.js and Emscripten
source .ci/scripts/setup-emscripten.sh

# Test selective build
PYTHON_EXECUTABLE=python bash examples/wasm/test_build_wasm.sh
10 changes: 10 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -726,6 +726,16 @@ if(EXECUTORCH_BUILD_EXECUTOR_RUNNER)
endif()
target_link_libraries(executor_runner ${_executor_runner_libs})
target_compile_options(executor_runner PUBLIC ${_common_compile_options})

if(EMSCRIPTEN)
# Directory of model pte files to embed in the wasm binary.
if(NOT DEFINED WASM_MODEL_DIR)
set(WASM_MODEL_DIR "${CMAKE_SOURCE_DIR}/models/")
endif()

set(CMAKE_EXECUTABLE_SUFFIX ".html")
target_link_options(executor_runner PUBLIC -sALLOW_MEMORY_GROWTH --embed-file "${WASM_MODEL_DIR}@/")
endif()
endif()

if(EXECUTORCH_BUILD_VULKAN)
Expand Down
72 changes: 72 additions & 0 deletions examples/wasm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# ExecuTorch Wasm Build

This guide describes how to build ExecuTorch for WebAssembly (Wasm).

## Directory Structure

```
examples/wasm
└── README.md # This file
```

## Prerequisites

- [emscripten](https://emscripten.org/docs/getting_started/Tutorial.html)
- [Node.js](https://nodejs.org/en/) (Optional)

## Generate Models

JavaScript does not have access to the filesystem. To load a model, it needs to be preloaded or embedded into the virtual filesystem. In this example, models in the `./models/` directory are embedded by default. We will then build `executorch_runner` in Wasm.

1. Following the setup guide in [Setting up ExecuTorch](https://pytorch.org/executorch/main/getting-started-setup)
you should be able to get the basic development environment for ExecuTorch working.

2. Using the script `portable/scripts/export.py` generate a model binary file by selecting a
model name from the list of available models in the `examples/models` dir.

```bash
cd executorch # To the top level dir

mkdir models

# To get a list of example models
python3 -m examples.portable.script.export -h

# To generate a specific pte model into the models/ directory
python3 -m examples.portable.scripts.export --model_name="mv2" --output_dir="models/" # for MobileNetv2

# This should generate ./models/mv2.pte file, if successful.
```

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.

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/`.

```bash
./install_executorch.sh --clean
(mkdir cmake-out-wasm \
&& cd cmake-out-wasm \
&& emcmake cmake -DEXECUTORCH_PAL_DEFAULT=posix ..) \
&& cmake --build cmake-out-wasm -j32 --target executor_runner
```

If you need to rebuild `executor_runner` after modifying the contents of `./models/`, you can run the following command

```bash
cmake --build cmake-out-wasm -j32 --target executor_runner --clean-first
```

4. Run the model with Node.js.

```bash
# Run the tool on the generated model.
node cmake-out-wasm/executor_runner.js --model_path mv2.pte
```

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:

```bash
python3 -m http.server --directory cmake-out-wasm
```

The page will be available at http://localhost:8000/executor_runner.html.
37 changes: 37 additions & 0 deletions examples/wasm/test_build_wasm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@

set -e

source "$(dirname "${BASH_SOURCE[0]}")/../../.ci/scripts/utils.sh"

test_build_wasm() {
local model_name=$1
local model_export_name="${model_name}.pte"
local model_dir_name="./models_test/"
echo "Exporting ${model_name}"
mkdir -p "${model_dir_name}"
${PYTHON_EXECUTABLE} -m examples.portable.scripts.export --model_name="${model_name}" --output_dir="$model_dir_name"

local example_dir=examples/wasm
local build_dir=cmake-out/${example_dir}
rm -rf ${build_dir}
retry emcmake cmake -DWASM_MODEL_DIR="$(realpath "${model_dir_name}")" -B${build_dir} .

echo "Building ${example_dir}"
cmake --build ${build_dir} -j9 --target executor_runner

echo "Removing ${model_dir_name}"
rm -rf "${model_dir_name}"

echo 'Running wasm build test'
node ${build_dir}/executor_runner.js --model_path="${model_export_name}"
}

if [[ -z $PYTHON_EXECUTABLE ]];
then
PYTHON_EXECUTABLE=python3
fi

cmake_install_executorch_lib

test_build_wasm add_mul
test_build_wasm mv2
Loading