use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use onnx_runtime_ep_api::{EpContext, EpError, EpId, ExecutionProvider, build_ep_context_registry};
use onnx_runtime_ir::{Graph, NodeId};
use onnx_runtime_loader::{
EpContextDumpConfig, EpContextNode, EpContextPartition, Model, dump_ep_context,
ep_context_nodes, resolve_ep_context,
};
use crate::error::{Result, SessionError};
#[derive(Clone, Debug, Default)]
pub struct EpContextPlacement {
pub handled: Vec<NodeId>,
}
impl EpContextPlacement {
pub fn is_empty(&self) -> bool {
self.handled.is_empty()
}
}
type PrimaryKey = (Option<String>, Option<String>);
pub fn load_ep_context_nodes(
graph: &Graph,
model_dir: &Path,
eps: &[(EpId, &dyn ExecutionProvider)],
) -> Result<EpContextPlacement> {
let nodes: Vec<EpContextNode<'_>> = ep_context_nodes(graph).collect();
if nodes.is_empty() {
return Ok(EpContextPlacement::default());
}
let registry = build_ep_context_registry(eps.iter().copied())?;
let ep_by_id: HashMap<EpId, &dyn ExecutionProvider> = eps.iter().copied().collect();
let mut handled = Vec::with_capacity(nodes.len());
let mut primaries: HashSet<PrimaryKey> = HashSet::new();
let mut loaded_payloads: HashSet<(Option<String>, Vec<u8>)> = HashSet::new();
for node in nodes.iter().filter(|n| n.main_context) {
let ep = claim_ep(®istry, &ep_by_id, node)?;
let blob = resolve_ep_context(model_dir, node)?;
let dedup_key = (node.source.map(str::to_owned), blob.bytes().to_vec());
if loaded_payloads.insert(dedup_key) {
let ctx = EpContext {
ep_name: ep.name().to_string(),
ep_version: node.sdk_version.unwrap_or_default().to_string(),
data: blob.bytes().to_vec(),
covered_nodes: vec![node.node],
device_fingerprint: String::new(),
};
ep.load_context(&ctx)?;
}
primaries.insert((
node.source.map(str::to_owned),
node.partition_name.map(str::to_owned),
));
handled.push(node.node);
}
for node in nodes.iter().filter(|n| !n.main_context) {
claim_ep(®istry, &ep_by_id, node)?;
let key = (
node.source.map(str::to_owned),
node.partition_name.map(str::to_owned),
);
if !primaries.contains(&key) {
return Err(SessionError::DanglingEpContext {
source_key: node.source.map(str::to_owned),
partition_name: node.partition_name.map(str::to_owned),
});
}
handled.push(node.node);
}
Ok(EpContextPlacement { handled })
}
fn claim_ep<'e>(
registry: &onnx_runtime_ep_api::EpContextRegistry,
ep_by_id: &HashMap<EpId, &'e dyn ExecutionProvider>,
node: &EpContextNode<'_>,
) -> Result<&'e dyn ExecutionProvider> {
let ep_id = registry
.claim(node.source)
.ok_or_else(|| EpError::NoEpForContext {
source_key: node.source.map(str::to_owned),
})?;
ep_by_id.get(&ep_id).copied().ok_or_else(|| {
SessionError::Internal(format!(
"EPContext registry returned unknown Ep id {ep_id:?} for source {:?}",
node.source
))
})
}
pub struct CompiledPartition<'a> {
pub ep: &'a dyn ExecutionProvider,
pub partition_name: String,
pub covered_nodes: Vec<NodeId>,
}
pub fn dump_session_ep_context(
model: &Model,
orig_path: &Path,
partitions: &[CompiledPartition],
config: &EpContextDumpConfig,
) -> Result<PathBuf> {
if !config.enable {
return Ok(dump_ep_context(model, orig_path, &[], config)?);
}
struct Owned {
source: String,
ctx: EpContext,
partition_name: String,
covered_nodes: Vec<NodeId>,
}
let mut owned = Vec::with_capacity(partitions.len());
for part in partitions {
let ctx = part.ep.save_context()?;
let source = part
.ep
.context_source_keys()
.into_iter()
.next()
.ok_or_else(|| {
SessionError::Internal(format!(
"EP {:?} produced a context but declares no `source` key — the \
EPContext node could not be dispatched on reload (§55.6)",
part.ep.name()
))
})?;
owned.push(Owned {
source,
ctx,
partition_name: part.partition_name.clone(),
covered_nodes: part.covered_nodes.clone(),
});
}
let loader_parts: Vec<EpContextPartition<'_>> = owned
.iter()
.map(|o| EpContextPartition {
source: &o.source,
ep_sdk_version: &o.ctx.ep_version,
partition_name: &o.partition_name,
main_context: true,
blob: &o.ctx.data,
covered_nodes: &o.covered_nodes,
})
.collect();
Ok(dump_ep_context(model, orig_path, &loader_parts, config)?)
}