use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, OnceLock};
use async_trait::async_trait;
use dataflow_rs::datalogic_rs as datalogic;
use super::handler::ArtifactSource;
use super::loader::{ManifestEntry, ModelEntry, ModelSet};
use super::runtimes::LoadError;
use crate::config::ModelsConfig;
pub struct OfflineModels {
manifests: Vec<ManifestEntry>,
config: Arc<ModelsConfig>,
set: OnceLock<Arc<ModelSet>>,
}
impl OfflineModels {
pub fn new(manifests: Vec<ManifestEntry>, config: Arc<ModelsConfig>) -> Self {
Self {
manifests,
config,
set: OnceLock::new(),
}
}
pub fn set_on(&self, datalogic: &datalogic::Engine) -> Arc<ModelSet> {
self.set
.get_or_init(|| {
Arc::new(ModelSet::from_manifests(
self.manifests.iter().cloned(),
&self.config,
datalogic,
))
})
.clone()
}
pub fn ids(&self) -> impl Iterator<Item = &str> {
self.manifests.iter().map(|m| m.manifest.name.as_str())
}
pub fn artifacts(&self) -> LocalArtifacts {
LocalArtifacts {
paths: self
.manifests
.iter()
.map(|m| (m.manifest.name.clone(), m.artifact_path.clone()))
.collect(),
}
}
}
pub struct LocalArtifacts {
paths: HashMap<String, PathBuf>,
}
#[async_trait]
impl ArtifactSource for LocalArtifacts {
async fn bytes(&self, entry: &ModelEntry) -> Result<Vec<u8>, LoadError> {
let Some(path) = self.paths.get(&entry.id) else {
return Err(LoadError::new(
"artifact",
format!("no artifact on disk for model '{}'", entry.id),
));
};
tokio::fs::read(path)
.await
.map_err(|e| LoadError::new("artifact", format!("reading '{}': {e}", path.display())))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::fixture;
fn entry(path: PathBuf) -> ManifestEntry {
ManifestEntry {
manifest: fixture::manifest(),
artifact_path: path,
digest: crate::crypto::sha256_digest(fixture::ONNX),
stats: None,
}
}
#[tokio::test]
async fn the_set_compiles_once_and_the_bytes_come_from_the_file() {
let dir = std::env::temp_dir().join(format!("orion-offline-{}", uuid::Uuid::new_v4()));
std::fs::create_dir_all(&dir).expect("dir");
let path = dir.join("c4-tiny.onnx");
std::fs::write(&path, fixture::ONNX).expect("write");
let offline = OfflineModels::new(
vec![entry(path.clone())],
Arc::new(ModelsConfig {
enabled: true,
..ModelsConfig::default()
}),
);
assert_eq!(offline.ids().collect::<Vec<_>>(), ["ada.c4-tiny"]);
let engine = crate::engine::operators::add_to_datalogic(
datalogic::Engine::builder()
.with_templating(true)
.with_template_key_escape('$'),
)
.build();
let first = offline.set_on(&engine);
let second = offline.set_on(&engine);
assert!(Arc::ptr_eq(&first, &second), "compiled once");
let compiled = first.get("ada.c4-tiny").expect("compiled");
let bytes = offline
.artifacts()
.bytes(compiled)
.await
.expect("the file is read");
assert_eq!(bytes, fixture::ONNX);
std::fs::remove_file(&path).expect("remove");
let err = offline
.artifacts()
.bytes(compiled)
.await
.expect_err("the file is gone");
assert_eq!(err.stage, "artifact");
let _ = std::fs::remove_dir_all(&dir);
}
}