use std::collections::HashMap;
use onnx_runtime_ir::{
Attribute, DataType, Dim, Graph, Node, NodeId, Shape, TensorData, TypeProto, ValueId,
};
use crate::LoaderError;
use crate::proto::onnx::{
self, AttributeProto, GraphProto, ModelProto, TensorShapeProto, attribute_proto,
tensor_shape_proto, type_proto,
};
use crate::weights::tensor_data_from_proto;
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, type_known, shape_known) = value_info_type(graph, vi)?;
let vid = graph.create_named_value(vi.name.clone(), dtype, shape);
mark_unknown_type_info(graph, vid, type_known, shape_known);
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, type_known, shape_known) = value_info_type(graph, vi)?;
let vid = graph.create_named_value(vi.name.clone(), dtype, shape);
mark_unknown_type_info(graph, vid, type_known, shape_known);
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, type_known, shape_known) = value_info_type(graph, vi)?;
let vid = graph.create_named_value(vi.name.clone(), dtype, shape);
mark_unknown_type_info(graph, vid, type_known, shape_known);
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());
graph.mark_value_type_unknown(vid);
graph.mark_value_shape_unknown(vid);
names.insert(name.to_string(), vid);
vid
}
fn mark_unknown_type_info(graph: &mut Graph, value: ValueId, type_known: bool, shape_known: bool) {
if !type_known {
graph.mark_value_type_unknown(value);
}
if !shape_known {
graph.mark_value_shape_unknown(value);
}
}
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, bool, bool), LoaderError> {
match vi.r#type.as_ref() {
Some(tp) => type_proto_to_dtype_shape(graph, tp, &vi.name),
None => Ok((DataType::Float32, Vec::new(), false, false)),
}
}
fn type_proto_to_dtype_shape(
graph: &mut Graph,
tp: &onnx::TypeProto,
name: &str,
) -> Result<(DataType, Shape, bool, bool), 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_known = t.shape.is_some();
let shape = t
.shape
.as_ref()
.map(|s| tensor_shape_to_shape(graph, s))
.unwrap_or_default();
Ok((dtype, shape, true, shape_known))
}
Some(type_proto::Value::SparseTensorType(t)) => {
let dtype = decode_dtype(t.elem_type, || format!("value-info '{name}'"))?;
let shape_known = t.shape.is_some();
let shape = t
.shape
.as_ref()
.map(|s| tensor_shape_to_shape(graph, s))
.unwrap_or_default();
Ok((dtype, shape, true, shape_known))
}
_ => Ok((DataType::Float32, Vec::new(), false, false)),
}
}
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::Tensors => Attribute::Tensors(
ap.tensors
.iter()
.map(convert_tensor)
.collect::<Result<_, _>>()?,
),
AT::SparseTensor => match ap.sparse_tensor.as_ref() {
Some(t) => Attribute::SparseTensor(convert_sparse_tensor(t)?),
None => return Ok(None),
},
AT::SparseTensors => Attribute::SparseTensors(
ap.sparse_tensors
.iter()
.map(convert_sparse_tensor)
.collect::<Result<_, _>>()?,
),
AT::Graph => match ap.g.as_ref() {
Some(g) => Attribute::Graph(Box::new(build_subgraph(g)?)),
None => return Ok(None),
},
AT::Graphs => Attribute::Graphs(
ap.graphs
.iter()
.map(build_subgraph)
.collect::<Result<_, _>>()?,
),
AT::TypeProto => match ap.tp.as_ref() {
Some(tp) => Attribute::TypeProto(convert_type_proto(graph, tp)?),
None => return Ok(None),
},
AT::TypeProtos => Attribute::TypeProtos(
ap.type_protos
.iter()
.map(|tp| convert_type_proto(graph, tp))
.collect::<Result<_, _>>()?,
),
AT::Undefined => {
if let Some(g) = ap.g.as_ref() {
Attribute::Graph(Box::new(build_subgraph(g)?))
} else if !ap.graphs.is_empty() {
Attribute::Graphs(
ap.graphs
.iter()
.map(build_subgraph)
.collect::<Result<_, _>>()?,
)
} else 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);
}
}
};
Ok(Some((ap.name.clone(), attr)))
}
fn build_subgraph(gp: &GraphProto) -> Result<Graph, LoaderError> {
let mut graph = Graph::new();
build_graph_proto(&mut graph, gp, false)?;
Ok(graph)
}
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_sparse_tensor(
tensor: &onnx::SparseTensorProto,
) -> Result<onnx_runtime_ir::SparseTensorData, LoaderError> {
let values = tensor
.values
.as_ref()
.ok_or_else(|| LoaderError::GraphBuild("sparse tensor is missing values".into()))
.and_then(convert_tensor)?;
let indices = tensor
.indices
.as_ref()
.ok_or_else(|| LoaderError::GraphBuild("sparse tensor is missing indices".into()))
.and_then(convert_tensor)?;
Ok(onnx_runtime_ir::SparseTensorData {
values,
indices,
dims: tensor.dims.iter().map(|&dim| dim.max(0) as usize).collect(),
})
}
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),
}
}
Some(type_proto::Value::OpaqueType(_)) => TypeProto::Tensor {
dtype: DataType::Float32,
shape: Vec::new(),
},
None => TypeProto::Tensor {
dtype: DataType::Float32,
shape: Vec::new(),
},
};
Ok(ty)
}