Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: initial WASM support #4562

Merged
merged 16 commits into from
Apr 25, 2023
Merged
Show file tree
Hide file tree
Changes from 6 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
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
41 changes: 40 additions & 1 deletion CMakePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
"configurePresets": [
{
"name": "default",
"generator": "Ninja",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": {
Expand Down Expand Up @@ -158,6 +157,46 @@
"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"
},
"VW_FEAT_FLATBUFFERS": {
"type": "BOOL",
"value": "Off"
},
"VW_FEAT_CSV": {
"type": "BOOL",
"value": "Off"
},
"VW_FEAT_LDA": {
"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
4 changes: 2 additions & 2 deletions vowpalwabbit/core/src/parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ typedef int socklen_t;
// windows doesn't define SOL_TCP and use an enum for the later, so can't check for its presence with a macro.
# define SOL_TCP IPPROTO_TCP

int daemon(int /*a*/, int /*b*/) { exit(0); }
// int daemon(int /*a*/, int /*b*/) { exit(0); }

// Starting with v142 the fix in the else block no longer works due to mismatching linkage. Going forward we should just
// use the actual isocpp version.
Expand Down Expand Up @@ -464,7 +464,7 @@ void VW::details::enable_sources(
// FIXME switch to posix_spawn
VW_WARNING_STATE_PUSH
VW_WARNING_DISABLE_DEPRECATED_USAGE
if (!all.reduction_state.active && daemon(1, 1)) THROWERRNO("daemon");
// if (!all.reduction_state.active && daemon(1, 1)) THROWERRNO("daemon");
VW_WARNING_STATE_POP
}

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.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

build the wasm blob standalone


## 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