pub mod burn_engine;
pub mod onnx_engine;
#[cfg(feature = "hub")]
use std::path::PathBuf;
use dspk_core::{DspError, DspResult};
#[cfg(feature = "hub")]
use crate::manifest::ModelManifest;
use crate::manifest::TensorPortSpec;
pub use burn_engine::{burn_conv1d, burn_linear_2d};
pub use onnx_engine::{OnnxRuntimeSession, RuntimeTensor};
#[cfg(feature = "hub")]
pub fn pull_model(model_id: &str) -> DspResult<(ModelManifest, PathBuf)> {
let catalog = crate::manifest::ModelCatalog::load_default()?;
let manifest = catalog
.get(model_id)
.cloned()
.ok_or_else(|| DspError::InvalidConfig(format!("unknown model id '{model_id}' in the dspk-synapse-ml catalog")))?;
let path = dspk_hub::pull(model_id).map_err(|e| DspError::Io(e.to_string()))?;
Ok((manifest, path))
}
pub fn validate_tensor_port(spec: &TensorPortSpec, actual_shape: &[usize]) -> DspResult<()> {
if spec.shape.len() != actual_shape.len() {
return Err(DspError::InvalidConfig(format!(
"tensor port '{}' expects rank {} ({:?}), got rank {} ({:?})",
spec.name,
spec.shape.len(),
spec.shape,
actual_shape.len(),
actual_shape
)));
}
for (axis, (&expected, &actual)) in spec.shape.iter().zip(actual_shape.iter()).enumerate() {
if expected > 0 && (expected as usize) != actual {
return Err(DspError::InvalidConfig(format!(
"tensor port '{}' axis {axis} expects dimension {expected} (spec {:?}), got {actual} (actual {:?})",
spec.name, spec.shape, actual_shape
)));
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_validate_tensor_port_dynamic_and_fixed_dims() {
let spec = TensorPortSpec {
name: "waveforms".to_string(),
dtype: "float32".to_string(),
shape: vec![-1, -1, 61],
description: Some("batch x channels x window".to_string()),
};
assert!(validate_tensor_port(&spec, &[32, 4, 61]).is_ok());
assert!(validate_tensor_port(&spec, &[1, 384, 61]).is_ok());
assert!(validate_tensor_port(&spec, &[32, 4, 40]).is_err());
assert!(validate_tensor_port(&spec, &[32, 61]).is_err());
}
}