Skip to content

Commit

Permalink
feat: initial WASM support (VowpalWabbit#4562)
Browse files Browse the repository at this point in the history

Co-authored-by: olgavrou <[email protected]>
  • Loading branch information
jackgerrits and olgavrou authored Apr 25, 2023
1 parent eb15bf9 commit 71d3d41
Show file tree
Hide file tree
Showing 15 changed files with 657 additions and 3 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/asan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ jobs:
cmakeListsTxtPath: "${{ github.workspace }}/CMakeLists.txt"
configurePreset: "${{ matrix.preset }}"
- name: Build
run: cmake --build build -t vw_cli_bin spanning_tree vw_core_test
run: cmake --build build -t vw_cli_bin vw_spanning_tree vw_spanning_tree_bin vw_core_test
- name: Run unit tests
run: ./build/vowpalwabbit/core/vw_core_test --gtest_filter=-\*WIiterations
- name: Run python test script
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/wasm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Wasm

on:
push:
branches:
- master
- 'releases/**'
pull_request:
branches:
- '*'

concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.sha }}
cancel-in-progress: true

jobs:
job:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
submodules: true
- uses: lukka/get-cmake@latest
- uses: mymindstorm/setup-emsdk@v11
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Configure
run: emcmake cmake --preset wasm -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=$(pwd)/ext_libs/vcpkg/scripts/buildsystems/vcpkg.cmake
- name: Build
run: cmake --build build --target vw-wasm
- uses: actions/upload-artifact@v3
with:
path: wasm/out
- name: Test
working-directory: wasm
run: |
npm install
npm test
5 changes: 5 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ option(vw_BUILD_NET_CORE "Build .NET Core targets" OFF)
option(vw_BUILD_NET_FRAMEWORK "Build .NET Framework targets" OFF)
option(VW_USE_ASAN "Compile with AddressSanitizer" OFF)
option(VW_USE_UBSAN "Compile with UndefinedBehaviorSanitizer" OFF)
option(VW_BUILD_WASM "Add WASM target" OFF)

if(VW_USE_ASAN)
add_compile_definitions(VW_USE_ASAN)
Expand Down Expand Up @@ -260,6 +261,10 @@ if(BUILD_BENCHMARKS)
add_subdirectory(test/benchmarks)
endif()

if(VW_BUILD_WASM)
add_subdirectory(wasm)
endif()

if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME AND BUILD_TESTING)
add_subdirectory(test)

