From 5fd82a1162b61aa2f0a1a2dc1757add57f498074 Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Thu, 4 Jan 2024 10:56:53 -0600 Subject: [PATCH 01/23] docs: update --- docs/introduction.mdx | 52 +++++++++++++++++----------- docs/migrating/v2.mdx | 9 +++++ docs/setup/linking.mdx | 15 +++----- docs/troubleshooting/performance.mdx | 4 +-- 4 files changed, 47 insertions(+), 33 deletions(-) diff --git a/docs/introduction.mdx b/docs/introduction.mdx index c94c808d..df6235b7 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -3,49 +3,59 @@ title: Introduction ---

- `ort` is an open-source Rust binding for [ONNX Runtime](https://github.com/microsoft/onnxruntime). + `ort` is an open-source Rust binding for [ONNX Runtime](https://onnxruntime.ai/).

-`ort` makes it easy to deploy your machine learning models to production via ONNX Runtime, a [hardware-accelerated](/perf/execution-providers) inference engine. With `ort` + ONNX Runtime, you can run almost any ML model (including ResNet, YOLOv8, BERT, LLaMA), often much faster than PyTorch, and with the added bonus of Rust's efficiency. +`ort` makes it easy to deploy your machine learning models to production via [ONNX Runtime](https://onnxruntime.ai/), a hardware-accelerated inference engine. With `ort` + ONNX Runtime, you can run almost any ML model (including ResNet, YOLOv8, BERT, LLaMA) on almost any hardware, often far faster than PyTorch, and with the added bonus of Rust's efficiency. + + + These docs are for the latest alpha version of `ort`, `2.0.0-alpha.4`. This alpha version is production-ready (just not API stable) and we recommend new & existing projects use it. + # Why `ort`? There are a few other ONNX Runtime crates out there, so why use `ort`? For one, `ort` simply supports more features: -| Feature comparison | **๐Ÿ“• ort** | **๐Ÿ“— [ors](https://github.com/HaoboGu/ors)** | **๐ŸชŸ [onnxruntime-rs](https://github.com/microsoft/onnxruntime/tree/main/rust)** | -|------------------------|-----------|-----------|----------------------| -| Upstream version | **v1.16.3** | v1.12.0 | v1.8 | -| `dlopen()`? | โœ… | โœ… | โŒ | -| Execution providers? | โœ… | โŒ | โŒ | -| IOBinding? | โœ… | โŒ | โŒ | -| String tensors? | โœ… | โŒ | โš ๏ธ input only | -| Multiple output types? | โœ… | โœ… | โŒ | -| Multiple input types? | โœ… | โœ… | โŒ | -| In-memory session? | โœ… | โœ… | โœ… | -| WebAssembly? | โœ… | โŒ | โŒ | +| Feature comparison | **๐Ÿ“• ort** | **๐Ÿ“— [ors](https://github.com/HaoboGu/ors)** | **๐ŸชŸ [onnxruntime-rs](https://github.com/microsoft/onnxruntime/tree/main/rust)** | +|---------------------------|-----------|-----------|----------------------| +| Upstream version | **v1.16.3** | v1.12.0 | v1.8 | +| `dlopen()`? | โœ… | โœ… | โŒ | +| Execution providers? | โœ… | โŒ | โŒ | +| I/O Binding? | โœ… | โŒ | โŒ | +| String tensors? | โœ… | โŒ | โš ๏ธ input only | +| Multiple output types? | โœ… | โœ… | โŒ | +| Multiple input types? | โœ… | โœ… | โŒ | +| In-memory session? | โœ… | โœ… | โœ… | +| WebAssembly? | โœ… | โŒ | โŒ | +| Provides static binaries? | โœ… | โŒ | โŒ | +| Sequence & map types? | โœ… | โŒ | โŒ | Users of `ort` appreciate its ease of use and ergonomic API. `ort` is also battle tested in some pretty serious production scenarios. - [**Twitter**](https://twitter.com/) uses `ort` in part of their recommendations system, serving hundreds of millions of requests a day. - [**Bloop**](https://bloop.ai/)'s semantic code search feature is powered by `ort`. +- [**SurrealDB**](https://surrealdb.com/) uses `ort` in their [`surrealml`](https://github.com/surrealdb/surrealml) package. - [**Numerical Elixir**](https://github.com/elixir-nx) uses `ort` to create ONNX Runtime bindings for the Elixir language. -- [**`rust-bert`**](https://github.com/guillaume-be/rust-bert) implements many ready-to-use NLP pipelines in Rust a la Hugging Face Transformers, with an optional `ort` backend. +- [**`rust-bert`**](https://github.com/guillaume-be/rust-bert) implements many ready-to-use NLP pipelines in Rust ร  la Hugging Face Transformers with both [`tch`](https://crates.io/crates/tch) & `ort` backends. - [**`edge-transformers`**](https://github.com/npc-engine/edge-transformers) also implements Hugging Face Transformers pipelines in Rust using `ort`. - We use `ort` in nearly all of our ML projects, including [VITRI](https://vitri.pyke.io/) ๐Ÿ˜Š # Getting started - If you have a [supported platform](/setup/platforms), installing `ort` couldn't be any simpler! + If you have a [supported platform](/setup/platforms) (and you probably do), installing `ort` couldn't be any simpler! Just add it to your Cargo dependencies: ```toml [dependencies] - ort = "2.0" + ort = "2.0.0-alpha.4" ``` - Your model will need to be converted to the [ONNX](https://onnx.ai/) format before you can use it; here's how to do that: - - The awesome folks at Hugging Face have [a guide](https://huggingface.co/docs/transformers/serialization) to export Transformers models to ONNX with ๐Ÿค— Optimum. - - For other PyTorch models, see the [`torch.onnx` module docs](https://pytorch.org/docs/stable/onnx.html). + Your model will need to be converted to the [ONNX](https://onnx.ai/) format before you can use it. + - The awesome folks at Hugging Face have [a guide](https://huggingface.co/docs/transformers/serialization) to export ๐Ÿค— Transformers models to ONNX with ๐Ÿค— Optimum. + - For other PyTorch models: [`torch.onnx`](https://pytorch.org/docs/stable/onnx.html) + - For `scikit-learn`: [`sklearn-onnx`](https://onnx.ai/sklearn-onnx/) + - For TensorFlow, Keras, TFlite, TensorFlow.js: [`tf2onnx`](https://github.com/onnx/tensorflow-onnx) + - For PaddlePaddle: [`Paddle2ONNX`](https://github.com/PaddlePaddle/Paddle2ONNX) Once you've got a model, load it via `ort` by creating a [`Session`](/fundamentals/session): @@ -64,6 +74,8 @@ Users of `ort` appreciate its ease of use and ergonomic API. `ort` is also battl ```rust let outputs = model.run(ort::inputs!["image" => image]?)?; + + // Postprocessing let output = outputs["output0"] .extract_tensor::() .unwrap() @@ -74,7 +86,7 @@ Users of `ort` appreciate its ease of use and ergonomic API. `ort` is also battl ... ``` - There are some [more useful examples](https://github.com/pykeio/ort/tree/main/examples) in our repo! + There are some more useful examples [in the `ort` repo](https://github.com/pykeio/ort/tree/main/examples)! diff --git a/docs/migrating/v2.mdx b/docs/migrating/v2.mdx index 2c22484f..3bc2407d 100644 --- a/docs/migrating/v2.mdx +++ b/docs/migrating/v2.mdx @@ -144,6 +144,14 @@ The `ort::sys` module has been split out into [its own `ort-sys` crate](https:// ### `ndarray` is now optional The dependency on `ndarray` is now declared optional. If you use `ort` with `default-features = false`, you'll need to add the `ndarray` feature. +## Model Zoo structs have been removed +ONNX pushed a new Model Zoo structure that adds hundreds of different models. This is impractical to maintain, so the built-in structs have been removed. + +You can still use `Session::with_model_downloaded`, it just now takes a URL string instead of a struct. + +## Changes to logging +Environment-level logging configuration (i.e. `EnvironmentBuilder::with_log_level`) has been removed because it could cause unnecessary confusion with our `tracing` integration. + ## The Flattening All modules except `download` are no longer public. Exports have been flattened to the crate root, so i.e. `ort::session::Session` becomes `ort::Session`. @@ -153,3 +161,4 @@ The following types have been renamed with no other changes. - `OrtOwnedTensor` -> `Tensor` - `OrtResult`, `OrtError` -> `ort::Result`, `ort::Error` - `TensorDataToType` -> `ExtractTensorData` +- `TensorElementDataType`, `IntoTensorElementDataType` -> `TensorElementType`, `IntoTensorElementType` diff --git a/docs/setup/linking.mdx b/docs/setup/linking.mdx index a39da4e0..15a95267 100644 --- a/docs/setup/linking.mdx +++ b/docs/setup/linking.mdx @@ -32,7 +32,7 @@ To use `load-dynamic`: ort = { version = "2", features = [ "load-dynamic" ] } ``` - + ```shell @@ -41,22 +41,15 @@ To use `load-dynamic`: ```rust - use std::env; - fn main() -> anyhow::Result<()> { - // Find our downloaded ONNX Runtime dylibs and set the environment variable for ort - env::set_var("ORT_DYLIB_PATH", crate::internal::find_onnxruntime_dylib()?); + // Find our custom ONNX Runtime dylibs and initialize `ort` with it. + let dylib_path = crate::internal::find_onnxruntime_dylib()?; // /etc/.../libonnxruntime.so - // IMPORTANT: You must set the environment variable **before** you use `ort`!!! - ort::init().commit()?; + ort::init_from(dylib_path).commit()?; Ok(()) } ``` - - - You MUST set the environment variable **before** you use any `ort` APIs! - diff --git a/docs/troubleshooting/performance.mdx b/docs/troubleshooting/performance.mdx index 71be35ff..9351076e 100644 --- a/docs/troubleshooting/performance.mdx +++ b/docs/troubleshooting/performance.mdx @@ -52,7 +52,7 @@ title: 'Troubleshoot: Performance' ## Inference is slow, even with an EP! There are a few things you could try to improve performance: -- **Run `onnxsim` on the model.** Direct exports from PyTorch can leave a lot of junk nodes in the graph, which could hinder performance. [`onnxsim`](https://github.com/daquexian/onnx-simplifier) is a neat tool that can be used to simplify the ONNX graph and remove junk. +- **Run `onnxsim` on the model.** Direct exports from PyTorch can leave a lot of junk nodes in the graph, which could hinder performance. [`onnxsim`](https://github.com/daquexian/onnx-simplifier) is a neat tool that can be used to simplify the ONNX graph and potentially improve performance. - **Export with an older opset.** Some EPs might not support newer, more complex nodes. Try targeting an older ONNX opset when exporting your model to force it to export with simpler operations. - **Use the [transformer optimization tool](https://github.com/microsoft/onnxruntime/tree/main/onnxruntime/python/tools/transformers).** This is another neat tool that converts certain transformer-based models to far more optimized graphs. - **Try other EPs.** There may be multiple EPs for your hardware that have a more performant implementation. @@ -60,5 +60,5 @@ There are a few things you could try to improve performance: - For AMD, you can try ROCm, MIGraphX, or DirectML. - For ARM, you can try ArmNN, ACL, or XNNPACK. - See [Execution providers](/perf/execution-providers) for more information on supported EPs. -- **Use [`I/O binding`](/perf/io-binding).** This can reduce the latency between copying the session inputs/outputs to/from devices. +- **Use [`I/O binding`](/perf/io-binding).** This can reduce latency caused by copying the session inputs/outputs to/from devices. - **[Quantize your model.](https://onnxruntime.ai/docs/performance/model-optimizations/quantization.html)** You can try quantizing your model to 8-bit precision. This comes with a small accuracy loss, but can sometimes provide a large performance boost. If the accuracy loss is too high, you can also use [float16/mixed precision](https://onnxruntime.ai/docs/performance/model-optimizations/float16.html). From c1c960ff298c38b5b531e445174740286e0cbaac Mon Sep 17 00:00:00 2001 From: aykut-bozkurt Date: Fri, 12 Jan 2024 13:17:41 +0300 Subject: [PATCH 02/23] Export `EnvironmentGlobalThreadPoolOptions` Makes `with_global_thread_pool` usable by external packages. --- src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib.rs b/src/lib.rs index 7c783b0b..0af737a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ use tracing::Level; #[cfg(feature = "load-dynamic")] pub use self::environment::init_from; -pub use self::environment::{init, EnvironmentBuilder}; +pub use self::environment::{init, EnvironmentBuilder, EnvironmentGlobalThreadPoolOptions}; #[cfg(feature = "fetch-models")] #[cfg_attr(docsrs, doc(cfg(feature = "fetch-models")))] pub use self::error::FetchModelError; From 45f96c11bd82634c319a8c06f3162a490d90ec25 Mon Sep 17 00:00:00 2001 From: aykut-bozkurt <51649454+aykut-bozkurt@users.noreply.github.com> Date: Fri, 12 Jan 2024 17:51:44 +0300 Subject: [PATCH 03/23] feat: basic `RunOptions` support (#139) --- examples/gpt2/examples/gpt2-no-ndarray.rs | 3 +- examples/gpt2/examples/gpt2.rs | 3 +- examples/yolov8/examples/yolov8.rs | 3 +- src/error.rs | 9 ++++ src/io_binding.rs | 16 ++++-- src/lib.rs | 2 +- src/session/input.rs | 6 ++- src/session/mod.rs | 62 ++++++++++++++++++++--- tests/mnist.rs | 3 +- tests/squeezenet.rs | 3 +- tests/upsample.rs | 6 ++- tests/vectorizer.rs | 3 +- 12 files changed, 99 insertions(+), 20 deletions(-) diff --git a/examples/gpt2/examples/gpt2-no-ndarray.rs b/examples/gpt2/examples/gpt2-no-ndarray.rs index 832bb39d..c9557ec5 100644 --- a/examples/gpt2/examples/gpt2-no-ndarray.rs +++ b/examples/gpt2/examples/gpt2-no-ndarray.rs @@ -50,7 +50,8 @@ fn main() -> ort::Result<()> { // Raw tensor construction takes a tuple of (dimensions, data). // The model expects our input to have shape [B, _, S] let input = (vec![1, 1, tokens.len() as i64], Arc::clone(&tokens)); - let outputs = session.run(inputs![input]?)?; + let run_options = None; + let outputs = session.run(inputs![input]?, run_options)?; let (dim, mut probabilities) = outputs["output1"].extract_raw_tensor()?; // The output tensor will have shape [B, _, S + 1, V] diff --git a/examples/gpt2/examples/gpt2.rs b/examples/gpt2/examples/gpt2.rs index c75cae9c..39aa73fa 100644 --- a/examples/gpt2/examples/gpt2.rs +++ b/examples/gpt2/examples/gpt2.rs @@ -50,7 +50,8 @@ fn main() -> ort::Result<()> { for _ in 0..GEN_TOKENS { let array = tokens.view().insert_axis(Axis(0)).insert_axis(Axis(1)); - let outputs = session.run(inputs![array]?)?; + let run_options = None; + let outputs = session.run(inputs![array]?, run_options)?; let generated_tokens: Tensor = outputs["output1"].extract_tensor()?; let generated_tokens = generated_tokens.view(); diff --git a/examples/yolov8/examples/yolov8.rs b/examples/yolov8/examples/yolov8.rs index b7fa423d..c5f6cf1c 100644 --- a/examples/yolov8/examples/yolov8.rs +++ b/examples/yolov8/examples/yolov8.rs @@ -62,7 +62,8 @@ fn main() -> ort::Result<()> { let model = Session::builder()?.with_model_downloaded(YOLOV8M_URL)?; // Run YOLOv8 inference - let outputs: SessionOutputs = model.run(inputs!["images" => input.view()]?)?; + let run_options = None; + let outputs: SessionOutputs = model.run(inputs!["images" => input.view()]?, run_options)?; let output = outputs["output0"].extract_tensor::().unwrap().view().t().into_owned(); let mut boxes = Vec::new(); diff --git a/src/error.rs b/src/error.rs index ba270dfa..aa6ba509 100644 --- a/src/error.rs +++ b/src/error.rs @@ -105,6 +105,15 @@ pub enum Error { /// Error occurred when extracting string data from an ONNX tensor #[error("Failed to get tensor string data: {0}")] GetStringTensorContent(ErrorInternal), + /// Error occurred when creating run options. + #[error("Failed to create run options: {0}")] + CreateRunOptions(ErrorInternal), + /// Error occurred when terminating run options. + #[error("Failed to terminate run options: {0}")] + RunOptionsSetTerminate(ErrorInternal), + /// Error occurred when unterminating run options. + #[error("Failed to unterminate run options: {0}")] + RunOptionsUnsetTerminate(ErrorInternal), /// Error occurred when converting data to a String #[error("Data was not UTF-8: {0}")] StringFromUtf8Error(#[from] string::FromUtf8Error), diff --git a/src/io_binding.rs b/src/io_binding.rs index 2d61256f..b60378f9 100644 --- a/src/io_binding.rs +++ b/src/io_binding.rs @@ -1,6 +1,12 @@ use std::{ffi::CString, fmt::Debug, ptr, sync::Arc}; -use crate::{memory::MemoryInfo, ortsys, session::output::SessionOutputs, value::Value, Error, Result, Session}; +use crate::{ + memory::MemoryInfo, + ortsys, + session::{output::SessionOutputs, RunOptions}, + value::Value, + Error, Result, Session +}; /// Enables binding of session inputs and/or outputs to pre-allocated memory. /// @@ -58,8 +64,12 @@ impl<'s> IoBinding<'s> { Ok(()) } - pub fn run<'i: 's>(&'i self) -> Result> { - let run_options_ptr: *const ort_sys::OrtRunOptions = std::ptr::null(); + pub fn run<'i: 's>(&'i self, run_options: Option>) -> Result> { + let run_options_ptr = if let Some(run_options) = run_options { + run_options.run_options_ptr + } else { + std::ptr::null_mut() + }; ortsys![unsafe RunWithBinding(self.session.inner.session_ptr, run_options_ptr, self.ptr) -> Error::SessionRunWithIoBinding]; let mut count = self.output_names.len() as ort_sys::size_t; diff --git a/src/lib.rs b/src/lib.rs index 0af737a8..449a6cf0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,7 +42,7 @@ pub use self::execution_providers::*; pub use self::io_binding::IoBinding; pub use self::memory::{AllocationDevice, Allocator, MemoryInfo}; pub use self::metadata::ModelMetadata; -pub use self::session::{InMemorySession, Session, SessionBuilder, SessionInputs, SessionOutputs, SharedSessionInner}; +pub use self::session::{InMemorySession, RunOptions, Session, SessionBuilder, SessionInputs, SessionOutputs, SharedSessionInner}; #[cfg(feature = "ndarray")] #[cfg_attr(docsrs, doc(cfg(feature = "ndarray")))] pub use self::tensor::{ArrayExtensions, ArrayViewHolder, Tensor, TensorData}; diff --git a/src/session/input.rs b/src/session/input.rs index 15b91a2e..67591e1d 100644 --- a/src/session/input.rs +++ b/src/session/input.rs @@ -44,7 +44,8 @@ impl<'i, const N: usize> From<[Value; N]> for SessionInputs<'i, N> { /// # use ort::{GraphOptimizationLevel, Session}; /// # fn main() -> Result<(), Box> { /// let mut session = Session::builder()?.with_model_from_file("model.onnx")?; -/// let _ = session.run(ort::inputs![Array1::from_vec(vec![1, 2, 3, 4, 5])]?); +/// let run_options = None; +/// let _ = session.run(ort::inputs![Array1::from_vec(vec![1, 2, 3, 4, 5])]?, run_options); /// # Ok(()) /// # } /// ``` @@ -57,8 +58,9 @@ impl<'i, const N: usize> From<[Value; N]> for SessionInputs<'i, N> { /// # use ort::{GraphOptimizationLevel, Session}; /// # fn main() -> Result<(), Box> { /// let mut session = Session::builder()?.with_model_from_file("model.onnx")?; +/// let run_options = None; /// let _ = session.run(ort::inputs! { -/// "tokens" => Array1::from_vec(vec![1, 2, 3, 4, 5]) +/// "tokens" => Array1::from_vec(vec![1, 2, 3, 4, 5], run_options) /// }?); /// # Ok(()) /// # } diff --git a/src/session/mod.rs b/src/session/mod.rs index 0e3de974..023d59cb 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -562,6 +562,46 @@ pub struct Output { pub output_type: ValueType } +/// ONNX Run Options which is used to terminate/unterminate run(s) in a session +#[derive(Debug)] +pub struct RunOptions { + pub(crate) run_options_ptr: *mut ort_sys::OrtRunOptions +} + +// https://onnxruntime.ai/docs/api/c/struct_ort_api.html#ac2a08cac0a657604bd5899e0d1a13675 +unsafe impl Send for RunOptions {} +unsafe impl Sync for RunOptions {} + +impl RunOptions { + /// Creates a new [`RunOptions`]. + pub fn new() -> Result { + let mut run_options_ptr: *mut ort_sys::OrtRunOptions = std::ptr::null_mut(); + ortsys![unsafe CreateRunOptions(&mut run_options_ptr) -> Error::CreateRunOptions; nonNull(run_options_ptr)]; + Ok(Self { run_options_ptr }) + } + + /// Terminates the runs associated with [`RunOptions`]. + pub fn set_terminate(&self) -> Result<()> { + ortsys![unsafe RunOptionsSetTerminate(self.run_options_ptr) -> Error::RunOptionsSetTerminate]; + Ok(()) + } + + /// Unterminates the runs associated with [`RunOptions`]. + pub fn set_unterminate(&self) -> Result<()> { + ortsys![unsafe RunOptionsUnsetTerminate(self.run_options_ptr) -> Error::RunOptionsUnsetTerminate]; + Ok(()) + } +} + +impl Drop for RunOptions { + fn drop(&mut self) { + if !self.run_options_ptr.is_null() { + ortsys![unsafe ReleaseRunOptions(self.run_options_ptr)]; + } + self.run_options_ptr = std::ptr::null_mut(); + } +} + impl Session { pub fn builder() -> Result { SessionBuilder::new() @@ -583,24 +623,28 @@ impl Session { } /// Run the input data through the ONNX graph, performing inference. - pub fn run<'s, 'i, const N: usize>(&'s self, input_values: impl Into>) -> Result> { + pub fn run<'s, 'i, const N: usize>( + &'s self, + input_values: impl Into>, + run_options: Option> + ) -> Result> { match input_values.into() { SessionInputs::ValueSlice(input_values) => { - let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), input_values)?; + let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), input_values, run_options)?; Ok(outputs) } SessionInputs::ValueArray(input_values) => { - let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), &input_values)?; + let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), &input_values, run_options)?; Ok(outputs) } SessionInputs::ValueMap(input_values) => { let (input_names, values): (Vec<&'static str>, Vec) = input_values.into_iter().unzip(); - self.run_inner(&input_names, &values) + self.run_inner(&input_names, &values, run_options) } } } - fn run_inner(&self, input_names: &[&str], input_values: &[Value]) -> Result> { + fn run_inner(&self, input_names: &[&str], input_values: &[Value], run_options: Option>) -> Result> { let input_names_ptr: Vec<*const c_char> = input_names .iter() .map(|n| CString::new(*n).unwrap()) @@ -618,10 +662,16 @@ impl Session { // The C API expects pointers for the arrays (pointers to C-arrays) let input_ort_values: Vec<*const ort_sys::OrtValue> = input_values.iter().map(|input_array_ort| input_array_ort.ptr() as *const _).collect(); + let run_options_ptr = if let Some(run_options) = run_options { + run_options.run_options_ptr + } else { + std::ptr::null_mut() + }; + ortsys![ unsafe Run( self.inner.session_ptr, - ptr::null(), + run_options_ptr, input_names_ptr.as_ptr(), input_ort_values.as_ptr(), input_ort_values.len() as _, diff --git a/tests/mnist.rs b/tests/mnist.rs index 9ab190f9..30ec44a5 100644 --- a/tests/mnist.rs +++ b/tests/mnist.rs @@ -41,7 +41,8 @@ fn mnist_5() -> ort::Result<()> { }); // Perform the inference - let outputs = session.run(inputs![array]?)?; + let run_options = None; + let outputs = session.run(inputs![array]?, run_options)?; let output: Tensor<_> = outputs[0].extract_tensor()?; let mut probabilities: Vec<(usize, f32)> = output.view().softmax(ndarray::Axis(1)).iter().copied().enumerate().collect::>(); diff --git a/tests/squeezenet.rs b/tests/squeezenet.rs index 7dba0371..349fd1b9 100644 --- a/tests/squeezenet.rs +++ b/tests/squeezenet.rs @@ -66,7 +66,8 @@ fn squeezenet_mushroom() -> ort::Result<()> { } // Perform the inference - let outputs = session.run(inputs![array]?)?; + let run_options = None; + let outputs = session.run(inputs![array]?, run_options)?; // Downloaded model does not have a softmax as final layer; call softmax on second axis // and iterate on resulting probabilities, creating an index to later access labels. diff --git a/tests/upsample.rs b/tests/upsample.rs index cd999e39..4aa82dbf 100644 --- a/tests/upsample.rs +++ b/tests/upsample.rs @@ -66,7 +66,8 @@ fn upsample() -> ort::Result<()> { let array = convert_image_to_cow_array(&image_buffer); // Perform the inference - let outputs = session.run(inputs![&array]?)?; + let run_options = None; + let outputs = session.run(inputs![&array]?, run_options)?; assert_eq!(outputs.len(), 1); let output: Tensor = outputs[0].extract_tensor()?; @@ -103,7 +104,8 @@ fn upsample_with_ort_model() -> ort::Result<()> { let array = convert_image_to_cow_array(&image_buffer); // Perform the inference - let outputs = session.run(inputs![&array]?)?; + let run_options = None; + let outputs = session.run(inputs![&array]?, run_options)?; assert_eq!(outputs.len(), 1); let output: Tensor = outputs[0].extract_tensor()?; diff --git a/tests/vectorizer.rs b/tests/vectorizer.rs index 89f09668..5ade7ed9 100644 --- a/tests/vectorizer.rs +++ b/tests/vectorizer.rs @@ -25,7 +25,8 @@ fn vectorizer() -> ort::Result<()> { let input_tensor_values = inputs![Value::from_string_array(session.allocator(), &array)?]?; // Perform the inference - let outputs = session.run(input_tensor_values)?; + let run_options = None; + let outputs = session.run(input_tensor_values, run_options)?; assert_eq!( *outputs[0].extract_tensor::()?.view(), ArrayD::from_shape_vec(IxDyn(&[1, 9]), vec![0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) From a68370297da02babcfb1ec2e3f0b7d270442ec94 Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Fri, 12 Jan 2024 11:12:20 -0600 Subject: [PATCH 04/23] refactor: split out `run_with_options` --- examples/gpt2/examples/gpt2-no-ndarray.rs | 3 +-- examples/gpt2/examples/gpt2.rs | 3 +-- examples/yolov8/examples/yolov8.rs | 3 +-- src/io_binding.rs | 10 +++++++- src/session/input.rs | 6 ++--- src/session/mod.rs | 28 +++++++++++++++++++---- tests/mnist.rs | 3 +-- tests/squeezenet.rs | 3 +-- tests/upsample.rs | 6 ++--- tests/vectorizer.rs | 3 +-- 10 files changed, 42 insertions(+), 26 deletions(-) diff --git a/examples/gpt2/examples/gpt2-no-ndarray.rs b/examples/gpt2/examples/gpt2-no-ndarray.rs index c9557ec5..832bb39d 100644 --- a/examples/gpt2/examples/gpt2-no-ndarray.rs +++ b/examples/gpt2/examples/gpt2-no-ndarray.rs @@ -50,8 +50,7 @@ fn main() -> ort::Result<()> { // Raw tensor construction takes a tuple of (dimensions, data). // The model expects our input to have shape [B, _, S] let input = (vec![1, 1, tokens.len() as i64], Arc::clone(&tokens)); - let run_options = None; - let outputs = session.run(inputs![input]?, run_options)?; + let outputs = session.run(inputs![input]?)?; let (dim, mut probabilities) = outputs["output1"].extract_raw_tensor()?; // The output tensor will have shape [B, _, S + 1, V] diff --git a/examples/gpt2/examples/gpt2.rs b/examples/gpt2/examples/gpt2.rs index 39aa73fa..c75cae9c 100644 --- a/examples/gpt2/examples/gpt2.rs +++ b/examples/gpt2/examples/gpt2.rs @@ -50,8 +50,7 @@ fn main() -> ort::Result<()> { for _ in 0..GEN_TOKENS { let array = tokens.view().insert_axis(Axis(0)).insert_axis(Axis(1)); - let run_options = None; - let outputs = session.run(inputs![array]?, run_options)?; + let outputs = session.run(inputs![array]?)?; let generated_tokens: Tensor = outputs["output1"].extract_tensor()?; let generated_tokens = generated_tokens.view(); diff --git a/examples/yolov8/examples/yolov8.rs b/examples/yolov8/examples/yolov8.rs index c5f6cf1c..b7fa423d 100644 --- a/examples/yolov8/examples/yolov8.rs +++ b/examples/yolov8/examples/yolov8.rs @@ -62,8 +62,7 @@ fn main() -> ort::Result<()> { let model = Session::builder()?.with_model_downloaded(YOLOV8M_URL)?; // Run YOLOv8 inference - let run_options = None; - let outputs: SessionOutputs = model.run(inputs!["images" => input.view()]?, run_options)?; + let outputs: SessionOutputs = model.run(inputs!["images" => input.view()]?)?; let output = outputs["output0"].extract_tensor::().unwrap().view().t().into_owned(); let mut boxes = Vec::new(); diff --git a/src/io_binding.rs b/src/io_binding.rs index b60378f9..f2051edf 100644 --- a/src/io_binding.rs +++ b/src/io_binding.rs @@ -64,7 +64,15 @@ impl<'s> IoBinding<'s> { Ok(()) } - pub fn run<'i: 's>(&'i self, run_options: Option>) -> Result> { + pub fn run<'i: 's>(&'i self) -> Result> { + self.run_inner(None) + } + + pub fn run_with_options<'i: 's>(&'i self, run_options: Arc) -> Result> { + self.run_inner(Some(run_options)) + } + + fn run_inner<'i: 's>(&'i self, run_options: Option>) -> Result> { let run_options_ptr = if let Some(run_options) = run_options { run_options.run_options_ptr } else { diff --git a/src/session/input.rs b/src/session/input.rs index 67591e1d..15b91a2e 100644 --- a/src/session/input.rs +++ b/src/session/input.rs @@ -44,8 +44,7 @@ impl<'i, const N: usize> From<[Value; N]> for SessionInputs<'i, N> { /// # use ort::{GraphOptimizationLevel, Session}; /// # fn main() -> Result<(), Box> { /// let mut session = Session::builder()?.with_model_from_file("model.onnx")?; -/// let run_options = None; -/// let _ = session.run(ort::inputs![Array1::from_vec(vec![1, 2, 3, 4, 5])]?, run_options); +/// let _ = session.run(ort::inputs![Array1::from_vec(vec![1, 2, 3, 4, 5])]?); /// # Ok(()) /// # } /// ``` @@ -58,9 +57,8 @@ impl<'i, const N: usize> From<[Value; N]> for SessionInputs<'i, N> { /// # use ort::{GraphOptimizationLevel, Session}; /// # fn main() -> Result<(), Box> { /// let mut session = Session::builder()?.with_model_from_file("model.onnx")?; -/// let run_options = None; /// let _ = session.run(ort::inputs! { -/// "tokens" => Array1::from_vec(vec![1, 2, 3, 4, 5], run_options) +/// "tokens" => Array1::from_vec(vec![1, 2, 3, 4, 5]) /// }?); /// # Ok(()) /// # } diff --git a/src/session/mod.rs b/src/session/mod.rs index 023d59cb..2dd607a2 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -623,23 +623,41 @@ impl Session { } /// Run the input data through the ONNX graph, performing inference. - pub fn run<'s, 'i, const N: usize>( + pub fn run<'s, 'i, const N: usize>(&'s self, input_values: impl Into>) -> Result> { + match input_values.into() { + SessionInputs::ValueSlice(input_values) => { + let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), input_values, None)?; + Ok(outputs) + } + SessionInputs::ValueArray(input_values) => { + let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), &input_values, None)?; + Ok(outputs) + } + SessionInputs::ValueMap(input_values) => { + let (input_names, values): (Vec<&'static str>, Vec) = input_values.into_iter().unzip(); + self.run_inner(&input_names, &values, None) + } + } + } + + /// Run the input data through the ONNX graph, performing inference. + pub fn run_with_options<'s, 'i, const N: usize>( &'s self, input_values: impl Into>, - run_options: Option> + run_options: Arc ) -> Result> { match input_values.into() { SessionInputs::ValueSlice(input_values) => { - let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), input_values, run_options)?; + let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), input_values, Some(run_options))?; Ok(outputs) } SessionInputs::ValueArray(input_values) => { - let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), &input_values, run_options)?; + let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), &input_values, Some(run_options))?; Ok(outputs) } SessionInputs::ValueMap(input_values) => { let (input_names, values): (Vec<&'static str>, Vec) = input_values.into_iter().unzip(); - self.run_inner(&input_names, &values, run_options) + self.run_inner(&input_names, &values, Some(run_options)) } } } diff --git a/tests/mnist.rs b/tests/mnist.rs index 30ec44a5..9ab190f9 100644 --- a/tests/mnist.rs +++ b/tests/mnist.rs @@ -41,8 +41,7 @@ fn mnist_5() -> ort::Result<()> { }); // Perform the inference - let run_options = None; - let outputs = session.run(inputs![array]?, run_options)?; + let outputs = session.run(inputs![array]?)?; let output: Tensor<_> = outputs[0].extract_tensor()?; let mut probabilities: Vec<(usize, f32)> = output.view().softmax(ndarray::Axis(1)).iter().copied().enumerate().collect::>(); diff --git a/tests/squeezenet.rs b/tests/squeezenet.rs index 349fd1b9..7dba0371 100644 --- a/tests/squeezenet.rs +++ b/tests/squeezenet.rs @@ -66,8 +66,7 @@ fn squeezenet_mushroom() -> ort::Result<()> { } // Perform the inference - let run_options = None; - let outputs = session.run(inputs![array]?, run_options)?; + let outputs = session.run(inputs![array]?)?; // Downloaded model does not have a softmax as final layer; call softmax on second axis // and iterate on resulting probabilities, creating an index to later access labels. diff --git a/tests/upsample.rs b/tests/upsample.rs index 4aa82dbf..cd999e39 100644 --- a/tests/upsample.rs +++ b/tests/upsample.rs @@ -66,8 +66,7 @@ fn upsample() -> ort::Result<()> { let array = convert_image_to_cow_array(&image_buffer); // Perform the inference - let run_options = None; - let outputs = session.run(inputs![&array]?, run_options)?; + let outputs = session.run(inputs![&array]?)?; assert_eq!(outputs.len(), 1); let output: Tensor = outputs[0].extract_tensor()?; @@ -104,8 +103,7 @@ fn upsample_with_ort_model() -> ort::Result<()> { let array = convert_image_to_cow_array(&image_buffer); // Perform the inference - let run_options = None; - let outputs = session.run(inputs![&array]?, run_options)?; + let outputs = session.run(inputs![&array]?)?; assert_eq!(outputs.len(), 1); let output: Tensor = outputs[0].extract_tensor()?; diff --git a/tests/vectorizer.rs b/tests/vectorizer.rs index 5ade7ed9..89f09668 100644 --- a/tests/vectorizer.rs +++ b/tests/vectorizer.rs @@ -25,8 +25,7 @@ fn vectorizer() -> ort::Result<()> { let input_tensor_values = inputs![Value::from_string_array(session.allocator(), &array)?]?; // Perform the inference - let run_options = None; - let outputs = session.run(input_tensor_values, run_options)?; + let outputs = session.run(input_tensor_values)?; assert_eq!( *outputs[0].extract_tensor::()?.view(), ArrayD::from_shape_vec(IxDyn(&[1, 9]), vec![0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]) From ea7d0597f7b4c35c041e3cc783d0b146be77256c Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Fri, 12 Jan 2024 16:47:27 -0600 Subject: [PATCH 05/23] feat: update environment after initialization --- src/environment.rs | 126 +++++++++++++++++++++++++-------------------- src/session/mod.rs | 18 +++++-- tests/upsample.rs | 38 +++++++++++++- 3 files changed, 120 insertions(+), 62 deletions(-) diff --git a/src/environment.rs b/src/environment.rs index 1373db0d..ba4ad641 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -1,9 +1,4 @@ -#[cfg(feature = "load-dynamic")] -use std::sync::Arc; -use std::{ - ffi::CString, - sync::{atomic::AtomicPtr, OnceLock} -}; +use std::{cell::UnsafeCell, ffi::CString, sync::atomic::AtomicPtr, sync::Arc}; use tracing::debug; @@ -15,20 +10,40 @@ use super::{ #[cfg(feature = "load-dynamic")] use crate::G_ORT_DYLIB_PATH; -static G_ENV: OnceLock = OnceLock::new(); +struct EnvironmentSingleton { + cell: UnsafeCell>> +} + +unsafe impl Sync for EnvironmentSingleton {} + +static G_ENV: EnvironmentSingleton = EnvironmentSingleton { cell: UnsafeCell::new(None) }; #[derive(Debug)] -pub(crate) struct EnvironmentSingleton { +pub(crate) struct Environment { pub(crate) execution_providers: Vec, pub(crate) env_ptr: AtomicPtr } -pub(crate) fn get_environment() -> Result<&'static EnvironmentSingleton> { - if G_ENV.get().is_none() { - EnvironmentBuilder::default().commit()?; - Ok(G_ENV.get().unwrap()) +impl Drop for Environment { + #[tracing::instrument] + fn drop(&mut self) { + let env_ptr: *mut ort_sys::OrtEnv = *self.env_ptr.get_mut(); + + debug!("Releasing environment"); + + assert_ne!(env_ptr, std::ptr::null_mut()); + ortsys![unsafe ReleaseEnv(env_ptr)]; + } +} + +pub(crate) fn get_environment() -> Result<&'static Arc> { + if let Some(c) = unsafe { &*G_ENV.cell.get() } { + Ok(c) } else { - Ok(unsafe { G_ENV.get().unwrap_unchecked() }) + debug!("Environment not yet initialized, creating a new one"); + EnvironmentBuilder::default().commit()?; + + Ok(unsafe { (*G_ENV.cell.get()).as_ref().unwrap_unchecked() }) } } @@ -131,32 +146,29 @@ impl EnvironmentBuilder { /// Commit the configuration to a new [`Environment`]. pub fn commit(self) -> Result<()> { - if G_ENV.get().is_none() { - debug!("Environment not yet initialized, creating a new one"); - - let env_ptr = if let Some(global_thread_pool) = self.global_thread_pool_options { - let mut env_ptr: *mut ort_sys::OrtEnv = std::ptr::null_mut(); - let logging_function: ort_sys::OrtLoggingFunction = Some(custom_logger); - let logger_param: *mut std::ffi::c_void = std::ptr::null_mut(); - let cname = CString::new(self.name.clone()).unwrap(); - - let mut thread_options: *mut ort_sys::OrtThreadingOptions = std::ptr::null_mut(); - ortsys![unsafe CreateThreadingOptions(&mut thread_options) -> Error::CreateEnvironment; nonNull(thread_options)]; - if let Some(inter_op_parallelism) = global_thread_pool.inter_op_parallelism { - ortsys![unsafe SetGlobalInterOpNumThreads(thread_options, inter_op_parallelism) -> Error::CreateEnvironment]; - } - if let Some(intra_op_parallelism) = global_thread_pool.intra_op_parallelism { - ortsys![unsafe SetGlobalIntraOpNumThreads(thread_options, intra_op_parallelism) -> Error::CreateEnvironment]; - } - if let Some(spin_control) = global_thread_pool.spin_control { - ortsys![unsafe SetGlobalSpinControl(thread_options, if spin_control { 1 } else { 0 }) -> Error::CreateEnvironment]; - } - if let Some(intra_op_thread_affinity) = global_thread_pool.intra_op_thread_affinity { - let cstr = CString::new(intra_op_thread_affinity).unwrap(); - ortsys![unsafe SetGlobalIntraOpThreadAffinity(thread_options, cstr.as_ptr()) -> Error::CreateEnvironment]; - } - - ortsys![unsafe CreateEnvWithCustomLoggerAndGlobalThreadPools( + let env_ptr = if let Some(global_thread_pool) = self.global_thread_pool_options { + let mut env_ptr: *mut ort_sys::OrtEnv = std::ptr::null_mut(); + let logging_function: ort_sys::OrtLoggingFunction = Some(custom_logger); + let logger_param: *mut std::ffi::c_void = std::ptr::null_mut(); + let cname = CString::new(self.name.clone()).unwrap(); + + let mut thread_options: *mut ort_sys::OrtThreadingOptions = std::ptr::null_mut(); + ortsys![unsafe CreateThreadingOptions(&mut thread_options) -> Error::CreateEnvironment; nonNull(thread_options)]; + if let Some(inter_op_parallelism) = global_thread_pool.inter_op_parallelism { + ortsys![unsafe SetGlobalInterOpNumThreads(thread_options, inter_op_parallelism) -> Error::CreateEnvironment]; + } + if let Some(intra_op_parallelism) = global_thread_pool.intra_op_parallelism { + ortsys![unsafe SetGlobalIntraOpNumThreads(thread_options, intra_op_parallelism) -> Error::CreateEnvironment]; + } + if let Some(spin_control) = global_thread_pool.spin_control { + ortsys![unsafe SetGlobalSpinControl(thread_options, if spin_control { 1 } else { 0 }) -> Error::CreateEnvironment]; + } + if let Some(intra_op_thread_affinity) = global_thread_pool.intra_op_thread_affinity { + let cstr = CString::new(intra_op_thread_affinity).unwrap(); + ortsys![unsafe SetGlobalIntraOpThreadAffinity(thread_options, cstr.as_ptr()) -> Error::CreateEnvironment]; + } + + ortsys![unsafe CreateEnvWithCustomLoggerAndGlobalThreadPools( logging_function, logger_param, ort_sys::OrtLoggingLevel::ORT_LOGGING_LEVEL_VERBOSE, @@ -164,30 +176,32 @@ impl EnvironmentBuilder { thread_options, &mut env_ptr ) -> Error::CreateEnvironment; nonNull(env_ptr)]; - ortsys![unsafe ReleaseThreadingOptions(thread_options)]; - env_ptr - } else { - let mut env_ptr: *mut ort_sys::OrtEnv = std::ptr::null_mut(); - let logging_function: ort_sys::OrtLoggingFunction = Some(custom_logger); - // FIXME: What should go here? - let logger_param: *mut std::ffi::c_void = std::ptr::null_mut(); - let cname = CString::new(self.name.clone()).unwrap(); - ortsys![unsafe CreateEnvWithCustomLogger( + ortsys![unsafe ReleaseThreadingOptions(thread_options)]; + env_ptr + } else { + let mut env_ptr: *mut ort_sys::OrtEnv = std::ptr::null_mut(); + let logging_function: ort_sys::OrtLoggingFunction = Some(custom_logger); + // FIXME: What should go here? + let logger_param: *mut std::ffi::c_void = std::ptr::null_mut(); + let cname = CString::new(self.name.clone()).unwrap(); + ortsys![unsafe CreateEnvWithCustomLogger( logging_function, logger_param, ort_sys::OrtLoggingLevel::ORT_LOGGING_LEVEL_VERBOSE, cname.as_ptr(), &mut env_ptr ) -> Error::CreateEnvironment; nonNull(env_ptr)]; - env_ptr - }; - debug!(env_ptr = format!("{:?}", env_ptr).as_str(), "Environment created"); + env_ptr + }; + debug!(env_ptr = format!("{:?}", env_ptr).as_str(), "Environment created"); - let _ = G_ENV.set(EnvironmentSingleton { + unsafe { + *G_ENV.cell.get() = Some(Arc::new(Environment { execution_providers: self.execution_providers, env_ptr: AtomicPtr::new(env_ptr) - }); - } + })); + }; + Ok(()) } } @@ -223,11 +237,11 @@ mod tests { use super::*; fn is_env_initialized() -> bool { - G_ENV.get().is_some() && !G_ENV.get().unwrap().env_ptr.load(Ordering::Relaxed).is_null() + unsafe { (*G_ENV.cell.get()).as_ref() }.is_some() && !unsafe { (*G_ENV.cell.get()).as_ref() }.unwrap().env_ptr.load(Ordering::Relaxed).is_null() } fn env_ptr() -> Option<*mut ort_sys::OrtEnv> { - G_ENV.get().map(|f| f.env_ptr.load(Ordering::Relaxed)) + unsafe { (*G_ENV.cell.get()).as_ref() }.map(|f| f.env_ptr.load(Ordering::Relaxed)) } struct ConcurrentTestRun { diff --git a/src/session/mod.rs b/src/session/mod.rs index 2dd607a2..7ce0a393 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -32,6 +32,7 @@ use super::{ value::{Value, ValueType}, AllocatorType, GraphOptimizationLevel, MemType }; +use crate::environment::Environment; pub(crate) mod input; pub(crate) mod output; @@ -432,7 +433,11 @@ impl SessionBuilder { .collect::>>()?; Ok(Session { - inner: Arc::new(SharedSessionInner { session_ptr, allocator }), + inner: Arc::new(SharedSessionInner { + session_ptr, + allocator, + _environment: Arc::clone(env) + }), inputs, outputs }) @@ -490,7 +495,11 @@ impl SessionBuilder { .collect::>>()?; let session = Session { - inner: Arc::new(SharedSessionInner { session_ptr, allocator }), + inner: Arc::new(SharedSessionInner { + session_ptr, + allocator, + _environment: Arc::clone(env) + }), inputs, outputs }; @@ -503,7 +512,8 @@ impl SessionBuilder { #[derive(Debug)] pub struct SharedSessionInner { pub(crate) session_ptr: *mut ort_sys::OrtSession, - allocator: Allocator + allocator: Allocator, + _environment: Arc } unsafe impl Send for SharedSessionInner {} @@ -680,7 +690,7 @@ impl Session { // The C API expects pointers for the arrays (pointers to C-arrays) let input_ort_values: Vec<*const ort_sys::OrtValue> = input_values.iter().map(|input_array_ort| input_array_ort.ptr() as *const _).collect(); - let run_options_ptr = if let Some(run_options) = run_options { + let run_options_ptr = if let Some(run_options) = &run_options { run_options.run_options_ptr } else { std::ptr::null_mut() diff --git a/tests/upsample.rs b/tests/upsample.rs index cd999e39..7eac4732 100644 --- a/tests/upsample.rs +++ b/tests/upsample.rs @@ -1,8 +1,11 @@ -use std::path::Path; +use std::{ + path::Path, + sync::{mpsc, Arc} +}; use image::RgbImage; use ndarray::{Array, CowArray, Ix4}; -use ort::{inputs, GraphOptimizationLevel, Session, Tensor}; +use ort::{inputs, GraphOptimizationLevel, RunOptions, Session, Tensor}; use test_log::test; fn load_input_image>(name: P) -> RgbImage { @@ -113,3 +116,34 @@ fn upsample_with_ort_model() -> ort::Result<()> { Ok(()) } + +#[test] +fn upsample_termination() -> ort::Result<()> { + const IMAGE_TO_LOAD: &str = "mushroom.png"; + + ort::init().with_name("integration_test").commit()?; + + let session_data = + std::fs::read(Path::new(env!("CARGO_MANIFEST_DIR")).join("tests").join("data").join("upsample.onnx")).expect("Could not open model from file"); + let session = Session::builder()? + .with_model_from_memory(&session_data) + .expect("Could not read model from memory"); + + // Load image, converting to RGB format + let image_buffer = load_input_image(IMAGE_TO_LOAD); + let array = convert_image_to_cow_array(&image_buffer); + + let run_options = Arc::new(RunOptions::new()?); + let (sender, receiver) = mpsc::channel::<()>(); + + let run_options_ = Arc::clone(&run_options); + std::thread::spawn(move || { + receiver.recv().unwrap(); + run_options_.set_terminate().unwrap(); + }); + + sender.send(()).unwrap(); + panic!("{:?}", session.run_with_options(inputs![&array]?, run_options).map(|_| ()).unwrap_err()); + + Ok(()) +} From ca8ea3942147d930c56f4a306824f8b4ae31e219 Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Fri, 12 Jan 2024 16:50:11 -0600 Subject: [PATCH 06/23] fix: revert upsample.rs --- tests/upsample.rs | 38 ++------------------------------------ 1 file changed, 2 insertions(+), 36 deletions(-) diff --git a/tests/upsample.rs b/tests/upsample.rs index 7eac4732..cd999e39 100644 --- a/tests/upsample.rs +++ b/tests/upsample.rs @@ -1,11 +1,8 @@ -use std::{ - path::Path, - sync::{mpsc, Arc} -}; +use std::path::Path; use image::RgbImage; use ndarray::{Array, CowArray, Ix4}; -use ort::{inputs, GraphOptimizationLevel, RunOptions, Session, Tensor}; +use ort::{inputs, GraphOptimizationLevel, Session, Tensor}; use test_log::test; fn load_input_image>(name: P) -> RgbImage { @@ -116,34 +113,3 @@ fn upsample_with_ort_model() -> ort::Result<()> { Ok(()) } - -#[test] -fn upsample_termination() -> ort::Result<()> { - const IMAGE_TO_LOAD: &str = "mushroom.png"; - - ort::init().with_name("integration_test").commit()?; - - let session_data = - std::fs::read(Path::new(env!("CARGO_MANIFEST_DIR")).join("tests").join("data").join("upsample.onnx")).expect("Could not open model from file"); - let session = Session::builder()? - .with_model_from_memory(&session_data) - .expect("Could not read model from memory"); - - // Load image, converting to RGB format - let image_buffer = load_input_image(IMAGE_TO_LOAD); - let array = convert_image_to_cow_array(&image_buffer); - - let run_options = Arc::new(RunOptions::new()?); - let (sender, receiver) = mpsc::channel::<()>(); - - let run_options_ = Arc::clone(&run_options); - std::thread::spawn(move || { - receiver.recv().unwrap(); - run_options_.set_terminate().unwrap(); - }); - - sender.send(()).unwrap(); - panic!("{:?}", session.run_with_options(inputs![&array]?, run_options).map(|_| ()).unwrap_err()); - - Ok(()) -} From 127154b591f335e328226ae0fe6257bfc7c09da3 Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Sat, 13 Jan 2024 11:32:56 -0600 Subject: [PATCH 07/23] fix: drop global reference to previous environment on new environment commit --- src/environment.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/environment.rs b/src/environment.rs index ba4ad641..69a7f8e2 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -146,6 +146,9 @@ impl EnvironmentBuilder { /// Commit the configuration to a new [`Environment`]. pub fn commit(self) -> Result<()> { + // drop global reference to previous environment + drop(unsafe { (*G_ENV.cell.get()).take() }); + let env_ptr = if let Some(global_thread_pool) = self.global_thread_pool_options { let mut env_ptr: *mut ort_sys::OrtEnv = std::ptr::null_mut(); let logging_function: ort_sys::OrtLoggingFunction = Some(custom_logger); From e33c7b8ed5b1ff734168385e0ff64b90cd1ae831 Mon Sep 17 00:00:00 2001 From: ling jia Date: Tue, 16 Jan 2024 02:01:36 +0800 Subject: [PATCH 08/23] examples: add example for image matting with ModNet (#142) * Add ModNet example * Add show-image crate for visualizing output * use parcel model download --------- Co-authored-by: Carson M --- Cargo.toml | 6 ++- examples/modnet/Cargo.toml | 17 ++++++ examples/modnet/data/photo.jpg | Bin 0 -> 128709 bytes examples/modnet/examples/modnet.rs | 83 +++++++++++++++++++++++++++++ 4 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 examples/modnet/Cargo.toml create mode 100644 examples/modnet/data/photo.jpg create mode 100644 examples/modnet/examples/modnet.rs diff --git a/Cargo.toml b/Cargo.toml index 2a6fd5fa..ea810df3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,13 +3,15 @@ members = [ 'ort-sys', 'examples/gpt2', 'examples/model-info', - 'examples/yolov8' + 'examples/yolov8', + 'examples/modnet' ] default-members = [ '.', 'examples/gpt2', 'examples/model-info', - 'examples/yolov8' + 'examples/yolov8', + 'examples/modnet' ] [package] diff --git a/examples/modnet/Cargo.toml b/examples/modnet/Cargo.toml new file mode 100644 index 00000000..8ee083be --- /dev/null +++ b/examples/modnet/Cargo.toml @@ -0,0 +1,17 @@ +[package] +publish = false +name = "example-modnet" +version = "0.0.0" +edition = "2021" + +[dependencies] +ort = { path = "../../" } +ndarray = "0.15" +tracing-subscriber = { version = "0.3", default-features = false, features = [ "env-filter", "fmt" ] } +image = "0.24" +tracing = "0.1" +show-image = { version = "0.13", features = [ "image", "raqote" ] } + +[features] +load-dynamic = [ "ort/load-dynamic" ] +cuda = [ "ort/cuda" ] diff --git a/examples/modnet/data/photo.jpg b/examples/modnet/data/photo.jpg new file mode 100644 index 0000000000000000000000000000000000000000..0ccd686976f404ed5bb448e01d12a92661f52e75 GIT binary patch literal 128709 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
Coverage Results Crates.io Open Collective backers and sponsors
- Crates.io ONNX Runtime + Crates.io ONNX Runtime -`ort` is an (unofficial) [ONNX Runtime](https://onnxruntime.ai/) 1.16 wrapper for Rust based on the now inactive [`onnxruntime-rs`](https://github.com/nbigaouette/onnxruntime-rs). ONNX Runtime accelerates ML inference on both CPU & GPU. +`ort` is an (unofficial) [ONNX Runtime](https://onnxruntime.ai/) 1.17 wrapper for Rust based on the now inactive [`onnxruntime-rs`](https://github.com/nbigaouette/onnxruntime-rs). ONNX Runtime accelerates ML inference on both CPU & GPU. ## ๐Ÿ“– Documentation - [Guide](https://ort.pyke.io/) diff --git a/docs/introduction.mdx b/docs/introduction.mdx index df6235b7..388b9977 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -19,7 +19,7 @@ For one, `ort` simply supports more features: | Feature comparison | **๐Ÿ“• ort** | **๐Ÿ“— [ors](https://github.com/HaoboGu/ors)** | **๐ŸชŸ [onnxruntime-rs](https://github.com/microsoft/onnxruntime/tree/main/rust)** | |---------------------------|-----------|-----------|----------------------| -| Upstream version | **v1.16.3** | v1.12.0 | v1.8 | +| Upstream version | **v1.17.0** | v1.12.0 | v1.8 | | `dlopen()`? | โœ… | โœ… | โŒ | | Execution providers? | โœ… | โŒ | โŒ | | I/O Binding? | โœ… | โŒ | โŒ | diff --git a/docs/migrating/v2.mdx b/docs/migrating/v2.mdx index 3bc2407d..03bdada4 100644 --- a/docs/migrating/v2.mdx +++ b/docs/migrating/v2.mdx @@ -14,7 +14,7 @@ ort::init() .commit()?; ``` -`commit()` must be called before any sessions are created to take effect. Otherwise, the environment will be set to default and cannot be modified afterwards. +`commit()` must be called before any sessions are created to take effect. Otherwise, a default environment will be created. The global environment can be updated afterward by calling `commit()` on another `EnvironmentBuilder`, however you'll need to recreate sessions after comitting the new environment in order for them to use it. ## Session creation `SessionBuilder::new(&environment)` has been soft-replaced with `Session::builder()`: diff --git a/docs/migrating/version-mapping.mdx b/docs/migrating/version-mapping.mdx index ad5abae6..a48a6306 100644 --- a/docs/migrating/version-mapping.mdx +++ b/docs/migrating/version-mapping.mdx @@ -6,7 +6,7 @@ description: Information about `ort`'s versioning and relation to ONNX Runtime v ## A note on SemVer `ort` versions pre-2.0 were not SemVer compatible. From v2.0 onwards, breaking API changes are accompanied by a **major version update**. -Updates to the version of ONNX Runtime used by `ort` may occur on **minor** version updates, i.e. 2.0 ships with ONNX Runtime 1.16.2, but 2.1 may ship with 1.17.0. ONNX Runtime is generally forward compatible, but in case you require a specific version of ONNX Runtime, you should pin the minor version in your `Cargo.toml` using a [tilde requirement](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements): +Updates to the version of ONNX Runtime used by `ort` may occur on **minor** version updates, i.e. 2.0 ships with ONNX Runtime 1.17.0, but 2.1 may ship with 1.18.0. ONNX Runtime is generally forward compatible, but in case you require a specific version of ONNX Runtime, you should pin the minor version in your `Cargo.toml` using a [tilde requirement](https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#tilde-requirements): ```toml [dependencies] ort = { version = "~2.0", ... } @@ -16,12 +16,10 @@ ort = { version = "~2.0", ... } | **ort** | **ONNX Runtime** | | -------- | ----------------:| -| v2.0.0+ | v1.16.2 | +| v2.0.0+ | v1.17.0 | | v1.16.0-v1.16.2 | v1.16.0 | | v1.15.0-v1.15.5 | v1.15.1 | | v1.14.2-v1.14.8 | v1.14.1 | | v1.14.0-v1.14.1 | v1.14.0 | | v1.13.1-v1.13.3 | v1.13.1 | | v1.13.0 | v1.12.1 | - -If you need support for an old (<1.15) version of `ort`, or need an even older version of ONNX Runtime, [contact us](mailto:contact@pyke.io). diff --git a/ort-sys/Cargo.toml b/ort-sys/Cargo.toml index 6d31ee77..97230dc2 100644 --- a/ort-sys/Cargo.toml +++ b/ort-sys/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "ort-sys" -description = "Unsafe Rust bindings for ONNX Runtime 1.16 - Optimize and Accelerate Machine Learning Inferencing" +description = "Unsafe Rust bindings for ONNX Runtime 1.17 - Optimize and Accelerate Machine Learning Inferencing" version = "2.0.0-alpha.4" edition = "2021" rust-version = "1.70" From 018cff16dae938b9b2acc0077c0b5683f7765563 Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Tue, 6 Feb 2024 14:56:29 -0600 Subject: [PATCH 22/23] docs: add notes on platform version support --- docs/introduction.mdx | 3 +-- docs/setup/platforms.mdx | 11 ++++++++--- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/introduction.mdx b/docs/introduction.mdx index 388b9977..75d63cd0 100644 --- a/docs/introduction.mdx +++ b/docs/introduction.mdx @@ -9,7 +9,7 @@ title: Introduction `ort` makes it easy to deploy your machine learning models to production via [ONNX Runtime](https://onnxruntime.ai/), a hardware-accelerated inference engine. With `ort` + ONNX Runtime, you can run almost any ML model (including ResNet, YOLOv8, BERT, LLaMA) on almost any hardware, often far faster than PyTorch, and with the added bonus of Rust's efficiency. - These docs are for the latest alpha version of `ort`, `2.0.0-alpha.4`. This alpha version is production-ready (just not API stable) and we recommend new & existing projects use it. + These docs are for the latest alpha version of `ort`, `2.0.0-rc.0`. This version is production-ready (just not API stable) and we recommend new & existing projects use it. # Why `ort`? @@ -38,7 +38,6 @@ Users of `ort` appreciate its ease of use and ergonomic API. `ort` is also battl - [**Numerical Elixir**](https://github.com/elixir-nx) uses `ort` to create ONNX Runtime bindings for the Elixir language. - [**`rust-bert`**](https://github.com/guillaume-be/rust-bert) implements many ready-to-use NLP pipelines in Rust ร  la Hugging Face Transformers with both [`tch`](https://crates.io/crates/tch) & `ort` backends. - [**`edge-transformers`**](https://github.com/npc-engine/edge-transformers) also implements Hugging Face Transformers pipelines in Rust using `ort`. -- We use `ort` in nearly all of our ML projects, including [VITRI](https://vitri.pyke.io/) ๐Ÿ˜Š # Getting started diff --git a/docs/setup/platforms.mdx b/docs/setup/platforms.mdx index 6384b8ce..00ece644 100644 --- a/docs/setup/platforms.mdx +++ b/docs/setup/platforms.mdx @@ -12,13 +12,18 @@ Here are the supported platforms and binary availability status, as of v2.0. | Platform | x86 | x86-64 | ARMv7 | ARM64 | WASM32 | |:-------- |:------- |:------ |:------ |:------ |:------ | -| **Windows** | โญ• | ๐ŸŸข | โญ• | ๐Ÿ”ท | โŒ | -| **Linux** | โญ• | ๐ŸŸข | โญ• | ๐Ÿ”ท | โŒ | -| **macOS** | โŒ | ๐Ÿ”ท | โŒ | ๐Ÿ”ท | โŒ | +| **Windows** | โญ• | ๐ŸŸข\* | โญ• | ๐Ÿ”ท\* | โŒ | +| **Linux** | โญ• | ๐ŸŸขโ€  | โญ• | ๐Ÿ”ทโ€ก | โŒ | +| **macOS** | โŒ | ๐Ÿ”ทยง | โŒ | ๐Ÿ”ทยง | โŒ | | **iOS** | โŒ | โŒ | โŒ | โญ• | โŒ | | **Android** | โŒ | โŒ | โญ• | โญ• | โŒ | | **Web** | โŒ | โŒ | โŒ | โŒ | ๐Ÿ”ท | +\* Recent version of Windows 10/11 required for pyke binaries.
+โ€  glibc โ‰ฅ 2.31 (Ubuntu โ‰ฅ 20.04) required for pyke binaries.
+โ€ก glibc โ‰ฅ 2.35 (Ubuntu โ‰ฅ 22.04) required for pyke binaries.
+ยง macOS โ‰ฅ 10.15 required for pyke binaries. + If your platform is marked as ๐ŸŸข or ๐Ÿ”ท, you're in luck! Almost no setup will be required to get `ort` up and running. For platforms marked as โญ•, you'll need to [compile ONNX Runtime from source](https://onnxruntime.ai/docs/build/). Certain execution providers may not have binaries available. You can check EP binary support in the [execution providers](/perf/execution-providers) documentation. From 90b8d3045fe7b483ebea0b7bf0d31a4368bc2e49 Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Tue, 6 Feb 2024 14:57:48 -0600 Subject: [PATCH 23/23] 2.0.0-rc.0 --- Cargo.toml | 4 ++-- ort-sys/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e6fee67b..1f1c6874 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ default-members = [ [package] name = "ort" description = "A safe Rust wrapper for ONNX Runtime 1.17 - Optimize and Accelerate Machine Learning Inferencing" -version = "2.0.0-alpha.4" +version = "2.0.0-rc.0" edition = "2021" rust-version = "1.70" license = "MIT OR Apache-2.0" @@ -30,7 +30,7 @@ authors = [ "pyke.io ", "Nicolas Bigaouette " ] -include = [ "src/", "examples/", "tests/", "LICENSE-APACHE", "LICENSE-MIT", "README.md" ] +include = [ "src/", "LICENSE-APACHE", "LICENSE-MIT", "README.md" ] [profile.release] opt-level = 3 diff --git a/ort-sys/Cargo.toml b/ort-sys/Cargo.toml index 97230dc2..32f6f6da 100644 --- a/ort-sys/Cargo.toml +++ b/ort-sys/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "ort-sys" description = "Unsafe Rust bindings for ONNX Runtime 1.17 - Optimize and Accelerate Machine Learning Inferencing" -version = "2.0.0-alpha.4" +version = "2.0.0-rc.0" edition = "2021" rust-version = "1.70" license = "MIT OR Apache-2.0"

BRojg5rdqkarEn4EVgh9sU6V8kGvZ= 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 ort::Result<()> { + tracing_subscriber::fmt::init(); + + ort::init() + .with_execution_providers([CUDAExecutionProvider::default().build()]) + .commit()?; + + let model = + Session::builder()?.with_model_downloaded("https://parcel.pyke.io/v2/cdn/assetdelivery/ortrsv2/ex_models/modnet_photographic_portrait_matting.onnx")?; + + let original_img = image::open(Path::new(env!("CARGO_MANIFEST_DIR")).join("data").join("photo.jpg")).unwrap(); + let (img_width, img_height) = (original_img.width(), original_img.height()); + let img = original_img.resize_exact(512, 512, FilterType::Triangle); + let mut input = Array::zeros((1, 3, 512, 512)); + for pixel in img.pixels() { + let x = pixel.0 as _; + let y = pixel.1 as _; + let [r, g, b, _] = pixel.2.0; + input[[0, 0, y, x]] = (r as f32 - 127.5) / 127.5; + input[[0, 1, y, x]] = (g as f32 - 127.5) / 127.5; + input[[0, 2, y, x]] = (b as f32 - 127.5) / 127.5; + } + + let outputs = model.run(inputs!["input" => input.view()]?)?; + + let binding = outputs["output"].extract_tensor::().unwrap(); + let output = binding.view(); + + // convert to 8-bit + let output = output.mul(255.0).map(|x| *x as u8); + let output = output.into_raw_vec(); + + // change rgb to rgba + let output_img = ImageBuffer::from_fn(512, 512, |x, y| { + let i = (x + y * 512) as usize; + Rgba([output[i], output[i], output[i], 255]) + }); + + let mut output = image::imageops::resize(&output_img, img_width, img_height, FilterType::Triangle); + output.enumerate_pixels_mut().for_each(|(x, y, pixel)| { + let origin = original_img.get_pixel(x, y); + pixel.0[3] = pixel.0[0]; + pixel.0[0] = origin.0[0]; + pixel.0[1] = origin.0[1]; + pixel.0[2] = origin.0[2]; + }); + + let window = show_image::context() + .run_function_wait(move |context| -> Result<_, String> { + let mut window = context + .create_window( + "ort + modnet", + WindowOptions { + size: Some([img_width, img_height]), + ..WindowOptions::default() + } + ) + .map_err(|e| e.to_string())?; + window.set_image("photo", &output.as_image_view().map_err(|e| e.to_string())?); + Ok(window.proxy()) + }) + .unwrap(); + + for event in window.event_channel().unwrap() { + if let event::WindowEvent::KeyboardInput(event) = event { + if event.input.key_code == Some(event::VirtualKeyCode::Escape) && event.input.state.is_pressed() { + break; + } + } + } + + Ok(()) +} From 187cb008af7dc27c0e16611474d9f00229a0b9a5 Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Mon, 15 Jan 2024 20:07:08 -0600 Subject: [PATCH 09/23] config(codecov): ignore execution provider tree --- codecov.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/codecov.yml b/codecov.yml index d39c511f..08ad6b8c 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,2 @@ ignore: - - src/download/**/*.rs - - src/download.rs + - src/execution_providers/**/*.rs From d4a9de3bb86dd3408a6bb4880970ea4bbd775163 Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Mon, 15 Jan 2024 20:07:49 -0600 Subject: [PATCH 10/23] test: add tensor value tests --- src/value.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/value.rs b/src/value.rs index 51feb82b..7bd737c0 100644 --- a/src/value.rs +++ b/src/value.rs @@ -764,3 +764,69 @@ pub(crate) unsafe fn extract_data_type_from_map_info(info_ptr: *const ort_sys::O value: value_type_sys.into() }) } + +#[cfg(test)] +mod tests { + use ndarray::{ArcArray1, Array1, CowArray}; + + use crate::*; + + #[test] + #[cfg(feature = "ndarray")] + fn test_tensor_value() -> crate::Result<()> { + let v: Vec = vec![1., 2., 3., 4., 5.]; + let value = Value::from_array(Array1::from_vec(v.clone()))?; + assert!(value.is_tensor()?); + assert_eq!(value.tensor_element_type()?, TensorElementType::Float32); + assert_eq!( + value.dtype()?, + ValueType::Tensor { + ty: TensorElementType::Float32, + dimensions: vec![v.len() as i64] + } + ); + + let (shape, data) = value.extract_raw_tensor::()?; + assert_eq!(shape, vec![v.len() as i64]); + assert_eq!(data, &v); + + Ok(()) + } + + #[test] + #[cfg(feature = "ndarray")] + fn test_tensor_lifetimes() -> crate::Result<()> { + let v: Vec = vec![1., 2., 3., 4., 5.]; + + let arc1 = ArcArray1::from_vec(v.clone()); + let mut arc2 = ArcArray1::clone(&arc1); + let value = Value::from_array(&mut arc2)?; + drop((arc1, arc2)); + + assert_eq!(value.extract_raw_tensor::()?.1, &v); + + let cow = CowArray::from(Array1::from_vec(v.clone())); + let value = Value::from_array(&cow)?; + assert_eq!(value.extract_raw_tensor::()?.1, &v); + + let owned = Array1::from_vec(v.clone()); + let value = Value::from_array(owned.view())?; + drop(owned); + assert_eq!(value.extract_raw_tensor::()?.1, &v); + + Ok(()) + } + + #[test] + fn test_tensor_raw_lifetimes() -> crate::Result<()> { + let v: Vec = vec![1., 2., 3., 4., 5.]; + + let arc = Arc::new(v.clone().into_boxed_slice()); + let shape = vec![v.len() as i64]; + let value = Value::from_array((shape, Arc::clone(&arc)))?; + drop(arc); + assert_eq!(value.extract_raw_tensor::()?.1, &v); + + Ok(()) + } +} From 4c748d519054a8fcad037bd572abba76b36026ba Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Mon, 15 Jan 2024 20:18:24 -0600 Subject: [PATCH 11/23] ci: update codecov-action to v3 --- .github/workflows/code-quality.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 5a3e2466..d3a1a0dc 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -73,6 +73,7 @@ jobs: run: | cargo tarpaulin -p ort --features fetch-models --verbose --timeout 120 --out xml - name: Upload to codecov.io - uses: codecov/codecov-action@v2 + uses: codecov/codecov-action@v3 with: + token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: false From 2fdba8f021924d40c6281427b4f8142e932d9bfb Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Mon, 15 Jan 2024 20:18:41 -0600 Subject: [PATCH 12/23] config(codecov): fix ignore path? --- codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/codecov.yml b/codecov.yml index 08ad6b8c..49416a8b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,2 +1,2 @@ ignore: - - src/execution_providers/**/*.rs + - "src/execution_providers" From 0380d4ff45cd4091864a63fab145c3c9fc6bfb40 Mon Sep 17 00:00:00 2001 From: Ryo Yamashita Date: Sun, 28 Jan 2024 02:47:58 +0900 Subject: [PATCH 13/23] fix: `copy-dylibs` should renew dangling symlinks (#146) --- ort-sys/build.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/ort-sys/build.rs b/ort-sys/build.rs index 1708242f..989f3554 100644 --- a/ort-sys/build.rs +++ b/ort-sys/build.rs @@ -67,6 +67,8 @@ fn copy_libraries(lib_dir: &Path, out_dir: &Path) { for out_dir in [out_dir.to_path_buf(), out_dir.join("examples"), out_dir.join("deps")] { #[cfg(windows)] let mut copy_fallback = false; + #[cfg(not(windows))] + let copy_fallback = false; let lib_files = std::fs::read_dir(lib_dir).unwrap_or_else(|_| panic!("Failed to read contents of `{}` (does it exist?)", lib_dir.display())); for lib_file in lib_files.filter(|e| { @@ -79,18 +81,23 @@ fn copy_libraries(lib_dir: &Path, out_dir: &Path) { let lib_name = lib_path.file_name().unwrap(); let out_path = out_dir.join(lib_name); if !out_path.exists() { + if out_path.is_symlink() { + fs::remove_file(&out_path).unwrap(); + } #[cfg(windows)] if std::os::windows::fs::symlink_file(&lib_path, &out_path).is_err() { copy_fallback = true; - std::fs::copy(&lib_path, out_path).unwrap(); + std::fs::copy(&lib_path, &out_path).unwrap(); } #[cfg(unix)] - std::os::unix::fs::symlink(&lib_path, out_path).unwrap(); + std::os::unix::fs::symlink(&lib_path, &out_path).unwrap(); + } + if !copy_fallback { + println!("cargo:rerun-if-changed={}", out_path.to_str().unwrap()); } } // If we had to fallback to copying files on Windows, break early to avoid copying to 3 different directories - #[cfg(windows)] if copy_fallback { break; } From 67cf09ba1c81928178f21e6585e255c9532254cf Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Sat, 27 Jan 2024 13:12:23 -0600 Subject: [PATCH 14/23] fix: log session drop as debug --- src/session/mod.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/session/mod.rs b/src/session/mod.rs index 7ce0a393..bc31553e 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -520,11 +520,11 @@ unsafe impl Send for SharedSessionInner {} unsafe impl Sync for SharedSessionInner {} impl Drop for SharedSessionInner { - #[tracing::instrument(skip_all)] + #[tracing::instrument] fn drop(&mut self) { - tracing::warn!("dropping SharedSessionInner"); + tracing::debug!("dropping SharedSessionInner"); if !self.session_ptr.is_null() { - tracing::warn!("dropping session ptr"); + tracing::debug!("dropping session ptr"); ortsys![unsafe ReleaseSession(self.session_ptr)]; } self.session_ptr = std::ptr::null_mut(); From 5f426362d0708f626143dc39a9c59c7ad41ba763 Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Thu, 1 Feb 2024 00:35:17 -0600 Subject: [PATCH 15/23] feat: use `CompactString` as `ValueMap` keys, closes #149 --- Cargo.toml | 1 + src/session/input.rs | 10 +++++---- src/session/mod.rs | 50 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 49 insertions(+), 12 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ea810df3..ee2e750b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,6 +76,7 @@ ndarray = { version = "0.15", optional = true } thiserror = "1.0" ort-sys = { version = "2.0.0-alpha.4", path = "ort-sys" } libloading = { version = "0.8", optional = true } +compact_str = "0.7" ureq = { version = "2.1", optional = true, default-features = false, features = [ "tls" ] } tracing = "0.1" diff --git a/src/session/input.rs b/src/session/input.rs index 15b91a2e..b3f3b904 100644 --- a/src/session/input.rs +++ b/src/session/input.rs @@ -1,16 +1,18 @@ use std::collections::HashMap; +use compact_str::CompactString; + use crate::Value; pub enum SessionInputs<'i, const N: usize = 0> { - ValueMap(HashMap<&'static str, Value>), + ValueMap(HashMap), ValueSlice(&'i [Value]), ValueArray([Value; N]) } -impl<'i> From> for SessionInputs<'i> { - fn from(val: HashMap<&'static str, Value>) -> Self { - SessionInputs::ValueMap(val) +impl<'i, K: Into> From> for SessionInputs<'i> { + fn from(val: HashMap) -> Self { + SessionInputs::ValueMap(val.into_iter().map(|c| (c.0.into(), c.1)).collect()) } } diff --git a/src/session/mod.rs b/src/session/mod.rs index bc31553e..e0b7e579 100644 --- a/src/session/mod.rs +++ b/src/session/mod.rs @@ -17,6 +17,8 @@ use std::{ #[cfg(feature = "fetch-models")] use std::{path::PathBuf, time::Duration}; +use compact_str::CompactString; + #[cfg(feature = "fetch-models")] use super::error::FetchModelError; use super::{ @@ -636,15 +638,31 @@ impl Session { pub fn run<'s, 'i, const N: usize>(&'s self, input_values: impl Into>) -> Result> { match input_values.into() { SessionInputs::ValueSlice(input_values) => { - let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), input_values, None)?; + let outputs = self.run_inner( + &self + .inputs + .iter() + .map(|input| CompactString::new(input.name.as_str())) + .collect::>(), + input_values, + None + )?; Ok(outputs) } SessionInputs::ValueArray(input_values) => { - let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), &input_values, None)?; + let outputs = self.run_inner( + &self + .inputs + .iter() + .map(|input| CompactString::new(input.name.as_str())) + .collect::>(), + &input_values, + None + )?; Ok(outputs) } SessionInputs::ValueMap(input_values) => { - let (input_names, values): (Vec<&'static str>, Vec) = input_values.into_iter().unzip(); + let (input_names, values): (Vec, Vec) = input_values.into_iter().unzip(); self.run_inner(&input_names, &values, None) } } @@ -658,24 +676,40 @@ impl Session { ) -> Result> { match input_values.into() { SessionInputs::ValueSlice(input_values) => { - let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), input_values, Some(run_options))?; + let outputs = self.run_inner( + &self + .inputs + .iter() + .map(|input| CompactString::new(input.name.as_str())) + .collect::>(), + input_values, + Some(run_options) + )?; Ok(outputs) } SessionInputs::ValueArray(input_values) => { - let outputs = self.run_inner(&self.inputs.iter().map(|input| input.name.as_str()).collect::>(), &input_values, Some(run_options))?; + let outputs = self.run_inner( + &self + .inputs + .iter() + .map(|input| CompactString::new(input.name.as_str())) + .collect::>(), + &input_values, + Some(run_options) + )?; Ok(outputs) } SessionInputs::ValueMap(input_values) => { - let (input_names, values): (Vec<&'static str>, Vec) = input_values.into_iter().unzip(); + let (input_names, values): (Vec, Vec) = input_values.into_iter().unzip(); self.run_inner(&input_names, &values, Some(run_options)) } } } - fn run_inner(&self, input_names: &[&str], input_values: &[Value], run_options: Option>) -> Result> { + fn run_inner(&self, input_names: &[CompactString], input_values: &[Value], run_options: Option>) -> Result> { let input_names_ptr: Vec<*const c_char> = input_names .iter() - .map(|n| CString::new(*n).unwrap()) + .map(|n| CString::new(n.as_bytes()).unwrap()) .map(|n| n.into_raw() as *const c_char) .collect(); let output_names_ptr: Vec<*const c_char> = self From 4fb6fba5c5a8a2da7308687ccb7b310c035c47a4 Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Thu, 1 Feb 2024 00:35:29 -0600 Subject: [PATCH 16/23] feat: add squeezenet benchmark --- .gitignore | 3 ++ Cargo.toml | 5 +++ benches/squeezenet.rs | 95 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 103 insertions(+) create mode 100644 benches/squeezenet.rs diff --git a/.gitignore b/.gitignore index 6d829e87..a76dbd4f 100644 --- a/.gitignore +++ b/.gitignore @@ -191,3 +191,6 @@ WixTools/ # IDEA .idea + +# Glassbench results +/glassbench*.db diff --git a/Cargo.toml b/Cargo.toml index ee2e750b..4d7bc5b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -95,3 +95,8 @@ ureq = "2.1" image = "0.24" test-log = { version = "0.2", default-features = false, features = [ "trace" ] } tracing-subscriber = { version = "0.3", default-features = false, features = [ "env-filter", "fmt" ] } +glassbench = "0.4" + +[[bench]] +name = "squeezenet" +harness = false diff --git a/benches/squeezenet.rs b/benches/squeezenet.rs new file mode 100644 index 00000000..6b56fc02 --- /dev/null +++ b/benches/squeezenet.rs @@ -0,0 +1,95 @@ +use std::{ + fs, + io::{self, BufRead, BufReader}, + path::Path, + sync::Arc, + time::Duration +}; + +use glassbench::{pretend_used, Bench}; +use image::{imageops::FilterType, ImageBuffer, Pixel, Rgb}; +use ndarray::{s, Array4}; +use ort::{inputs, ArrayExtensions, FetchModelError, GraphOptimizationLevel, Session, Tensor}; +use test_log::test; + +fn load_squeezenet_data() -> ort::Result<(Session, Array4)> { + const IMAGE_TO_LOAD: &str = "mushroom.png"; + + ort::init().with_name("integration_test").commit()?; + + let session = Session::builder()? + .with_optimization_level(GraphOptimizationLevel::Level1)? + .with_intra_threads(1)? + .with_model_downloaded("https://parcel.pyke.io/v2/cdn/assetdelivery/ortrsv2/ex_models/squeezenet.onnx") + .expect("Could not download model from file"); + + let input0_shape: &Vec = session.inputs[0].input_type.tensor_dimensions().expect("input0 to be a tensor type"); + + let image_buffer: ImageBuffer, Vec> = image::open(Path::new(env!("CARGO_MANIFEST_DIR")).join("tests").join("data").join(IMAGE_TO_LOAD)) + .unwrap() + .resize(input0_shape[2] as u32, input0_shape[3] as u32, FilterType::Nearest) + .to_rgb8(); + + let mut array = ndarray::Array::from_shape_fn((1, 3, 224, 224), |(_, c, j, i)| { + let pixel = image_buffer.get_pixel(i as u32, j as u32); + let channels = pixel.channels(); + (channels[c] as f32) / 255.0 + }); + + let mean = [0.485, 0.456, 0.406]; + let std = [0.229, 0.224, 0.225]; + for c in 0..3 { + let mut channel_array = array.slice_mut(s![0, c, .., ..]); + channel_array -= mean[c]; + channel_array /= std[c]; + } + + Ok((session, array)) +} + +fn get_imagenet_labels() -> Result, FetchModelError> { + // Download the ImageNet class labels, matching SqueezeNet's classes. + let labels_path = Path::new(env!("CARGO_TARGET_TMPDIR")).join("synset.txt"); + if !labels_path.exists() { + let url = "https://s3.amazonaws.com/onnx-model-zoo/synset.txt"; + println!("Downloading {:?} to {:?}...", url, labels_path); + let resp = ureq::get(url) + .timeout(Duration::from_secs(180)) // 3 minutes + .call() + .map_err(Box::new) + .map_err(FetchModelError::FetchError)?; + + assert!(resp.has("Content-Length")); + let len = resp.header("Content-Length").and_then(|s| s.parse::().ok()).unwrap(); + println!("Downloading {} bytes...", len); + + let mut reader = resp.into_reader(); + let f = fs::File::create(&labels_path).unwrap(); + let mut writer = io::BufWriter::new(f); + + let bytes_io_count = io::copy(&mut reader, &mut writer).unwrap(); + assert_eq!(bytes_io_count, len as u64); + } + + let file = BufReader::new(fs::File::open(labels_path).unwrap()); + file.lines().map(|line| line.map_err(FetchModelError::IoError)).collect() +} + +fn bench_squeezenet(bench: &mut Bench) { + let (session, data) = load_squeezenet_data().unwrap(); + bench.task("ArrayView", |task| { + task.iter(|| { + pretend_used(session.run(ort::inputs![data.view()].unwrap()).unwrap()); + }) + }); + + let raw = Arc::new(data.as_standard_layout().as_slice().unwrap().to_owned().into_boxed_slice()); + let shape: Vec = data.shape().iter().map(|c| *c as _).collect(); + bench.task("Raw data", |task| { + task.iter(|| { + pretend_used(session.run(ort::inputs![(shape.clone(), Arc::clone(&raw))].unwrap()).unwrap()); + }) + }); +} + +glassbench::glassbench!("SqueezeNet", bench_squeezenet,); From 7419ef3bcf27f6121f1e6e7a6f6de7c309c3464a Mon Sep 17 00:00:00 2001 From: rustui <90625190+rustui@users.noreply.github.com> Date: Sun, 4 Feb 2024 03:42:44 +0800 Subject: [PATCH 17/23] fix(sys): android dependency search path (#153) --- ort-sys/build.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ort-sys/build.rs b/ort-sys/build.rs index 989f3554..7fb05905 100644 --- a/ort-sys/build.rs +++ b/ort-sys/build.rs @@ -160,11 +160,12 @@ fn prepare_libort_dir() -> (PathBuf, bool) { #[allow(clippy::type_complexity)] let static_configs: Vec<(PathBuf, PathBuf, PathBuf, Box PathBuf>)> = vec![ (lib_dir.join(&profile), lib_dir.join("lib"), lib_dir.join("_deps"), Box::new(|p: PathBuf, profile| p.join(profile))), + (lib_dir.join(&profile), lib_dir.join("lib"), lib_dir.join(&profile).join("_deps"), Box::new(|p: PathBuf, _| p)), (lib_dir.clone(), lib_dir.join("lib"), lib_dir.parent().unwrap().join("_deps"), Box::new(|p: PathBuf, _| p)), (lib_dir.join("onnxruntime"), lib_dir.join("onnxruntime").join("lib"), lib_dir.join("_deps"), Box::new(|p: PathBuf, _| p)), ]; for (lib_dir, extension_lib_dir, external_lib_dir, transform_dep) in static_configs { - if lib_dir.join(platform_format_lib("onnxruntime_common")).exists() { + if lib_dir.join(platform_format_lib("onnxruntime_common")).exists() && external_lib_dir.exists() { add_search_dir(&lib_dir); for lib in &["common", "flatbuffers", "framework", "graph", "mlas", "optimizer", "providers", "session", "util"] { From cee88d92ffed83079179f40fbc3ee05dbc60e21e Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Sat, 3 Feb 2024 16:09:05 -0600 Subject: [PATCH 18/23] chore: update to onnxruntime 1.17.0 --- ort-sys/build.rs | 39 +++++------ ort-sys/src/lib.rs | 162 ++++++++++++++++++++++++++++++++++++++++++--- src/lib.rs | 6 +- 3 files changed, 175 insertions(+), 32 deletions(-) diff --git a/ort-sys/build.rs b/ort-sys/build.rs index 7fb05905..558c09d7 100644 --- a/ort-sys/build.rs +++ b/ort-sys/build.rs @@ -124,6 +124,7 @@ fn static_link_prerequisites(using_pyke_libs: bool) { println!("cargo:rustc-link-lib=stdc++"); } else if target_os == "windows" && (using_pyke_libs || cfg!(feature = "directml")) { println!("cargo:rustc-link-lib=dxguid"); + println!("cargo:rustc-link-lib=DXCORE"); println!("cargo:rustc-link-lib=DXGI"); println!("cargo:rustc-link-lib=D3D12"); println!("cargo:rustc-link-lib=DirectML"); @@ -259,48 +260,48 @@ fn prepare_libort_dir() -> (PathBuf, bool) { let target = env::var("TARGET").unwrap().to_string(); let (prebuilt_url, prebuilt_hash) = match target.as_str() { "aarch64-apple-darwin" => ( - "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.16.3/ortrs-msort_static-v1.16.3-aarch64-apple-darwin.tgz", - "188E07B9304CCC28877195ECD2177EF3EA6603A0B5B3497681A6C9E584721387" + "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.17.0/ortrs-msort_static-v1.17.0-aarch64-apple-darwin.tgz", + "71C2D65E05AA3E3CE7DACC87E4918D92E48DCE0E8B0123A839590A6513137E99" ), "aarch64-pc-windows-msvc" => ( - "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.16.3/ortrs-msort_static-v1.16.3-aarch64-pc-windows-msvc.tgz", - "B35F6526EAF61527531D6F73EBA19EF09D6B0886FB66C14E1B594EE70F447817" + "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.17.0/ortrs-msort_static-v1.17.0-aarch64-pc-windows-msvc.tgz", + "27DDC61E1416E3F1BC6137C8365B563F73BA5A6CE8D7008E5CD4E36B4F037FDA" ), "aarch64-unknown-linux-gnu" => ( - "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.16.3/ortrs-msort_static-v1.16.3-aarch64-unknown-linux-gnu.tgz", - "C1E315515856D7880545058479020756BC5CE4C0BA07FB3DD2104233EC7C3C81" + "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.17.0/ortrs-msort_static-v1.17.0-aarch64-unknown-linux-gnu.tgz", + "5DA93D6C54475A2D4413B9C592930BF6B2426BD8D281717006C9FF248D8AC592" ), "wasm32-unknown-emscripten" => ( - "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.16.3/ortrs-msort_static-v1.16.3-wasm32-unknown-emscripten.tgz", - "468F74FB4C7451DC94EBABC080779CDFF0C7DA0617D85ADF21D5435A96F9D470" + "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.17.0/ortrs-msort_static-v1.17.0-wasm32-unknown-emscripten.tgz", + "E1ADBF06922649A59AB9D0459E9D5985B002C3AE830B512B7AED030BDA859C55" ), "x86_64-apple-darwin" => ( - "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.16.3/ortrs-msort_static-v1.16.3-x86_64-apple-darwin.tgz", - "0191C95D9E797BF77C723AD82DC078C6400834B55B8465FA5176BA984FFEAB08" + "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.17.0/ortrs-msort_static-v1.17.0-x86_64-apple-darwin.tgz", + "0786B8D4029EFB43AFAA4C4D012B5D83F53893F9AFADE677A4FDD1C7BA3697B8" ), "x86_64-pc-windows-msvc" => { if cfg!(any(feature = "cuda", feature = "tensorrt")) { ( - "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.16.3/ortrs-msort_dylib_cuda-v1.16.3-x86_64-pc-windows-msvc.tgz", - "B0F08E93E580297C170F04933742D04813C9C3BAD3705E1100CA9EF464AE4011" + "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.17.0/ortrs-msort_dylib_cuda-v1.17.0-x86_64-pc-windows-msvc.tgz", + "802C5EB4E34B5382CB5BB4205FA770FC1465EBBD14AE34C4257EDDF13A924FD7" ) } else { ( - "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.16.3/ortrs-msort_static-v1.16.3-x86_64-pc-windows-msvc.tgz", - "32ADC031C0EAA6C521680EEB9A8C39572C600A5B4F90AFE984590EA92B99E3BE" + "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.17.0/ortrs-msort_static-v1.17.0-x86_64-pc-windows-msvc.tgz", + "C01AAEED3BC2E6E0BAF13371E7147CCC8D037549C77E35AC3EBB45C4187C81A5" ) } } "x86_64-unknown-linux-gnu" => { if cfg!(any(feature = "cuda", feature = "tensorrt")) { ( - "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.16.3/ortrs-msort_dylib_cuda-v1.16.3-x86_64-unknown-linux-gnu.tgz", - "0F0651D10BA56A6EA613F10B60E5C4D892384416C4D76E1F618BE57D1270993F" + "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.17.0/ortrs-msort_dylib_cuda-v1.17.0-x86_64-unknown-linux-gnu.tgz", + "869FB84B50AECD9E0F41464ACC0C621B4162BD338AFD5208F6B8EA77FB7F6C22" ) } else { ( - "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.16.3/ortrs-msort_static-v1.16.3-x86_64-unknown-linux-gnu.tgz", - "D0E63AC1E5A56D0480009049183542E0BB1482CE29A1D110CC8550BEC5D994E2" + "https://parcel.pyke.io/v2/delivery/ortrs/packages/msort-binary/1.17.0/ortrs-msort_static-v1.17.0-x86_64-unknown-linux-gnu.tgz", + "6EA7424A0DFC4E5EE200EF8D2564DB9591CA4A2AA1A1D69234B4408CFE849382" ) } } @@ -319,7 +320,7 @@ fn prepare_libort_dir() -> (PathBuf, bool) { let lib_dir = cache_dir.join(ORT_EXTRACT_DIR); if !lib_dir.exists() { let downloaded_file = fetch_file(prebuilt_url); - assert!(verify_file(&downloaded_file, prebuilt_hash)); + assert!(verify_file(&downloaded_file, prebuilt_hash), "hash does not match!"); extract_tgz(&downloaded_file, &cache_dir); } diff --git a/ort-sys/src/lib.rs b/ort-sys/src/lib.rs index 33c97fcb..4edc4c1f 100644 --- a/ort-sys/src/lib.rs +++ b/ort-sys/src/lib.rs @@ -76,7 +76,11 @@ pub enum ONNXTensorElementDataType { ONNX_TENSOR_ELEMENT_DATA_TYPE_UINT64 = 13, ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX64 = 14, ONNX_TENSOR_ELEMENT_DATA_TYPE_COMPLEX128 = 15, - ONNX_TENSOR_ELEMENT_DATA_TYPE_BFLOAT16 = 16 + ONNX_TENSOR_ELEMENT_DATA_TYPE_BFLOAT16 = 16, + ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT8E4M3FN = 17, + ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT8E4M3FNUZ = 18, + ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT8E5M2 = 19, + ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT8E5M2FNUZ = 20 } #[repr(i32)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] @@ -277,6 +281,11 @@ pub struct OrtOpAttr { pub struct OrtLogger { _unused: [u8; 0] } +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct OrtShapeInferContext { + _unused: [u8; 0] +} pub type OrtStatusPtr = *mut OrtStatus; #[doc = " \\brief Memory allocation interface\n\n Structure of function pointers that defines a memory allocator. This can be created and filled in by the user for custom allocators.\n\n When an allocator is passed to any function, be sure that the allocator object is not destroyed until the last allocated object using it is freed."] #[repr(C)] @@ -406,6 +415,7 @@ pub enum OrtCudnnConvAlgoSearch { } #[doc = " \\brief CUDA Provider Options\n\n \\see OrtApi::SessionOptionsAppendExecutionProvider_CUDA"] #[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct OrtCUDAProviderOptions { #[doc = " \\brief CUDA device Id\n Defaults to 0."] pub device_id: ::std::os::raw::c_int, @@ -494,6 +504,7 @@ fn bindgen_test_layout_OrtCUDAProviderOptions() { } #[doc = " \\brief ROCM Provider Options\n\n \\see OrtApi::SessionOptionsAppendExecutionProvider_ROCM"] #[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct OrtROCMProviderOptions { #[doc = " \\brief ROCM device Id\n Defaults to 0."] pub device_id: ::std::os::raw::c_int, @@ -582,6 +593,7 @@ fn bindgen_test_layout_OrtROCMProviderOptions() { } #[doc = " \\brief TensorRT Provider Options\n\n \\see OrtApi::SessionOptionsAppendExecutionProvider_TensorRT"] #[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct OrtTensorRTProviderOptions { #[doc = "< CUDA device id (0 = default device)"] pub device_id: ::std::os::raw::c_int, @@ -706,14 +718,16 @@ fn bindgen_test_layout_OrtTensorRTProviderOptions() { pub struct OrtMIGraphXProviderOptions { pub device_id: ::std::os::raw::c_int, pub migraphx_fp16_enable: ::std::os::raw::c_int, - pub migraphx_int8_enable: ::std::os::raw::c_int + pub migraphx_int8_enable: ::std::os::raw::c_int, + pub migraphx_use_native_calibration_table: ::std::os::raw::c_int, + pub migraphx_int8_calibration_table_name: *const ::std::os::raw::c_char } #[test] fn bindgen_test_layout_OrtMIGraphXProviderOptions() { const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); let ptr = UNINIT.as_ptr(); - assert_eq!(::std::mem::size_of::(), 12usize, concat!("Size of: ", stringify!(OrtMIGraphXProviderOptions))); - assert_eq!(::std::mem::align_of::(), 4usize, concat!("Alignment of ", stringify!(OrtMIGraphXProviderOptions))); + assert_eq!(::std::mem::size_of::(), 24usize, concat!("Size of: ", stringify!(OrtMIGraphXProviderOptions))); + assert_eq!(::std::mem::align_of::(), 8usize, concat!("Alignment of ", stringify!(OrtMIGraphXProviderOptions))); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).device_id) as usize - ptr as usize }, 0usize, @@ -729,14 +743,25 @@ fn bindgen_test_layout_OrtMIGraphXProviderOptions() { 8usize, concat!("Offset of field: ", stringify!(OrtMIGraphXProviderOptions), "::", stringify!(migraphx_int8_enable)) ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).migraphx_use_native_calibration_table) as usize - ptr as usize }, + 12usize, + concat!("Offset of field: ", stringify!(OrtMIGraphXProviderOptions), "::", stringify!(migraphx_use_native_calibration_table)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).migraphx_int8_calibration_table_name) as usize - ptr as usize }, + 16usize, + concat!("Offset of field: ", stringify!(OrtMIGraphXProviderOptions), "::", stringify!(migraphx_int8_calibration_table_name)) + ); } #[doc = " \\brief OpenVINO Provider Options\n\n \\see OrtApi::SessionOptionsAppendExecutionProvider_OpenVINO"] #[repr(C)] +#[derive(Debug, Copy, Clone)] pub struct OrtOpenVINOProviderOptions { #[doc = " \\brief Device type string\n\n Valid settings are one of: \"CPU_FP32\", \"CPU_FP16\", \"GPU_FP32\", \"GPU_FP16\""] pub device_type: *const ::std::os::raw::c_char, #[doc = "< 0 = disabled, nonzero = enabled"] - pub enable_vpu_fast_compile: ::std::os::raw::c_uchar, + pub enable_npu_fast_compile: ::std::os::raw::c_uchar, pub device_id: *const ::std::os::raw::c_char, #[doc = "< 0 = Use default number of threads"] pub num_of_threads: size_t, @@ -759,9 +784,9 @@ fn bindgen_test_layout_OrtOpenVINOProviderOptions() { concat!("Offset of field: ", stringify!(OrtOpenVINOProviderOptions), "::", stringify!(device_type)) ); assert_eq!( - unsafe { ::std::ptr::addr_of!((*ptr).enable_vpu_fast_compile) as usize - ptr as usize }, + unsafe { ::std::ptr::addr_of!((*ptr).enable_npu_fast_compile) as usize - ptr as usize }, 8usize, - concat!("Offset of field: ", stringify!(OrtOpenVINOProviderOptions), "::", stringify!(enable_vpu_fast_compile)) + concat!("Offset of field: ", stringify!(OrtOpenVINOProviderOptions), "::", stringify!(enable_npu_fast_compile)) ); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).device_id) as usize - ptr as usize }, @@ -1760,13 +1785,59 @@ pub struct OrtApi { resource: *mut *mut ::std::os::raw::c_void ) -> OrtStatusPtr ) + >, + pub SetUserLoggingFunction: ::std::option::Option< + _system!( + unsafe fn( + options: *mut OrtSessionOptions, + user_logging_function: OrtLoggingFunction, + user_logging_param: *mut ::std::os::raw::c_void + ) -> OrtStatusPtr + ) + >, + pub ShapeInferContext_GetInputCount: ::std::option::Option<_system!(unsafe fn(context: *const OrtShapeInferContext, out: *mut size_t) -> OrtStatusPtr)>, + pub ShapeInferContext_GetInputTypeShape: ::std::option::Option< + _system!(unsafe fn(context: *const OrtShapeInferContext, index: size_t, info: *mut *mut OrtTensorTypeAndShapeInfo) -> OrtStatusPtr) + >, + pub ShapeInferContext_GetAttribute: ::std::option::Option< + _system!(unsafe fn(context: *const OrtShapeInferContext, attr_name: *const ::std::os::raw::c_char, attr: *mut *const OrtOpAttr) -> OrtStatusPtr) + >, + pub ShapeInferContext_SetOutputTypeShape: + ::std::option::Option<_system!(unsafe fn(context: *const OrtShapeInferContext, index: size_t, info: *const OrtTensorTypeAndShapeInfo) -> OrtStatusPtr)>, + pub SetSymbolicDimensions: ::std::option::Option< + _system!(unsafe fn(info: *mut OrtTensorTypeAndShapeInfo, dim_params: *mut *const ::std::os::raw::c_char, dim_params_length: size_t) -> OrtStatusPtr) + >, + pub ReadOpAttr: ::std::option::Option< + _system!(unsafe fn(op_attr: *const OrtOpAttr, type_: OrtOpAttrType, data: *mut ::std::os::raw::c_void, len: size_t, out: *mut size_t) -> OrtStatusPtr) + >, + pub SetDeterministicCompute: ::std::option::Option<_system!(unsafe fn(options: *mut OrtSessionOptions, value: bool) -> OrtStatusPtr)>, + pub KernelContext_ParallelFor: ::std::option::Option< + _system!( + unsafe fn( + context: *const OrtKernelContext, + fn_: ::std::option::Option<_system!(unsafe fn(arg1: *mut ::std::os::raw::c_void, arg2: size_t))>, + total: size_t, + num_batch: size_t, + usr_data: *mut ::std::os::raw::c_void + ) -> OrtStatusPtr + ) + >, + pub SessionOptionsAppendExecutionProvider_OpenVINO_V2: ::std::option::Option< + _system!( + unsafe fn( + options: *mut OrtSessionOptions, + provider_options_keys: *const *const ::std::os::raw::c_char, + provider_options_values: *const *const ::std::os::raw::c_char, + num_keys: size_t + ) -> OrtStatusPtr + ) > } #[test] fn bindgen_test_layout_OrtApi() { const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); let ptr = UNINIT.as_ptr(); - assert_eq!(::std::mem::size_of::(), 2128usize, concat!("Size of: ", stringify!(OrtApi))); + assert_eq!(::std::mem::size_of::(), 2208usize, concat!("Size of: ", stringify!(OrtApi))); assert_eq!(::std::mem::align_of::(), 8usize, concat!("Alignment of ", stringify!(OrtApi))); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).CreateStatus) as usize - ptr as usize }, @@ -3098,6 +3169,56 @@ fn bindgen_test_layout_OrtApi() { 2120usize, concat!("Offset of field: ", stringify!(OrtApi), "::", stringify!(KernelContext_GetResource)) ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).SetUserLoggingFunction) as usize - ptr as usize }, + 2128usize, + concat!("Offset of field: ", stringify!(OrtApi), "::", stringify!(SetUserLoggingFunction)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).ShapeInferContext_GetInputCount) as usize - ptr as usize }, + 2136usize, + concat!("Offset of field: ", stringify!(OrtApi), "::", stringify!(ShapeInferContext_GetInputCount)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).ShapeInferContext_GetInputTypeShape) as usize - ptr as usize }, + 2144usize, + concat!("Offset of field: ", stringify!(OrtApi), "::", stringify!(ShapeInferContext_GetInputTypeShape)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).ShapeInferContext_GetAttribute) as usize - ptr as usize }, + 2152usize, + concat!("Offset of field: ", stringify!(OrtApi), "::", stringify!(ShapeInferContext_GetAttribute)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).ShapeInferContext_SetOutputTypeShape) as usize - ptr as usize }, + 2160usize, + concat!("Offset of field: ", stringify!(OrtApi), "::", stringify!(ShapeInferContext_SetOutputTypeShape)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).SetSymbolicDimensions) as usize - ptr as usize }, + 2168usize, + concat!("Offset of field: ", stringify!(OrtApi), "::", stringify!(SetSymbolicDimensions)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).ReadOpAttr) as usize - ptr as usize }, + 2176usize, + concat!("Offset of field: ", stringify!(OrtApi), "::", stringify!(ReadOpAttr)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).SetDeterministicCompute) as usize - ptr as usize }, + 2184usize, + concat!("Offset of field: ", stringify!(OrtApi), "::", stringify!(SetDeterministicCompute)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).KernelContext_ParallelFor) as usize - ptr as usize }, + 2192usize, + concat!("Offset of field: ", stringify!(OrtApi), "::", stringify!(KernelContext_ParallelFor)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).SessionOptionsAppendExecutionProvider_OpenVINO_V2) as usize - ptr as usize }, + 2200usize, + concat!("Offset of field: ", stringify!(OrtApi), "::", stringify!(SessionOptionsAppendExecutionProvider_OpenVINO_V2)) + ); } #[repr(i32)] #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] @@ -3130,13 +3251,16 @@ pub struct OrtCustomOp { pub CreateKernelV2: ::std::option::Option< _system!(unsafe fn(op: *const OrtCustomOp, api: *const OrtApi, info: *const OrtKernelInfo, kernel: *mut *mut ::std::os::raw::c_void) -> OrtStatusPtr) >, - pub KernelComputeV2: ::std::option::Option<_system!(unsafe fn(op_kernel: *mut ::std::os::raw::c_void, context: *mut OrtKernelContext) -> OrtStatusPtr)> + pub KernelComputeV2: ::std::option::Option<_system!(unsafe fn(op_kernel: *mut ::std::os::raw::c_void, context: *mut OrtKernelContext) -> OrtStatusPtr)>, + pub InferOutputShapeFn: ::std::option::Option<_system!(unsafe fn(op: *const OrtCustomOp, arg1: *mut OrtShapeInferContext) -> OrtStatusPtr)>, + pub GetStartVersion: ::std::option::Option<_system!(unsafe fn(op: *const OrtCustomOp) -> ::std::os::raw::c_int)>, + pub GetEndVersion: ::std::option::Option<_system!(unsafe fn(op: *const OrtCustomOp) -> ::std::os::raw::c_int)> } #[test] fn bindgen_test_layout_OrtCustomOp() { const UNINIT: ::std::mem::MaybeUninit = ::std::mem::MaybeUninit::uninit(); let ptr = UNINIT.as_ptr(); - assert_eq!(::std::mem::size_of::(), 152usize, concat!("Size of: ", stringify!(OrtCustomOp))); + assert_eq!(::std::mem::size_of::(), 176usize, concat!("Size of: ", stringify!(OrtCustomOp))); assert_eq!(::std::mem::align_of::(), 8usize, concat!("Alignment of ", stringify!(OrtCustomOp))); assert_eq!( unsafe { ::std::ptr::addr_of!((*ptr).version) as usize - ptr as usize }, @@ -3233,6 +3357,21 @@ fn bindgen_test_layout_OrtCustomOp() { 144usize, concat!("Offset of field: ", stringify!(OrtCustomOp), "::", stringify!(KernelComputeV2)) ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).InferOutputShapeFn) as usize - ptr as usize }, + 152usize, + concat!("Offset of field: ", stringify!(OrtCustomOp), "::", stringify!(InferOutputShapeFn)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).GetStartVersion) as usize - ptr as usize }, + 160usize, + concat!("Offset of field: ", stringify!(OrtCustomOp), "::", stringify!(GetStartVersion)) + ); + assert_eq!( + unsafe { ::std::ptr::addr_of!((*ptr).GetEndVersion) as usize - ptr as usize }, + 168usize, + concat!("Offset of field: ", stringify!(OrtCustomOp), "::", stringify!(GetEndVersion)) + ); } _system_block! { pub fn OrtSessionOptionsAppendExecutionProvider_CUDA(options: *mut OrtSessionOptions, device_id: ::std::os::raw::c_int) -> OrtStatusPtr; @@ -3246,3 +3385,6 @@ _system_block! { _system_block! { pub fn OrtSessionOptionsAppendExecutionProvider_Dnnl(options: *mut OrtSessionOptions, use_arena: ::std::os::raw::c_int) -> OrtStatusPtr; } +_system_block! { + pub fn OrtSessionOptionsAppendExecutionProvider_Tensorrt(options: *mut OrtSessionOptions, device_id: ::std::os::raw::c_int) -> OrtStatusPtr; +} diff --git a/src/lib.rs b/src/lib.rs index 449a6cf0..94a8645d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,13 +139,13 @@ pub fn api() -> ort_sys::OrtApi { tracing::info!("Using ONNX Runtime version '{version_string}'"); let lib_minor_version = version_string.split('.').nth(1).map(|x| x.parse::().unwrap_or(0)).unwrap_or(0); - match lib_minor_version.cmp(&16) { + match lib_minor_version.cmp(&17) { std::cmp::Ordering::Less => panic!( - "ort 2.0 is not compatible with the ONNX Runtime binary found at `{}`; expected GetVersionString to return '1.16.x', but got '{version_string}'", + "ort 2.0 is not compatible with the ONNX Runtime binary found at `{}`; expected GetVersionString to return '1.17.x', but got '{version_string}'", dylib_path() ), std::cmp::Ordering::Greater => tracing::warn!( - "ort 2.0 may have compatibility issues with the ONNX Runtime binary found at `{}`; expected GetVersionString to return '1.16.x', but got '{version_string}'", + "ort 2.0 may have compatibility issues with the ONNX Runtime binary found at `{}`; expected GetVersionString to return '1.17.x', but got '{version_string}'", dylib_path() ), std::cmp::Ordering::Equal => {} From 03558a705961d6ba1955f45191729bf14a40245b Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Sat, 3 Feb 2024 16:11:45 -0600 Subject: [PATCH 19/23] fix(openvino): `enable_vpu_fast_compile`, rename vpu -> npu --- src/execution_providers/openvino.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/execution_providers/openvino.rs b/src/execution_providers/openvino.rs index 47d2d20e..4133480c 100644 --- a/src/execution_providers/openvino.rs +++ b/src/execution_providers/openvino.rs @@ -12,7 +12,7 @@ pub struct OpenVINOExecutionProvider { context: *mut c_void, enable_opencl_throttling: bool, enable_dynamic_shapes: bool, - enable_vpu_fast_compile: bool + enable_npu_fast_compile: bool } unsafe impl Send for OpenVINOExecutionProvider {} @@ -28,7 +28,7 @@ impl Default for OpenVINOExecutionProvider { context: std::ptr::null_mut(), enable_opencl_throttling: false, enable_dynamic_shapes: false, - enable_vpu_fast_compile: false + enable_npu_fast_compile: false } } } @@ -82,8 +82,8 @@ impl OpenVINOExecutionProvider { self } - pub fn with_vpu_fast_compile(mut self) -> Self { - self.enable_vpu_fast_compile = true; + pub fn with_npu_fast_compile(mut self) -> Self { + self.enable_npu_fast_compile = true; self } @@ -127,7 +127,7 @@ impl ExecutionProvider for OpenVINOExecutionProvider { context: self.context, enable_opencl_throttling: self.enable_opencl_throttling.into(), enable_dynamic_shapes: self.enable_dynamic_shapes.into(), - enable_vpu_fast_compile: self.enable_vpu_fast_compile.into() + enable_npu_fast_compile: self.enable_npu_fast_compile.into() }; return crate::error::status_to_result( crate::ortsys![unsafe SessionOptionsAppendExecutionProvider_OpenVINO(session_builder.session_options_ptr, &openvino_options as *const _)] From 05b3547a737c0d49d4b658af49d6ee78cab0fd39 Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Tue, 6 Feb 2024 14:33:35 -0600 Subject: [PATCH 20/23] benches(squeezenet): cleanup unused imports --- benches/squeezenet.rs | 39 ++------------------------------------- 1 file changed, 2 insertions(+), 37 deletions(-) diff --git a/benches/squeezenet.rs b/benches/squeezenet.rs index 6b56fc02..5da0239e 100644 --- a/benches/squeezenet.rs +++ b/benches/squeezenet.rs @@ -1,16 +1,9 @@ -use std::{ - fs, - io::{self, BufRead, BufReader}, - path::Path, - sync::Arc, - time::Duration -}; +use std::{path::Path, sync::Arc}; use glassbench::{pretend_used, Bench}; use image::{imageops::FilterType, ImageBuffer, Pixel, Rgb}; use ndarray::{s, Array4}; -use ort::{inputs, ArrayExtensions, FetchModelError, GraphOptimizationLevel, Session, Tensor}; -use test_log::test; +use ort::{GraphOptimizationLevel, Session}; fn load_squeezenet_data() -> ort::Result<(Session, Array4)> { const IMAGE_TO_LOAD: &str = "mushroom.png"; @@ -47,34 +40,6 @@ fn load_squeezenet_data() -> ort::Result<(Session, Array4)> { Ok((session, array)) } -fn get_imagenet_labels() -> Result, FetchModelError> { - // Download the ImageNet class labels, matching SqueezeNet's classes. - let labels_path = Path::new(env!("CARGO_TARGET_TMPDIR")).join("synset.txt"); - if !labels_path.exists() { - let url = "https://s3.amazonaws.com/onnx-model-zoo/synset.txt"; - println!("Downloading {:?} to {:?}...", url, labels_path); - let resp = ureq::get(url) - .timeout(Duration::from_secs(180)) // 3 minutes - .call() - .map_err(Box::new) - .map_err(FetchModelError::FetchError)?; - - assert!(resp.has("Content-Length")); - let len = resp.header("Content-Length").and_then(|s| s.parse::().ok()).unwrap(); - println!("Downloading {} bytes...", len); - - let mut reader = resp.into_reader(); - let f = fs::File::create(&labels_path).unwrap(); - let mut writer = io::BufWriter::new(f); - - let bytes_io_count = io::copy(&mut reader, &mut writer).unwrap(); - assert_eq!(bytes_io_count, len as u64); - } - - let file = BufReader::new(fs::File::open(labels_path).unwrap()); - file.lines().map(|line| line.map_err(FetchModelError::IoError)).collect() -} - fn bench_squeezenet(bench: &mut Bench) { let (session, data) = load_squeezenet_data().unwrap(); bench.task("ArrayView", |task| { From 7d1abd2b10565fc7062aa6218aebea9a877bc161 Mon Sep 17 00:00:00 2001 From: "Carson M." Date: Tue, 6 Feb 2024 14:49:19 -0600 Subject: [PATCH 21/23] docs: 1.17.0 update, add new environment commit behavior --- Cargo.toml | 2 +- README.md | 4 ++-- docs/introduction.mdx | 2 +- docs/migrating/v2.mdx | 2 +- docs/migrating/version-mapping.mdx | 6 ++---- ort-sys/Cargo.toml | 2 +- 6 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 4d7bc5b4..e6fee67b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ default-members = [ [package] name = "ort" -description = "A safe Rust wrapper for ONNX Runtime 1.16 - Optimize and Accelerate Machine Learning Inferencing" +description = "A safe Rust wrapper for ONNX Runtime 1.17 - Optimize and Accelerate Machine Learning Inferencing" version = "2.0.0-alpha.4" edition = "2021" rust-version = "1.70" diff --git a/README.md b/README.md index b512344a..83294348 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,10 @@