Files
rustytorch/crates/production/rtx-onnx-codegen/src/ir/graph.rs
T
osobhandClaude Opus 4.6 02d382d5f6 style: apply rustfmt across all crates and demos
Consistent formatting pass: line wrapping, import sorting, trailing
whitespace removal, let-chain indentation, merged derive attributes,
and unsafe block reformatting.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
2026-04-12 07:01:58 -07:00

223 lines
6.2 KiB
Rust

//! ONNX graph representation.
use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
use crate::ir::{Node, Tensor, ValueInfo};
use crate::onnx_proto::GraphProto;
/// An ONNX computation graph.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OnnxGraph {
/// Graph name.
pub name: String,
/// ONNX opset version.
pub opset_version: i64,
/// Graph inputs.
pub inputs: Vec<ValueInfo>,
/// Graph outputs.
pub outputs: Vec<ValueInfo>,
/// Computation nodes (operations).
pub nodes: Vec<Node>,
/// Initializers (weights/constants).
pub initializers: HashMap<String, Tensor>,
/// Value info for intermediate tensors.
pub value_info: HashMap<String, ValueInfo>,
}
impl OnnxGraph {
/// Create from ONNX GraphProto.
pub fn from_proto(proto: GraphProto, opset_version: i64) -> Result<Self> {
let name = proto.name.clone();
// Parse inputs
let inputs: Vec<ValueInfo> = proto
.input
.iter()
.filter(|i| !proto.initializer.iter().any(|init| init.name == i.name))
.map(ValueInfo::from_proto)
.collect::<Result<_>>()?;
// Parse outputs
let outputs: Vec<ValueInfo> = proto
.output
.iter()
.map(ValueInfo::from_proto)
.collect::<Result<_>>()?;
// Parse nodes
let nodes: Vec<Node> = proto
.node
.iter()
.map(Node::from_proto)
.collect::<Result<_>>()?;
// Parse initializers (weights)
let mut initializers = HashMap::new();
for init in &proto.initializer {
let tensor = Tensor::from_proto(init)?;
initializers.insert(tensor.name.clone(), tensor);
}
// Parse value info
let mut value_info = HashMap::new();
for vi in &proto.value_info {
let info = ValueInfo::from_proto(vi)?;
value_info.insert(info.name.clone(), info);
}
log::debug!(
"Graph '{}': {} inputs, {} outputs, {} nodes, {} initializers",
name,
inputs.len(),
outputs.len(),
nodes.len(),
initializers.len()
);
Ok(Self {
name,
opset_version,
inputs,
outputs,
nodes,
initializers,
value_info,
})
}
/// Get input names.
pub fn input_names(&self) -> Vec<&str> {
self.inputs.iter().map(|i| i.name.as_str()).collect()
}
/// Get output names.
pub fn output_names(&self) -> Vec<&str> {
self.outputs.iter().map(|o| o.name.as_str()).collect()
}
/// Check if a tensor is an initializer (weight).
pub fn is_initializer(&self, name: &str) -> bool {
self.initializers.contains_key(name)
}
/// Get an initializer by name.
pub fn get_initializer(&self, name: &str) -> Option<&Tensor> {
self.initializers.get(name)
}
/// Validate the graph structure.
pub fn validate(&self) -> Result<()> {
// Check all inputs are defined
let mut defined: std::collections::HashSet<&str> =
self.inputs.iter().map(|i| i.name.as_str()).collect();
// Add initializers
for name in self.initializers.keys() {
defined.insert(name);
}
// Check each node's inputs and track outputs
for node in &self.nodes {
for input in &node.inputs {
if !input.is_empty() && !defined.contains(input.as_str()) {
return Err(Error::InvalidModel(format!(
"Node '{}' ({:?}) references undefined input '{}'",
node.name, node.kind, input
)));
}
}
for output in &node.outputs {
defined.insert(output);
}
}
// Check all outputs are defined
for output in &self.outputs {
if !defined.contains(output.name.as_str()) {
return Err(Error::InvalidModel(format!(
"Output '{}' is not defined",
output.name
)));
}
}
Ok(())
}
/// Get statistics about the graph.
pub fn stats(&self) -> GraphStats {
let mut op_counts = HashMap::new();
let mut unsupported = Vec::new();
for node in &self.nodes {
*op_counts.entry(format!("{:?}", node.kind)).or_insert(0) += 1;
if !node.kind.is_supported() {
unsupported.push(format!("{:?}", node.kind));
}
}
let total_params: usize = self
.initializers
.values()
.map(super::tensor::Tensor::numel)
.sum();
GraphStats {
num_inputs: self.inputs.len(),
num_outputs: self.outputs.len(),
num_nodes: self.nodes.len(),
num_initializers: self.initializers.len(),
total_parameters: total_params,
op_counts,
unsupported_ops: unsupported,
}
}
}
/// Statistics about an ONNX graph.
#[derive(Debug, Clone)]
pub struct GraphStats {
/// Number of inputs.
pub num_inputs: usize,
/// Number of outputs.
pub num_outputs: usize,
/// Number of nodes.
pub num_nodes: usize,
/// Number of initializers.
pub num_initializers: usize,
/// Total number of parameters.
pub total_parameters: usize,
/// Count of each operator type.
pub op_counts: HashMap<String, usize>,
/// List of unsupported operators.
pub unsupported_ops: Vec<String>,
}
impl std::fmt::Display for GraphStats {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "Graph Statistics:")?;
writeln!(f, " Inputs: {}", self.num_inputs)?;
writeln!(f, " Outputs: {}", self.num_outputs)?;
writeln!(f, " Nodes: {}", self.num_nodes)?;
writeln!(f, " Initializers: {}", self.num_initializers)?;
writeln!(f, " Total Parameters: {}", self.total_parameters)?;
if !self.unsupported_ops.is_empty() {
writeln!(f, " Unsupported ops: {:?}", self.unsupported_ops)?;
}
Ok(())
}
}