mod cache;
mod dispatch;
mod domain;
mod error;
mod opset;
mod tensor;
use std::sync::Arc;
use std::sync::{Mutex, OnceLock, RwLock};
use onnx_runtime_ep_api::{EpConfig, ExecutionProvider};
use onnx_runtime_ep_cpu::CpuExecutionProvider;
use onnx_runtime_ir::DeviceId;
use onnx_runtime_shape_inference::InferenceRegistry;
pub use cache::{CacheStats, KernelCache, KernelCacheKey};
pub use domain::{DomainInfo, DomainRegistry};
pub use error::{EagerError, Result};
pub use opset::{LATEST_ONNX_OPSET, resolve_opset};
pub use tensor::Tensor;
const DEFAULT_CACHE_CAPACITY: usize = 4096;
pub struct EagerContext {
pub(crate) eps: Vec<Arc<dyn ExecutionProvider>>,
pub(crate) cache: Mutex<KernelCache>,
pub(crate) domains: RwLock<DomainRegistry>,
pub(crate) inference: InferenceRegistry,
pub(crate) default_device: DeviceId,
}
fn detect_available_eps() -> Result<Vec<Arc<dyn ExecutionProvider>>> {
let mut cpu = CpuExecutionProvider::new();
cpu.initialize(&EpConfig::default())?;
Ok(vec![Arc::new(cpu) as Arc<dyn ExecutionProvider>])
}
impl EagerContext {
pub fn new() -> Result<Self> {
let eps = detect_available_eps()?;
let default_device = eps
.first()
.map(|ep| ep.device_id())
.unwrap_or_else(DeviceId::cpu);
Ok(Self {
eps,
cache: Mutex::new(KernelCache::new(DEFAULT_CACHE_CAPACITY)),
domains: RwLock::new(DomainRegistry::new()),
inference: InferenceRegistry::default_registry(),
default_device,
})
}
pub fn default_device(&self) -> DeviceId {
self.default_device
}
pub fn register_domain(&self, domain: &str, default_opset: u64) {
self.domains
.write()
.expect("domain registry lock poisoned")
.register(domain, default_opset);
}
pub fn domains(&self) -> Vec<(String, u64)> {
self.domains
.read()
.expect("domain registry lock poisoned")
.domains()
}
pub fn cache_stats(&self) -> CacheStats {
self.cache
.lock()
.expect("kernel cache lock poisoned")
.stats()
}
pub(crate) fn resolve_device(&self, inputs: &[&Tensor]) -> Result<DeviceId> {
let devices: Vec<DeviceId> = inputs.iter().map(|t| t.device()).collect();
resolve_device_from_ids(&devices, self.default_device)
}
pub(crate) fn ep_for_device(&self, device: DeviceId) -> Result<Arc<dyn ExecutionProvider>> {
self.eps
.iter()
.find(|ep| ep.device_id() == device)
.cloned()
.ok_or(EagerError::NoEpForDevice(device))
}
}
pub(crate) fn resolve_device_from_ids(devices: &[DeviceId], default: DeviceId) -> Result<DeviceId> {
let mut unique: Vec<DeviceId> = Vec::new();
for &d in devices {
if !unique.contains(&d) {
unique.push(d);
}
}
match unique.len() {
0 => Ok(default),
1 => Ok(unique[0]),
_ => Err(EagerError::MixedDeviceInputs {
devices: unique,
hint: "use .to(device) to move all inputs to the same device".to_string(),
}),
}
}
static GLOBAL_CONTEXT: OnceLock<EagerContext> = OnceLock::new();
pub fn global_context() -> &'static EagerContext {
GLOBAL_CONTEXT.get_or_init(|| EagerContext::new().expect("failed to initialize eager context"))
}
#[cfg(test)]
mod tests {
use super::*;
fn _assert_send_sync<T: Send + Sync>() {}
#[test]
fn context_is_send_sync() {
_assert_send_sync::<EagerContext>();
}
#[test]
fn new_context_detects_cpu() {
let ctx = EagerContext::new().unwrap();
assert_eq!(ctx.default_device(), DeviceId::cpu());
assert!(ctx.domains().iter().any(|(d, _)| d.is_empty()));
}
#[test]
fn resolve_device_same_device_ok() {
let ctx = EagerContext::new().unwrap();
let a = Tensor::from_raw_in(
ctx.ep_for_device(DeviceId::cpu()).unwrap(),
onnx_runtime_ir::DataType::Float32,
vec![2],
&1.0f32.to_le_bytes().repeat(2),
)
.unwrap();
let b = a.clone();
assert_eq!(ctx.resolve_device(&[&a, &b]).unwrap(), DeviceId::cpu());
}
#[test]
fn resolve_device_no_inputs_uses_default() {
let ctx = EagerContext::new().unwrap();
assert_eq!(ctx.resolve_device(&[]).unwrap(), ctx.default_device());
}
#[test]
fn resolve_device_mixed_is_error() {
use onnx_runtime_ir::DeviceType;
let cpu = DeviceId::cpu();
let cuda = DeviceId::new(DeviceType::Cuda, 0);
assert!(matches!(
resolve_device_from_ids(&[cpu, cuda], cpu),
Err(EagerError::MixedDeviceInputs { .. })
));
assert_eq!(resolve_device_from_ids(&[cpu, cpu], cpu).unwrap(), cpu);
assert_eq!(resolve_device_from_ids(&[], cpu).unwrap(), cpu);
}
}