From 942344ca022982b3f7b470450c5a4dadbfd546a1 Mon Sep 17 00:00:00 2001 From: Angel M De Miguel Date: Mon, 21 Aug 2023 13:38:36 +0200 Subject: [PATCH 1/6] feat: add WASI-NN bindings to Wasm Workers Server --- Cargo.lock | 89 +- Cargo.toml | 8 +- crates/worker/Cargo.toml | 1 + crates/worker/src/config.rs | 4 + crates/worker/src/features/mod.rs | 1 + crates/worker/src/features/wasi_nn.rs | 13 + crates/worker/src/lib.rs | 28 + examples/Makefile | 7 +- examples/rust-wasi-nn/.gitignore | 3 + examples/rust-wasi-nn/Cargo.lock | 544 +++++++++++ examples/rust-wasi-nn/Cargo.toml | 15 + examples/rust-wasi-nn/README.md | 53 ++ examples/rust-wasi-nn/_images/.gitkeep | 0 examples/rust-wasi-nn/_models/dataset.json | 1004 ++++++++++++++++++++ examples/rust-wasi-nn/index.js | 68 ++ examples/rust-wasi-nn/inference.toml | 17 + examples/rust-wasi-nn/prepare.sh | 11 + examples/rust-wasi-nn/public/main.css | 84 ++ examples/rust-wasi-nn/public/main.js | 48 + examples/rust-wasi-nn/src/main.rs | 115 +++ 20 files changed, 2107 insertions(+), 6 deletions(-) create mode 100644 crates/worker/src/features/wasi_nn.rs create mode 100644 examples/rust-wasi-nn/.gitignore create mode 100644 examples/rust-wasi-nn/Cargo.lock create mode 100644 examples/rust-wasi-nn/Cargo.toml create mode 100644 examples/rust-wasi-nn/README.md create mode 100644 examples/rust-wasi-nn/_images/.gitkeep create mode 100644 examples/rust-wasi-nn/_models/dataset.json create mode 100644 examples/rust-wasi-nn/index.js create mode 100644 examples/rust-wasi-nn/inference.toml create mode 100755 examples/rust-wasi-nn/prepare.sh create mode 100644 examples/rust-wasi-nn/public/main.css create mode 100644 examples/rust-wasi-nn/public/main.js create mode 100644 examples/rust-wasi-nn/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 699fa89a..11a053de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1054,6 +1054,19 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime 1.3.0", + "log", + "regex", + "termcolor", +] + [[package]] name = "env_logger" version = "0.9.3" @@ -1061,7 +1074,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a12e6657c4c97ebab115a42dcee77225f7f482cdd841cf7088c657a42e9e00e7" dependencies = [ "atty", - "humantime", + "humantime 2.1.0", "log", "regex", "termcolor", @@ -1073,7 +1086,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ - "humantime", + "humantime 2.1.0", "is-terminal", "log", "regex", @@ -1430,6 +1443,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + [[package]] name = "humantime" version = "2.1.0" @@ -1959,6 +1981,39 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "openvino" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbc731d9a7805dd533b69de3ee33062d5ea1dfa9fca1c19f8fd165b62e2cdde7" +dependencies = [ + "openvino-finder", + "openvino-sys", + "thiserror", +] + +[[package]] +name = "openvino-finder" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8bbd80eea06c2b9ec3dce85900ff3ae596c01105b759b38a005af69bbeb4d07" +dependencies = [ + "cfg-if", + "log", +] + +[[package]] +name = "openvino-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "318ed662bdf05a3f86486408159e806d53363171621a8000b81366fab5158713" +dependencies = [ + "libloading", + "once_cell", + "openvino-finder", + "pretty_env_logger", +] + [[package]] name = "os_str_bytes" version = "6.5.1" @@ -2045,6 +2100,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger 0.7.1", + "log", +] + [[package]] name = "prettytable-rs" version = "0.10.0" @@ -2112,6 +2177,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quickjs-wasm-rs" version = "1.0.0" @@ -3469,6 +3540,19 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "wasmtime-wasi-nn" +version = "10.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a74375e57624b24d790b8d1427bdde6c4ef9a76e4148b5b73998353dc973c24e" +dependencies = [ + "anyhow", + "openvino", + "thiserror", + "walkdir", + "wiggle", +] + [[package]] name = "wasmtime-winch" version = "10.0.1" @@ -4101,6 +4185,7 @@ dependencies = [ "wasi-common", "wasmtime", "wasmtime-wasi", + "wasmtime-wasi-nn", "wit-bindgen-wasmtime", "wws-config", "wws-data-kv", diff --git a/Cargo.toml b/Cargo.toml index 98bdc0ab..fbc71466 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ repository = { workspace = true } [workspace.package] version = "1.4.0" edition = "2021" -authors = [ "Wasm Labs " ] +authors = ["Wasm Labs "] license = "Apache-2.0" repository = "https://github.com/vmware-labs/wasm-workers-server/" @@ -60,7 +60,7 @@ members = [ "crates/worker", "kits/rust", "kits/rust/worker", - "kits/javascript" + "kits/javascript", ] # Exclude examples exclude = [ @@ -68,7 +68,8 @@ exclude = [ "examples/rust-basic", "examples/rust-fetch", "examples/rust-kv", - "examples/rust-params" + "examples/rust-params", + "examples/rust-wasi-nn", ] [workspace.dependencies] @@ -93,6 +94,7 @@ wws-api-manage = { path = "./crates/api-manage" } wws-api-manage-openapi = { path = "./crates/api-manage-openapi" } wasmtime = "10.0.1" wasmtime-wasi = "10.0.1" +wasmtime-wasi-nn = "10.0.1" wasi-common = "10.0.1" path-slash = "0.2.1" openssl = { version = "=0.10.55" } diff --git a/crates/worker/Cargo.toml b/crates/worker/Cargo.toml index 5f7495eb..6110b6bc 100644 --- a/crates/worker/Cargo.toml +++ b/crates/worker/Cargo.toml @@ -19,6 +19,7 @@ tokio = { workspace = true } toml = { workspace = true } wasmtime = { workspace = true } wasmtime-wasi = { workspace = true } +wasmtime-wasi-nn = { workspace = true } wasi-common = { workspace = true } wws-config = { workspace = true } wws-data-kv = { workspace = true } diff --git a/crates/worker/src/config.rs b/crates/worker/src/config.rs index f2a6763b..befa3c9c 100644 --- a/crates/worker/src/config.rs +++ b/crates/worker/src/config.rs @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache-2.0 use crate::features::http_requests::HttpRequestsConfig; +use crate::features::wasi_nn::WasiNnConfig; use crate::features::{data::ConfigData, folders::Folder}; use anyhow::{anyhow, Result}; use serde::{Deserialize, Deserializer}; @@ -13,9 +14,12 @@ use wws_data_kv::KVConfigData; /// List all available features for a worker #[derive(Deserialize, Clone, Default)] +#[serde(default)] pub struct Features { /// Allow to perform http requests from a worker pub http_requests: HttpRequestsConfig, + /// Enables WASI-NN bindings for Machine Learning inference + pub wasi_nn: WasiNnConfig, } /// Workers configuration. These files are optional when no configuration change is required. diff --git a/crates/worker/src/features/mod.rs b/crates/worker/src/features/mod.rs index b510ab48..93bc3d51 100644 --- a/crates/worker/src/features/mod.rs +++ b/crates/worker/src/features/mod.rs @@ -4,3 +4,4 @@ pub mod data; pub mod folders; pub mod http_requests; +pub mod wasi_nn; diff --git a/crates/worker/src/features/wasi_nn.rs b/crates/worker/src/features/wasi_nn.rs new file mode 100644 index 00000000..0d25cd7e --- /dev/null +++ b/crates/worker/src/features/wasi_nn.rs @@ -0,0 +1,13 @@ +// Copyright 2023 VMware, Inc. +// SPDX-License-Identifier: Apache-2.0 + +use serde::Deserialize; + +pub const WASI_NN_BACKEND_OPENVINO: &str = "openvino"; + +#[derive(Deserialize, Clone, Default)] +#[serde(default)] +pub struct WasiNnConfig { + /// List of Machine Learning backends. For now, only "openvino" option is supported + pub allowed_backends: Vec, +} diff --git a/crates/worker/src/lib.rs b/crates/worker/src/lib.rs index 0d897129..1c724800 100644 --- a/crates/worker/src/lib.rs +++ b/crates/worker/src/lib.rs @@ -11,15 +11,18 @@ use actix_web::HttpRequest; use anyhow::{anyhow, Result}; use bindings::http::{add_to_linker as http_add_to_linker, HttpBindings}; use config::Config; +use features::wasi_nn::WASI_NN_BACKEND_OPENVINO; use io::{WasmInput, WasmOutput}; use sha256::digest as sha256_digest; use std::fs::{self, File}; use std::path::PathBuf; +use std::sync::Arc; use std::{collections::HashMap, path::Path}; use stdio::Stdio; use wasi_common::WasiCtx; use wasmtime::{Engine, Linker, Module, Store}; use wasmtime_wasi::{ambient_authority, Dir, WasiCtxBuilder}; +use wasmtime_wasi_nn::WasiNnCtx; use wws_config::Config as ProjectConfig; use wws_runtimes::{init_runtime, Runtime}; @@ -43,6 +46,7 @@ pub struct Worker { struct WorkerState { pub wasi: WasiCtx, + pub wasi_nn: Option>, pub http: HttpBindings, } @@ -134,12 +138,36 @@ impl Worker { } } + // WASI-NN + let wasi_nn; + let allowed_backends = &self.config.features.wasi_nn.allowed_backends; + + if !allowed_backends.is_empty() { + // For now, we only support OpenVINO + if allowed_backends.len() != 1 + || !allowed_backends.contains(&WASI_NN_BACKEND_OPENVINO.to_string()) + { + eprintln!("❌ The only WASI-NN supported backend name is \"{WASI_NN_BACKEND_OPENVINO}\". Please, update your config."); + wasi_nn = None; + } else { + wasmtime_wasi_nn::add_to_linker(&mut linker, |s: &mut WorkerState| { + Arc::get_mut(s.wasi_nn.as_mut().unwrap()) + .expect("wasi-nn is not implemented with multi-threading support") + })?; + + wasi_nn = Some(Arc::new(WasiNnCtx::new()?)); + } + } else { + wasi_nn = None; + } + // Pass to the runtime to add any WASI specific requirement wasi_builder = self.runtime.prepare_wasi_ctx(wasi_builder)?; let wasi = wasi_builder.build(); let state = WorkerState { wasi, + wasi_nn, http: HttpBindings { http_config: self.config.features.http_requests.clone(), }, diff --git a/examples/Makefile b/examples/Makefile index 0f040c55..ad7dfbbd 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -20,9 +20,14 @@ rust-params: cargo build --target wasm32-wasi --release && \ mv target/wasm32-wasi/release/rust-params.wasm "./[id].wasm" +rust-wasi-nn: + cd rust-wasi-nn && \ + cargo build --target wasm32-wasi --release && \ + mv target/wasm32-wasi/release/rust-wasi-nn.wasm "./inference.wasm" + rust-pdf-create: cd rust-pdf-create && \ cargo build --target wasm32-wasi --release && \ mv target/wasm32-wasi/release/rust-pdf-create.wasm ./index.wasm -all: rust-basic rust-fetch rust-kv rust-params +all: rust-basic rust-fetch rust-kv rust-params rust-wasi-nn diff --git a/examples/rust-wasi-nn/.gitignore b/examples/rust-wasi-nn/.gitignore new file mode 100644 index 00000000..0cffe78e --- /dev/null +++ b/examples/rust-wasi-nn/.gitignore @@ -0,0 +1,3 @@ +_images/image.jpg +_models/*.bin +_models/*.xml diff --git a/examples/rust-wasi-nn/Cargo.lock b/examples/rust-wasi-nn/Cargo.lock new file mode 100644 index 00000000..e8d416d5 --- /dev/null +++ b/examples/rust-wasi-nn/Cargo.lock @@ -0,0 +1,544 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "anyhow" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" + +[[package]] +name = "async-trait" +version = "0.1.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6dde6e4ed435a4c1ee4e73592f5ba9da2151af10076cc04858746af9352d09" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "flate2" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "http" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "image" +version = "0.24.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-rational", + "num-traits", + "png", + "tiff", +] + +[[package]] +name = "image2tensor" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b172871a8ff226b37c2905a457866e2d950037c325699575d4bea5b1c7f32fdb" +dependencies = [ + "image", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" +dependencies = [ + "autocfg", +] + +[[package]] +name = "png" +version = "0.17.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" +dependencies = [ + "bitflags", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "proc-macro2" +version = "1.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pulldown-cmark" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "quote" +version = "1.0.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f3b39ccfb720540debaa0164757101c08ecb8d326b15358ce76a62c7e85965" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rust-wasi-nn" +version = "0.1.0" +dependencies = [ + "anyhow", + "base64", + "image2tensor", + "serde", + "serde_json", + "wasi-nn", + "wasm-workers-rs", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "serde" +version = "1.0.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32ac8da02677876d532745a130fc9d8e6edfa81a269b107c5b00829b91d8eb3c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.183" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aafe972d60b0b9bee71a91b92fee2d4fb3c9d7e8f6b179aa99f27203d99a4816" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "serde_json" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "076066c5f1078eac5b722a31827a8832fe108bed65dfa75e233c89f8206e976c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04361975b3f5e348b2189d8dc55bc942f278b2d482a6a0365de5bdd62d351567" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.28", +] + +[[package]] +name = "tiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-ident" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301abaae475aa91687eb82514b328ab47a211a533026cb25fc3e519b86adfc3c" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi-nn" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4541d3bf5ef0833f41834b1df9900fb79551178418c86679eab2285ebe817d" +dependencies = [ + "thiserror", +] + +[[package]] +name = "wasm-workers-rs" +version = "1.4.0" +dependencies = [ + "anyhow", + "base64", + "http", + "serde", + "serde_json", + "wit-bindgen-rust", + "worker", +] + +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + +[[package]] +name = "wit-bindgen-gen-core" +version = "0.2.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=cb871cfa1ee460b51eb1d144b175b9aab9c50aba#cb871cfa1ee460b51eb1d144b175b9aab9c50aba" +dependencies = [ + "anyhow", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-gen-rust" +version = "0.2.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=cb871cfa1ee460b51eb1d144b175b9aab9c50aba#cb871cfa1ee460b51eb1d144b175b9aab9c50aba" +dependencies = [ + "heck", + "wit-bindgen-gen-core", +] + +[[package]] +name = "wit-bindgen-gen-rust-wasm" +version = "0.2.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=cb871cfa1ee460b51eb1d144b175b9aab9c50aba#cb871cfa1ee460b51eb1d144b175b9aab9c50aba" +dependencies = [ + "heck", + "wit-bindgen-gen-core", + "wit-bindgen-gen-rust", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.2.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=cb871cfa1ee460b51eb1d144b175b9aab9c50aba#cb871cfa1ee460b51eb1d144b175b9aab9c50aba" +dependencies = [ + "async-trait", + "bitflags", + "wit-bindgen-rust-impl", +] + +[[package]] +name = "wit-bindgen-rust-impl" +version = "0.2.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=cb871cfa1ee460b51eb1d144b175b9aab9c50aba#cb871cfa1ee460b51eb1d144b175b9aab9c50aba" +dependencies = [ + "proc-macro2", + "syn 1.0.109", + "wit-bindgen-gen-core", + "wit-bindgen-gen-rust-wasm", +] + +[[package]] +name = "wit-parser" +version = "0.2.0" +source = "git+https://github.com/bytecodealliance/wit-bindgen?rev=cb871cfa1ee460b51eb1d144b175b9aab9c50aba#cb871cfa1ee460b51eb1d144b175b9aab9c50aba" +dependencies = [ + "anyhow", + "id-arena", + "pulldown-cmark", + "unicode-normalization", + "unicode-xid", +] + +[[package]] +name = "worker" +version = "1.4.0" +dependencies = [ + "anyhow", + "http", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "wasi", +] diff --git a/examples/rust-wasi-nn/Cargo.toml b/examples/rust-wasi-nn/Cargo.toml new file mode 100644 index 00000000..f2327bf8 --- /dev/null +++ b/examples/rust-wasi-nn/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "rust-wasi-nn" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +wasi-nn = "0.4.0" +anyhow = "1.0.63" +image2tensor = "0.2.0" +wasm-workers-rs = { path = "../../kits/rust" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0.85" +base64 = "0.21.2" diff --git a/examples/rust-wasi-nn/README.md b/examples/rust-wasi-nn/README.md new file mode 100644 index 00000000..16ac0419 --- /dev/null +++ b/examples/rust-wasi-nn/README.md @@ -0,0 +1,53 @@ +# Rust WASI-NN example + +Compile a Rust that performs Machine Learning (ML) inference with [WASI-NN](https://github.com/WebAssembly/wasi-nn) to WebAssembly and run it in Wasm Workers Server. + +## Prerequisites + +* Wasm Workers Server (wws): + + ```shell-session + curl -fsSL https://workers.wasmlabs.dev/install | bash + ``` + +* [Install Rust with rustup](https://www.rust-lang.org/tools/install) +* Install the `wasm32-wasi` target: + + ```shell-session + rustup target add wasm32-wasi + ``` + +* Install the [OpenVINO™ Runtime (2023.0.1)](https://docs.openvino.ai/2023.0) + * [Windows](https://docs.openvino.ai/2023.0/openvino_docs_install_guides_installing_openvino_from_archive_windows.html) + * [Linux](https://docs.openvino.ai/2023.0/openvino_docs_install_guides_installing_openvino_from_archive_linux.html) + * [MacOS](https://docs.openvino.ai/2023.0/openvino_docs_install_guides_installing_openvino_from_archive_macos.html) +* Configure the OpenVINO™ environment: + * [Windows](https://docs.openvino.ai/2023.0/openvino_docs_install_guides_installing_openvino_from_archive_windows.html#step-2-configure-the-environment) + * [Linux](https://docs.openvino.ai/2023.0/openvino_docs_install_guides_installing_openvino_from_archive_linux.html#step-2-configure-the-environment) + * [MacOS](https://docs.openvino.ai/2023.0/openvino_docs_install_guides_installing_openvino_from_archive_macos.html#step-2-configure-the-environment) +* Run the `prepare.sh` script to download the ML model: + + ```shell-session + $ ./prepare.sh + + Downloading the model from https://github.com/intel/openvino-rs/tree/main/crates/openvino/tests/fixtures/mobilenet + .... + Finished! + ``` + +## Build + +```shell-session +cargo build --target wasm32-wasi --release && \ + cp target/wasm32-wasi/release/rust-wasi-nn.wasm ./inference.wasm +``` + +## Run + +```shell-session +wws . +``` + +## Resources + +* [Rust documentation](https://workers.wasmlabs.dev/docs/languages/rust) diff --git a/examples/rust-wasi-nn/_images/.gitkeep b/examples/rust-wasi-nn/_images/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/examples/rust-wasi-nn/_models/dataset.json b/examples/rust-wasi-nn/_models/dataset.json new file mode 100644 index 00000000..240ef510 --- /dev/null +++ b/examples/rust-wasi-nn/_models/dataset.json @@ -0,0 +1,1004 @@ +{ + "names": [ + "tench, Tinca tinca", + "goldfish, Carassius auratus", + "great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias", + "tiger shark, Galeocerdo cuvieri", + "hammerhead, hammerhead shark", + "electric ray, crampfish, numbfish, torpedo", + "stingray", + "cock", + "hen", + "ostrich, Struthio camelus", + "brambling, Fringilla montifringilla", + "goldfinch, Carduelis carduelis", + "house finch, linnet, Carpodacus mexicanus", + "junco, snowbird", + "indigo bunting, indigo finch, indigo bird, Passerina cyanea", + "robin, American robin, Turdus migratorius", + "bulbul", + "jay", + "magpie", + "chickadee", + "water ouzel, dipper", + "kite", + "bald eagle, American eagle, Haliaeetus leucocephalus", + "vulture", + "great grey owl, great gray owl, Strix nebulosa", + "European fire salamander, Salamandra salamandra", + "common newt, Triturus vulgaris", + "eft", + "spotted salamander, Ambystoma maculatum", + "axolotl, mud puppy, Ambystoma mexicanum", + "bullfrog, Rana catesbeiana", + "tree frog, tree-frog", + "tailed frog, bell toad, ribbed toad, tailed toad, Ascaphus trui", + "loggerhead, loggerhead turtle, Caretta caretta", + "leatherback turtle, leatherback, leathery turtle, Dermochelys coriacea", + "mud turtle", + "terrapin", + "box turtle, box tortoise", + "banded gecko", + "common iguana, iguana, Iguana iguana", + "American chameleon, anole, Anolis carolinensis", + "whiptail, whiptail lizard", + "agama", + "frilled lizard, Chlamydosaurus kingi", + "alligator lizard", + "Gila monster, Heloderma suspectum", + "green lizard, Lacerta viridis", + "African chameleon, Chamaeleo chamaeleon", + "Komodo dragon, Komodo lizard, dragon lizard, giant lizard, Varanus komodoensis", + "African crocodile, Nile crocodile, Crocodylus niloticus", + "American alligator, Alligator mississipiensis", + "triceratops", + "thunder snake, worm snake, Carphophis amoenus", + "ringneck snake, ring-necked snake, ring snake", + "hognose snake, puff adder, sand viper", + "green snake, grass snake", + "king snake, kingsnake", + "garter snake, grass snake", + "water snake", + "vine snake", + "night snake, Hypsiglena torquata", + "boa constrictor, Constrictor constrictor", + "rock python, rock snake, Python sebae", + "Indian cobra, Naja naja", + "green mamba", + "sea snake", + "horned viper, cerastes, sand viper, horned asp, Cerastes cornutus", + "diamondback, diamondback rattlesnake, Crotalus adamanteus", + "sidewinder, horned rattlesnake, Crotalus cerastes", + "trilobite", + "harvestman, daddy longlegs, Phalangium opilio", + "scorpion", + "black and gold garden spider, Argiope aurantia", + "barn spider, Araneus cavaticus", + "garden spider, Aranea diademata", + "black widow, Latrodectus mactans", + "tarantula", + "wolf spider, hunting spider", + "tick", + "centipede", + "black grouse", + "ptarmigan", + "ruffed grouse, partridge, Bonasa umbellus", + "prairie chicken, prairie grouse, prairie fowl", + "peacock", + "quail", + "partridge", + "African grey, African gray, Psittacus erithacus", + "macaw", + "sulphur-crested cockatoo, Kakatoe galerita, Cacatua galerita", + "lorikeet", + "coucal", + "bee eater", + "hornbill", + "hummingbird", + "jacamar", + "toucan", + "drake", + "red-breasted merganser, Mergus serrator", + "goose", + "black swan, Cygnus atratus", + "tusker", + "echidna, spiny anteater, anteater", + "platypus, duckbill, duckbilled platypus, duck-billed platypus, Ornithorhynchus anatinus", + "wallaby, brush kangaroo", + "koala, koala bear, kangaroo bear, native bear, Phascolarctos cinereus", + "wombat", + "jellyfish", + "sea anemone, anemone", + "brain coral", + "flatworm, platyhelminth", + "nematode, nematode worm, roundworm", + "conch", + "snail", + "slug", + "sea slug, nudibranch", + "chiton, coat-of-mail shell, sea cradle, polyplacophore", + "chambered nautilus, pearly nautilus, nautilus", + "Dungeness crab, Cancer magister", + "rock crab, Cancer irroratus", + "fiddler crab", + "king crab, Alaska crab, Alaskan king crab, Alaska king crab, Paralithodes camtschatica", + "American lobster, Northern lobster, Maine lobster, Homarus americanus", + "spiny lobster, langouste, rock lobster, crawfish, crayfish, sea crawfish", + "crayfish, crawfish, crawdad, crawdaddy", + "hermit crab", + "isopod", + "white stork, Ciconia ciconia", + "black stork, Ciconia nigra", + "spoonbill", + "flamingo", + "little blue heron, Egretta caerulea", + "American egret, great white heron, Egretta albus", + "bittern", + "crane", + "limpkin, Aramus pictus", + "European gallinule, Porphyrio porphyrio", + "American coot, marsh hen, mud hen, water hen, Fulica americana", + "bustard", + "ruddy turnstone, Arenaria interpres", + "red-backed sandpiper, dunlin, Erolia alpina", + "redshank, Tringa totanus", + "dowitcher", + "oystercatcher, oyster catcher", + "pelican", + "king penguin, Aptenodytes patagonica", + "albatross, mollymawk", + "grey whale, gray whale, devilfish, Eschrichtius gibbosus, Eschrichtius robustus", + "killer whale, killer, orca, grampus, sea wolf, Orcinus orca", + "dugong, Dugong dugon", + "sea lion", + "Chihuahua", + "Japanese spaniel", + "Maltese dog, Maltese terrier, Maltese", + "Pekinese, Pekingese, Peke", + "Shih-Tzu", + "Blenheim spaniel", + "papillon", + "toy terrier", + "Rhodesian ridgeback", + "Afghan hound, Afghan", + "basset, basset hound", + "beagle", + "bloodhound, sleuthhound", + "bluetick", + "black-and-tan coonhound", + "Walker hound, Walker foxhound", + "English foxhound", + "redbone", + "borzoi, Russian wolfhound", + "Irish wolfhound", + "Italian greyhound", + "whippet", + "Ibizan hound, Ibizan Podenco", + "Norwegian elkhound, elkhound", + "otterhound, otter hound", + "Saluki, gazelle hound", + "Scottish deerhound, deerhound", + "Weimaraner", + "Staffordshire bullterrier, Staffordshire bull terrier", + "American Staffordshire terrier, Staffordshire terrier, American pit bull terrier, pit bull terrier", + "Bedlington terrier", + "Border terrier", + "Kerry blue terrier", + "Irish terrier", + "Norfolk terrier", + "Norwich terrier", + "Yorkshire terrier", + "wire-haired fox terrier", + "Lakeland terrier", + "Sealyham terrier, Sealyham", + "Airedale, Airedale terrier", + "cairn, cairn terrier", + "Australian terrier", + "Dandie Dinmont, Dandie Dinmont terrier", + "Boston bull, Boston terrier", + "miniature schnauzer", + "giant schnauzer", + "standard schnauzer", + "Scotch terrier, Scottish terrier, Scottie", + "Tibetan terrier, chrysanthemum dog", + "silky terrier, Sydney silky", + "soft-coated wheaten terrier", + "West Highland white terrier", + "Lhasa, Lhasa apso", + "flat-coated retriever", + "curly-coated retriever", + "golden retriever", + "Labrador retriever", + "Chesapeake Bay retriever", + "German short-haired pointer", + "vizsla, Hungarian pointer", + "English setter", + "Irish setter, red setter", + "Gordon setter", + "Brittany spaniel", + "clumber, clumber spaniel", + "English springer, English springer spaniel", + "Welsh springer spaniel", + "cocker spaniel, English cocker spaniel, cocker", + "Sussex spaniel", + "Irish water spaniel", + "kuvasz", + "schipperke", + "groenendael", + "malinois", + "briard", + "kelpie", + "komondor", + "Old English sheepdog, bobtail", + "Shetland sheepdog, Shetland sheep dog, Shetland", + "collie", + "Border collie", + "Bouvier des Flandres, Bouviers des Flandres", + "Rottweiler", + "German shepherd, German shepherd dog, German police dog, alsatian", + "Doberman, Doberman pinscher", + "miniature pinscher", + "Greater Swiss Mountain dog", + "Bernese mountain dog", + "Appenzeller", + "EntleBucher", + "boxer", + "bull mastiff", + "Tibetan mastiff", + "French bulldog", + "Great Dane", + "Saint Bernard, St Bernard", + "Eskimo dog, husky", + "malamute, malemute, Alaskan malamute", + "Siberian husky", + "dalmatian, coach dog, carriage dog", + "affenpinscher, monkey pinscher, monkey dog", + "basenji", + "pug, pug-dog", + "Leonberg", + "Newfoundland, Newfoundland dog", + "Great Pyrenees", + "Samoyed, Samoyede", + "Pomeranian", + "chow, chow chow", + "keeshond", + "Brabancon griffon", + "Pembroke, Pembroke Welsh corgi", + "Cardigan, Cardigan Welsh corgi", + "toy poodle", + "miniature poodle", + "standard poodle", + "Mexican hairless", + "timber wolf, grey wolf, gray wolf, Canis lupus", + "white wolf, Arctic wolf, Canis lupus tundrarum", + "red wolf, maned wolf, Canis rufus, Canis niger", + "coyote, prairie wolf, brush wolf, Canis latrans", + "dingo, warrigal, warragal, Canis dingo", + "dhole, Cuon alpinus", + "African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus", + "hyena, hyaena", + "red fox, Vulpes vulpes", + "kit fox, Vulpes macrotis", + "Arctic fox, white fox, Alopex lagopus", + "grey fox, gray fox, Urocyon cinereoargenteus", + "tabby, tabby cat", + "tiger cat", + "Persian cat", + "Siamese cat, Siamese", + "Egyptian cat", + "cougar, puma, catamount, mountain lion, painter, panther, Felis concolor", + "lynx, catamount", + "leopard, Panthera pardus", + "snow leopard, ounce, Panthera uncia", + "jaguar, panther, Panthera onca, Felis onca", + "lion, king of beasts, Panthera leo", + "tiger, Panthera tigris", + "cheetah, chetah, Acinonyx jubatus", + "brown bear, bruin, Ursus arctos", + "American black bear, black bear, Ursus americanus, Euarctos americanus", + "ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus", + "sloth bear, Melursus ursinus, Ursus ursinus", + "mongoose", + "meerkat, mierkat", + "tiger beetle", + "ladybug, ladybeetle, lady beetle, ladybird, ladybird beetle", + "ground beetle, carabid beetle", + "long-horned beetle, longicorn, longicorn beetle", + "leaf beetle, chrysomelid", + "dung beetle", + "rhinoceros beetle", + "weevil", + "fly", + "bee", + "ant, emmet, pismire", + "grasshopper, hopper", + "cricket", + "walking stick, walkingstick, stick insect", + "cockroach, roach", + "mantis, mantid", + "cicada, cicala", + "leafhopper", + "lacewing, lacewing fly", + "dragonfly, darning needle, devil's darning needle, sewing needle, snake feeder, snake doctor, mosquito hawk, skeeter hawk", + "damselfly", + "admiral", + "ringlet, ringlet butterfly", + "monarch, monarch butterfly, milkweed butterfly, Danaus plexippus", + "cabbage butterfly", + "sulphur butterfly, sulfur butterfly", + "lycaenid, lycaenid butterfly", + "starfish, sea star", + "sea urchin", + "sea cucumber, holothurian", + "wood rabbit, cottontail, cottontail rabbit", + "hare", + "Angora, Angora rabbit", + "hamster", + "porcupine, hedgehog", + "fox squirrel, eastern fox squirrel, Sciurus niger", + "marmot", + "beaver", + "guinea pig, Cavia cobaya", + "sorrel", + "zebra", + "hog, pig, grunter, squealer, Sus scrofa", + "wild boar, boar, Sus scrofa", + "warthog", + "hippopotamus, hippo, river horse, Hippopotamus amphibius", + "ox", + "water buffalo, water ox, Asiatic buffalo, Bubalus bubalis", + "bison", + "ram, tup", + "bighorn, bighorn sheep, cimarron, Rocky Mountain bighorn, Rocky Mountain sheep, Ovis canadensis", + "ibex, Capra ibex", + "hartebeest", + "impala, Aepyceros melampus", + "gazelle", + "Arabian camel, dromedary, Camelus dromedarius", + "llama", + "weasel", + "mink", + "polecat, fitch, foulmart, foumart, Mustela putorius", + "black-footed ferret, ferret, Mustela nigripes", + "otter", + "skunk, polecat, wood pussy", + "badger", + "armadillo", + "three-toed sloth, ai, Bradypus tridactylus", + "orangutan, orang, orangutang, Pongo pygmaeus", + "gorilla, Gorilla gorilla", + "chimpanzee, chimp, Pan troglodytes", + "gibbon, Hylobates lar", + "siamang, Hylobates syndactylus, Symphalangus syndactylus", + "guenon, guenon monkey", + "patas, hussar monkey, Erythrocebus patas", + "baboon", + "macaque", + "langur", + "colobus, colobus monkey", + "proboscis monkey, Nasalis larvatus", + "marmoset", + "capuchin, ringtail, Cebus capucinus", + "howler monkey, howler", + "titi, titi monkey", + "spider monkey, Ateles geoffroyi", + "squirrel monkey, Saimiri sciureus", + "Madagascar cat, ring-tailed lemur, Lemur catta", + "indri, indris, Indri indri, Indri brevicaudatus", + "Indian elephant, Elephas maximus", + "African elephant, Loxodonta africana", + "lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens", + "giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca", + "barracouta, snoek", + "eel", + "coho, cohoe, coho salmon, blue jack, silver salmon, Oncorhynchus kisutch", + "rock beauty, Holocanthus tricolor", + "anemone fish", + "sturgeon", + "gar, garfish, garpike, billfish, Lepisosteus osseus", + "lionfish", + "puffer, pufferfish, blowfish, globefish", + "abacus", + "abaya", + "academic gown, academic robe, judge's robe", + "accordion, piano accordion, squeeze box", + "acoustic guitar", + "aircraft carrier, carrier, flattop, attack aircraft carrier", + "airliner", + "airship, dirigible", + "altar", + "ambulance", + "amphibian, amphibious vehicle", + "analog clock", + "apiary, bee house", + "apron", + "ashcan, trash can, garbage can, wastebin, ash bin, ash-bin, ashbin, dustbin, trash barrel, trash bin", + "assault rifle, assault gun", + "backpack, back pack, knapsack, packsack, rucksack, haversack", + "bakery, bakeshop, bakehouse", + "balance beam, beam", + "balloon", + "ballpoint, ballpoint pen, ballpen, Biro", + "Band Aid", + "banjo", + "bannister, banister, balustrade, balusters, handrail", + "barbell", + "barber chair", + "barbershop", + "barn", + "barometer", + "barrel, cask", + "barrow, garden cart, lawn cart, wheelbarrow", + "baseball", + "basketball", + "bassinet", + "bassoon", + "bathing cap, swimming cap", + "bath towel", + "bathtub, bathing tub, bath, tub", + "beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon", + "beacon, lighthouse, beacon light, pharos", + "beaker", + "bearskin, busby, shako", + "beer bottle", + "beer glass", + "bell cote, bell cot", + "bib", + "bicycle-built-for-two, tandem bicycle, tandem", + "bikini, two-piece", + "binder, ring-binder", + "binoculars, field glasses, opera glasses", + "birdhouse", + "boathouse", + "bobsled, bobsleigh, bob", + "bolo tie, bolo, bola tie, bola", + "bonnet, poke bonnet", + "bookcase", + "bookshop, bookstore, bookstall", + "bottlecap", + "bow", + "bow tie, bow-tie, bowtie", + "brass, memorial tablet, plaque", + "brassiere, bra, bandeau", + "breakwater, groin, groyne, mole, bulwark, seawall, jetty", + "breastplate, aegis, egis", + "broom", + "bucket, pail", + "buckle", + "bulletproof vest", + "bullet train, bullet", + "butcher shop, meat market", + "cab, hack, taxi, taxicab", + "caldron, cauldron", + "candle, taper, wax light", + "cannon", + "canoe", + "can opener, tin opener", + "cardigan", + "car mirror", + "carousel, carrousel, merry-go-round, roundabout, whirligig", + "carpenter's kit, tool kit", + "carton", + "car wheel", + "cash machine, cash dispenser, automated teller machine, automatic teller machine, automated teller, automatic teller, ATM", + "cassette", + "cassette player", + "castle", + "catamaran", + "CD player", + "cello, violoncello", + "cellular telephone, cellular phone, cellphone, cell, mobile phone", + "chain", + "chainlink fence", + "chain mail, ring mail, mail, chain armor, chain armour, ring armor, ring armour", + "chain saw, chainsaw", + "chest", + "chiffonier, commode", + "chime, bell, gong", + "china cabinet, china closet", + "Christmas stocking", + "church, church building", + "cinema, movie theater, movie theatre, movie house, picture palace", + "cleaver, meat cleaver, chopper", + "cliff dwelling", + "cloak", + "clog, geta, patten, sabot", + "cocktail shaker", + "coffee mug", + "coffeepot", + "coil, spiral, volute, whorl, helix", + "combination lock", + "computer keyboard, keypad", + "confectionery, confectionary, candy store", + "container ship, containership, container vessel", + "convertible", + "corkscrew, bottle screw", + "cornet, horn, trumpet, trump", + "cowboy boot", + "cowboy hat, ten-gallon hat", + "cradle", + "crane2", + "crash helmet", + "crate", + "crib, cot", + "Crock Pot", + "croquet ball", + "crutch", + "cuirass", + "dam, dike, dyke", + "desk", + "desktop computer", + "dial telephone, dial phone", + "diaper, nappy, napkin", + "digital clock", + "digital watch", + "dining table, board", + "dishrag, dishcloth", + "dishwasher, dish washer, dishwashing machine", + "disk brake, disc brake", + "dock, dockage, docking facility", + "dogsled, dog sled, dog sleigh", + "dome", + "doormat, welcome mat", + "drilling platform, offshore rig", + "drum, membranophone, tympan", + "drumstick", + "dumbbell", + "Dutch oven", + "electric fan, blower", + "electric guitar", + "electric locomotive", + "entertainment center", + "envelope", + "espresso maker", + "face powder", + "feather boa, boa", + "file, file cabinet, filing cabinet", + "fireboat", + "fire engine, fire truck", + "fire screen, fireguard", + "flagpole, flagstaff", + "flute, transverse flute", + "folding chair", + "football helmet", + "forklift", + "fountain", + "fountain pen", + "four-poster", + "freight car", + "French horn, horn", + "frying pan, frypan, skillet", + "fur coat", + "garbage truck, dustcart", + "gasmask, respirator, gas helmet", + "gas pump, gasoline pump, petrol pump, island dispenser", + "goblet", + "go-kart", + "golf ball", + "golfcart, golf cart", + "gondola", + "gong, tam-tam", + "gown", + "grand piano, grand", + "greenhouse, nursery, glasshouse", + "grille, radiator grille", + "grocery store, grocery, food market, market", + "guillotine", + "hair slide", + "hair spray", + "half track", + "hammer", + "hamper", + "hand blower, blow dryer, blow drier, hair dryer, hair drier", + "hand-held computer, hand-held microcomputer", + "handkerchief, hankie, hanky, hankey", + "hard disc, hard disk, fixed disk", + "harmonica, mouth organ, harp, mouth harp", + "harp", + "harvester, reaper", + "hatchet", + "holster", + "home theater, home theatre", + "honeycomb", + "hook, claw", + "hoopskirt, crinoline", + "horizontal bar, high bar", + "horse cart, horse-cart", + "hourglass", + "iPod", + "iron, smoothing iron", + "jack-o'-lantern", + "jean, blue jean, denim", + "jeep, landrover", + "jersey, T-shirt, tee shirt", + "jigsaw puzzle", + "jinrikisha, ricksha, rickshaw", + "joystick", + "kimono", + "knee pad", + "knot", + "lab coat, laboratory coat", + "ladle", + "lampshade, lamp shade", + "laptop, laptop computer", + "lawn mower, mower", + "lens cap, lens cover", + "letter opener, paper knife, paperknife", + "library", + "lifeboat", + "lighter, light, igniter, ignitor", + "limousine, limo", + "liner, ocean liner", + "lipstick, lip rouge", + "Loafer", + "lotion", + "loudspeaker, speaker, speaker unit, loudspeaker system, speaker system", + "loupe, jeweler's loupe", + "lumbermill, sawmill", + "magnetic compass", + "mailbag, postbag", + "mailbox, letter box", + "maillot", + "maillot, tank suit", + "manhole cover", + "maraca", + "marimba, xylophone", + "mask", + "matchstick", + "maypole", + "maze, labyrinth", + "measuring cup", + "medicine chest, medicine cabinet", + "megalith, megalithic structure", + "microphone, mike", + "microwave, microwave oven", + "military uniform", + "milk can", + "minibus", + "miniskirt, mini", + "minivan", + "missile", + "mitten", + "mixing bowl", + "mobile home, manufactured home", + "Model T", + "modem", + "monastery", + "monitor", + "moped", + "mortar", + "mortarboard", + "mosque", + "mosquito net", + "motor scooter, scooter", + "mountain bike, all-terrain bike, off-roader", + "mountain tent", + "mouse, computer mouse", + "mousetrap", + "moving van", + "muzzle", + "nail", + "neck brace", + "necklace", + "nipple", + "notebook, notebook computer", + "obelisk", + "oboe, hautboy, hautbois", + "ocarina, sweet potato", + "odometer, hodometer, mileometer, milometer", + "oil filter", + "organ, pipe organ", + "oscilloscope, scope, cathode-ray oscilloscope, CRO", + "overskirt", + "oxcart", + "oxygen mask", + "packet", + "paddle, boat paddle", + "paddlewheel, paddle wheel", + "padlock", + "paintbrush", + "pajama, pyjama, pj's, jammies", + "palace", + "panpipe, pandean pipe, syrinx", + "paper towel", + "parachute, chute", + "parallel bars, bars", + "park bench", + "parking meter", + "passenger car, coach, carriage", + "patio, terrace", + "pay-phone, pay-station", + "pedestal, plinth, footstall", + "pencil box, pencil case", + "pencil sharpener", + "perfume, essence", + "Petri dish", + "photocopier", + "pick, plectrum, plectron", + "pickelhaube", + "picket fence, paling", + "pickup, pickup truck", + "pier", + "piggy bank, penny bank", + "pill bottle", + "pillow", + "ping-pong ball", + "pinwheel", + "pirate, pirate ship", + "pitcher, ewer", + "plane, carpenter's plane, woodworking plane", + "planetarium", + "plastic bag", + "plate rack", + "plow, plough", + "plunger, plumber's helper", + "Polaroid camera, Polaroid Land camera", + "pole", + "police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria", + "poncho", + "pool table, billiard table, snooker table", + "pop bottle, soda bottle", + "pot, flowerpot", + "potter's wheel", + "power drill", + "prayer rug, prayer mat", + "printer", + "prison, prison house", + "projectile, missile", + "projector", + "puck, hockey puck", + "punching bag, punch bag, punching ball, punchball", + "purse", + "quill, quill pen", + "quilt, comforter, comfort, puff", + "racer, race car, racing car", + "racket, racquet", + "radiator", + "radio, wireless", + "radio telescope, radio reflector", + "rain barrel", + "recreational vehicle, RV, R.V.", + "reel", + "reflex camera", + "refrigerator, icebox", + "remote control, remote", + "restaurant, eating house, eating place, eatery", + "revolver, six-gun, six-shooter", + "rifle", + "rocking chair, rocker", + "rotisserie", + "rubber eraser, rubber, pencil eraser", + "rugby ball", + "rule, ruler", + "running shoe", + "safe", + "safety pin", + "saltshaker, salt shaker", + "sandal", + "sarong", + "sax, saxophone", + "scabbard", + "scale, weighing machine", + "school bus", + "schooner", + "scoreboard", + "screen, CRT screen", + "screw", + "screwdriver", + "seat belt, seatbelt", + "sewing machine", + "shield, buckler", + "shoe shop, shoe-shop, shoe store", + "shoji", + "shopping basket", + "shopping cart", + "shovel", + "shower cap", + "shower curtain", + "ski", + "ski mask", + "sleeping bag", + "slide rule, slipstick", + "sliding door", + "slot, one-armed bandit", + "snorkel", + "snowmobile", + "snowplow, snowplough", + "soap dispenser", + "soccer ball", + "sock", + "solar dish, solar collector, solar furnace", + "sombrero", + "soup bowl", + "space bar", + "space heater", + "space shuttle", + "spatula", + "speedboat", + "spider web, spider's web", + "spindle", + "sports car, sport car", + "spotlight, spot", + "stage", + "steam locomotive", + "steel arch bridge", + "steel drum", + "stethoscope", + "stole", + "stone wall", + "stopwatch, stop watch", + "stove", + "strainer", + "streetcar, tram, tramcar, trolley, trolley car", + "stretcher", + "studio couch, day bed", + "stupa, tope", + "submarine, pigboat, sub, U-boat", + "suit, suit of clothes", + "sundial", + "sunglass", + "sunglasses, dark glasses, shades", + "sunscreen, sunblock, sun blocker", + "suspension bridge", + "swab, swob, mop", + "sweatshirt", + "swimming trunks, bathing trunks", + "swing", + "switch, electric switch, electrical switch", + "syringe", + "table lamp", + "tank, army tank, armored combat vehicle, armoured combat vehicle", + "tape player", + "teapot", + "teddy, teddy bear", + "television, television system", + "tennis ball", + "thatch, thatched roof", + "theater curtain, theatre curtain", + "thimble", + "thresher, thrasher, threshing machine", + "throne", + "tile roof", + "toaster", + "tobacco shop, tobacconist shop, tobacconist", + "toilet seat", + "torch", + "totem pole", + "tow truck, tow car, wrecker", + "toyshop", + "tractor", + "trailer truck, tractor trailer, trucking rig, rig, articulated lorry, semi", + "tray", + "trench coat", + "tricycle, trike, velocipede", + "trimaran", + "tripod", + "triumphal arch", + "trolleybus, trolley coach, trackless trolley", + "trombone", + "tub, vat", + "turnstile", + "typewriter keyboard", + "umbrella", + "unicycle, monocycle", + "upright, upright piano", + "vacuum, vacuum cleaner", + "vase", + "vault", + "velvet", + "vending machine", + "vestment", + "viaduct", + "violin, fiddle", + "volleyball", + "waffle iron", + "wall clock", + "wallet, billfold, notecase, pocketbook", + "wardrobe, closet, press", + "warplane, military plane", + "washbasin, handbasin, washbowl, lavabo, wash-hand basin", + "washer, automatic washer, washing machine", + "water bottle", + "water jug", + "water tower", + "whiskey jug", + "whistle", + "wig", + "window screen", + "window shade", + "Windsor tie", + "wine bottle", + "wing", + "wok", + "wooden spoon", + "wool, woolen, woollen", + "worm fence, snake fence, snake-rail fence, Virginia fence", + "wreck", + "yawl", + "yurt", + "web site, website, internet site, site", + "comic book", + "crossword puzzle, crossword", + "street sign", + "traffic light, traffic signal, stoplight", + "book jacket, dust cover, dust jacket, dust wrapper", + "menu", + "plate", + "guacamole", + "consomme", + "hot pot, hotpot", + "trifle", + "ice cream, icecream", + "ice lolly, lolly, lollipop, popsicle", + "French loaf", + "bagel, beigel", + "pretzel", + "cheeseburger", + "hotdog, hot dog, red hot", + "mashed potato", + "head cabbage", + "broccoli", + "cauliflower", + "zucchini, courgette", + "spaghetti squash", + "acorn squash", + "butternut squash", + "cucumber, cuke", + "artichoke, globe artichoke", + "bell pepper", + "cardoon", + "mushroom", + "Granny Smith", + "strawberry", + "orange", + "lemon", + "fig", + "pineapple, ananas", + "banana", + "jackfruit, jak, jack", + "custard apple", + "pomegranate", + "hay", + "carbonara", + "chocolate sauce, chocolate syrup", + "dough", + "meat loaf, meatloaf", + "pizza, pizza pie", + "potpie", + "burrito", + "red wine", + "espresso", + "cup", + "eggnog", + "alp", + "bubble", + "cliff, drop, drop-off", + "coral reef", + "geyser", + "lakeside, lakeshore", + "promontory, headland, head, foreland", + "sandbar, sand bar", + "seashore, coast, seacoast, sea-coast", + "valley, vale", + "volcano", + "ballplayer, baseball player", + "groom, bridegroom", + "scuba diver", + "rapeseed", + "daisy", + "yellow lady's slipper, yellow lady-slipper, Cypripedium calceolus, Cypripedium parviflorum", + "corn", + "acorn", + "hip, rose hip, rosehip", + "buckeye, horse chestnut, conker", + "coral fungus", + "agaric", + "gyromitra", + "stinkhorn, carrion fungus", + "earthstar", + "hen-of-the-woods, hen of the woods, Polyporus frondosus, Grifola frondosa", + "bolete", + "ear, spike, capitulum", + "toilet tissue, toilet paper, bathroom tissue" + ] +} diff --git a/examples/rust-wasi-nn/index.js b/examples/rust-wasi-nn/index.js new file mode 100644 index 00000000..e4e1d5c3 --- /dev/null +++ b/examples/rust-wasi-nn/index.js @@ -0,0 +1,68 @@ +/** + * Builds a reply to the given request + */ +const reply = (request) => { + if (request.method != "GET") { + // Don't allow other methods. + // Here you can see how to return a custom status + return new Response("Method not allowed", { + status: 405 + }); + } + + // Body response + const body = ` + + Wasm Workers Server + + + + + + +
+

