Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add examples and documentation related to burn backend #156

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions wasmedge-burn/squeezenet/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Dockerfile
FROM scratch
COPY wasmedge-wasinn-example-squeezenet.wasm /app.wasm
ENTRYPOINT [ "/app.wasm" ]
80 changes: 80 additions & 0 deletions wasmedge-burn/squeezenet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Squeezenet Example For WASI-NN with Burn Backend

The current version of the plugin has not been officially released yet, so it needs to be compiled manually. And here we are using two different branches for running in non-container and container environments.

## Non container

### Build plugin

The plugin can ==only support one model type== at a time.

```bash
git clone https://github.com/WasmEdge/WasmEdge.git
cd WasmEdge

// For squeezenet model
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=Release -DWASMEDGE_BUILD_AOT_RUNTIME=OFF -DWASMEDGE_PLUGIN_WASI_NN_BURNRS_MODEL=squeezenet
cmake --build build

// Replace the path to your WasmEdge
export PATH=<Your WasmEdge Path>/build/tools/wasmedge:$PATH
export WASMEDGE_PLUGIN_PATH=<Your WasmEdge Path>/build/plugins/wasi_nn_burnrs
```
([More about how to build Wasmedge](https://wasmedge.org/docs/contribute/source/os/linux/))

### Execute

```bash
// Make sure you build your plugin with the Squeezenet model enabled then
cd WasmEdge-WASINN-examples/wasmedge-burn/squeezenet

// Verify with CPU
wasmedge --dir .:. --nn-preload="default:Burn:CPU:squeezenet1.mpk" wasmedge-wasinn-example-squeezenet.wasm samples/bridge.jpg default

// Verify with GPU
wasmedge --dir .:. --nn-preload="default:Burn:GPU:squeezenet1.mpk" wasmedge-wasinn-example-squeezenet.wasm samples/bridge.jpg default
```

## Inside container

We could use the wasmedge shim as docker wasm runtime to run the example.

### Build the docker image include the wasm application only

```bash
cd wasmedge-burn/squeezenet
docker build . --platform wasi/wasm -t squeezenet
```

### Execute

A specific version of Docker Desktop is required to support using WebGPU inside container.
It hasn't been released yet, but you can refer to below link for a related demo.

https://www.youtube.com/watch?v=ODhJFe4-n6Y

And our plugin will be packaged as part of the runtime in this experimental version of Docker Desktop.

```bash
cd WasmEdge-WASINN-examples/wasmedge-burn/squeezenet

// Verify with CPU
docker run \
--runtime=io.containerd.wasmedge.v1 \
--platform=wasi/wasm \
-v $(pwd):/resource \
--env WASMEDGE_WASINN_PRELOAD=default:Burn:CPU:/resource/squeezenet1.mpk \
squeezenet:latest /resource/samples/bridge.jpg default

// Verify with GPU
docker run \
--runtime=io.containerd.wasmedge.v1 \
--platform=wasi/wasm \
-v $(pwd):/resource \
--env WASMEDGE_WASINN_PRELOAD=default:Burn:GPU:/resource/squeezenet1.mpk \
squeezenet:latest /resource/samples/bridge.jpg default
```

### Appendix

Verify CPU-only usage inside the container with the Docker Engine and a self-built Wamsedge shim + plugin. (on going)
14 changes: 14 additions & 0 deletions wasmedge-burn/squeezenet/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[package]
name = "wasmedge-wasinn-example-squeezenet"
version = "0.1.0"
authors = ["Second-State"]
readme = "README.md"
edition = "2021"
publish = false

[dependencies]
wasmedge-wasi-nn = "0.8.0"
image = { version = "0.24.7", features = ["png", "jpeg"] }
squeezenet-burn = { git = "https://github.com/second-state/burn-rs-models.git", branch = "prebuilt-feature", features = [
"weights_file",
], default-features = false }
86 changes: 86 additions & 0 deletions wasmedge-burn/squeezenet/rust/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
use image;
use std::fs::File;
use std::io::Read;
use squeezenet_burn::model::label::LABELS;
use wasmedge_wasi_nn as wasi_nn;

pub fn main() {
let img_path = std::env::args().nth(1).expect("No image path provided");
let model_name = std::env::args().nth(2).expect("No model name provided");

let graph =
wasi_nn::GraphBuilder::new(wasi_nn::GraphEncoding::Burn, wasi_nn::ExecutionTarget::AUTO)
.build_from_cache(&model_name)
.expect("Failed to build graph");

println!("Loaded graph into wasi-nn with ID: {:?}", graph);

let mut context = graph.init_execution_context().unwrap();
println!("Created wasi-nn execution context with ID: {:?}", context);

let tensor_data = image_to_tensor(img_path, 224, 224);
context
.set_input(0, wasi_nn::TensorType::F32, &[1, 3, 224, 224], &tensor_data)
.unwrap();

context.compute().unwrap();
println!("Executed graph inference");

let mut output_buffer = vec![0f32; 1000];
context.get_output(0, &mut output_buffer).unwrap();

top_5_classes(output_buffer)
}

// Take the image located at 'path', open it, resize it to height x width, and then converts
// the pixel precision to FP32. The resulting BGR pixel vector is then returned.
fn image_to_tensor(path: String, height: u32, width: u32) -> Vec<f32> {
let mut file_img = File::open(path).unwrap();
let mut img_buf = Vec::new();
file_img.read_to_end(&mut img_buf).unwrap();
let img = image::load_from_memory(&img_buf).unwrap().to_rgb8();
// Resize the image
let resized =
image::imageops::resize(&img, height, width, ::image::imageops::FilterType::Triangle);
let mut flat_img: Vec<f32> = Vec::new();
// Normize the image and rearrange the tensor data order
for rgb in resized.pixels() {
flat_img.push((rgb[0] as f32 / 255. - 0.485) / 0.229);
}
for rgb in resized.pixels() {
flat_img.push((rgb[1] as f32 / 255. - 0.456) / 0.224);
}
for rgb in resized.pixels() {
flat_img.push((rgb[2] as f32 / 255. - 0.406) / 0.225);
}
return flat_img;
}

#[derive(Debug)]
pub struct InferenceResult {
index: usize,
probability: f32,
label: String,
}

fn top_5_classes(probabilities: Vec<f32>) {
// Convert the probabilities into a vector of (index, probability)
let mut probabilities: Vec<_> = probabilities.iter().enumerate().collect();

// Sort the probabilities in descending order
probabilities.sort_by(|a, b| b.1.partial_cmp(a.1).unwrap());

// Take the top 5 probabilities
probabilities.truncate(5);

// Convert the probabilities into InferenceResult
let result: Vec<InferenceResult> = probabilities
.into_iter()
.map(|(index, probability)| InferenceResult {
index,
probability: *probability,
label: LABELS[index].to_string(),
})
.collect();
println!("Top 5 classes: {:?}", result);
}
Binary file added wasmedge-burn/squeezenet/samples/bridge.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added wasmedge-burn/squeezenet/samples/cat.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added wasmedge-burn/squeezenet/samples/coyote.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added wasmedge-burn/squeezenet/samples/flamingo.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added wasmedge-burn/squeezenet/samples/pelican.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added wasmedge-burn/squeezenet/samples/table-lamp.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added wasmedge-burn/squeezenet/samples/torch.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added wasmedge-burn/squeezenet/squeezenet1.mpk
Binary file not shown.
Binary file not shown.
4 changes: 4 additions & 0 deletions wasmedge-burn/whisper/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Dockerfile
FROM scratch
COPY wasmedge-wasinn-example-whisper.wasm /app.wasm
ENTRYPOINT [ "/app.wasm" ]
110 changes: 110 additions & 0 deletions wasmedge-burn/whisper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# Whisper Example For WASI-NN with Burn Backend

The current version of the plugin has not been officially released yet, so it needs to be compiled manually. And here we are using two different branches for running in non-container and container environments.

## Non container

### Build plugin

The plugin can ==only support one model type== at a time.

```bash
git clone https://github.com/WasmEdge/WasmEdge.git
cd WasmEdge

// For whisper model
cmake -GNinja -Bbuild -DCMAKE_BUILD_TYPE=Release -DWASMEDGE_BUILD_AOT_RUNTIME=OFF -DWASMEDGE_PLUGIN_WASI_NN_BURNRS_MODEL=whisper
cmake --build build

// Replace the path to your WasmEdge
export PATH=<Your WasmEdge Path>/build/tools/wasmedge:$PATH
export WASMEDGE_PLUGIN_PATH=<Your WasmEdge Path>/build/plugins/wasi_nn_burnrs
```
([More about how to build Wasmedge](https://wasmedge.org/docs/contribute/source/os/linux/))

### Execute

```bash
// Make sure you build your plugin with the Whisper model enabled then
cd WasmEdge-WASINN-examples/wasmedge-burn/whisper

// Untar model suite
tar -xvzf model.tar.gz

// Verify with CPU
wasmedge --dir .:. --nn-preload="default:Burn:CPU:tiny_en.mpk:tiny_en.cfg:tokenizer.json:en" wasmedge-wasinn-example-whisper.wasm audio16k.wav default

// Verify with GPU
wasmedge --dir .:. --nn-preload="default:Burn:GPU:tiny_en.mpk:tiny_en.cfg:tokenizer.json:en" wasmedge-wasinn-example-whisper.wasm audio16k.wav default
```

## Inside container

We could use the wasmedge shim as docker wasm runtime to run the example.

### Build the docker image include the wasm application only

```bash
cd wasmedge-burn/whisper
docker build . --platform wasi/wasm -t whisper
```

### Execute

A specific version of Docker Desktop is required to support using WebGPU inside container.
It hasn't been released yet, but you can refer to below link for a related demo.

https://www.youtube.com/watch?v=ODhJFe4-n6Y

And our plugin will be packaged as part of the runtime in this experimental version of Docker Desktop.

```bash
cd WasmEdge-WASINN-examples/wasmedge-burn/whisper
tar -xvzf model.tar.gz

// Verify with CPU
docker run \
--runtime=io.containerd.wasmedge.v1 \
--platform=wasi/wasm \
-v $(pwd):/resource \
--env WASMEDGE_WASINN_PRELOAD=default:Burn:CPU:/resource/tiny_en.mpk:/resource/tiny_en.cfg:/resource/tokenizer.json:en \
whisper:latest /resource/audio16k.wav default

// Verify with GPU
docker run \
--runtime=io.containerd.wasmedge.v1 \
--platform=wasi/wasm \
-v $(pwd):/resource \
--env WASMEDGE_WASINN_PRELOAD=default:Burn:GPU:/resource/tiny_en.mpk:/resource/tiny_en.cfg:/resource/tokenizer.json:en \
whisper:latest /resource/audio16k.wav default
```

or

You can directly use our prebuilt docker image, which already includes the model weights, configuration file, and other necessary files, so there's no need for additional extraction model.tar.gz.

```bash
cd WasmEdge-WASINN-examples/wasmedge-burn/whisper

docker pull secondstate/burn-whisper:latest

// Verify with CPU
docker run \
--runtime=io.containerd.wasmedge.v1 \
--platform=wasi/wasm \
-v $(pwd):/resource \
--env WASMEDGE_WASINN_PRELOAD=default:Burn:CPU:/tiny_en.mpk:/tiny_en.cfg:/tokenizer.json:en \
whisper:latest /resource/audio16k.wav default

// Verify with GPU
docker run \
--runtime=io.containerd.wasmedge.v1 \
--platform=wasi/wasm \
-v $(pwd):/resource \
--env WASMEDGE_WASINN_PRELOAD=default:Burn:GPU:/tiny_en.mpk:/tiny_en.cfg:/tokenizer.json:en \
whisper:latest /resource/audio16k.wav default
```

### Appendix

Verify CPU-only usage inside the container with the Docker Engine and a self-built Wamsedge shim + plugin. (on going)
Binary file added wasmedge-burn/whisper/audio16k.wav
Binary file not shown.
Binary file added wasmedge-burn/whisper/model.tar.gz
Binary file not shown.
11 changes: 11 additions & 0 deletions wasmedge-burn/whisper/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "wasmedge-wasinn-example-whisper"
version = "0.1.0"
authors = ["Second-State"]
readme = "README.md"
edition = "2021"
publish = false

[dependencies]
wasmedge-wasi-nn = "0.8.0"
hound = "3.5.0"
69 changes: 69 additions & 0 deletions wasmedge-burn/whisper/rust/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use hound::{self, SampleFormat};
use std::process;
use wasmedge_wasi_nn as wasi_nn;

pub fn main() {
let wav_file = std::env::args().nth(1).expect("No wav file name provided");
let model_name = std::env::args().nth(2).expect("No model name provided");

let graph =
wasi_nn::GraphBuilder::new(wasi_nn::GraphEncoding::Burn, wasi_nn::ExecutionTarget::AUTO)
.build_from_cache(&model_name)
.expect("Failed to build graph");

println!("Loaded graph into wasi-nn with ID: {:?}", graph);

let mut context = graph.init_execution_context().unwrap();
println!("Created wasi-nn execution context with ID: {:?}", context);

println!("Loading waveform...");
let (waveform, sample_rate) = match load_audio_waveform(&wav_file) {
Ok((w, sr)) => (w, sr),
Err(e) => {
eprintln!("Failed to load audio file: {}", e);
process::exit(1);
}
};
assert_eq!(sample_rate, 16000, "The audio sample rate must be 16k.");

context
.set_input(0, wasi_nn::TensorType::F32, &[1, waveform.len()], &waveform)
.unwrap();

context.compute().unwrap();
println!("Executed audio to text converter.");

let mut output_buffer = vec![0u8; 100];
context.get_output(0, &mut output_buffer).unwrap();

match String::from_utf8(output_buffer) {
Ok(s) => println!("Text: {}", s),
Err(e) => println!("Error: {}", e),
}
}

fn load_audio_waveform(filename: &str) -> hound::Result<(Vec<f32>, usize)> {
let reader = hound::WavReader::open(filename)?;
let spec = reader.spec();

// let duration = reader.duration() as usize;
let channels = spec.channels as usize;
let sample_rate = spec.sample_rate as usize;
// let bits_per_sample = spec.bits_per_sample;
let sample_format = spec.sample_format;

assert_eq!(sample_rate, 16000, "The audio sample rate must be 16k.");
assert_eq!(channels, 1, "The audio must be single-channel.");

let max_int_val = 2_u32.pow(spec.bits_per_sample as u32 - 1) - 1;

let floats = match sample_format {
SampleFormat::Float => reader.into_samples::<f32>().collect::<hound::Result<_>>()?,
SampleFormat::Int => reader
.into_samples::<i32>()
.map(|s| s.map(|s| s as f32 / max_int_val as f32))
.collect::<hound::Result<_>>()?,
};

return Ok((floats, sample_rate));
}
Binary file not shown.