Expand Down
52 changes: 52 additions & 0 deletions CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,58 @@
"value": "On"
}
}
},
{
"name": "wasm",
"description": "CMAKE_TOOLCHAIN_FILE has to be passed explicitly since emcmake overwrites it if we define it here.",
"inherits": "default",
"cacheVariables": {
"VCPKG_CHAINLOAD_TOOLCHAIN_FILE":
{
"type": "FILEPATH",
"value": "$env{EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake"
},
"VW_BUILD_WASM": {
"type": "BOOL",
"value": "On"
},
"USE_LATEST_STD": {
"type": "BOOL",
"value": "On"
},
"VW_FEAT_FLATBUFFERS": {
"type": "BOOL",
"value": "Off"
},
"VW_FEAT_CSV": {
"type": "BOOL",
"value": "Off"
},
"VW_FEAT_LDA": {
"type": "BOOL",
"value": "Off"
},
"VW_FEAT_SEARCH": {
"type": "BOOL",
"value": "Off"
},
"VW_FEAT_NETWORKING": {
"type": "BOOL",
"value": "Off"
},
"BUILD_TESTING": {
"type": "BOOL",
"value": "Off"
},
"VW_BUILD_VW_C_WRAPPER": {
"type": "BOOL",
"value": "Off"
},
"VCPKG_TARGET_TRIPLET": {
"type": "STRING",
"value": "wasm32-emscripten"
}
}
}
],
"buildPresets": [
Expand Down
2 changes: 1 addition & 1 deletion ext_libs/vcpkg
Submodule vcpkg updated 2506 files
2 changes: 1 addition & 1 deletion vowpalwabbit/core/src/confidence_sequence.cc
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ double confidence_sequence::approxpolygammaone(double b) const

double confidence_sequence::lblogwealth(double sumXt, double v, double eta, double s, double lb_alpha) const
{
#if !defined(__APPLE__) && !defined(_WIN32)
#if !defined(__APPLE__) && !defined(_WIN32) && !defined(__EMSCRIPTEN__)
double zeta_s = std::riemann_zeta(s);
#else
double zeta_s = 10.584448464950803; // std::riemann_zeta(s) -- Assuming s=1.1 is constant
Expand Down
9 changes: 9 additions & 0 deletions wasm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
if(VW_INSTALL)
message(FATAL_ERROR "Install not supported for WASM build" )
endif()

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/out/")

add_executable(vw-wasm wasm_wrapper.cc)
set_target_properties(vw-wasm PROPERTIES LINK_FLAGS "-fexceptions -s WASM=1 -s NO_DYNAMIC_EXECUTION=1 --bind -s ALLOW_MEMORY_GROWTH=1 -s EXPORTED_FUNCTIONS=\"['_malloc', '_free']\"")
target_link_libraries(vw-wasm PUBLIC vw_explore vw_core "-fexceptions")
44 changes: 44 additions & 0 deletions wasm/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

const VWModule = require('./out/vw-wasm.js');

// You must wait for the WASM runtime to be ready before using the module.
VWModule['onRuntimeInitialized'] = () => {
try {
// Create a model with default options
let model = new VWModule.VWModel("");

let example_line = "0 | price:.23 sqft:.25 age:.05 2006";
// For multi_ex learners, the input to parse should have newlines in it.
// One call to parse should only ever get input for either a single
// example or a single multi_ex grouping.
let parsedExample = model.parse(example_line);

// Prediction returns a normal javascript value which corresponds to the
// expected prediction type. Here is just a number.
console.log(`prediction: ${model.predict(parsedExample)}`);

model.learn(parsedExample);

// Any examples which were given to either learn and/or predict must be
// given to finishExample.
model.finishExample(parsedExample);

// Every object returned by parse must be deleted manually.
// Additionally, shallow or deep example copies must be deleted.
// let shallowCopyExample = parsedExample;
// shallowCopyExample.delete();
//
// let deepCopyExample = parsedExample.clone(model);
// deepCopyExample.delete();
parsedExample.delete();

// VWModel must also always be manually deleted.
model.delete();
}
catch (e) {
// Exceptions that are produced by the module must be passed through
// this transformation function to get the error info.
console.error(VWModule.getExceptionMessage(e));
}
}

9 changes: 9 additions & 0 deletions wasm/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"devDependencies": {
"mocha": "^9.1.2"
},
"scripts": {
"test1": "mocha --delay",
"test": "node --experimental-wasm-threads ./node_modules/mocha/bin/mocha --delay"
}
}
46 changes: 46 additions & 0 deletions wasm/readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# VW - WASM

This module is currently very experimental. It is not currently built standalone and still requires the glue JS to function. Ideally it will be standalone WASM module eventually.

## Use docker container
### Build
```sh
# Run in VW root directory
docker run --rm -v $(pwd):/src -it emscripten/emsdk emcmake cmake -G "Unix Makefiles" --preset wasm -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=/src/ext_libs/vcpkg/scripts/buildsystems/vcpkg.cmake
docker run --rm -v $(pwd):/src -it emscripten/emsdk emcmake cmake --build /src/build --target vw-wasm -j $(nproc)
```

Artifacts are placed in `wasm/out`

### Test
Assumes required build artifacts are in `wasm/out`

```sh
# Run in VW root directory
docker run --workdir="/src/wasm" --rm -v $(pwd):/src -t node:16-alpine npm install
docker run --workdir="/src/wasm" --rm -v $(pwd):/src -t node:16-alpine npm test
```

## Or, setup local environment to build

### Install Emscripten and activate environment
Instructions here: https://emscripten.org/docs/getting_started/downloads.html
```sh
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
./emsdk install latest
./emsdk activate latest
source ./emsdk_env.sh
```

### Build
Make sure Emscripten is activated.
```sh
emcmake cmake --preset wasm -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_TOOLCHAIN_FILE=$(pwd)/ext_libs/vcpkg/scripts/buildsystems/vcpkg.cmake
cmake --build build --target vw-wasm
```

### Test
```sh
npm test
```
Loading

0 comments on commit 71d3d41

Please sign in to comment.