Hello from Wasm Workers Server 👋

+

Upload an image to detect the content!

+

+
+ +
+
+
+
+
-
+
+
+
+
-
+
+
+
+
-
+
+
+
+
-
+
+
+
+
-
+
+
+
+ +`; + + // Build a new response + let response = new Response(body); + + // Add a new header + response.headers.set("x-generated-by", "wasm-workers-server"); + + return response; +} + +// Subscribe to the Fetch event +addEventListener("fetch", event => { + return event.respondWith(reply(event.request)); +}); diff --git a/examples/rust-wasi-nn/inference.toml b/examples/rust-wasi-nn/inference.toml new file mode 100644 index 00000000..a3e2765c --- /dev/null +++ b/examples/rust-wasi-nn/inference.toml @@ -0,0 +1,17 @@ +name = "wasi-nn-mobilenet" +version = "1" + +[vars] +MODEL = "model" + +[features] +[features.wasi_nn] +allowed_backends = ["openvino"] + +[[folders]] +from = "./_models" +to = "/tmp/model" + +[[folders]] +from = "./_images" +to = "/tmp/images" diff --git a/examples/rust-wasi-nn/prepare.sh b/examples/rust-wasi-nn/prepare.sh new file mode 100755 index 00000000..ba3e65b1 --- /dev/null +++ b/examples/rust-wasi-nn/prepare.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +MODEL_GITHUB=https://github.com/intel/openvino-rs/tree/main/crates/openvino/tests/fixtures/mobilenet +MODEL=https://github.com/intel/openvino-rs/raw/main/crates/openvino/tests/fixtures/mobilenet + +echo "Downloading the model from ${MODEL_GITHUB}" + +wget --no-clobber $MODEL/mobilenet.bin --output-document=_models/model.bin +wget --no-clobber $MODEL/mobilenet.xml --output-document=_models/model.xml + +echo "Finished!" diff --git a/examples/rust-wasi-nn/public/main.css b/examples/rust-wasi-nn/public/main.css new file mode 100644 index 00000000..b77873b9 --- /dev/null +++ b/examples/rust-wasi-nn/public/main.css @@ -0,0 +1,84 @@ +body { + max-width: 1000px; +} + +main { + margin: 5rem 0; +} + +h1, +p { + text-align: center; +} + +h1 { + margin-bottom: 2rem; +} + +pre { + font-size: .9rem; +} + +pre>code { + padding: 2rem; +} + +p { + margin-top: 2rem; +} + +input { + margin: 0 auto; + width: auto; +} + +.image { + width: 500px; + height: 224px; + border-radius: 5px; + background-color: #151f27; + margin: 2rem auto 1rem; + text-align: center; +} + +#image { + display: inline-block; + height: calc(224px - 2rem); + border-radius: 5px; + margin: 1rem; + width: auto; +} + +.results { + margin: 2rem auto; + width: 500px; +} + +.result { + margin-top: 1.25rem; +} + +.result_progress { + --progress: 0%; + position: relative; + height: 5px; + border-radius: 5px; + width: 100%; + background-color: #151f27; +} + +.result_progress::after { + background-color: blueviolet; + content: ""; + border-radius: 5px; + height: 5px; + width: var(--progress); + position: absolute; + top: 0; + left: 0; +} + +.result_label { + margin-top: .5rem; + font-style: italic; +} diff --git a/examples/rust-wasi-nn/public/main.js b/examples/rust-wasi-nn/public/main.js new file mode 100644 index 00000000..1ead93a5 --- /dev/null +++ b/examples/rust-wasi-nn/public/main.js @@ -0,0 +1,48 @@ +document.getElementById('file') + .addEventListener('change', getFile) + +function getFile(event) { + const input = event.target + if ('files' in input && input.files.length > 0) { + runInference(input.files[0]) + } +} + +function runInference(file) { + readFileContent(file).then(content => { + let result = btoa(content); + + // Set the image + document + .getElementById("image") + .src = `data:image/jpeg;base64,${result}`; + + fetch("/inference", { + method: "POST", + body: result + }).then(res => res.json()) + .then(json => setResults(json)); + + }).catch(error => console.log(error)) +} + +function setResults(json) { + for (let i = 0; i < 5; i++) { + let value = json.data[i]; + let res = document.getElementById(`result-${i}`); + let progress = res.querySelector(".result_progress"); + let label = res.querySelector(".result_label"); + + label.textContent = value[0]; + progress.style.setProperty("--progress", `${Math.max(value[1] * 100, 1)}%`); + } +} + +function readFileContent(file) { + const reader = new FileReader() + return new Promise((resolve, reject) => { + reader.onload = event => resolve(event.target.result) + reader.onerror = error => reject(error) + reader.readAsBinaryString(file) + }) +} diff --git a/examples/rust-wasi-nn/src/main.rs b/examples/rust-wasi-nn/src/main.rs new file mode 100644 index 00000000..84155be9 --- /dev/null +++ b/examples/rust-wasi-nn/src/main.rs @@ -0,0 +1,115 @@ +use anyhow::Result; +use base64::{engine::general_purpose, Engine as _}; +use image2tensor::{ColorOrder, TensorType as ImageTensorType}; +use serde::{Deserialize, Serialize}; +use std::env; +use std::fs; +use std::fs::File; +use std::io::BufReader; +use wasi_nn::{ExecutionTarget, GraphBuilder, GraphEncoding, TensorType}; +use wasm_workers_rs::{ + http::{self, Request, Response}, + worker, Content, +}; + +/// Result labels +#[derive(Deserialize)] +pub struct Dataset { + pub names: Vec, +} + +// A wrapper for label and probability +#[derive(Debug, PartialEq, Serialize, Clone)] +pub struct InferenceResult(String, f32); + +pub fn inference(path: &str, labels: &Dataset) -> Result, wasi_nn::Error> { + let model = env::var("MODEL").unwrap(); + + let model_xml = format!("/tmp/model/{model}.xml"); + let model_bin = format!("/tmp/model/{model}.bin"); + + let input_dim = vec![1, 3, 224, 224]; + let mut output_buffer = vec![0f32; 1001]; + + let graph = GraphBuilder::new(GraphEncoding::Openvino, ExecutionTarget::CPU) + .build_from_files([&model_xml, &model_bin]) + .unwrap(); + + eprintln!("Load graph"); + let mut ctx = graph.init_execution_context()?; + eprintln!("Init execution context"); + + let width: u32 = 224; + let height: u32 = 224; + + let bytes; + + if path.contains("bgr") { + bytes = fs::read(path).unwrap(); + } else { + bytes = image2tensor::convert_image_to_bytes( + path, + width, + height, + ImageTensorType::F32, + ColorOrder::BGR, + ) + .unwrap(); + } + + ctx.set_input(0, TensorType::F32, &input_dim, &bytes)?; + + // Do the inference. + ctx.compute()?; + eprintln!("Run graph inference"); + + // Retrieve the output. + ctx.get_output(0, &mut output_buffer)?; + + Ok(sort_results(&output_buffer, labels)) +} + +// Sort the buffer of probabilities. +fn sort_results(buffer: &[f32], labels: &Dataset) -> Vec { + let mut results: Vec = buffer + .iter() + .skip(1) + .enumerate() + .map(|(c, p)| InferenceResult(labels.names.get(c).unwrap().to_string(), *p)) + .collect(); + results.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap()); + results +} + +#[derive(Serialize)] +struct Results { + data: Vec, +} + +#[worker] +fn handler(req: Request) -> Result> { + let body = req.body(); + + let image = general_purpose::STANDARD.decode(&body).unwrap(); + // Save the image for image2tensor + // TODO: Avoid this step by processing the bytes directly + fs::write("/tmp/images/image.jpg", &image).unwrap(); + + let labels_file = File::open("/tmp/model/dataset.json").unwrap(); + let reader = BufReader::new(labels_file); + let labels: Dataset = serde_json::from_reader(reader).unwrap(); + + let result = inference("/tmp/images/image.jpg", &labels).unwrap(); + + // Applied changes here to use the Response method. This requires changes + // on signature and how it returns the data. + let results = Results { + data: result[..10].to_vec().into(), + }; + let response = serde_json::to_string(&results).unwrap(); + + Ok(http::Response::builder() + .status(200) + .header("x-generated-by", "wasm-workers-server") + .body(response.into())?) +} From 16ca8c79ad456c6d3264944f2aff5d8e4dc0ac1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81ngel=20M?= Date: Wed, 23 Aug 2023 12:02:41 +0200 Subject: [PATCH 2/6] fix: remove unnecessary var initialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafael Fernández López --- crates/worker/src/lib.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/worker/src/lib.rs b/crates/worker/src/lib.rs index 1c724800..c932bfa6 100644 --- a/crates/worker/src/lib.rs +++ b/crates/worker/src/lib.rs @@ -139,26 +139,25 @@ impl Worker { } // WASI-NN - let wasi_nn; let allowed_backends = &self.config.features.wasi_nn.allowed_backends; - if !allowed_backends.is_empty() { + let wasi_nn = if !allowed_backends.is_empty() { // For now, we only support OpenVINO if allowed_backends.len() != 1 || !allowed_backends.contains(&WASI_NN_BACKEND_OPENVINO.to_string()) { eprintln!("❌ The only WASI-NN supported backend name is \"{WASI_NN_BACKEND_OPENVINO}\". Please, update your config."); - wasi_nn = None; + None } else { wasmtime_wasi_nn::add_to_linker(&mut linker, |s: &mut WorkerState| { Arc::get_mut(s.wasi_nn.as_mut().unwrap()) .expect("wasi-nn is not implemented with multi-threading support") })?; - wasi_nn = Some(Arc::new(WasiNnCtx::new()?)); + Some(Arc::new(WasiNnCtx::new()?)) } } else { - wasi_nn = None; + None } // Pass to the runtime to add any WASI specific requirement From ccc02abca489701ff53108afb8c6bb7a03dfced1 Mon Sep 17 00:00:00 2001 From: Angel M De Miguel Date: Wed, 23 Aug 2023 14:19:31 +0200 Subject: [PATCH 3/6] fix: typo error when closing a conditional assignment --- crates/worker/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/worker/src/lib.rs b/crates/worker/src/lib.rs index c932bfa6..93f858b8 100644 --- a/crates/worker/src/lib.rs +++ b/crates/worker/src/lib.rs @@ -158,7 +158,7 @@ impl Worker { } } else { None - } + }; // Pass to the runtime to add any WASI specific requirement wasi_builder = self.runtime.prepare_wasi_ctx(wasi_builder)?; From 815e485fbb278ce19939fb503192faafd19299b7 Mon Sep 17 00:00:00 2001 From: Angel M De Miguel Date: Wed, 23 Aug 2023 15:10:39 +0200 Subject: [PATCH 4/6] feat: add new documentation about ML inference and WASI-NN --- docs/docs/features/dynamic-routes.md | 7 +- docs/docs/features/http-requests.md | 7 +- docs/docs/features/machine-learning.md | 72 ++++++++++++++++++ docs/docs/features/mount-folders.md | 10 ++- .../features/multiple-language-runtimes.md | 9 ++- docs/docs/features/static-assets.md | 7 +- docs/static/img/docs/features/wasi-nn.webp | Bin 0 -> 25026 bytes 7 files changed, 106 insertions(+), 6 deletions(-) create mode 100644 docs/docs/features/machine-learning.md create mode 100644 docs/static/img/docs/features/wasi-nn.webp diff --git a/docs/docs/features/dynamic-routes.md b/docs/docs/features/dynamic-routes.md index db457578..41ea9872 100644 --- a/docs/docs/features/dynamic-routes.md +++ b/docs/docs/features/dynamic-routes.md @@ -1,8 +1,13 @@ --- +title: Dynamic Routes sidebar_position: 4 --- -# Dynamic routes +:::info + +[Available since v1.0](https://github.com/vmware-labs/wasm-workers-server/releases/tag/v1.0.0) + +::: Defining static routes may not be enough for some applications. You may need a worker to process URLs that includes identifiers. **To create a worker associated with a dynamic route, include the route parameter in brackets when setting the worker filename**. diff --git a/docs/docs/features/http-requests.md b/docs/docs/features/http-requests.md index 4c4b8895..30695083 100644 --- a/docs/docs/features/http-requests.md +++ b/docs/docs/features/http-requests.md @@ -1,8 +1,13 @@ --- +title: HTTP Requests (fetch) sidebar_position: 3 --- -# HTTP Requests (fetch) +:::info + +[Available since v1.4](https://github.com/vmware-labs/wasm-workers-server/releases/tag/v1.4.0) + +::: Often times, workers require to access data from an external resource like a website or an API. This feature allows workers to perform HTTP requests to external resources. It follows the capability-based model, so workers cannot perform any HTTP request until you configure the allowed hosts and HTTP methods. diff --git a/docs/docs/features/machine-learning.md b/docs/docs/features/machine-learning.md new file mode 100644 index 00000000..5cf3dd64 --- /dev/null +++ b/docs/docs/features/machine-learning.md @@ -0,0 +1,72 @@ +--- +title: Machine Learning inference +--- + +:::caution + +This is a feature preview. It will be available in v1.5.0 + +::: + +Artificial Intelligence (AI) and Machine Learning (ML) are how topics in the community. This feature enables you to expand the capabilities of your workers by running ML models in your models. For example, you can develop an application that uses image classification or text-to-speech. + +To provide this feature, Wasm Workers Server relies on the [WASI-NN standard](https://github.com/WebAssembly/wasi-nn). This standard defines a set of APIs to send and retrieve data, and run the ML inference at the host side. The main benefits of this approach are to reuse the existing ML ecosystem (like Tensorflow and OpenVINO) and use hardware acceleration when it's available (GPUs, TPUs, etc.). + +## Available backends + +A backend or ML engine is an application that parses the ML model, loads the inputs, run them and returns the output. There are multiple backends like PyTorch, [Tensorflow](https://www.tensorflow.org/) (and [Lite version]((https://www.tensorflow.org/lite))), [ONNX](https://onnxruntime.ai/) and [OpenVINO™](https://docs.openvino.ai/). + +Currently, Wasm Workers Server only supports [OpenVINO™](https://docs.openvino.ai/) as ML inference engine or backend. The community is actively working on adding support for more backends, so you may expect new backends in the future. + +## Prerequisites + +### Install OpenVINO + +Install the [OpenVINO™ Runtime (2023.0.1)](https://docs.openvino.ai/2023.0): + + * [Windows](https://docs.openvino.ai/2023.0/openvino_docs_install_guides_installing_openvino_from_archive_windows.html) + * [Linux](https://docs.openvino.ai/2023.0/openvino_docs_install_guides_installing_openvino_from_archive_linux.html) + * [MacOS](https://docs.openvino.ai/2023.0/openvino_docs_install_guides_installing_openvino_from_archive_macos.html) + +Configure the OpenVINO™ environment: + + * [Windows](https://docs.openvino.ai/2023.0/openvino_docs_install_guides_installing_openvino_from_archive_windows.html#step-2-configure-the-environment) + * [Linux](https://docs.openvino.ai/2023.0/openvino_docs_install_guides_installing_openvino_from_archive_linux.html#step-2-configure-the-environment) + * [MacOS](https://docs.openvino.ai/2023.0/openvino_docs_install_guides_installing_openvino_from_archive_macos.html#step-2-configure-the-environment) + +## Run ML inference in a worker + +By default, workers cannot access the WASI-NN bindings. You need to configure it using the worker configuration file. For that, create a TOML file with the same name as the worker (like `index.wasm` and `index.toml`), and configure the WASI-NN feature: + +```toml +name = "wasi-nn" +version = "1" + +[features] +[features.wasi_nn] +allowed_backends = ["openvino"] + +[[folders]] +from = "./_models" +to = "/tmp/model" +``` + +In this specific configuration, we assume you are mounting a `_models` folder that contains your ML models. You need to adapt it to your specific case. + +### Example + +You can find a [full working example in the project repository](https://github.com/vmware-labs/wasm-workers-server/tree/main/examples/rust-wasi-nn). In this example, you have a worker that returns a website to upload an image. When you upload it, a second worker retrieves the image and runs a [MobileNet](https://arxiv.org/abs/1704.04861) ML model to classify the content of the image. + +We recommend to check this example to get started with ML and Wasm Workers Server. + +The sample application showing an image with a dog. The model predicts the image contains a 'Labrador retriever' with high confidence + +## Language compatibility + +| Language | Mount folders | +| --- | --- | +| JavaScript | ❌ | +| Rust | ✅ | +| Go | ❌ | +| Ruby | ❌ | +| Python | ❌ | diff --git a/docs/docs/features/mount-folders.md b/docs/docs/features/mount-folders.md index ba34c466..10b169be 100644 --- a/docs/docs/features/mount-folders.md +++ b/docs/docs/features/mount-folders.md @@ -1,4 +1,12 @@ -# Mount folders +--- +title: Mount folders +--- + +:::info + +[Available since v1.1](https://github.com/vmware-labs/wasm-workers-server/releases/tag/v1.1.0) + +::: Wasm Workers Server allows you to mount folders in the workers' execution context so they can access the files inside. This configuration is done through the `TOML` file associated to a worker (a `TOML` file with the same filename as the worker). **This means every worker has its own set of mount folders**. diff --git a/docs/docs/features/multiple-language-runtimes.md b/docs/docs/features/multiple-language-runtimes.md index 708ecd70..7af897cd 100644 --- a/docs/docs/features/multiple-language-runtimes.md +++ b/docs/docs/features/multiple-language-runtimes.md @@ -1,8 +1,13 @@ --- +title: Multiple language runtimes sidebar_position: 2 --- -# Multiple language runtimes +:::info + +[Available since v1.0](https://github.com/vmware-labs/wasm-workers-server/releases/tag/v1.0.0) + +::: Wasm Workers Server allows you to extend the supported languages by adding new language runtimes. In other words, you can run workers based on languages like Python or Ruby. @@ -163,4 +168,4 @@ wws runtimes list --repo-name=my-repo --repo-url=https://example.com/index.toml wws runtimes install ruby 3.2.0 --repo-name=my-repo --repo-url=https://example.com/index.toml ``` -After installing a language runtime, the repository information is also stored in the `.wws.toml` file. Developers that install the required language runtimes for an existing project will download them always from the right repository. \ No newline at end of file +After installing a language runtime, the repository information is also stored in the `.wws.toml` file. Developers that install the required language runtimes for an existing project will download them always from the right repository. diff --git a/docs/docs/features/static-assets.md b/docs/docs/features/static-assets.md index 5efd98f5..b7f83dfa 100644 --- a/docs/docs/features/static-assets.md +++ b/docs/docs/features/static-assets.md @@ -1,8 +1,13 @@ --- +title: Static assets sidebar_position: 5 --- -# Static assets +:::info + +[Available since v0.6](https://github.com/vmware-labs/wasm-workers-server/releases/tag/v0.6.0) + +::: Wasm Workers Server allows you to serve any static asset required by your workers. For that, place any static asset in a `public` folder. It must be present in the root of the directory you're serving with `wws`. diff --git a/docs/static/img/docs/features/wasi-nn.webp b/docs/static/img/docs/features/wasi-nn.webp new file mode 100644 index 0000000000000000000000000000000000000000..c20853f0d824d5d0134be373147fbf701e220dbc GIT binary patch literal 25026 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�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 Date: Wed, 23 Aug 2023 16:33:08 +0200 Subject: [PATCH 5/6] docs: improve docs and remove typos MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Rafael Fernández López --- docs/docs/features/machine-learning.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/docs/features/machine-learning.md b/docs/docs/features/machine-learning.md index 5cf3dd64..ac49ac45 100644 --- a/docs/docs/features/machine-learning.md +++ b/docs/docs/features/machine-learning.md @@ -8,13 +8,13 @@ This is a feature preview. It will be available in v1.5.0 ::: -Artificial Intelligence (AI) and Machine Learning (ML) are how topics in the community. This feature enables you to expand the capabilities of your workers by running ML models in your models. For example, you can develop an application that uses image classification or text-to-speech. +Artificial Intelligence (AI) and Machine Learning (ML) are hot topics in the community. This feature enables you to expand the capabilities of your workers by running ML models in your models. For example, you can develop an application that uses image classification or text-to-speech. -To provide this feature, Wasm Workers Server relies on the [WASI-NN standard](https://github.com/WebAssembly/wasi-nn). This standard defines a set of APIs to send and retrieve data, and run the ML inference at the host side. The main benefits of this approach are to reuse the existing ML ecosystem (like Tensorflow and OpenVINO) and use hardware acceleration when it's available (GPUs, TPUs, etc.). +To provide this feature, Wasm Workers Server relies on the [WASI-NN proposal](https://github.com/WebAssembly/wasi-nn). This proposal defines a set of APIs to send and retrieve data, and run the ML inference at the host side. The main benefits of this approach are to reuse the existing ML ecosystem (like Tensorflow and OpenVINO) and use hardware acceleration when it's available (GPUs, TPUs, etc.). ## Available backends -A backend or ML engine is an application that parses the ML model, loads the inputs, run them and returns the output. There are multiple backends like PyTorch, [Tensorflow](https://www.tensorflow.org/) (and [Lite version]((https://www.tensorflow.org/lite))), [ONNX](https://onnxruntime.ai/) and [OpenVINO™](https://docs.openvino.ai/). +A backend or ML engine is an application that parses the ML model, loads the inputs, runs them and returns the output. There are multiple backends like PyTorch, [Tensorflow](https://www.tensorflow.org/) (and [Lite version]((https://www.tensorflow.org/lite))), [ONNX](https://onnxruntime.ai/) and [OpenVINO™](https://docs.openvino.ai/). Currently, Wasm Workers Server only supports [OpenVINO™](https://docs.openvino.ai/) as ML inference engine or backend. The community is actively working on adding support for more backends, so you may expect new backends in the future. @@ -63,7 +63,7 @@ We recommend to check this example to get started with ML and Wasm Workers Serve ## Language compatibility -| Language | Mount folders | +| Language | Machine learning inference | | --- | --- | | JavaScript | ❌ | | Rust | ✅ | From 74fee34c41166d8d843d9f987023a7c7b6989af4 Mon Sep 17 00:00:00 2001 From: Angel M De Miguel Date: Wed, 23 Aug 2023 16:34:40 +0200 Subject: [PATCH 6/6] fix: remove support for tensor files and clarifies why we need to skip 1 --- examples/rust-wasi-nn/src/main.rs | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/examples/rust-wasi-nn/src/main.rs b/examples/rust-wasi-nn/src/main.rs index 84155be9..7edf6ca1 100644 --- a/examples/rust-wasi-nn/src/main.rs +++ b/examples/rust-wasi-nn/src/main.rs @@ -42,20 +42,14 @@ pub fn inference(path: &str, labels: &Dataset) -> Result, w let width: u32 = 224; let height: u32 = 224; - let bytes; - - if path.contains("bgr") { - bytes = fs::read(path).unwrap(); - } else { - bytes = image2tensor::convert_image_to_bytes( - path, - width, - height, - ImageTensorType::F32, - ColorOrder::BGR, - ) - .unwrap(); - } + let bytes = image2tensor::convert_image_to_bytes( + path, + width, + height, + ImageTensorType::F32, + ColorOrder::BGR, + ) + .unwrap(); ctx.set_input(0, TensorType::F32, &input_dim, &bytes)?; @@ -73,6 +67,8 @@ pub fn inference(path: &str, labels: &Dataset) -> Result, w fn sort_results(buffer: &[f32], labels: &Dataset) -> Vec { let mut results: Vec = buffer .iter() + // In this specific case, the inference probabilities start at index 1. + // TODO: research more about where this issue may come from. .skip(1) .enumerate() .map(|(c, p)| InferenceResult(labels.names.get(c).unwrap().to_string(), *p))