libmir-cuda 0.1.0

CUDA inference backend for libmir
mod batch;
mod execution;
mod model;
mod runner;
mod runtime;
mod trace;

use std::{
    collections::HashMap,
    fmt,
    sync::{Arc, Mutex, MutexGuard},
};

use ::runtime::{kv::CacheConfig, scheduler::SchedulerConfig};

use self::model::LoadedModel;
use crate::{CudaBackend, CudaConfig, Error, Result};

#[derive(Clone)]
pub struct CudaEngine {
    backend: CudaBackend,
    cache: CacheConfig,
    session_config: crate::CudaModelSessionConfig,
    scheduler: SchedulerConfig,
    models: Arc<Mutex<HashMap<String, Arc<LoadedModel>>>>,
}

#[derive(Debug, Clone)]
pub struct CudaMemoryStats {
    pub total: u64,
    pub available: u64,
    pub reserved: u64,
    pub used: u64,
    pub device: String,
    pub integrated: bool,
}

impl CudaEngine {
    pub fn new(config: CudaConfig, cache: CacheConfig) -> Result<Self> {
        Self::new_with_scheduler(config, cache, SchedulerConfig::default())
    }

    pub fn new_with_scheduler(
        config: CudaConfig,
        cache: CacheConfig,
        scheduler: SchedulerConfig,
    ) -> Result<Self> {
        let session_config = config.model_session;
        Ok(Self {
            backend: CudaBackend::new(config)?,
            cache,
            session_config,
            scheduler,
            models: Arc::new(Mutex::new(HashMap::new())),
        })
    }

    fn model(&self, id: &str) -> Result<Arc<LoadedModel>> {
        self.models()?
            .get(id)
            .cloned()
            .ok_or_else(|| Error::State(format!("model is not loaded: {id}")))
    }

    fn models(&self) -> Result<MutexGuard<'_, HashMap<String, Arc<LoadedModel>>>> {
        let Ok(models) = self.models.lock() else {
            return Err(Error::State("model registry lock is poisoned".into()));
        };
        Ok(models)
    }

    pub fn clear_model_sessions(&self, model_id: &str) -> Result<()> {
        self.model(model_id)?.clear_sessions()
    }

    pub fn release_session(&self, model_id: &str, session: uuid::Uuid) -> Result<()> {
        self.model(model_id)?.release_session(session)
    }

    pub fn unload_model(&self, model_id: &str) -> Result<bool> {
        let removed = self.models()?.remove(model_id);
        let unloaded = removed.is_some();
        drop(removed);
        Ok(unloaded)
    }

    pub fn clear_memory_cache(&self) -> Result<()> {
        self.backend.trim_memory_pool(0)
    }

    pub fn memory_stats(&self) -> Result<CudaMemoryStats> {
        let pool = self.backend.memory_pool_stats()?;
        let (available, total) = self.backend.memory_info()?;
        let device = self.backend.device_info();
        Ok(CudaMemoryStats {
            total: u64::try_from(total)?,
            available: u64::try_from(available)?,
            reserved: pool.reserved,
            used: pool.used,
            device: device.name.clone(),
            integrated: device.integrated,
        })
    }
}

impl fmt::Debug for CudaEngine {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter
            .debug_struct("CudaEngine")
            .field("device", &self.backend.device_info().name)
            .field("cache", &self.cache)
            .field("session_config", &self.session_config)
            .field("scheduler", &self.scheduler)
            .finish_non_exhaustive()
    }
}