10f4ae88aSAndrew Brown //! This module attempts to paper over the differences between the two
20f4ae88aSAndrew Brown //! implementations of wasi-nn: the legacy WITX-based version (`mod witx`) and
30f4ae88aSAndrew Brown //! the up-to-date WIT version (`mod wit`). Since the tests are mainly a simple
40f4ae88aSAndrew Brown //! classifier, this exposes a high-level `classify` function to go along with
50f4ae88aSAndrew Brown //! `load`, etc.
60f4ae88aSAndrew Brown //!
70f4ae88aSAndrew Brown //! This module exists solely for convenience--e.g., reduces test duplication.
80f4ae88aSAndrew Brown //! In the future can be safely disposed of or altered as more tests are added.
90f4ae88aSAndrew Brown 
100f4ae88aSAndrew Brown /// Call `wasi-nn` functions from WebAssembly using the canonical ABI of the
110f4ae88aSAndrew Brown /// component model via WIT-based tooling. Used by `bin/nn_wit_*.rs` tests.
120f4ae88aSAndrew Brown pub mod wit {
1390ac295eSAlex Crichton     use anyhow::{Result, anyhow};
140f4ae88aSAndrew Brown     use std::time::Instant;
150f4ae88aSAndrew Brown 
160f4ae88aSAndrew Brown     // Generate the wasi-nn bindings based on the `*.wit` files.
170f4ae88aSAndrew Brown     wit_bindgen::generate!({
180f4ae88aSAndrew Brown         path: "../wasi-nn/wit",
190f4ae88aSAndrew Brown         world: "ml",
200f4ae88aSAndrew Brown         default_bindings_module: "test_programs::ml"
210f4ae88aSAndrew Brown     });
220f4ae88aSAndrew Brown     use self::wasi::nn::errors;
230f4ae88aSAndrew Brown     use self::wasi::nn::graph::{self, Graph};
240f4ae88aSAndrew Brown     pub use self::wasi::nn::graph::{ExecutionTarget, GraphEncoding}; // Used by tests.
250f4ae88aSAndrew Brown     use self::wasi::nn::tensor::{Tensor, TensorType};
260f4ae88aSAndrew Brown 
270f4ae88aSAndrew Brown     /// Load a wasi-nn graph from a set of bytes.
load( bytes: &[Vec<u8>], encoding: GraphEncoding, target: ExecutionTarget, ) -> Result<Graph>280f4ae88aSAndrew Brown     pub fn load(
290f4ae88aSAndrew Brown         bytes: &[Vec<u8>],
300f4ae88aSAndrew Brown         encoding: GraphEncoding,
310f4ae88aSAndrew Brown         target: ExecutionTarget,
320f4ae88aSAndrew Brown     ) -> Result<Graph> {
330f4ae88aSAndrew Brown         graph::load(bytes, encoding, target).map_err(err_as_anyhow)
340f4ae88aSAndrew Brown     }
350f4ae88aSAndrew Brown 
360f4ae88aSAndrew Brown     /// Load a wasi-nn graph by name.
load_by_name(name: &str) -> Result<Graph>370f4ae88aSAndrew Brown     pub fn load_by_name(name: &str) -> Result<Graph> {
380f4ae88aSAndrew Brown         graph::load_by_name(name).map_err(err_as_anyhow)
390f4ae88aSAndrew Brown     }
400f4ae88aSAndrew Brown 
410f4ae88aSAndrew Brown     /// Run a wasi-nn inference using a simple classifier model (single input,
420f4ae88aSAndrew Brown     /// single output).
classify(graph: Graph, input: (&str, Vec<u8>)) -> Result<Vec<f32>>43*a7e11500SRahul     pub fn classify(graph: Graph, input: (&str, Vec<u8>)) -> Result<Vec<f32>> {
440f4ae88aSAndrew Brown         let context = graph.init_execution_context().map_err(err_as_anyhow)?;
45a0442ea0SHamir Mahal         println!("[nn] created wasi-nn execution context with ID: {context:?}");
460f4ae88aSAndrew Brown 
470f4ae88aSAndrew Brown         // Many classifiers have a single input; currently, this test suite also
480f4ae88aSAndrew Brown         // uses tensors of the same shape, though this is not usually the case.
490f4ae88aSAndrew Brown         let tensor = Tensor::new(&vec![1, 3, 224, 224], TensorType::Fp32, &input.1);
50*a7e11500SRahul         println!("[nn] input tensor: {} bytes", input.1.len());
510f4ae88aSAndrew Brown 
520f4ae88aSAndrew Brown         let before = Instant::now();
53*a7e11500SRahul         let input_tuple = (input.0.to_string(), tensor);
54*a7e11500SRahul         let output_tensors = context.compute(vec![input_tuple]).unwrap();
550f4ae88aSAndrew Brown         println!(
560f4ae88aSAndrew Brown             "[nn] executed graph inference in {} ms",
570f4ae88aSAndrew Brown             before.elapsed().as_millis()
580f4ae88aSAndrew Brown         );
590f4ae88aSAndrew Brown 
600f4ae88aSAndrew Brown         // Many classifiers emit probabilities as floating point values; here we
610f4ae88aSAndrew Brown         // convert the raw bytes to `f32` knowing all models used here use that
620f4ae88aSAndrew Brown         // type.
63*a7e11500SRahul         let output = &output_tensors[0].1;
640f4ae88aSAndrew Brown         println!(
650f4ae88aSAndrew Brown             "[nn] retrieved output tensor: {} bytes",
660f4ae88aSAndrew Brown             output.data().len()
670f4ae88aSAndrew Brown         );
680f4ae88aSAndrew Brown         let output: Vec<f32> = output
690f4ae88aSAndrew Brown             .data()
700f4ae88aSAndrew Brown             .chunks(4)
710f4ae88aSAndrew Brown             .map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
720f4ae88aSAndrew Brown             .collect();
730f4ae88aSAndrew Brown         Ok(output)
740f4ae88aSAndrew Brown     }
750f4ae88aSAndrew Brown 
err_as_anyhow(e: errors::Error) -> anyhow::Error760f4ae88aSAndrew Brown     fn err_as_anyhow(e: errors::Error) -> anyhow::Error {
770f4ae88aSAndrew Brown         anyhow!("error: {e:?}")
780f4ae88aSAndrew Brown     }
790f4ae88aSAndrew Brown }
800f4ae88aSAndrew Brown 
810f4ae88aSAndrew Brown /// Call `wasi-nn` functions from WebAssembly using the legacy WITX-based
820f4ae88aSAndrew Brown /// tooling. This older API has been deprecated for the newer WIT-based API but
830f4ae88aSAndrew Brown /// retained for backwards compatibility testing--i.e., `bin/nn_witx_*.rs`
840f4ae88aSAndrew Brown /// tests.
850f4ae88aSAndrew Brown pub mod witx {
8672afd847SAndrew Brown     use anyhow::Result;
8772afd847SAndrew Brown     use std::time::Instant;
880f4ae88aSAndrew Brown     pub use wasi_nn::{ExecutionTarget, GraphEncoding};
890f4ae88aSAndrew Brown     use wasi_nn::{Graph, GraphBuilder, TensorType};
900f4ae88aSAndrew Brown 
910f4ae88aSAndrew Brown     /// Load a wasi-nn graph from a set of bytes.
load( bytes: &[&[u8]], encoding: GraphEncoding, target: ExecutionTarget, ) -> Result<Graph>920f4ae88aSAndrew Brown     pub fn load(
930f4ae88aSAndrew Brown         bytes: &[&[u8]],
940f4ae88aSAndrew Brown         encoding: GraphEncoding,
950f4ae88aSAndrew Brown         target: ExecutionTarget,
960f4ae88aSAndrew Brown     ) -> Result<Graph> {
970f4ae88aSAndrew Brown         Ok(GraphBuilder::new(encoding, target).build_from_bytes(bytes)?)
980f4ae88aSAndrew Brown     }
990f4ae88aSAndrew Brown 
1000f4ae88aSAndrew Brown     /// Load a wasi-nn graph by name.
load_by_name( name: &str, encoding: GraphEncoding, target: ExecutionTarget, ) -> Result<Graph>1010f4ae88aSAndrew Brown     pub fn load_by_name(
1020f4ae88aSAndrew Brown         name: &str,
1030f4ae88aSAndrew Brown         encoding: GraphEncoding,
1040f4ae88aSAndrew Brown         target: ExecutionTarget,
1050f4ae88aSAndrew Brown     ) -> Result<Graph> {
1060f4ae88aSAndrew Brown         Ok(GraphBuilder::new(encoding, target).build_from_cache(name)?)
1070f4ae88aSAndrew Brown     }
10872afd847SAndrew Brown 
10972afd847SAndrew Brown     /// Run a wasi-nn inference using a simple classifier model (single input,
11072afd847SAndrew Brown     /// single output).
classify(graph: Graph, tensor: Vec<u8>) -> Result<Vec<f32>>11172afd847SAndrew Brown     pub fn classify(graph: Graph, tensor: Vec<u8>) -> Result<Vec<f32>> {
11272afd847SAndrew Brown         let mut context = graph.init_execution_context()?;
113a0442ea0SHamir Mahal         println!("[nn] created wasi-nn execution context with ID: {context}");
11472afd847SAndrew Brown 
11572afd847SAndrew Brown         // Many classifiers have a single input; currently, this test suite also
11672afd847SAndrew Brown         // uses tensors of the same shape, though this is not usually the case.
11772afd847SAndrew Brown         context.set_input(0, TensorType::F32, &[1, 3, 224, 224], &tensor)?;
11872afd847SAndrew Brown         println!("[nn] set input tensor: {} bytes", tensor.len());
11972afd847SAndrew Brown 
12072afd847SAndrew Brown         let before = Instant::now();
12172afd847SAndrew Brown         context.compute()?;
12272afd847SAndrew Brown         println!(
12372afd847SAndrew Brown             "[nn] executed graph inference in {} ms",
12472afd847SAndrew Brown             before.elapsed().as_millis()
12572afd847SAndrew Brown         );
12672afd847SAndrew Brown 
12772afd847SAndrew Brown         // Many classifiers emit probabilities as floating point values; here we
12872afd847SAndrew Brown         // convert the raw bytes to `f32` knowing all models used here use that
12972afd847SAndrew Brown         // type.
13072afd847SAndrew Brown         let mut output_buffer = vec![0u8; 1001 * std::mem::size_of::<f32>()];
13172afd847SAndrew Brown         let num_bytes = context.get_output(0, &mut output_buffer)?;
132a0442ea0SHamir Mahal         println!("[nn] retrieved output tensor: {num_bytes} bytes");
13372afd847SAndrew Brown         let output: Vec<f32> = output_buffer[..num_bytes]
13472afd847SAndrew Brown             .chunks(4)
13572afd847SAndrew Brown             .map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
13672afd847SAndrew Brown             .collect();
13772afd847SAndrew Brown         Ok(output)
13872afd847SAndrew Brown     }
1390f4ae88aSAndrew Brown }
14072afd847SAndrew Brown 
14172afd847SAndrew Brown /// Sort some classification probabilities.
14272afd847SAndrew Brown ///
14372afd847SAndrew Brown /// Many classification models output a buffer of probabilities for each class,
14472afd847SAndrew Brown /// placing the match probability for each class at the index for that class
14572afd847SAndrew Brown /// (the probability of class `N` is stored at `probabilities[N]`).
sort_results(probabilities: &[f32]) -> Vec<InferenceResult>14672afd847SAndrew Brown pub fn sort_results(probabilities: &[f32]) -> Vec<InferenceResult> {
14772afd847SAndrew Brown     let mut results: Vec<InferenceResult> = probabilities
14872afd847SAndrew Brown         .iter()
14972afd847SAndrew Brown         .enumerate()
15072afd847SAndrew Brown         .map(|(c, p)| InferenceResult(c, *p))
15172afd847SAndrew Brown         .collect();
15272afd847SAndrew Brown     results.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
15372afd847SAndrew Brown     results
15472afd847SAndrew Brown }
15572afd847SAndrew Brown 
15672afd847SAndrew Brown // A wrapper for class ID and match probabilities.
15772afd847SAndrew Brown #[derive(Debug, PartialEq)]
15872afd847SAndrew Brown pub struct InferenceResult(usize, f32);
15972afd847SAndrew Brown impl InferenceResult {
class_id(&self) -> usize16072afd847SAndrew Brown     pub fn class_id(&self) -> usize {
16172afd847SAndrew Brown         self.0
16272afd847SAndrew Brown     }
probability(&self) -> f3216372afd847SAndrew Brown     pub fn probability(&self) -> f32 {
16472afd847SAndrew Brown         self.1
16572afd847SAndrew Brown     }
16672afd847SAndrew Brown }
167