diff --git a/.github/workflows/asan.yml b/.github/workflows/asan.yml index 838b1620d3c..91366abed91 100644 --- a/.github/workflows/asan.yml +++ b/.github/workflows/asan.yml @@ -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 diff --git a/.github/workflows/wasm.yml b/.github/workflows/wasm.yml new file mode 100644 index 00000000000..e1173248cff --- /dev/null +++ b/.github/workflows/wasm.yml @@ -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 diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ee5ef75352..ea8c108eb68 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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) diff --git a/CMakePresets.json b/CMakePresets.json index cd527d205b8..235c09a25e9 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -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": [ diff --git a/ext_libs/vcpkg b/ext_libs/vcpkg index f14984af373..a7b6122f6b6 160000 --- a/ext_libs/vcpkg +++ b/ext_libs/vcpkg @@ -1 +1 @@ -Subproject commit f14984af3738e69f197bf0e647a8dca12de92996 +Subproject commit a7b6122f6b6504d16d96117336a0562693579933 diff --git a/vowpalwabbit/core/src/confidence_sequence.cc b/vowpalwabbit/core/src/confidence_sequence.cc index 687fc53386a..fc4b35d91f1 100644 --- a/vowpalwabbit/core/src/confidence_sequence.cc +++ b/vowpalwabbit/core/src/confidence_sequence.cc @@ -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 diff --git a/wasm/CMakeLists.txt b/wasm/CMakeLists.txt new file mode 100644 index 00000000000..9129f6566fc --- /dev/null +++ b/wasm/CMakeLists.txt @@ -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") diff --git a/wasm/example.js b/wasm/example.js new file mode 100644 index 00000000000..1527feb366e --- /dev/null +++ b/wasm/example.js @@ -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)); + } +} + diff --git a/wasm/package.json b/wasm/package.json new file mode 100644 index 00000000000..99f3e52deb7 --- /dev/null +++ b/wasm/package.json @@ -0,0 +1,9 @@ +{ + "devDependencies": { + "mocha": "^9.1.2" + }, + "scripts": { + "test1": "mocha --delay", + "test": "node --experimental-wasm-threads ./node_modules/mocha/bin/mocha --delay" + } +} diff --git a/wasm/readme.md b/wasm/readme.md new file mode 100644 index 00000000000..a5c889e2c11 --- /dev/null +++ b/wasm/readme.md @@ -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 +``` diff --git a/wasm/test/data.txt b/wasm/test/data.txt new file mode 100644 index 00000000000..9d2c53acdee --- /dev/null +++ b/wasm/test/data.txt @@ -0,0 +1,10 @@ +-1 |f 9:6.2699720e-02 14:3.3754818e-02 21:3.3519048e-02 25:8.5761078e-02 35:4.3872457e-02 39:9.4288357e-02 40:7.0441186e-02 45:2.8220249e-02 48:8.9253604e-02 51:5.5228550e-02 52:4.1887917e-02 54:2.6429206e-02 55:9.9164948e-02 63:5.2862287e-02 84:9.9822134e-02 86:6.8034925e-02 90:1.0517266e-01 92:6.2055584e-02 110:6.5685034e-02 119:7.6330863e-02 140:5.0301693e-02 141:1.1612565e-01 147:3.9149761e-02 170:9.9009261e-02 188:3.5585757e-02 193:7.3115274e-02 209:9.2374183e-02 213:5.0590482e-02 217:6.2177896e-02 218:3.7764907e-02 221:8.4123150e-02 229:3.2764092e-02 233:3.0372689e-02 239:7.0955716e-02 253:5.6435257e-02 269:3.4315083e-02 286:2.4014754e-02 291:5.8397882e-02 293:3.0822147e-02 313:6.0061093e-02 315:4.5512896e-02 354:7.9400860e-02 386:1.1543484e-01 437:5.2397292e-02 453:7.4224271e-02 454:4.2704083e-02 457:7.4147679e-02 481:4.7475278e-02 529:6.3872963e-02 550:4.5456979e-02 555:6.4154439e-02 558:4.3083046e-02 573:6.6483773e-02 575:4.7380056e-02 584:1.4771688e-01 587:4.9748477e-02 620:3.4510206e-02 644:4.0916756e-02 694:6.9995433e-02 702:4.0819209e-02 724:6.6982903e-02 767:5.1354416e-02 769:9.5669545e-02 802:2.7801929e-02 803:5.5700876e-02 811:6.4894013e-02 911:5.5302896e-02 945:5.4957401e-02 1007:6.3262910e-02 1011:5.1566351e-02 1012:7.5135358e-02 1028:3.5715062e-02 1036:5.4271437e-02 1040:8.2043037e-02 1059:3.1980850e-02 1094:8.9004174e-02 1099:1.2113762e-01 1122:3.8443342e-02 1127:3.1443361e-02 1132:7.9007350e-02 1213:3.2161996e-02 1235:5.8625709e-02 1308:6.4898208e-02 1340:1.3853024e-01 1420:6.2780119e-02 1454:4.4202637e-02 1455:3.4211244e-02 1492:6.1930560e-02 1523:1.0642930e-01 1545:7.6326557e-02 1731:5.4338336e-02 1753:1.0155998e-01 1755:6.3911997e-02 1874:7.8952558e-02 1970:7.0811190e-02 1984:7.1320996e-02 2039:1.0851215e-01 2234:1.1493931e-01 2277:9.2282124e-02 2353:1.1663673e-01 2362:7.1725652e-02 2506:5.7918329e-02 2701:7.0818663e-02 2738:1.3180126e-01 2771:7.1890347e-02 2824:8.1224762e-02 2933:8.0126077e-02 3137:6.1298497e-02 3219:9.8932967e-02 3230:1.1288441e-01 3231:1.3121083e-01 3272:5.3371817e-02 3387:7.7760033e-02 3656:7.6234907e-02 3860:1.0627814e-01 4523:2.7653658e-01 5740:8.9249440e-02 8168:1.3357452e-01 9026:1.0093617e-01 12058:1.0700921e-01 12275:2.6784596e-01 12276:2.7168319e-01 12282:1.2917174e-01 13346:1.1627406e-01 14967:2.0771316e-01 21623:1.2864026e-01 34501:1.8448383e-01 +-1 |f 5:3.4770757e-02 21:5.2058056e-02 22:1.0131893e-01 66:9.8602206e-02 126:7.3677950e-02 144:8.3734207e-02 258:1.3117002e-01 282:1.0712786e-01 286:8.9001723e-02 302:6.8243802e-02 454:1.1229499e-01 459:8.5936494e-02 533:4.6158761e-02 534:1.0838871e-01 537:2.2317035e-01 540:1.0823997e-01 550:7.0598722e-02 573:6.0984179e-02 617:6.9755495e-02 620:5.3597409e-02 809:1.2329303e-01 811:1.0078616e-01 866:1.6002567e-01 1071:6.7204006e-02 1134:8.8398121e-02 1305:8.9227840e-02 1314:1.5046395e-01 1383:8.7426804e-02 2298:3.2898754e-02 2477:1.0394224e-01 3000:2.1763121e-01 3793:1.5177026e-01 5225:1.4700839e-01 7108:2.1639362e-01 7165:1.5993990e-01 7275:1.5572040e-01 7851:2.7698877e-01 7853:2.0813602e-01 7854:4.2525813e-01 7876:1.9971584e-01 7922:2.3383786e-01 7928:2.2501056e-01 22893:2.4975674e-01 +1 |f 24:2.5941234e-02 45:1.0093741e-01 73:4.4377144e-02 142:1.1601033e-01 159:1.9448641e-01 197:2.5329438e-01 257:6.3567974e-02 488:1.6744833e-01 497:1.3232227e-01 547:1.2052315e-01 613:5.2947123e-02 759:2.0554376e-01 782:1.9669981e-01 905:1.0293012e-01 1211:1.1731350e-01 1383:9.5941707e-02 1398:1.7192762e-01 1479:1.2192473e-01 1590:2.0290023e-01 1765:1.7032301e-01 2493:1.7820080e-01 3132:1.4529058e-01 3271:2.2052087e-01 3405:1.6775523e-01 5317:1.7017807e-01 6556:2.1031970e-01 8228:2.1553637e-01 12077:4.8640794e-01 28872:2.9970971e-01 +-1 |f 7:4.4724721e-02 15:2.1997921e-02 17:3.3769939e-02 22:7.8040227e-02 23:3.2728940e-02 24:1.8207725e-02 26:2.8254675e-02 42:4.1978225e-02 45:1.9938400e-02 50:2.0109808e-02 51:2.3665180e-02 52:2.9594993e-02 53:6.1693095e-02 55:2.1214541e-02 74:2.7887834e-02 79:4.9039219e-02 81:3.8692791e-02 89:3.6646318e-02 90:2.8476419e-02 99:3.8045701e-02 107:3.4456529e-02 110:2.7409501e-02 121:3.4106642e-02 122:5.3606208e-02 146:4.8705038e-02 147:2.7660409e-02 175:7.5247101e-02 178:6.8830401e-02 182:2.8641831e-02 183:4.7949456e-02 188:3.1163281e-02 189:2.7851848e-02 191:5.7022180e-02 192:7.1189202e-02 209:3.1099102e-02 213:8.5294746e-02 219:9.3563519e-02 220:3.5511807e-02 233:2.1459159e-02 239:2.9608890e-02 241:9.2885911e-02 264:3.5783652e-02 271:4.4741906e-02 280:7.4716315e-02 288:2.2889469e-02 291:4.1259747e-02 297:2.9019466e-02 312:3.6049493e-02 313:8.9054309e-02 315:3.2156143e-02 319:6.2024117e-02 339:2.6347337e-02 379:4.5082387e-02 451:3.1505141e-02 476:4.0007304e-02 480:4.1698117e-02 516:6.3339397e-02 526:2.1261238e-02 535:3.3876535e-02 545:3.5082880e-02 553:3.1601660e-02 555:2.6770808e-02 558:3.0439384e-02 572:6.1017144e-02 578:1.0156165e-01 581:4.8116118e-02 585:1.0146070e-01 617:3.1733036e-02 631:4.2163212e-02 642:3.2645542e-02 655:2.4488568e-02 678:2.6912225e-02 684:3.7743933e-02 702:2.8839920e-02 703:6.7462221e-02 723:9.5831662e-02 773:3.1474717e-02 789:7.4855991e-02 802:3.3258229e-02 831:5.7469640e-02 836:3.9973188e-02 845:5.1243026e-02 871:7.3263653e-02 894:9.4162993e-02 916:7.0301078e-02 963:3.7125837e-02 988:4.5581188e-02 1028:2.5233695e-02 1040:3.4235485e-02 1049:5.5964489e-02 1059:3.8257286e-02 1066:4.9865920e-02 1098:5.8958139e-02 1099:1.0642000e-01 1122:2.7161302e-02 1132:5.5820916e-02 1133:4.6960417e-02 1162:3.9029490e-02 1188:2.8600320e-02 1194:5.0604455e-02 1196:6.5430172e-02 1212:4.2503651e-02 1214:5.0775778e-02 1232:5.6423597e-02 1240:6.1547466e-02 1246:7.7764101e-02 1280:1.3794197e-01 1296:5.1512964e-02 1340:7.8965351e-02 1342:3.7148494e-02 1352:5.1221535e-02 1376:4.2615250e-02 1380:3.7119050e-02 1387:4.7238745e-02 1426:5.5234205e-02 1454:5.2877679e-02 1455:5.0725996e-02 1479:8.5576959e-02 1492:7.4084811e-02 1493:4.8998870e-02 1528:4.4210665e-02 1530:4.8503999e-02 1554:9.3274266e-02 1623:3.6034379e-02 1648:6.2799916e-02 1690:4.8120685e-02 1755:4.5155622e-02 1756:4.8210379e-02 1777:9.0586245e-02 1881:5.0639965e-02 1947:7.9649575e-02 1994:4.8551779e-02 1997:7.6153621e-02 2009:4.0015411e-02 2039:1.3339286e-01 2111:7.2162248e-02 2262:4.8061442e-02 2280:6.4266250e-02 2345:5.6263927e-02 2465:6.3638307e-02 2614:4.7085270e-02 2734:8.9492761e-02 2752:5.2130960e-02 2756:8.0005348e-02 2834:5.1661562e-02 2871:5.2857738e-02 3008:1.1090731e-01 3027:4.8987020e-02 3236:6.1085127e-02 3272:3.7708689e-02 3284:5.8934376e-02 3291:4.9844332e-02 3338:1.4995308e-01 3371:1.9023912e-01 3374:1.3803375e-01 3377:6.8909340e-02 3441:8.7797664e-02 3457:4.5233984e-02 3478:6.2372923e-02 3713:6.7536190e-02 3973:8.4199615e-02 3993:6.1687645e-02 4353:7.1359947e-02 4404:7.1069397e-02 4405:8.3499864e-02 4840:1.3851443e-01 4958:7.3976889e-02 5497:7.9394184e-02 8075:7.4009515e-02 8467:7.9975940e-02 8641:8.5474081e-02 9527:7.6241784e-02 9537:7.7080742e-02 9555:7.8563809e-02 9728:8.4486142e-02 9788:7.1219534e-02 10323:1.6259536e-01 10376:8.6243302e-02 11017:1.3648555e-01 12299:1.9657618e-01 12866:8.6542256e-02 15564:1.4876729e-01 18171:3.0685624e-01 20279:1.9894341e-01 26695:1.1104724e-01 26696:1.1756326e-01 26913:9.9067703e-02 32323:1.3729194e-01 37167:8.7625824e-02 +1 |f 3:4.1986797e-02 5:2.6179912e-02 17:1.9496726e-02 21:2.3149800e-02 23:3.1993225e-02 24:2.5084825e-02 49:3.8114011e-02 50:1.9657761e-02 53:6.0306296e-02 54:3.8306452e-02 69:2.3690635e-02 70:1.7837172e-02 73:1.7982738e-02 91:3.6214627e-02 95:4.0163361e-02 99:3.7190471e-02 102:4.4804830e-02 106:7.8616023e-02 110:4.5365110e-02 129:9.2167243e-02 140:3.4740668e-02 147:2.7038630e-02 153:5.3785227e-02 183:2.7683120e-02 187:6.9057137e-02 207:3.9017066e-02 215:9.9909797e-02 217:6.6182606e-02 223:4.4721238e-02 238:4.6286445e-02 239:2.8943313e-02 253:3.8976792e-02 254:3.7552334e-02 256:6.0604967e-02 257:2.5759345e-02 269:2.3699578e-02 274:7.2829217e-02 275:4.5884371e-02 276:2.5180764e-02 284:3.8045619e-02 288:2.2374937e-02 302:3.0347472e-02 312:3.5239138e-02 313:7.0233375e-02 320:5.0995018e-02 321:3.7686653e-02 323:3.7712883e-02 326:4.4142779e-02 332:4.7283009e-02 334:3.4647163e-02 335:7.0727788e-02 355:5.6434717e-02 367:9.6835844e-02 406:4.9223509e-02 417:2.0013522e-02 428:4.1733503e-02 444:7.5417697e-02 453:3.0276578e-02 468:4.3970123e-02 489:3.9158344e-02 506:2.7590115e-02 510:3.3422902e-02 516:3.6568344e-02 532:7.1615264e-02 533:2.0526432e-02 543:8.1320181e-02 636:1.5416218e-01 638:6.2130053e-02 644:2.8258998e-02 675:1.3132864e-01 676:1.1248107e-01 678:2.6307266e-02 726:3.3511285e-02 757:3.4779027e-02 777:5.9977800e-02 783:5.1708918e-02 811:2.6470702e-02 839:1.0794318e-01 842:2.0697769e-02 871:7.1616754e-02 884:9.9041164e-02 955:4.1538641e-02 965:3.4062155e-02 999:8.6894937e-02 1000:3.2704212e-02 1001:2.5795811e-01 1002:4.3989230e-02 1028:2.4666468e-02 1038:4.1340951e-02 1063:5.6797281e-02 1068:6.5421291e-02 1188:2.7957413e-02 1193:6.7524910e-02 1212:4.1548211e-02 1218:3.0026717e-02 1230:1.1178771e-01 1294:5.3293657e-02 1313:5.6519736e-02 1324:6.8769053e-02 1375:8.7847814e-02 1437:5.3205792e-02 1497:1.0660116e-01 1500:4.9023256e-02 1544:3.6815152e-02 1563:5.5172045e-02 1609:6.3943833e-02 1647:4.1935220e-02 1666:5.9658233e-02 1694:4.9814355e-02 1696:1.0818209e-01 1699:1.8198134e-01 1727:4.0036179e-02 1804:6.2561035e-02 1966:4.3227285e-02 1991:7.3565029e-02 2027:6.9326729e-02 2065:4.3378502e-02 2090:4.0175326e-02 2096:1.0312126e-01 2153:3.4037523e-02 2207:6.6782206e-02 2214:4.5959804e-02 2222:9.9043794e-02 2247:6.5043472e-02 2359:6.1114829e-02 2474:4.3782581e-02 2478:7.8985400e-02 2495:4.6261843e-02 2509:5.3805720e-02 2752:5.0959107e-02 2824:5.6097563e-02 2899:1.0600434e-01 2942:4.9685217e-02 3294:6.2804215e-02 3533:9.1343574e-02 3661:6.3792802e-02 3779:9.2252716e-02 3862:2.1168050e-01 3871:1.1229920e-01 3874:6.9703743e-02 3881:6.6696659e-02 4459:5.6688782e-02 5168:6.9475479e-02 5775:6.0072321e-02 6112:7.5319275e-02 6459:9.4217762e-02 6628:1.5544818e-01 6638:1.3720584e-01 6646:1.4070532e-01 6647:1.5253116e-01 6729:9.3434729e-02 6824:8.0569461e-02 6882:9.6010573e-02 6911:1.0160501e-01 7056:7.4707553e-02 8572:2.5678426e-01 8880:6.9283508e-02 9837:8.1632257e-02 9843:8.1850044e-02 10233:7.8142323e-02 10622:7.4951001e-02 11138:7.1257427e-02 11593:1.7834796e-01 13401:1.0104093e-01 15240:6.9041334e-02 15796:9.0718590e-02 16054:1.5468997e-01 19824:9.2912525e-02 33289:1.1517279e-01 43246:1.0781153e-01 +-1 |f 15:2.8479176e-02 20:2.2463840e-02 23:4.2371877e-02 25:4.6330903e-02 33:5.8889594e-02 39:3.0892659e-02 45:2.5812857e-02 52:3.8314577e-02 55:4.6502270e-02 63:1.0147369e-01 75:7.6976635e-02 125:4.3622561e-02 138:4.3973591e-02 144:4.9315326e-02 161:5.8606591e-02 189:3.6057848e-02 191:4.3600846e-02 193:8.2893573e-02 208:4.7758926e-02 221:1.2687442e-01 230:9.5576935e-02 236:6.3797504e-02 239:3.8332570e-02 252:5.7719201e-02 259:6.3074701e-02 269:3.1387758e-02 271:2.7601205e-02 276:5.6465514e-02 288:2.9633401e-02 301:7.2265789e-02 302:6.8051375e-02 312:4.6670768e-02 319:3.8262572e-02 323:4.9947001e-02 364:3.5657953e-02 422:1.6551772e-01 437:8.1148177e-02 454:3.9061114e-02 460:6.9821924e-02 464:5.8992326e-02 481:4.3425296e-02 506:3.6540389e-02 526:6.5683812e-02 532:5.6018386e-02 547:3.8202513e-02 555:3.4658298e-02 573:7.5375184e-02 575:4.3338194e-02 585:1.6396713e-01 625:4.7290489e-02 655:3.1703644e-02 660:2.8935434e-02 678:5.8991589e-02 688:7.7477448e-02 690:5.0219595e-02 724:3.6186326e-02 773:4.0748123e-02 802:6.0683999e-02 837:1.4202154e-01 927:4.9204905e-02 945:1.0549542e-01 965:4.5111969e-02 1005:5.3301163e-02 1011:4.7167368e-02 1014:4.4800345e-02 1028:3.2668307e-02 1033:4.6181347e-02 1040:1.1565629e-01 1044:7.1853809e-02 1059:2.9252652e-02 1066:6.4557932e-02 1099:1.1080371e-01 1102:5.0469600e-02 1119:6.5886393e-02 1122:3.5163846e-02 1171:5.7316359e-02 1188:3.7026841e-02 1237:5.2241877e-02 1243:1.2012374e-01 1292:7.4377894e-02 1308:5.9361920e-02 1335:5.9102274e-02 1342:4.8093569e-02 1351:8.4335715e-02 1422:4.0325448e-02 1426:8.8632166e-02 1492:5.6647435e-02 1502:7.9012662e-02 1519:1.2547143e-01 1538:5.8987826e-02 1540:5.3479932e-02 1577:6.7274019e-02 1586:8.3305068e-02 1592:9.4966508e-02 1594:6.6864945e-02 1776:5.1703267e-02 1777:6.9264919e-02 1854:1.2355178e-01 1889:8.1340775e-02 1890:5.9499696e-02 1945:7.7040419e-02 1994:1.0642549e-01 2028:5.5188794e-02 2074:5.8859635e-02 2111:9.3423441e-02 2222:1.3117376e-01 2234:1.0513415e-01 2268:9.5954485e-02 2438:6.1372146e-02 2548:6.1178055e-02 2574:1.5212426e-01 2614:6.0958017e-02 2688:1.3912547e-01 2734:1.1586004e-01 2803:7.1856774e-02 3013:6.0782466e-02 3188:5.3082395e-02 3192:7.3183186e-02 3284:7.6298229e-02 3432:8.3512142e-02 3488:9.4829120e-02 3899:7.5703517e-02 3973:1.0900737e-01 4969:9.1419771e-02 5745:7.7066533e-02 5775:7.9559878e-02 5974:2.0083311e-01 6197:1.7981011e-01 8213:1.3504241e-01 8766:1.8207975e-01 9987:1.2012266e-01 10109:1.1744983e-01 11748:1.0037310e-01 15159:1.2140261e-01 17252:1.1719365e-01 19048:2.7880347e-01 19049:1.0691366e-01 23505:1.3456585e-01 40144:1.6520862e-01 +-1 |f 18:4.6056196e-02 50:9.5679328e-02 73:3.3542305e-02 157:1.0024465e-01 188:2.7075356e-02 210:5.2699324e-02 229:4.2207617e-02 233:6.6247672e-02 234:8.7591767e-02 278:1.8365175e-01 334:6.4625636e-02 353:1.2242922e-01 354:6.0411990e-02 369:9.2592202e-02 437:1.7613614e-01 547:9.1096997e-02 757:6.4871587e-02 802:3.5815217e-02 837:7.1646281e-02 910:2.2995535e-01 953:1.3653293e-01 965:6.3534439e-02 1042:3.0493781e-01 1073:7.6491348e-02 1145:1.0228382e-01 1173:6.0381923e-02 1237:1.2457500e-01 1422:9.6159309e-02 1666:4.6631940e-02 1789:1.2541972e-01 1791:1.3059698e-01 1953:1.0095011e-01 2131:9.8541506e-02 2132:1.2545407e-01 2256:1.0201104e-01 2313:1.2552865e-01 2315:1.0146101e-01 2548:8.6161472e-02 2562:1.1722676e-01 3282:1.6630229e-01 3307:1.0607360e-01 3416:1.2877280e-01 3540:9.8631248e-02 4065:1.1832068e-01 4099:9.5148496e-02 4241:1.2052083e-01 5129:2.9954630e-01 6024:3.3074304e-01 6059:1.5186512e-01 6195:1.5052530e-01 6200:1.2042308e-01 6513:1.5240510e-01 6709:1.6889554e-01 10199:2.5266066e-01 11347:1.6097301e-01 +1 |f 20:4.1563634e-02 21:5.6727868e-02 45:4.7760144e-02 48:8.9214578e-02 148:1.2773652e-01 152:1.6138925e-01 156:1.2904224e-01 179:5.7116259e-02 206:2.3923971e-01 284:1.1555570e-01 351:1.3505945e-01 366:1.6035202e-01 371:1.3003521e-01 393:1.6114947e-01 768:8.8684253e-02 778:1.4543906e-01 965:8.3468251e-02 992:9.0976045e-02 1127:5.3214960e-02 1162:9.3490653e-02 1200:1.2512271e-01 1397:1.0597364e-01 1468:1.1527381e-01 1544:9.0214387e-02 1793:2.0222849e-01 2667:2.1735094e-01 2701:1.1985400e-01 3405:1.6657957e-01 3719:4.2434746e-01 10225:1.6199465e-01 22844:2.7331567e-01 26060:2.1241799e-01 28578:4.5230788e-01 +-1 |f 6:5.2982334e-02 9:6.2744521e-02 20:3.3571914e-02 24:2.0806493e-02 35:3.5421308e-02 39:2.7267963e-02 50:2.2980059e-02 52:3.3819053e-02 55:7.1416140e-02 69:1.6356828e-02 73:2.1021945e-02 90:3.2540824e-02 98:3.8667805e-02 108:5.5731669e-02 131:7.4235141e-02 147:3.1608347e-02 161:5.1730167e-02 169:5.5573225e-02 170:3.8090467e-02 175:4.0973283e-02 182:3.2729849e-02 188:2.8730877e-02 189:6.6792771e-02 209:3.5537843e-02 210:3.3028211e-02 229:4.4788398e-02 251:8.6390018e-02 252:9.2660576e-02 254:4.3898940e-02 269:2.7704971e-02 276:2.9436490e-02 293:2.4884880e-02 297:5.6147102e-02 298:3.7819773e-02 309:4.2378280e-02 321:4.4055957e-02 322:5.3446811e-02 334:8.4999621e-02 386:4.4409651e-02 437:4.2304005e-02 475:6.2552527e-02 510:3.9071605e-02 535:3.8711693e-02 547:3.3720136e-02 555:3.0591775e-02 558:9.7108461e-02 585:4.1530110e-02 600:6.9536529e-02 655:2.7983794e-02 673:7.1089022e-02 709:5.4233506e-02 724:3.1940516e-02 769:7.7240728e-02 837:4.4902824e-02 838:5.2558392e-02 841:5.5120695e-02 842:4.0967111e-02 858:6.1720237e-02 871:4.9446672e-02 911:7.5598858e-02 929:4.1785121e-02 945:4.4370960e-02 955:4.8558962e-02 965:1.0390493e-01 1000:6.4731471e-02 1002:5.1423717e-02 1012:3.5827979e-02 1040:6.6239096e-02 1059:2.5820382e-02 1062:8.2865261e-02 1084:5.7188086e-02 1085:5.8276873e-02 1086:4.9740933e-02 1122:3.1038005e-02 1127:2.5386428e-02 1132:3.7674323e-02 1142:6.3518740e-02 1145:3.7861012e-02 1166:6.0473833e-02 1188:3.2682411e-02 1189:4.2796958e-02 1213:2.5966633e-02 1239:5.2572988e-02 1243:8.5543849e-02 1250:6.1925855e-02 1287:1.0774548e-01 1305:4.6384916e-02 1335:5.2167688e-02 1342:4.2450655e-02 1352:5.8532327e-02 1373:8.1857003e-02 1376:4.8697677e-02 1420:1.0637193e-01 1435:1.0252235e-01 1454:6.0424853e-02 1455:4.6766650e-02 1456:5.3716369e-02 1519:6.5410510e-02 1520:5.8369011e-02 1532:7.2321787e-02 1538:1.0926775e-01 1548:1.0717531e-01 1690:9.3104288e-02 1727:4.6802569e-02 1776:4.5636822e-02 1854:6.4409763e-02 1888:5.5238497e-02 2058:8.2990065e-02 2066:5.7305049e-02 2107:4.6076745e-02 2131:6.1758850e-02 2223:8.7590136e-02 2607:6.8193659e-02 2734:6.0399927e-02 2747:6.3771993e-02 2755:6.3948177e-02 2794:5.6540303e-02 2824:6.5578446e-02 3010:6.2620491e-02 3013:9.0838596e-02 3027:1.1747797e-01 3090:7.9230167e-02 3170:7.1368128e-02 3188:4.6854138e-02 3210:7.3022559e-02 3230:5.3828456e-02 3240:6.0575113e-02 3429:1.4870682e-01 3439:1.7212602e-01 3570:6.2487315e-02 3690:9.4784535e-02 3883:1.2023974e-01 3899:1.7436545e-01 4096:6.3664071e-02 4241:1.5851645e-01 4331:7.7730164e-02 4351:7.0299074e-02 4666:8.4313765e-02 5373:7.5695582e-02 5707:7.6307721e-02 5845:8.4146976e-02 5894:8.7468810e-02 5974:2.6482686e-01 6006:1.0460936e-01 6607:7.6045342e-02 8478:8.6456358e-02 8532:8.8204101e-02 8766:9.4921440e-02 9253:8.8008501e-02 9580:1.1787144e-01 9581:3.3045432e-01 10861:1.0920727e-01 13741:9.2334479e-02 14763:1.0013255e-01 17330:1.2771979e-01 17331:1.3072690e-01 18683:9.5006585e-02 18868:9.3350902e-02 18869:1.6941223e-01 19048:9.4308019e-02 19049:9.4369270e-02 +1 |f 42:3.6681876e-01 45:1.7422792e-01 56:1.6445871e-01 69:1.4222474e-01 73:7.6599330e-02 81:1.6111104e-01 82:3.7677154e-01 348:1.2549011e-01 386:1.6181897e-01 394:1.6376621e-01 417:1.4434023e-01 522:2.1393616e-01 526:1.4989202e-01 784:1.8537326e-01 942:2.0576195e-01 1195:1.6902253e-01 1380:1.5455821e-01 1664:1.4203092e-01 1726:1.6600534e-01 1970:2.0831792e-01 2007:3.7718076e-01 2212:1.8370849e-01 2522:2.4476728e-01 diff --git a/wasm/test/model.vw b/wasm/test/model.vw new file mode 100644 index 00000000000..1a29cacf1ff Binary files /dev/null and b/wasm/test/model.vw differ diff --git a/wasm/test/test.js b/wasm/test/test.js new file mode 100644 index 00000000000..66e973eb80a --- /dev/null +++ b/wasm/test/test.js @@ -0,0 +1,128 @@ +const assert = require('assert'); +const mocha = require('mocha'); +const fs = require('fs'); +const path = require('path'); + +const VWModule = require('../out/vw-wasm.js'); + +// Delay test execution until the WASM VWModule is ready +VWModule['onRuntimeInitialized'] = () => { mocha.run(); } + +describe('Call WASM VWModule', () => { + it('invalid positional argument', () => { + assert.throws( + () => new VWModule.VWModel("test")); + }); + it('Pass argument which is invalid', () => { + assert.throws( + () => new VWModule.VWModel("--data data.txt")); + }); + it('Default construct', () => { + let model = new VWModule.VWModel(""); + model.delete(); + }); + + it('Can clone example', () => { + let model = new VWModule.VWModel(""); + let ex = model.parse("|a ab"); + let clone = ex.get(0).clone(model); + model.finishExample(ex); + ex.delete(); + clone.delete(); + model.delete(); + }); + + it('Example toString', () => { + let model = new VWModule.VWModel(""); + let ex = model.parse("|a ab"); + assert.equal(ex.get(0).toString(), "Example"); + ex.delete(); + model.delete(); + }); + + it('Make scalar prediction', () => { + let model = new VWModule.VWModel(""); + assert.equal(model.predictionType, VWModule.PredictionType.scalar); + let example = model.parse("|a ab"); + let prediction = model.predict(example); + assert.equal(typeof prediction, "number"); + model.finishExample(example); + example.delete(); + model.delete(); + }); + + it('Make scalars prediction', () => { + let model = new VWModule.VWModel("--oaa 2 --probabilities"); + assert.equal(model.predictionType, VWModule.PredictionType.scalars); + let example = model.parse("|a ab"); + let prediction = model.predict(example); + assert.equal(typeof prediction, "object"); + assert.equal(prediction.length, 2); + assert.equal(typeof prediction[0], "number"); + model.finishExample(example); + example.delete(); + model.delete(); + }); + + it('Make action_scores prediction', () => { + let model = new VWModule.VWModel("--cb_explore_adf"); + assert.equal(model.predictionType, VWModule.PredictionType.action_probs); + let example = model.parse(` + shared | s_1 s_2 + 0:1.0:0.5 | a_1 b_1 c_1 + | a_2 b_2 c_2 + | a_3 b_3 c_3 + `); + assert.equal(example.size(), 4); + let prediction = model.predict(example); + assert.equal(typeof prediction, "object"); + assert.equal(prediction.length, 3); + assert(prediction[0].hasOwnProperty('action')); + assert(prediction[0].hasOwnProperty('score')); + model.finishExample(example); + example.delete(); + model.delete(); + }); + + it('Make decision_scores prediction', () => { + let model = new VWModule.VWModel("--ccb_explore_adf"); + assert.equal(model.predictionType, VWModule.PredictionType.decision_probs); + let example = model.parse(` + ccb shared |User b + ccb action |Action d + ccb action |Action e + ccb action |Action f + ccb action |Action ff + ccb action |Action fff + ccb slot 0:0:0.2 |Slot h + ccb slot 1:0:0.25 |Slot i + ccb slot 2:0:0.333333 |Slot j + `); + assert.equal(example.size(), 9); + let prediction = model.predict(example); + assert.equal(typeof prediction, "object"); + assert.equal(prediction.length, 3); + assert.equal(prediction[0].length, 5); + assert(prediction[0][0].hasOwnProperty('action')); + assert(prediction[0][0].hasOwnProperty('score')); + model.finishExample(example); + example.delete(); + model.delete(); + }); + + it('Load model', () => { + let modelBuffer = fs.readFileSync(path.join(__dirname, "model.vw")); + let ptr = VWModule._malloc(modelBuffer.byteLength); + let heapBytes = new Uint8Array(VWModule.HEAPU8.buffer, ptr, modelBuffer.byteLength); + heapBytes.set(new Uint8Array(modelBuffer)); + let model = new VWModule.VWModel("",ptr, modelBuffer.byteLength); + VWModule._free(ptr); + assert.equal(model.predictionType, VWModule.PredictionType.scalar); + let example = model.parse("|f 6:6.8953723e-02 10:2.4920074e-02 24:1.9822951e-02 50:1.7663756e-02 73:1.6158640e-02 121:5.0723456e-02 157:4.8291773e-02 178:6.0458232e-02 179:2.0943997e-02 188:1.3043258e-02 193:7.4816257e-02 209:4.6250634e-02 233:1.8848978e-02 236:3.4921709e-02 239:2.6007419e-02 264:3.1431116e-02 265:4.3809656e-02 267:3.5724755e-02 276:2.2626529e-02 293:1.9127907e-02 302:2.7269145e-02 307:4.2694855e-02 312:3.1664621e-02 326:2.3426855e-02 368:3.9957818e-02 433:2.1424608e-02 494:3.0670732e-02 506:2.4791485e-02 550:2.8210135e-02 567:4.9445845e-02 617:2.7873196e-02 625:3.2085080e-02 630:3.6478668e-02 631:3.7034698e-02 670:4.1411690e-02 682:4.8788730e-02 702:2.5331981e-02 724:2.4551263e-02 783:4.6463773e-02 817:3.9437063e-02 837:9.0064272e-02 842:1.8598272e-02 848:7.6375939e-02 850:5.0411887e-02 852:7.4332051e-02 855:7.8169920e-02 884:1.1030679e-01 889:9.8633111e-02 894:3.9411522e-02 905:3.7478998e-02 914:5.6504101e-02 949:4.6126790e-02 950:4.5762073e-02 963:3.2610044e-02 979:4.8457999e-02 1000:2.9386828e-02 1045:3.4139425e-02 1059:3.3603869e-02 1061:4.0301725e-02 1066:7.4160680e-02 1071:2.6853660e-02 1073:8.7932266e-02 1081:7.7701092e-02 1117:9.1598287e-02 1123:9.3790986e-02 1131:4.6108399e-02 1132:4.9031150e-02 1162:3.4282148e-02 1170:3.8612958e-02 1177:5.4951586e-02 1178:4.6940666e-02 1188:2.5121527e-02 1189:3.2896131e-02 1191:9.6172296e-02 1211:4.2716283e-02 1237:3.5444438e-02 1240:3.1929389e-02 1247:6.4616486e-02 1311:7.5592339e-02 1342:3.2629944e-02 1366:5.0296519e-02 1416:3.9530758e-02 1417:3.8943492e-02 1470:4.3616120e-02 1494:6.2730476e-02 1511:6.8593867e-02 1556:3.9865732e-02 1577:4.5643266e-02 1821:8.7437980e-02 1862:5.4120179e-02 1888:4.2459391e-02 1910:4.3156520e-02 1967:9.7915463e-02 1972:4.1025959e-02 2008:5.0531935e-02 2018:3.8799949e-02 2088:4.0381286e-02 2128:3.9645299e-02 2168:5.0549522e-02 2175:7.5826041e-02 2183:1.7829052e-01 2234:3.3989199e-02 2270:8.2511209e-02 2281:5.4877985e-02 2316:4.4665784e-02 2322:4.4940550e-02 2477:4.1533679e-02 2533:4.8195656e-02 2588:9.1189362e-02 2701:4.3949336e-02 2861:7.2961919e-02 2932:5.8073092e-02 2992:5.0242696e-02 3077:8.1162862e-02 3110:1.1454716e-01 3170:5.4857526e-02 3263:6.3250236e-02 3325:4.0466305e-02 3491:1.7403087e-01 3690:7.2856687e-02 3858:1.7263067e-01 3973:7.3958009e-02 4037:7.3799074e-02 4492:1.2360445e-01 5166:5.2890390e-02 5483:5.4483801e-02 5484:7.0126176e-02 6086:1.6554411e-01 6171:1.6998538e-01 6858:5.3109396e-02 7157:7.4319251e-02 7502:6.5168351e-02 7626:6.2204096e-02 7904:1.4150701e-01 7905:5.5091526e-02 7909:1.1336020e-01 8224:1.9635071e-01 8376:7.8653499e-02 8568:8.0502190e-02 8665:9.8245032e-02 8954:1.2403890e-01 9111:1.1121018e-01 9636:6.1581194e-02 11789:8.3692431e-02 12145:8.1212714e-02 15171:8.1602275e-02 16066:8.7211892e-02 17940:8.6479917e-02 19892:9.3631372e-02 28774:1.0198968e-01 29080:1.0360513e-01 37508:3.2965177e-01 38026:2.7250558e-01 38027:2.7823427e-01 39870:9.9310391e-02"); + let prediction = model.predict(example); + assert.equal(typeof prediction, "number"); + model.finishExample(example); + example.delete(); + model.delete(); + }); +}); diff --git a/wasm/test/train_model.sh b/wasm/test/train_model.sh new file mode 100644 index 00000000000..69257c29a2a --- /dev/null +++ b/wasm/test/train_model.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +vw -d ./data.txt -f model.vw diff --git a/wasm/wasm_wrapper.cc b/wasm/wasm_wrapper.cc new file mode 100644 index 00000000000..32fb6b2a706 --- /dev/null +++ b/wasm/wasm_wrapper.cc @@ -0,0 +1,309 @@ +#include "vw/config/options.h" +#include "vw/core/example.h" +#include "vw/core/learner.h" +#include "vw/core/parse_example.h" +#include "vw/core/parse_primitives.h" +#include "vw/core/prediction_type.h" +#include "vw/core/shared_data.h" +#include "vw/core/vw.h" + +#include +#include + +#include +#include +#include +#include +#include + +std::array illegal_options = {"feature_mask", "initial_regressor", "input_feature_regularizer", + "span_server", "unique_id", "total", "node", "span_server_port", "version", "audit", "progress", "limit_output", + "dry_run", "help", "dictionary", "dictionary_path", "holdout_off", "holdout_period", "holdout_after", + "early_terminate", "passes", "initial_pass_length", "named_labels", "final_regressor", "readable_model", + "invert_hash", "save_resume", "output_feature_regularizer_binary", "output_feature_regularizer_text", + "save_per_pass", "predictions", "raw_predictions", "extra_metrics", "audit_regressor", "sendto", "data", "daemon", + "foreground", "port", "num_children", "pid_file", "port_file", "cache", "cache_file", "json", "dsjson", + "kill_cache", "compressed", "no_daemon", "chain_hash", "flatbuffer"}; + +std::string get_exception_message(int exceptionPtr) +{ + return std::string(reinterpret_cast(exceptionPtr)->what()); +} + +void validate_options(const VW::config::options_i& options) +{ + for (const auto& illegal_option : illegal_options) + { + if (options.was_supplied(illegal_option)) { THROW("Illegal option passed: " << illegal_option); } + } + + if (!options.get_positional_tokens().empty()) { THROW("Positional options are not allowed") } +} + +struct vw_model; + +struct example_ptr +{ + static std::shared_ptr wrap_pooled_example(example* ex, const std::shared_ptr& vw_ptr) + { + assert(VW::is_ring_example(*vw_ptr, ex)); + return std::make_shared(ex, vw_ptr); + } + std::shared_ptr clone(const vw_model& vw_ptr) const; + + std::string to_string() const { return "Example"; } + + example* inner_ptr() { return _example; } + const example* inner_ptr() const { return _example; } + + example* release() + { + _released = true; + return _example; + } + + example_ptr(example* ex, const std::shared_ptr& vw_ptr) : _example(ex), weak_vw_ptr(vw_ptr), _released(false) {} + ~example_ptr() + { + if (!_released) + { + if (auto strong_vw_ptr = weak_vw_ptr.lock()) { VW::finish_example(*strong_vw_ptr, *_example); } + } + } + + example_ptr(const example_ptr&) = delete; + example_ptr& operator=(const example_ptr&) = delete; + example_ptr(example_ptr&& other) noexcept = delete; + example_ptr& operator=(example_ptr&& other) noexcept = delete; + +private: + example* _example; + std::weak_ptr weak_vw_ptr; + bool _released; +}; + +emscripten::val to_js_type(const v_array& float_array) +{ + return emscripten::val::array(float_array.begin(), float_array.end()); +} + +emscripten::val to_js_type(const v_array& array) +{ + return emscripten::val::array(array.begin(), array.end()); +} + +emscripten::val to_js_type(const v_array& action_scores_array) +{ + return emscripten::val::array(action_scores_array.begin(), action_scores_array.end()); +} +emscripten::val to_js_type(const VW::decision_scores_t& decision_scores_array) +{ + auto array = emscripten::val::array(); + for (const auto& action_score : decision_scores_array) { array.call("push", to_js_type(action_score)); } + return array; +} + +emscripten::val prediction_to_val(const polyprediction& pred, prediction_type_t pred_type) +{ + switch (pred_type) + { + case VW::prediction_type_t::SCALAR: + return emscripten::val(pred.scalar); + + case VW::prediction_type_t::SCALARS: + return to_js_type(pred.scalars); + + case VW::prediction_type_t::ACTION_SCORES: + return to_js_type(pred.a_s); + + case VW::prediction_type_t::PDF: + THROW("pdf prediction type is not implemented."); + + case VW::prediction_type_t::ACTION_PROBS: + return to_js_type(pred.a_s); + + case VW::prediction_type_t::MULTICLASS: + return emscripten::val(pred.multiclass); + + case VW::prediction_type_t::MULTILABELS: + return to_js_type(pred.multilabels.label_v); + + case VW::prediction_type_t::PROB: + return emscripten::val(pred.scalar); + + case VW::prediction_type_t::MULTICLASS_PROBS: + return to_js_type(pred.scalars); + + case VW::prediction_type_t::DECISION_PROBS: + return to_js_type(pred.decision_scores); + + case VW::prediction_type_t::ACTION_PDF_VALUE: + THROW("action_pdf_value prediction type is not implemented."); + + case VW::prediction_type_t::ACTIVE_MULTICLASS: + THROW("active_multiclass prediction type is not implemented."); + + case VW::prediction_type_t::NOPRED: + THROW("active_multiclass prediction type is not implemented."); + } +} + +struct vw_model +{ + vw_model(const std::string& args) + { + std::string all_args = "--quiet --no_stdin " + args; + vw_ptr.reset(VW::initialize(all_args)); + validate_options(*vw_ptr->options); + } + + vw_model(const std::string& args, size_t _bytes, int size) + { + std::string all_args = "--quiet --no_stdin " + args; + char* bytes = (char*)_bytes; + io_buf io; + io.add_file(VW::io::create_buffer_view(bytes, size)); + vw_ptr.reset(VW::initialize(all_args, &io)); + validate_options(*vw_ptr->options); + } + + std::vector> parse(const std::string& ex_str) + { + std::vector> example_collection; + VW::string_view trimmed_ex_str = VW::trim_whitespace(VW::string_view(ex_str)); + std::vector examples; + + vw_ptr->parser_runtime.example_parser->text_reader(vw_ptr.get(), trimmed_ex_str, examples); + + example_collection.reserve(example_collection.size() + examples.size()); + for (auto* ex : examples) + { + VW::setup_example(*vw_ptr, ex); + example_collection.push_back(example_ptr::wrap_pooled_example(ex, vw_ptr)); + } + + return example_collection; + } + + emscripten::val predict(std::vector>& example_list) + { + assert(!example_list.empty()); + if (example_list.size() == 1) + { + auto* ex = example_list[0]->inner_ptr(); + auto prev_test_only_value = ex->test_only; + vw_ptr->predict(*ex); + ex->test_only = prev_test_only_value; + return prediction_to_val(ex->pred, vw_ptr->l->get_output_prediction_type()); + } + else + { + std::vector raw_examples; + std::vector prev_test_only_value; + raw_examples.reserve(example_list.size()); + for (auto& ex : example_list) + { + raw_examples.push_back(ex->inner_ptr()); + prev_test_only_value.push_back(ex->inner_ptr()->test_only); + } + vw_ptr->predict(raw_examples); + for (int i = 0; i < example_list.size(); i++) { raw_examples[i]->test_only = prev_test_only_value[i]; } + return prediction_to_val(raw_examples[0]->pred, vw_ptr->l->get_output_prediction_type()); + } + } + + void learn(std::vector>& example_list) + { + assert(!example_list.empty()); + if (example_list.size() == 1) + { + auto* ex = example_list[0]->inner_ptr(); + vw_ptr->learn(*ex); + } + else + { + std::vector raw_examples; + raw_examples.reserve(example_list.size()); + for (auto& ex : example_list) { raw_examples.push_back(ex->inner_ptr()); } + vw_ptr->learn(raw_examples); + } + } + + void finish_example(std::vector>& example_list) + { + assert(!example_list.empty()); + if (example_list.size() == 1) + { + auto* ex = example_list[0]->inner_ptr(); + vw_ptr->finish_example(*ex); + } + else + { + std::vector raw_examples; + raw_examples.reserve(example_list.size()); + for (auto& ex : example_list) { raw_examples.push_back(ex->inner_ptr()); } + vw_ptr->finish_example(raw_examples); + } + + for (auto& ex_ptr : example_list) { ex_ptr->release(); } + } + + float sum_loss() const { return vw_ptr->sd->sum_loss; } + float weighted_labeled_examples() const { return vw_ptr->sd->weighted_labeled_examples; } + + prediction_type_t get_prediction_type() const { return vw_ptr->l->get_output_prediction_type(); } + + std::shared_ptr vw_ptr; +}; + +std::shared_ptr example_ptr::clone(const vw_model& vw_ptr) const +{ + auto* new_ex = &VW::get_unused_example(vw_ptr.vw_ptr.get()); + VW::copy_example_data_with_label(new_ex, _example); + return wrap_pooled_example(new_ex, vw_ptr.vw_ptr); +} + +EMSCRIPTEN_BINDINGS(vwwasm) +{ + emscripten::function("getExceptionMessage", &get_exception_message); + + emscripten::value_object("ActionScore") + .field("action", &ACTION_SCORE::action_score::action) + .field("score", &ACTION_SCORE::action_score::score); + + emscripten::class_("Example") + .function("clone", &example_ptr::clone) + .function("toString", &example_ptr::to_string) + .smart_ptr>("example_ptr"); + + emscripten::enum_("PredictionType") + .value("scalar", prediction_type_t::scalar) + .value("scalars", prediction_type_t::scalars) + .value("action_scores", prediction_type_t::action_scores) + .value("pdf", prediction_type_t::pdf) + .value("action_probs", prediction_type_t::action_probs) + .value("multiclass", prediction_type_t::multiclass) + .value("multilabels", prediction_type_t::multilabels) + .value("prob", prediction_type_t::prob) + .value("multiclassprobs", prediction_type_t::multiclassprobs) + .value("decision_probs", prediction_type_t::decision_probs) + .value("actionPDFValue", prediction_type_t::action_pdf_value) + .value("activeMulticlass", prediction_type_t::active_multiclass); + + // Currently this is structured such that parse returns a vector of example but to JS that is opaque. + // All the caller can do is pass this opaque object to the other functions. Is it possible to convert this to a JS + // array but it involves copying the contents of the array whenever going to/from js/c++ For now it is opaque as the + // protoyype doesnt support operations on the example itself. + emscripten::class_("VWModel") + .constructor() + .constructor() + .function("parse", &vw_model::parse) + .function("predict", &vw_model::predict) + .function("learn", &vw_model::learn) + .function("finishExample", &vw_model::finish_example) + .property("sumLoss", &vw_model::sum_loss) + .property("weightedLabeledExamples", &vw_model::weighted_labeled_examples) + .property("predictionType", &vw_model::get_prediction_type); + + emscripten::register_vector>("ExamplePtrVector"); +};