use crate::ai_inference::Tensor;
use anyhow::Result;
use async_trait::async_trait;
use std::sync::RwLock;
#[derive(Debug, Clone, PartialEq)]
pub enum ModelFormat {
ONNX,
PyTorch,
TensorFlowLite,
TensorFlow,
Custom(String),
}
#[derive(Debug, Clone, PartialEq)]
pub enum EngineType {
CPU,
CUDA,
ROCm,
Metal,
WebGPU,
NPU,
}
#[derive(Debug, Clone)]
pub struct InferenceOptions {
pub engine_type: EngineType,
pub batch_size: Option<usize>,
pub optimization: bool,
pub parallel_inferences: Option<usize>,
pub memory_optimization: Option<MemoryOptimization>,
pub custom_options: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub enum MemoryOptimization {
None,
Low,
Medium,
High,
Aggressive,
}
#[async_trait]
pub trait InferenceEngine: Send + Sync {
fn name(&self) -> &str;
fn supported_formats(&self) -> Vec<ModelFormat>;
fn is_available(&self) -> bool;
async fn load_model(&self, model_path: &str, options: InferenceOptions) -> Result<ModelHandle>;
async fn infer(&self, model: &ModelHandle, input: &Tensor) -> Result<InferenceResult>;
async fn batch_infer(
&self,
model: &ModelHandle,
inputs: &[Tensor],
) -> Result<Vec<InferenceResult>>;
async fn infer_stream(
&self,
model: &ModelHandle,
input: Tensor,
) -> Result<tokio::sync::mpsc::Receiver<Result<Tensor>>>;
async fn get_model_info(&self, model: &ModelHandle) -> Result<ModelInfo>;
async fn warmup(&self, model: &ModelHandle) -> Result<()>;
async fn unload_model(&self, model: &ModelHandle) -> Result<()>;
async fn get_stats(&self) -> Result<EngineStats>;
fn clone_engine(&self) -> Box<dyn InferenceEngine>;
}
#[derive(Debug, Clone)]
pub struct InferenceResult {
pub output: Tensor,
pub inference_time_ms: f64,
pub model_id: String,
pub gpu_used: bool,
}
#[derive(Debug, Clone)]
pub struct ModelHandle {
pub id: String,
pub path: String,
pub format: ModelFormat,
pub input_shape: Vec<usize>,
pub output_shape: Vec<usize>,
pub metadata: std::collections::HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct ModelInfo {
pub id: String,
pub name: String,
pub format: ModelFormat,
pub inputs: Vec<TensorInfo>,
pub outputs: Vec<TensorInfo>,
pub parameter_count: usize,
pub size_bytes: u64,
pub engine_type: EngineType,
}
#[derive(Debug, Clone)]
pub struct TensorInfo {
pub name: String,
pub shape: Vec<usize>,
pub data_type: String,
pub optional: bool,
}
#[derive(Debug, Clone)]
pub struct EngineStats {
pub engine_name: String,
pub total_inferences: u64,
pub successful_inferences: u64,
pub failed_inferences: u64,
pub total_time_ms: f64,
pub average_time_ms: f64,
pub gpu_utilization: f64,
pub memory_usage_bytes: u64,
pub cache_hit_rate: f64,
}
#[async_trait]
pub trait EngineFactory: Send + Sync {
async fn create(&self, engine_type: EngineType) -> Result<Box<dyn InferenceEngine>>;
fn name(&self) -> &str;
fn supported_formats(&self) -> Vec<ModelFormat>;
}
pub struct EngineManager {
factories: std::collections::HashMap<String, Box<dyn EngineFactory>>,
active_engines: Arc<RwLock<std::collections::HashMap<String, Box<dyn InferenceEngine>>>>,
default_engine_type: EngineType,
}
impl EngineManager {
pub fn new(default_engine_type: EngineType) -> Self {
EngineManager {
factories: std::collections::HashMap::new(),
active_engines: Arc::new(Mutex::new(std::collections::HashMap::new())),
default_engine_type,
}
}
pub fn register_factory(&mut self, name: String, factory: Box<dyn EngineFactory>) {
self.factories.insert(name, factory);
}
pub async fn get_or_create_engine(
&self,
factory_name: &str,
engine_type: Option<EngineType>,
) -> Result<Box<dyn InferenceEngine>> {
let engine_type: _ = engine_type
.clone()
.unwrap_or_else(|| self.default_engine_type.clone());
let cache_key: _ = format!("{}_{:?}", engine_type, factory_name);
{
let active_engines: _ = self.active_engines.read().await;
if let Some(engine) = active_engines.get(&cache_key) {
return Ok(engine.boxed_clone());
}
}
let factory: _ = self
.factories
.get(factory_name)
.ok_or_else(|| anyhow::anyhow!("Factory '{}' not found", factory_name))?;
let engine: _ = factory.create(engine_type).await?;
{
let mut active_engines = self.active_engines.write().await;
active_engines.insert(cache_key, engine.boxed_clone());
}
Ok(engine)
}
pub fn available_engines(&self) -> Vec<String> {
self.factories.keys().cloned().collect()
}
pub async fn is_engine_available(
&self,
factory_name: &str,
engine_type: Option<EngineType>,
) -> bool {
let engine_type: _ = engine_type
.clone()
.unwrap_or_else(|| self.default_engine_type.clone());
let cache_key: _ = format!("{}_{:?}", engine_type, factory_name);
{
let active_engines: _ = self.active_engines.read().await;
if let Some(engine) = active_engines.get(&cache_key) {
return engine.is_available();
}
}
if let Some(factory) = self.factories.get(factory_name) {
if let Ok(engine) = factory.create(engine_type).await {
return engine.is_available();
}
}
false
}
}
pub trait InferenceEngineExt {
fn boxed_clone(&self) -> Box<dyn InferenceEngine>;
}
impl InferenceEngineExt for Box<dyn InferenceEngine> {
fn boxed_clone(&self) -> Box<dyn InferenceEngine> {
self.as_ref().clone_engine()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_engine_manager_basic() {
let mut manager = EngineManager::new(EngineType::CPU);
struct MockFactory;
#[async_trait]
impl EngineFactory for MockFactory {
async fn create(&self, _engine_type: EngineType) -> Result<Box<dyn InferenceEngine>> {
Ok(Box::new(MockEngine))
}
fn name(&self) -> &str {
"mock"
}
fn supported_formats(&self) -> Vec<ModelFormat> {
vec![ModelFormat::ONNX]
}
}
struct MockEngine;
#[async_trait]
impl InferenceEngine for MockEngine {
fn clone_engine(&self) -> Box<dyn InferenceEngine> {
Box::new(MockEngine)
}
fn name(&self) -> &str {
"MockEngine"
}
fn supported_formats(&self) -> Vec<ModelFormat> {
vec![ModelFormat::ONNX]
}
fn is_available(&self) -> bool {
true
}
async fn load_model(
&self,
_model_path: &str,
_options: InferenceOptions,
) -> Result<ModelHandle> {
Ok(ModelHandle {
id: "test".to_string(),
path: _model_path.to_string(),
format: ModelFormat::ONNX,
input_shape: vec![1, 3, 224, 224],
output_shape: vec![1, 1000],
metadata: std::collections::HashMap::new(),
})
}
async fn infer(
&self,
_model: &ModelHandle,
_input: &Tensor,
) -> Result<InferenceResult> {
todo!()
}
async fn batch_infer(
&self,
_model: &ModelHandle,
_inputs: &[Tensor],
) -> Result<Vec<InferenceResult>> {
todo!()
}
async fn infer_stream(
&self,
_model: &ModelHandle,
_input: Tensor,
) -> Result<tokio::sync::mpsc::Receiver<Result<Tensor>>> {
todo!()
}
async fn get_model_info(&self, _model: &ModelHandle) -> Result<ModelInfo> {
todo!()
}
async fn warmup(&self, _model: &ModelHandle) -> Result<()> {
Ok(())
}
async fn unload_model(&self, _model: &ModelHandle) -> Result<()> {
Ok(())
}
async fn get_stats(&self) -> Result<EngineStats> {
Ok(EngineStats {
engine_name: "MockEngine".to_string(),
total_inferences: 0,
successful_inferences: 0,
failed_inferences: 0,
total_time_ms: 0.0,
average_time_ms: 0.0,
gpu_utilization: 0.0,
memory_usage_bytes: 0,
cache_hit_rate: 0.0,
})
}
}
manager.register_factory("mock".to_string(), Box::new(MockFactory));
let engines: _ = manager.available_engines();
assert_eq!(engines, vec!["mock"]);
let is_available: _ = manager.is_engine_available("mock", None).await;
assert!(is_available);
}
}
use std::collections::{BTreeMap, HashMap};
use std::sync::{Arc, Mutex};