1use std::collections::HashMap;
8
9use onnx_runtime_ir::{
10 Attribute, DataType, Dim, Graph, Node, NodeId, Shape, TensorData, TypeProto, ValueId,
11};
12
13use crate::proto::onnx::{
14 self, attribute_proto, tensor_shape_proto, type_proto, AttributeProto, GraphProto, ModelProto,
15 TensorShapeProto,
16};
17use crate::weights::tensor_data_from_proto;
18use crate::LoaderError;
19
20pub struct BuiltGraph {
24 pub graph: Graph,
25 pub name_map: HashMap<String, ValueId>,
26}
27
28pub fn build_graph(model: &ModelProto) -> Result<BuiltGraph, LoaderError> {
32 let mut graph = Graph::new();
33
34 for opset in &model.opset_import {
36 if opset.version > 0 {
37 graph
38 .opset_imports
39 .insert(opset.domain.clone(), opset.version as u64);
40 }
41 }
42
43 let graph_proto = model
44 .graph
45 .as_ref()
46 .ok_or_else(|| LoaderError::GraphBuild("ModelProto has no graph".into()))?;
47
48 let name_map = build_graph_proto(&mut graph, graph_proto, true)?;
49
50 graph
51 .validate()
52 .map_err(|errs| LoaderError::GraphBuild(format!("{errs:?}")))?;
53
54 Ok(BuiltGraph { graph, name_map })
55}
56
57fn build_graph_proto(
61 graph: &mut Graph,
62 gp: &GraphProto,
63 is_top_level: bool,
64) -> Result<HashMap<String, ValueId>, LoaderError> {
65 let mut names: HashMap<String, ValueId> = HashMap::new();
66
67 for init in &gp.initializer {
69 if init.name.is_empty() {
70 continue;
71 }
72 let dtype = decode_dtype(init.data_type, || {
73 format!("initializer '{}'", init.name)
74 })?;
75 let dims: Vec<usize> = init.dims.iter().map(|&d| d.max(0) as usize).collect();
76 let shape: Shape = dims.into_iter().map(Dim::Static).collect();
77 let vid = graph.create_named_value(init.name.clone(), dtype, shape);
78 names.insert(init.name.clone(), vid);
79 }
80
81 for vi in &gp.input {
84 if vi.name.is_empty() {
85 continue;
86 }
87 if names.contains_key(&vi.name) {
88 continue; }
90 let (dtype, shape) = value_info_type(graph, vi)?;
91 let vid = graph.create_named_value(vi.name.clone(), dtype, shape);
92 names.insert(vi.name.clone(), vid);
93 if is_top_level {
94 graph.add_input(vid);
95 }
96 }
97
98 for vi in &gp.value_info {
100 if vi.name.is_empty() || names.contains_key(&vi.name) {
101 continue;
102 }
103 let (dtype, shape) = value_info_type(graph, vi)?;
104 let vid = graph.create_named_value(vi.name.clone(), dtype, shape);
105 names.insert(vi.name.clone(), vid);
106 }
107
108 for vi in &gp.output {
110 if vi.name.is_empty() {
111 continue;
112 }
113 if !names.contains_key(&vi.name) {
114 let (dtype, shape) = value_info_type(graph, vi)?;
115 let vid = graph.create_named_value(vi.name.clone(), dtype, shape);
116 names.insert(vi.name.clone(), vid);
117 }
118 }
119
120 for np in &gp.node {
122 let inputs: Vec<Option<ValueId>> = np
123 .input
124 .iter()
125 .map(|name| {
126 if name.is_empty() {
127 None
128 } else {
129 Some(get_or_create(graph, &mut names, name))
130 }
131 })
132 .collect();
133
134 let outputs: Vec<ValueId> = np
135 .output
136 .iter()
137 .map(|name| {
138 if name.is_empty() {
139 graph.create_value(DataType::Float32, Vec::new())
142 } else {
143 get_or_create(graph, &mut names, name)
144 }
145 })
146 .collect();
147
148 let mut node = Node::new(NodeId(0), np.op_type.clone(), inputs, outputs);
149 node.domain = np.domain.clone();
150 if !np.doc_string.is_empty() {
151 node.doc_string = Some(np.doc_string.clone());
152 }
153 for ap in &np.attribute {
154 if let Some((key, attr)) = convert_attribute(graph, ap)? {
155 node.attributes.insert(key, attr);
156 }
157 }
158
159 let nid = graph.insert_node(node);
160 register_subgraphs(graph, nid);
161 }
162
163 if is_top_level {
165 for vi in &gp.output {
166 if let Some(&vid) = names.get(&vi.name) {
167 graph.add_output(vid);
168 }
169 }
170 }
171
172 Ok(names)
173}
174
175fn register_subgraphs(graph: &mut Graph, nid: NodeId) {
178 let attrs: Vec<(String, usize)> = graph
179 .node(nid)
180 .attributes
181 .iter()
182 .filter_map(|(k, v)| match v {
183 Attribute::Graph(_) => Some((k.clone(), 1)),
184 Attribute::Graphs(gs) => Some((k.clone(), gs.len())),
185 _ => None,
186 })
187 .collect();
188 for (key, count) in attrs {
189 match graph.node(nid).attributes.get(&key) {
190 Some(Attribute::Graph(g)) => {
191 let sub = (**g).clone();
192 graph.subgraphs.insert((nid, key), sub);
193 }
194 Some(Attribute::Graphs(_)) => {
195 for i in 0..count {
196 if let Some(Attribute::Graphs(gs)) = graph.node(nid).attributes.get(&key) {
197 let sub = gs[i].clone();
198 graph.subgraphs.insert((nid, format!("{key}[{i}]")), sub);
199 }
200 }
201 }
202 _ => {}
203 }
204 }
205}
206
207fn get_or_create(
210 graph: &mut Graph,
211 names: &mut HashMap<String, ValueId>,
212 name: &str,
213) -> ValueId {
214 if let Some(&vid) = names.get(name) {
215 return vid;
216 }
217 let vid = graph.create_named_value(name.to_string(), DataType::Float32, Vec::new());
218 names.insert(name.to_string(), vid);
219 vid
220}
221
222fn decode_dtype(
227 raw: i32,
228 context: impl FnOnce() -> String,
229) -> Result<DataType, LoaderError> {
230 DataType::from_onnx(raw).ok_or_else(|| LoaderError::UnsupportedDataType {
231 raw,
232 context: context(),
233 })
234}
235
236fn value_info_type(
239 graph: &mut Graph,
240 vi: &onnx::ValueInfoProto,
241) -> Result<(DataType, Shape), LoaderError> {
242 match vi.r#type.as_ref() {
243 Some(tp) => type_proto_to_dtype_shape(graph, tp, &vi.name),
244 None => Ok((DataType::Float32, Vec::new())),
247 }
248}
249
250fn type_proto_to_dtype_shape(
251 graph: &mut Graph,
252 tp: &onnx::TypeProto,
253 name: &str,
254) -> Result<(DataType, Shape), LoaderError> {
255 match tp.value.as_ref() {
256 Some(type_proto::Value::TensorType(t)) => {
257 let dtype = decode_dtype(t.elem_type, || format!("value-info '{name}'"))?;
258 let shape = t
259 .shape
260 .as_ref()
261 .map(|s| tensor_shape_to_shape(graph, s))
262 .unwrap_or_default();
263 Ok((dtype, shape))
264 }
265 Some(type_proto::Value::SparseTensorType(t)) => {
266 let dtype = decode_dtype(t.elem_type, || format!("value-info '{name}'"))?;
267 let shape = t
268 .shape
269 .as_ref()
270 .map(|s| tensor_shape_to_shape(graph, s))
271 .unwrap_or_default();
272 Ok((dtype, shape))
273 }
274 _ => Ok((DataType::Float32, Vec::new())),
278 }
279}
280
281fn tensor_shape_to_shape(graph: &mut Graph, tsp: &TensorShapeProto) -> Shape {
285 tsp.dim
286 .iter()
287 .map(|d| match d.value.as_ref() {
288 Some(tensor_shape_proto::dimension::Value::DimValue(v)) if *v >= 0 => {
289 Dim::Static(*v as usize)
290 }
291 Some(tensor_shape_proto::dimension::Value::DimParam(name)) if !name.is_empty() => {
292 Dim::Symbolic(graph.intern_symbol(name))
293 }
294 _ => Dim::Symbolic(graph.create_symbol(None)),
296 })
297 .collect()
298}
299
300fn convert_attribute(
305 graph: &mut Graph,
306 ap: &AttributeProto,
307) -> Result<Option<(String, Attribute)>, LoaderError> {
308 use attribute_proto::AttributeType as AT;
309
310 let ty = AT::try_from(ap.r#type).unwrap_or(AT::Undefined);
313
314 let attr = match ty {
315 AT::Float => Attribute::Float(ap.f),
316 AT::Int => Attribute::Int(ap.i),
317 AT::String => Attribute::String(ap.s.clone()),
321 AT::Floats => Attribute::Floats(ap.floats.clone()),
322 AT::Ints => Attribute::Ints(ap.ints.clone()),
323 AT::Strings => Attribute::Strings(ap.strings.clone()),
324 AT::Tensor => match ap.t.as_ref() {
325 Some(t) => Attribute::Tensor(convert_tensor(t)?),
326 None => return Ok(None),
327 },
328 AT::Graph => match ap.g.as_ref() {
329 Some(g) => {
330 let mut sub = Graph::new();
331 build_graph_proto(&mut sub, g, false)?;
332 Attribute::Graph(Box::new(sub))
333 }
334 None => return Ok(None),
335 },
336 AT::Graphs => {
337 let mut subs = Vec::new();
338 for g in &ap.graphs {
339 let mut sub = Graph::new();
340 build_graph_proto(&mut sub, g, false)?;
341 subs.push(sub);
342 }
343 Attribute::Graphs(subs)
344 }
345 AT::TypeProto => match ap.tp.as_ref() {
346 Some(tp) => Attribute::TypeProto(convert_type_proto(graph, tp)?),
347 None => return Ok(None),
348 },
349 AT::Undefined => {
351 if let Some(t) = ap.t.as_ref() {
352 Attribute::Tensor(convert_tensor(t)?)
353 } else if !ap.floats.is_empty() {
354 Attribute::Floats(ap.floats.clone())
355 } else if !ap.ints.is_empty() {
356 Attribute::Ints(ap.ints.clone())
357 } else if !ap.strings.is_empty() {
358 Attribute::Strings(ap.strings.clone())
359 } else if !ap.s.is_empty() {
360 Attribute::String(ap.s.clone())
361 } else if ap.i != 0 {
362 Attribute::Int(ap.i)
363 } else if ap.f != 0.0 {
364 Attribute::Float(ap.f)
365 } else {
366 return Ok(None);
367 }
368 }
369 AT::Tensors | AT::SparseTensor | AT::SparseTensors | AT::TypeProtos => {
373 return Err(LoaderError::GraphBuild(format!(
374 "attribute {:?} has unmodeled type {:?}",
375 ap.name, ty
376 )));
377 }
378 };
379 Ok(Some((ap.name.clone(), attr)))
380}
381
382fn convert_tensor(t: &onnx::TensorProto) -> Result<TensorData, LoaderError> {
383 let dtype = decode_dtype(t.data_type, || format!("attribute tensor '{}'", t.name))?;
384 let dims: Vec<usize> = t.dims.iter().map(|&d| d.max(0) as usize).collect();
385 tensor_data_from_proto(t, dtype, &dims)
386}
387
388fn convert_type_proto(
389 graph: &mut Graph,
390 tp: &onnx::TypeProto,
391) -> Result<TypeProto, LoaderError> {
392 let ty = match tp.value.as_ref() {
393 Some(type_proto::Value::TensorType(t)) => {
394 let dtype = decode_dtype(t.elem_type, || "type-proto attribute (tensor)".to_string())?;
395 let shape = t
396 .shape
397 .as_ref()
398 .map(|s| tensor_shape_to_shape(graph, s))
399 .unwrap_or_default();
400 TypeProto::Tensor { dtype, shape }
401 }
402 Some(type_proto::Value::SparseTensorType(t)) => {
403 let dtype =
404 decode_dtype(t.elem_type, || "type-proto attribute (sparse tensor)".to_string())?;
405 let shape = t
406 .shape
407 .as_ref()
408 .map(|s| tensor_shape_to_shape(graph, s))
409 .unwrap_or_default();
410 TypeProto::SparseTensor { dtype, shape }
411 }
412 Some(type_proto::Value::SequenceType(s)) => {
413 let inner = s
414 .elem_type
415 .as_ref()
416 .map(|e| convert_type_proto(graph, e))
417 .transpose()?
418 .unwrap_or(TypeProto::Tensor {
419 dtype: DataType::Float32,
420 shape: Vec::new(),
421 });
422 TypeProto::Sequence(Box::new(inner))
423 }
424 Some(type_proto::Value::OptionalType(o)) => {
425 let inner = o
426 .elem_type
427 .as_ref()
428 .map(|e| convert_type_proto(graph, e))
429 .transpose()?
430 .unwrap_or(TypeProto::Tensor {
431 dtype: DataType::Float32,
432 shape: Vec::new(),
433 });
434 TypeProto::Optional(Box::new(inner))
435 }
436 Some(type_proto::Value::MapType(m)) => {
437 let key = decode_dtype(m.key_type, || "type-proto attribute (map key)".to_string())?;
438 let value = m
439 .value_type
440 .as_ref()
441 .map(|e| convert_type_proto(graph, e))
442 .transpose()?
443 .unwrap_or(TypeProto::Tensor {
444 dtype: DataType::Float32,
445 shape: Vec::new(),
446 });
447 TypeProto::Map {
448 key,
449 value: Box::new(value),
450 }
451 }
452 None => TypeProto::Tensor {
453 dtype: DataType::Float32,
454 shape: Vec::new(),
455 },
456 };
457 Ok(ty)
458}