Skip to main content

ort_web/
lib.rs

1//! `ort-web` is an [`ort`] backend that enables the usage of ONNX Runtime in the web.
2//!
3//! For more information, see https://ort.pyke.io/backends/web
4
5#![deny(clippy::panic, clippy::panicking_unwrap)]
6#![warn(clippy::std_instead_of_alloc, clippy::std_instead_of_core)]
7
8extern crate alloc;
9extern crate core;
10
11use alloc::string::String;
12use core::fmt;
13
14use serde::Serialize;
15use wasm_bindgen::prelude::*;
16
17use crate::util::value_to_string;
18#[macro_use]
19pub(crate) mod private;
20
21mod api;
22mod binding;
23mod env;
24mod memory;
25mod session;
26mod tensor;
27mod util;
28
29pub use self::{
30	session::sync_outputs,
31	tensor::{SyncDirection, ValueExt}
32};
33
34pub type Result<T, E = Error> = core::result::Result<T, E>;
35
36#[derive(Debug, Clone)]
37pub struct Error {
38	msg: String
39}
40
41impl Error {
42	pub(crate) fn new(msg: impl Into<String>) -> Self {
43		Self { msg: msg.into() }
44	}
45}
46
47impl From<JsValue> for Error {
48	fn from(value: JsValue) -> Self {
49		Self::new(value_to_string(&value))
50	}
51}
52
53impl fmt::Display for Error {
54	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
55		self.msg.fmt(f)
56	}
57}
58
59impl core::error::Error for Error {}
60
61/// Do not enable any execution provider features (CPU-only).
62pub const FEATURE_NONE: u8 = 0;
63/// Enable the WebGL execution provider for hardware acceleration.
64///
65/// See: <https://caniuse.com/webgl2>
66pub const FEATURE_WEBGL: u8 = 1 << 0;
67/// Enable the WebGPU execution provider for hardware acceleration.
68///
69/// See: <https://caniuse.com/webgpu>
70pub const FEATURE_WEBGPU: u8 = 1 << 1;
71/// Enable the WebNN execution provider for hardware acceleration.
72///
73/// See: <https://webmachinelearning.github.io/webnn-status/>
74pub const FEATURE_WEBNN: u8 = FEATURE_WEBGPU;
75
76/// Loads an `ort`-compatible ONNX Runtime API from `config`.
77///
78/// Returns an error if:
79/// - The requested feature set is not supported by `ort-web`.
80/// - The JavaScript/WASM modules fail to load.
81///
82/// `config` can be a feature set, in which case the default pyke-hosted builds will be used:
83/// ```no_run
84/// use ort::session::Session;
85/// use ort_web::{FEATURE_WEBGL, FEATURE_WEBGPU};
86///
87/// async fn init_model() -> anyhow::Result<Session> {
88/// 	// This must be called at least once before using any `ort` API.
89/// 	ort::set_api(ort_web::api(FEATURE_WEBGL | FEATURE_WEBGPU).await?);
90///
91/// 	let session = Session::builder()?.commit_from_url("https://...").await?;
92/// 	Ok(session)
93/// }
94/// ```
95///
96/// You can also use [`Dist`] to self-host the build:
97/// ```no_run
98/// use ort::session::Session;
99/// use ort_web::Dist;
100///
101/// async fn init_model() -> anyhow::Result<Session> {
102/// 	let dist = Dist::new("https://cdn.jsdelivr.net/npm/onnxruntime-web@1.24.2/dist/")
103/// 		// load the WebGPU build
104/// 		.with_script_name("ort.webgpu.min.js");
105/// 	ort::set_api(ort_web::api(dist).await?);
106/// }
107/// ```
108pub async fn api<L: Loadable>(config: L) -> Result<ort_sys::OrtApi> {
109	let (features, dist) = config.into_features_and_dist()?;
110	binding::init_runtime(features, dist).await?;
111
112	Ok(self::api::api())
113}
114
115pub trait Loadable {
116	#[doc(hidden)]
117	fn into_features_and_dist(self) -> Result<(u8, JsValue)>;
118}
119
120impl Loadable for u8 {
121	fn into_features_and_dist(self) -> Result<(u8, JsValue)> {
122		Ok((self, JsValue::null()))
123	}
124}
125
126impl Loadable for Dist {
127	fn into_features_and_dist(self) -> Result<(u8, JsValue)> {
128		Ok((0, serde_wasm_bindgen::to_value(&self).map_err(|e| Error::new(e.to_string()))?))
129	}
130}
131
132#[derive(Default, Debug, Serialize, Clone)]
133#[serde(rename_all = "camelCase")]
134pub struct Integrities {
135	main: Option<String>,
136	wrapper: Option<String>,
137	binary: Option<String>
138}
139
140impl Integrities {
141	/// Set the SHA-384 SRI hash for the main (entrypoint) script.
142	pub fn set_main(&mut self, hash: impl Into<String>) {
143		self.main = Some(hash.into());
144	}
145
146	/// Set the SHA-384 SRI hash for the Emscripten wrapper script.
147	pub fn set_wrapper(&mut self, hash: impl Into<String>) {
148		self.wrapper = Some(hash.into());
149	}
150
151	/// Set the SHA-384 SRI hash for the WASM binary.
152	pub fn set_binary(&mut self, hash: impl Into<String>) {
153		self.binary = Some(hash.into());
154	}
155}
156
157#[derive(Debug, Serialize, Clone)]
158#[serde(rename_all = "camelCase")]
159pub struct Dist {
160	base_url: String,
161	script_name: String,
162	binary_name: Option<String>,
163	wrapper_name: Option<String>,
164	integrities: Integrities
165}
166
167impl Dist {
168	pub fn new(base_url: impl Into<String>) -> Self {
169		Self {
170			base_url: base_url.into(),
171			script_name: "ort.wasm.min.js".to_string(),
172			binary_name: None,
173			wrapper_name: None,
174			integrities: Integrities::default()
175		}
176	}
177
178	/// Configures the name of the entrypoint script file; defaults to `"ort.wasm.min.js"`.
179	pub fn with_script_name(mut self, name: impl Into<String>) -> Self {
180		self.script_name = name.into();
181		self
182	}
183
184	/// Enables preloading the WASM binary loaded by the entrypoint script.
185	pub fn with_binary_name(mut self, name: impl Into<String>) -> Self {
186		self.binary_name = Some(name.into());
187		self
188	}
189
190	/// Configures the name of the Emscripten wrapper script preloaded along with the WASM binary, if preloading is
191	/// enabled. Defaults to the binary name with the `.wasm` extension replaced with `.mjs`.
192	pub fn with_wrapper_name(mut self, name: impl Into<String>) -> Self {
193		self.wrapper_name = Some(name.into());
194		self
195	}
196
197	/// Modify Subresource Integrity (SRI) hashes.
198	pub fn integrities(&mut self) -> &mut Integrities {
199		&mut self.integrities
200	}
201}