use std::collections::HashMap;
use onnx_runtime_ir::{
Attribute, DataType, Dim, Graph, Node, NodeId, Shape, TensorData, TypeProto, ValueId,
};
use crate::proto::onnx::{
self, attribute_proto, tensor_shape_proto, type_proto, AttributeProto, GraphProto, ModelProto,
TensorShapeProto,
};
use crate::weights::tensor_data_from_proto;
use crate::LoaderError;
pub(crate) struct BuiltGraph {
pub(crate) graph: Graph,
pub(crate) name_map: HashMap<String, ValueId>,
}
pub(crate) fn build_graph(model: &ModelProto) -> Result<BuiltGraph, LoaderError> {
let inlined = crate::function_inline::inline_functions(model)?;
let model = inlined.as_ref();
let mut graph = Graph::new();
for opset in &model.opset_import {
if opset.version > 0 {
graph
.opset_imports
.insert(opset.domain.clone(), opset.version as u64);
}
}
let graph_proto = model
.graph
.as_ref()
.ok_or_else(|| LoaderError::GraphBuild("ModelProto has no graph".into()))?;
let name_map = build_graph_proto(&mut graph, graph_proto, true)?;
Ok(BuiltGraph { graph, name_map })
}
fn build_graph_proto(
graph: &mut Graph,
gp: &GraphProto,
is_top_level: bool,
) -> Result<HashMap<String, ValueId>, LoaderError> {
let mut names: HashMap<String, ValueId> = HashMap::new();
for init in &gp.initializer {
if init.name.is_empty() {
continue;
}
let dtype = decode_dtype(init.data_type, || {
format!("initializer '{}'", init.name)
})?;
let dims_vec: Vec<usize> = init.dims.iter().map(|&d| d.max(0) as usize).collect();
let shape: Shape = dims_vec.iter().copied().map(Dim::Static).collect();
let vid = graph.create_named_value(init.name.clone(), dtype, shape);
names.insert(init.name.clone(), vid);
if !is_top_level
&& init.data_location != crate::proto::onnx::tensor_proto::DataLocation::External as i32
{
let td = crate::weights::tensor_data_from_proto(init, dtype, &dims_vec)?;
graph.set_initializer(vid, onnx_runtime_ir::WeightRef::Inline(td));
}
}
for vi in &gp.input {
if vi.name.is_empty() {
continue;
}
if names.contains_key(&vi.name) {
continue; }
let (dtype, shape) = value_info_type(graph, vi)?;
let vid = graph.create_named_value(vi.name.clone(), dtype, shape);
names.insert(vi.name.clone(), vid);
graph.add_input(vid);
}
for vi in &gp.value_info {
if vi.name.is_empty() || names.contains_key(&vi.name) {
continue;
}
let (dtype, shape) = value_info_type(graph, vi)?;
let vid = graph.create_named_value(vi.name.clone(), dtype, shape);
names.insert(vi.name.clone(), vid);
}
for vi in &gp.output {
if vi.name.is_empty() {
continue;
}
if !names.contains_key(&vi.name) {
let (dtype, shape) = value_info_type(graph, vi)?;
let vid = graph.create_named_value(vi.name.clone(), dtype, shape);
names.insert(vi.name.clone(), vid);
}
}
for np in &gp.node {
let inputs: Vec<Option<ValueId>> = np
.input
.iter()
.map(|name| {
if name.is_empty() {
None
} else {
Some(get_or_create(graph, &mut names, name))
}
})
.collect();
let outputs: Vec<ValueId> = np
.output
.iter()
.map(|name| {
if name.is_empty() {
graph.create_value(DataType::Float32, Vec::new())
} else {
get_or_create(graph, &mut names, name)
}
})
.collect();
let mut node = Node::new(NodeId(0), np.op_type.clone(), inputs, outputs);
node.name = np.name.clone();
node.domain = np.domain.clone();
if !np.doc_string.is_empty() {
node.doc_string = Some(np.doc_string.clone());
}
for ap in &np.attribute {
if let Some((key, attr)) = convert_attribute(graph, ap)? {
node.attributes.insert(key, attr);
}
}
let nid = graph.insert_node(node);
register_subgraphs(graph, nid);
}
for vi in &gp.output {
if let Some(&vid) = names.get(&vi.name) {
graph.add_output(vid);
}
}
Ok(names)
}
fn register_subgraphs(graph: &mut Graph, nid: NodeId) {
let attrs: Vec<(String, usize)> = graph
.node(nid)
.attributes
.iter()
.filter_map(|(k, v)| match v {
Attribute::Graph(_) => Some((k.clone(), 1)),
Attribute::Graphs(gs) => Some((k.clone(), gs.len())),
_ => None,
})
.collect();
for (key, count) in attrs {
match graph.node(nid).attributes.get(&key) {
Some(Attribute::Graph(g)) => {
let sub = (**g).clone();
graph.subgraphs.insert((nid, key), sub);
}
Some(Attribute::Graphs(_)) => {
for i in 0..count {
if let Some(Attribute::Graphs(gs)) = graph.node(nid).attributes.get(&key) {
let sub = gs[i].clone();
graph.subgraphs.insert((nid, format!("{key}[{i}]")), sub);
}
}
}
_ => {}
}
}
}
fn get_or_create(
graph: &mut Graph,
names: &mut HashMap<String, ValueId>,
name: &str,
) -> ValueId {
if let Some(&vid) = names.get(name) {
return vid;
}
let vid = graph.create_named_value(name.to_string(), DataType::Float32, Vec::new());
names.insert(name.to_string(), vid);
vid
}
fn decode_dtype(
raw: i32,
context: impl FnOnce() -> String,
) -> Result<DataType, LoaderError> {
DataType::from_onnx(raw).ok_or_else(|| LoaderError::UnsupportedDataType {
raw,
context: context(),
})
}
fn value_info_type(
graph: &mut Graph,
vi: &onnx::ValueInfoProto,
) -> Result<(DataType, Shape), LoaderError> {
match vi.r#type.as_ref() {
Some(tp) => type_proto_to_dtype_shape(graph, tp, &vi.name),
None => Ok((DataType::Float32, Vec::new())),
}
}
fn type_proto_to_dtype_shape(
graph: &mut Graph,
tp: &onnx::TypeProto,
name: &str,
) -> Result<(DataType, Shape), LoaderError> {
match tp.value.as_ref() {
Some(type_proto::Value::TensorType(t)) => {
let dtype = decode_dtype(t.elem_type, || format!("value-info '{name}'"))?;
let shape = t
.shape
.as_ref()
.map(|s| tensor_shape_to_shape(graph, s))
.unwrap_or_default();
Ok((dtype, shape))
}
Some(type_proto::Value::SparseTensorType(t)) => {
let dtype = decode_dtype(t.elem_type, || format!("value-info '{name}'"))?;
let shape = t
.shape
.as_ref()
.map(|s| tensor_shape_to_shape(graph, s))
.unwrap_or_default();
Ok((dtype, shape))
}
_ => Ok((DataType::Float32, Vec::new())),
}
}
fn tensor_shape_to_shape(graph: &mut Graph, tsp: &TensorShapeProto) -> Shape {
tsp.dim
.iter()
.map(|d| match d.value.as_ref() {
Some(tensor_shape_proto::dimension::Value::DimValue(v)) if *v >= 0 => {
Dim::Static(*v as usize)
}
Some(tensor_shape_proto::dimension::Value::DimParam(name)) if !name.is_empty() => {
Dim::Symbolic(graph.intern_symbol(name))
}
_ => Dim::Symbolic(graph.create_symbol(None)),
})
.collect()
}
fn convert_attribute(
graph: &mut Graph,
ap: &AttributeProto,
) -> Result<Option<(String, Attribute)>, LoaderError> {
use attribute_proto::AttributeType as AT;
let ty = AT::try_from(ap.r#type).unwrap_or(AT::Undefined);
let attr = match ty {
AT::Float => Attribute::Float(ap.f),
AT::Int => Attribute::Int(ap.i),
AT::String => Attribute::String(ap.s.clone()),
AT::Floats => Attribute::Floats(ap.floats.clone()),
AT::Ints => Attribute::Ints(ap.ints.clone()),
AT::Strings => Attribute::Strings(ap.strings.clone()),
AT::Tensor => match ap.t.as_ref() {
Some(t) => Attribute::Tensor(convert_tensor(t)?),
None => return Ok(None),
},
AT::Graph => match ap.g.as_ref() {
Some(g) => {
let mut sub = Graph::new();
build_graph_proto(&mut sub, g, false)?;
Attribute::Graph(Box::new(sub))
}
None => return Ok(None),
},
AT::Graphs => {
let mut subs = Vec::new();
for g in &ap.graphs {
let mut sub = Graph::new();
build_graph_proto(&mut sub, g, false)?;
subs.push(sub);
}
Attribute::Graphs(subs)
}
AT::TypeProto => match ap.tp.as_ref() {
Some(tp) => Attribute::TypeProto(convert_type_proto(graph, tp)?),
None => return Ok(None),
},
AT::Undefined => {
if let Some(t) = ap.t.as_ref() {
Attribute::Tensor(convert_tensor(t)?)
} else if !ap.floats.is_empty() {
Attribute::Floats(ap.floats.clone())
} else if !ap.ints.is_empty() {
Attribute::Ints(ap.ints.clone())
} else if !ap.strings.is_empty() {
Attribute::Strings(ap.strings.clone())
} else if !ap.s.is_empty() {
Attribute::String(ap.s.clone())
} else if ap.i != 0 {
Attribute::Int(ap.i)
} else if ap.f != 0.0 {
Attribute::Float(ap.f)
} else {
return Ok(None);
}
}
AT::Tensors | AT::SparseTensor | AT::SparseTensors | AT::TypeProtos => {
return Err(LoaderError::GraphBuild(format!(
"attribute {:?} has unmodeled type {:?}",
ap.name, ty
)));
}
};
Ok(Some((ap.name.clone(), attr)))
}
fn convert_tensor(t: &onnx::TensorProto) -> Result<TensorData, LoaderError> {
let dtype = decode_dtype(t.data_type, || format!("attribute tensor '{}'", t.name))?;
let dims: Vec<usize> = t.dims.iter().map(|&d| d.max(0) as usize).collect();
tensor_data_from_proto(t, dtype, &dims)
}
fn convert_type_proto(
graph: &mut Graph,
tp: &onnx::TypeProto,
) -> Result<TypeProto, LoaderError> {
let ty = match tp.value.as_ref() {
Some(type_proto::Value::TensorType(t)) => {
let dtype = decode_dtype(t.elem_type, || "type-proto attribute (tensor)".to_string())?;
let shape = t
.shape
.as_ref()
.map(|s| tensor_shape_to_shape(graph, s))
.unwrap_or_default();
TypeProto::Tensor { dtype, shape }
}
Some(type_proto::Value::SparseTensorType(t)) => {
let dtype =
decode_dtype(t.elem_type, || "type-proto attribute (sparse tensor)".to_string())?;
let shape = t
.shape
.as_ref()
.map(|s| tensor_shape_to_shape(graph, s))
.unwrap_or_default();
TypeProto::SparseTensor { dtype, shape }
}
Some(type_proto::Value::SequenceType(s)) => {
let inner = s
.elem_type
.as_ref()
.map(|e| convert_type_proto(graph, e))
.transpose()?
.unwrap_or(TypeProto::Tensor {
dtype: DataType::Float32,
shape: Vec::new(),
});
TypeProto::Sequence(Box::new(inner))
}
Some(type_proto::Value::OptionalType(o)) => {
let inner = o
.elem_type
.as_ref()
.map(|e| convert_type_proto(graph, e))
.transpose()?
.unwrap_or(TypeProto::Tensor {
dtype: DataType::Float32,
shape: Vec::new(),
});
TypeProto::Optional(Box::new(inner))
}
Some(type_proto::Value::MapType(m)) => {
let key = decode_dtype(m.key_type, || "type-proto attribute (map key)".to_string())?;
let value = m
.value_type
.as_ref()
.map(|e| convert_type_proto(graph, e))
.transpose()?
.unwrap_or(TypeProto::Tensor {
dtype: DataType::Float32,
shape: Vec::new(),
});
TypeProto::Map {
key,
value: Box::new(value),
}
}
None => TypeProto::Tensor {
dtype: DataType::Float32,
shape: Vec::new(),
},
};
Ok(ty)
}