Rune works by letting users create a machine learning pipeline declaratively and have everything compiled into a single WebAssembly module that can be copied around or used offline.
An integral part of declaring these ML pipelines is the ability to use custom operations ("processing block") to pre-process data before it gets passed to a model.
Currently, we build a Rune by generating a Rust crate that imports each
processing block as a normal Rust dependendency then compiling everything to
wasm32-unknown-unknown.
We would like to move to something like this:
- Someone writes their own custom operation (e.g. to rescale an image) and
uploads the compiled
image-rescale.wasmfile to WAPM (e.g. as@hotg-ai/image-rescale v1.2) - Another user writes a Runefile which depends on
@hotg-ai/image-rescale - The user uses
rune build ./Runefile.ymlto build their Rune - The
runetool downloads theimage-rescale.wasmfrom WAPM and generates a Rust crate which uses functionality from it - The
runetool compiles the generated Rust crate to WebAssembly (e.g. asmy-rune.wasm) and runswasmer-link my-rune.wasm image-rescale.wasmto bundle everything into a single "statically linked" WebAssembly module
One of our core goals is to make it easy to deploy a ML pipeline without caring about which platform it will eventually run on, so having a single file that can be copied around is important for us.
Some other things to know about Rune:
- There is no guarantee that users will have internet access
- Our SAAS offering includes a security system where your app can download a Rune at runtime which has (among other things) been encrypted for just the current user
- We fall back to a WASM3 interpreter for devices that Wasmer either doesn't support or where we can't load WebAssembly dynamically (i.e. iOS)
This repository contains 3 Rust crates,
dependencyis a Rust crate that will be compiled to adependency.wasmfile and exports the functions defined independency.witguestis a Rust crate that will be compiled to WebAssembly. It imports functions fromdependency.wasmand exports the functions declared inguest.withostis a Wasmer program that runs on the host. It manually links the modules together at runtime by loadingdependency.wasmand using its exports to satisfy thedependencyimports forguest.wasm.
(assume dependency is published to WAPM and downloaded via wapm install)
An important part of what makes this process nice to work with is
[wit-bindgen][wit-binden]. Similar to a *.proto file in Protocol Buffers,
wit-bindgen lets us declare the functionality each component will provide and
generate glue code to use it.
You can install Wasmer's fork of wit-bindgen using the following command:
cargo install --force --git https://github.com/wasmerio/wit-bindgen wit-bindgen-cli --branch wasmerNormally we would use the procedural macros provided by the wit-bindgen
project to generate glue code for importing and exporting functions, but we'll
write the code to disk to let you see what is generated.
The make build command to write the glue code to disk and compile everything
to WebAssembly.
$ wit-bindgen rust-wasm --out-dir dependency/src --import host.wit --export dependency.wit
Generating "dependency/src/bindings.rs"
$ cargo build --manifest-path dependency/Cargo.toml --target wasm32-unknown-unknown
Compiling dependency v0.1.0 (~/Documents/hotg-ai/static-linking-example/dependency)
Finished dev [unoptimized + debuginfo] target(s) in 0.17s
$ cp target/wasm32-unknown-unknown/debug/dependency.wasm .
$ wit-bindgen rust-wasm --out-dir guest/src \
--import dependency.wit \
--import host.wit \
--export guest.wit
Generating "guest/src/bindings.rs"
$ cargo build --manifest-path guest/Cargo.toml --target wasm32-unknown-unknown
Compiling guest v0.1.0 (~/Documents/hotg-ai/static-linking-example/guest)
Finished dev [unoptimized + debuginfo] target(s) in 0.08s
$ cp target/wasm32-unknown-unknown/debug/guest.wasm .
$ wit-bindgen wasmer --out-dir host/src \
--import guest.wit \
--export host.wit
Generating "host/src/bindings.rs"You can run the host executable to see an example where I've worked around the
lack of static linking by manually wiring up the ImportObjects for
guest.wasm and dependency.wasm.
$ cargo run guest.wasm dependency.wasm
Finished dev [unoptimized + debuginfo] target(s) in 0.04s
Running `target/debug/host guest.wasm dependency.wasm`
Loading the dependency
Loading the guest
Calling loaded()
[*] Hello, Hello!Outstanding questions:
- Is Wasmer able to "statically linking" multiple WebAssembly modules into a
single
*.wasmfile, using the exports from one module to satisfy the imports from the other? - Are there easier ways to achieve the same result?
- How feasible is it for Wasmer to have a fallback engine which interprets WebAssembly instead of doing JIT or AOT compiling?