use std::fs::File;
use std::path::{Path, PathBuf};
use memmap2::Mmap;
use onnx_runtime_ir::{Attribute, Graph, Node, NodeId, ValueId};
use crate::{LoaderError, pathsafe::guarded_join};
pub(crate) const EP_CONTEXT_OP: &str = "EPContext";
pub(crate) const MS_DOMAIN: &str = "com.microsoft";
pub(crate) mod attr {
pub const MAIN_CONTEXT: &str = "main_context";
pub const EP_CACHE_CONTEXT: &str = "ep_cache_context";
pub const EMBED_MODE: &str = "embed_mode";
pub const EP_SDK_VERSION: &str = "ep_sdk_version";
pub const SOURCE: &str = "source";
pub const PARTITION_NAME: &str = "partition_name";
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EmbedMode {
Embedded,
ExternalFile,
}
impl EmbedMode {
fn from_attr_int(v: i64) -> Self {
match v {
0 => EmbedMode::ExternalFile,
_ => EmbedMode::Embedded,
}
}
}
#[derive(Debug)]
pub enum EpContextBlob {
Embedded(Vec<u8>),
External {
path: PathBuf,
map: Mmap,
},
}
impl EpContextBlob {
pub fn bytes(&self) -> &[u8] {
match self {
EpContextBlob::Embedded(b) => b,
EpContextBlob::External { map, .. } => &map[..],
}
}
}
#[derive(Debug)]
pub struct EpContextNode<'g> {
pub node: NodeId,
pub source: Option<&'g str>,
pub main_context: bool,
pub embed_mode: EmbedMode,
pub sdk_version: Option<&'g str>,
pub partition_name: Option<&'g str>,
inner: &'g Node,
}
impl<'g> EpContextNode<'g> {
pub fn from_node(node: &'g Node) -> Option<Self> {
if !is_ep_context_op(&node.op_type, &node.domain) {
return None;
}
let a = &node.attributes;
let main_context = a
.get(attr::MAIN_CONTEXT)
.and_then(Attribute::as_int)
.map(|v| v != 0)
.unwrap_or(true);
let embed_mode = a
.get(attr::EMBED_MODE)
.and_then(Attribute::as_int)
.map(EmbedMode::from_attr_int)
.unwrap_or(EmbedMode::Embedded);
Some(EpContextNode {
node: node.id,
source: str_attr(node, attr::SOURCE),
main_context,
embed_mode,
sdk_version: str_attr(node, attr::EP_SDK_VERSION),
partition_name: str_attr(node, attr::PARTITION_NAME),
inner: node,
})
}
pub fn inputs(&self) -> &'g [Option<ValueId>] {
&self.inner.inputs
}
pub fn outputs(&self) -> &'g [ValueId] {
&self.inner.outputs
}
pub fn inner(&self) -> &'g Node {
self.inner
}
fn ep_cache_context_bytes(&self) -> Option<&'g [u8]> {
match self.inner.attributes.get(attr::EP_CACHE_CONTEXT)? {
Attribute::String(s) => Some(s),
Attribute::Tensor(t) => Some(&t.data),
_ => None,
}
}
}
pub fn is_ep_context_op(op_type: &str, domain: &str) -> bool {
op_type == EP_CONTEXT_OP && domain == MS_DOMAIN
}
fn str_attr<'g>(node: &'g Node, name: &str) -> Option<&'g str> {
node.attributes
.get(name)
.and_then(Attribute::as_str)
.filter(|s| !s.is_empty())
}
pub fn ep_context_nodes(graph: &Graph) -> impl Iterator<Item = EpContextNode<'_>> {
graph.nodes.values().filter_map(EpContextNode::from_node)
}
pub fn ep_context_node_ids(graph: &Graph) -> Vec<NodeId> {
ep_context_nodes(graph).map(|n| n.node).collect()
}
pub fn resolve_ep_context(
model_dir: &Path,
n: &EpContextNode,
) -> Result<EpContextBlob, LoaderError> {
let raw = n.ep_cache_context_bytes().ok_or_else(|| {
LoaderError::EpContext(format!(
"EPContext node {:?} is missing the 'ep_cache_context' attribute",
n.node
))
})?;
match n.embed_mode {
EmbedMode::Embedded => Ok(EpContextBlob::Embedded(raw.to_vec())),
EmbedMode::ExternalFile => {
let rel = std::str::from_utf8(raw).map_err(|_| {
LoaderError::EpContext(format!(
"EPContext node {:?}: external 'ep_cache_context' path is not valid UTF-8",
n.node
))
})?;
let path = resolve_external_path(model_dir, rel)?;
let file = File::open(&path)
.map_err(|_| LoaderError::ExternalDataNotFound { path: path.clone() })?;
let map = unsafe { Mmap::map(&file) }.map_err(|e| LoaderError::Mmap(e.to_string()))?;
Ok(EpContextBlob::External { path, map })
}
}
}
fn resolve_external_path(model_dir: &Path, rel: &str) -> Result<PathBuf, LoaderError> {
guarded_join(model_dir, rel).map_err(|reason| LoaderError::EpContextPath {
path: rel.to_string(),
reason,
})
}