fn optimize_branch(
        &mut self,
        node: Arc<Node<'model>>,
        chain: &mut Vec<(String, Arc<Node<'model>>)>,
    ) -> Result<Sequence<'model>, OptimizerError> {
        log::info!(
            "Optimize {:?} chain length={:?}",
            node.definition,
            chain.len()
        );

        if let NodeDefinition::Operator(op_def) = &node.definition {
            // Specific operators can be spliced out of the DAG
            match op_def.proto.get_op_type() {
                // Identity: we just remove these nodes and connect the input of the destination node to our input's output
                // A -> Identity -> B => A -> B
                "Identity" => {
                    if node.inputs.len() != 1 {
                        return Err(OptimizerError::NoInputs);
                    }
                    return self.optimize_branch_cached(node.inputs[0].source_node.clone(), chain);
                }
                // The Dropout operation does nothing when its training_mode is set to 0. We do not support training_mode=1
                "Dropout" => {
                    if node.inputs.is_empty() {
                        return Err(OptimizerError::NoInputs);
                    }
                    let training_mode =
                        get_attribute("training_mode", Some(0), &op_def.proto)? == 1;

                    if training_mode {
                        return Err(OptimizerError::Unsupported(String::from(
                            "Dropout with training_mode=1",
                        )));
                    }

                    return self.optimize_branch_cached(node.inputs[0].source_node.clone(), chain);
                }
                _ => {}
            }

            // Optimize chains of operators. A chain can only be formed for operations that each have at most one 'dynamic'
            // input (e.g. inference input or the input from another operation)
            let dynamic_input_count = node
                .inputs
                .iter()
                .filter(|input| {
                    matches!(
                        input.source_node.definition,
                        NodeDefinition::Operator(..) | NodeDefinition::Input(..)
                    )
                })
                .count();

            if dynamic_input_count == 1 {
                chain.push((op_def.proto.get_op_type().to_string(), node.clone()));

                if let Some(seq) = self.optimize_chain(chain)? {
                    log::info!(
                        "chain optimization: fuse {} operators to {:?}",
                        seq.skip,
                        seq.node.definition
                    );
                    return Ok(seq);
                }

                // Continue optimizing inputs, making the chain longer for one input
                let mut new_inputs = Vec::with_capacity(node.inputs.len());

                for input in &node.inputs {
                    let source_sequence =
                        self.optimize_branch_cached(input.source_node.clone(), chain)?;

                    if source_sequence.skip > 0 {
                        log::info!("operator is optimized away: {:?}", node.definition);
                        return Ok(Sequence {
                            node: source_sequence.node,
                            skip: source_sequence.skip - 1,
                        });
                    }

                    new_inputs.push(Input {
                        source_node: source_sequence.node,
                        output_index: input.output_index,
                    });
                }

                return Ok(Sequence {
                    node: self.optimized_with(&node, new_inputs)?,
                    skip: 0,
                });
            }
        }

        // This node has multiple inputs; optimize each input as a new chain
        let new_inputs = node
            .inputs
            .iter()
            .map(|input| {
                let mut input_chain = vec![];
                let source_sequence =
                    self.optimize_branch_cached(input.source_node.clone(), &mut input_chain)?;

                assert!(
                    source_sequence.skip == 0,
                    "cannot skip items in a chain that has multiple inputs for a single op"
                );

                Ok(Input {
                    source_node: source_sequence.node,
                    output_index: input.output_index,
                })
            })
            .collect::<Result<Vec<Input>, OptimizerError>>()?;

        Ok(Sequence {
            node: self.optimized_with(&node, new_inputs)?,
            skip: 0,
        })
    }
/// Optimize a branch of a graph. This function is recursively called on inputs. The 'chain' parameter is a list of
/// operations that follow the node on which optimize_branch is called, and that have exactly one 'dynamic' input
/// (either inference input or output of the previous operator). For instance, if the model is A->B->C, then the
/// following calls will happen:
///
/// - C.optimize_branch(chain = [])
///   - B.optimize_branch(chain = ["C"])
///     - A.optimize_branch(chain = ["C", "B"])
///
/// If the model were A -> B+X -> C instead, then the chain would be 'broken' as 'B' has more than one input. The
/// 'chain' information can be used to fuse multiple steps. When optimize_branch detects that a chain can be fused,
/// it will generate the fused operation and return a Sequence struct with 'skip' set to a positive number.
/// This number equals the number of operations that have been fused, and  which  should be omitted from the DAG.
/// In the above example, suppose B->C can be fused. This can be detected in the last call:
///
/// - C.optimize_branch(chain = []) receives BC(skip=1) from B.optimize_branch, will omit C and return BC(skip=0)
///   - B.optimize_branch(chain = ["C"]) receives BC(skip=2) from A.optimize_branch, will omit B and return BC(skip=1)
///     - A.optimize_branch(chain = ["C", "B"]) detects B->C fusable, returns BC node with skip=2
Optimize a branch of a graph. This function is recursively called on inputs. The 'chain' parameter is a list of
operations that follow the node on which optimize_branch is called, and that have exactly one 'dynamic' input
(either inference input or output of the previous operator). For instance, if the model is A->B->C, then the
following calls will happen.



If the model were A -> B+X -> C instead, then the chain would be 'broken' as 'B' has more than one input. The
'chain' information can be used to fuse multiple steps. When optimize_branch detects that a chain can be fused,
it will generate the fused operation and return a Sequence struct with 'skip' set to a positive number.
This number equals the number of operations that have been fused, and  which  should be omitted from the DAG.
In the above example, suppose B->C can be fused. This can be detected in the last call.


Optimize a branch of a graph . This function is recursively called on inputs . The ' chain ' parameter is a list of operations that follow the node on which optimize_branch is called and that have exactly one ' dynamic ' input ( either inference input or output of the previous operator ) . For instance if the model is A - > B - > C then the following calls will happen . If the model were A - > B + X - > C instead then the chain would be ' broken ' as ' B ' has more than one input . The ' chain ' information can be used to fuse multiple steps . When optimize_branch detects that a chain can be fused it will generate the fused operation and return a Sequence struct with ' skip ' set to a positive number . This number equals the number of operations that have been fused and which should be omitted from the DAG . In the above example suppose B - > C can be fused . This can be detected in the last call .
fn optimize_branch(
        &mut self,
        node: Arc<Node<'model>>,
        chain: &mut Vec<(String, Arc<Node<'model>>)>,
    ) -> Result<Sequence<'model>, OptimizerError> {
        log::info!(
            "Optimize {:?} chain length={:?}",
            node.definition,
            chain.len()
        );
        if let NodeDefinition::Operator(op_def) = &node.definition {
            // Specific operators can be spliced out of the DAG
            match op_def.proto.get_op_type() {
                // Identity: we just remove these nodes and connect the input of the destination node to our input's output
                // A -> Identity -> B => A -> B
                "Identity" => {
                    if node.inputs.len() != 1 {
                        return Err(OptimizerError::NoInputs);
                    }
                    return self.optimize_branch_cached(node.inputs[0].source_node.clone(), chain);
                }
                // The Dropout operation does nothing when its training_mode is set to 0. We do not support training_mode=1
                "Dropout" => {
                    if node.inputs.is_empty() {
                        return Err(OptimizerError::NoInputs);
                    }
                    let training_mode =
                        get_attribute("training_mode", Some(0), &op_def.proto)? == 1;
                    if training_mode {
                        return Err(OptimizerError::Unsupported(String::from(
                            "Dropout with training_mode=1",
                        )));
                    }
                    return self.optimize_branch_cached(node.inputs[0].source_node.clone(), chain);
                }
                _ => {}
            }
            // Optimize chains of operators. A chain can only be formed for operations that each have at most one 'dynamic'
            // input (e.g. inference input or the input from another operation)
            let dynamic_input_count = node
                .inputs
                .iter()
                .filter(|input| {
                    matches!(
                        input.source_node.definition,
                        NodeDefinition::Operator(..) | NodeDefinition::Input(..)
                    )
                })
                .count();
            if dynamic_input_count == 1 {
                chain.push((op_def.proto.get_op_type().to_string(), node.clone()));
                if let Some(seq) = self.optimize_chain(chain)? {
                    log::info!(
                        "chain optimization: fuse {} operators to {:?}",
                        seq.skip,
                        seq.node.definition
                    );
                    return Ok(seq);
                }
                // Continue optimizing inputs, making the chain longer for one input
                let mut new_inputs = Vec::with_capacity(node.inputs.len());
                for input in &node.inputs {
                    let source_sequence =
                        self.optimize_branch_cached(input.source_node.clone(), chain)?;
                    if source_sequence.skip > 0 {
                        log::info!("operator is optimized away: {:?}", node.definition);
                        return Ok(Sequence {
                            node: source_sequence.node,
                            skip: source_sequence.skip - 1,
                        });
                    }
                    new_inputs.push(Input {
                        source_node: source_sequence.node,
                        output_index: input.output_index,
                    });
                }
                return Ok(Sequence {
                    node: self.optimized_with(&node, new_inputs)?,
                    skip: 0,
                });
            }
        }
        // This node has multiple inputs; optimize each input as a new chain
        let new_inputs = node
            .inputs
            .iter()
            .map(|input| {
                let mut input_chain = vec![];
                let source_sequence =
                    self.optimize_branch_cached(input.source_node.clone(), &mut input_chain)?;
                assert!(
                    source_sequence.skip == 0,
                    "cannot skip items in a chain that has multiple inputs for a single op"
                );
                Ok(Input {
                    source_node: source_sequence.node,
                    output_index: input.output_index,
                })
            })
            .collect::<Result<Vec<Input>, OptimizerError>>()?;
        Ok(Sequence {
            node: self.optimized_with(&node, new_inputs)?,
            skip: 0,
        })
    }
fn optimize_branch ( & mut self , node : Arc < Node < ' model > > , chain : & mut Vec < ( String , Arc < Node < ' model > > ) > , ) -> Result < Sequence < ' model > , OptimizerError > { log :: info ! ( " " node definition chain len ( ) ) ; if let NodeDefinition :: Operator ( op_def ) = & node . definition { // Specific operators can be spliced out of the DAG match op_def . proto . get_op_type ( ) { // Identity: we just remove these nodes and connect the input of the destination node to our input's output // A -> Identity -> B => A -> B " " => { if node . inputs . len ( ) != 1 { return Err ( OptimizerError :: NoInputs ) ; } return self . optimize_branch_cached ( node . inputs [ 0 ] . source_node . clone ( ) , chain ) ; } // The Dropout operation does nothing when its training_mode is set to 0. We do not support training_mode=1 " " => { if node . inputs . is_empty ( ) { return Err ( OptimizerError :: NoInputs ) ; } let training_mode = get_attribute ( " " , Some ( 0 ) , & op_def . proto ) ? == 1 ; if training_mode { return Err ( OptimizerError :: Unsupported ( String :: from ( " " , ) ) ) ; } return self . optimize_branch_cached ( node . inputs [ 0 ] . source_node . clone ( ) , chain ) ; } _ => { } } // Optimize chains of operators. A chain can only be formed for operations that each have at most one 'dynamic' // input (e.g. inference input or the input from another operation) let dynamic_input_count = node . inputs . iter ( ) . filter ( | input | { matches ! ( input source_node definition NodeDefinition Operator ( ) NodeDefinition Input ( ) ) } ) . count ( ) ; if dynamic_input_count == 1 { chain . push ( ( op_def . proto . get_op_type ( ) . to_string ( ) , node . clone ( ) ) ) ; if let Some ( seq ) = self . optimize_chain ( chain ) ? { log :: info ! ( " " seq skip seq node definition ) ; return Ok ( seq ) ; } // Continue optimizing inputs, making the chain longer for one input let mut new_inputs = Vec :: with_capacity ( node . inputs . len ( ) ) ; for input in & node . inputs { let source_sequence = self . optimize_branch_cached ( input . source_node . clone ( ) , chain ) ? ; if source_sequence . skip > 0 { log :: info ! ( " " node definition ) ; return Ok ( Sequence { node : source_sequence . node , skip : source_sequence . skip - 1 , } ) ; } new_inputs . push ( Input { source_node : source_sequence . node , output_index : input . output_index , } ) ; } return Ok ( Sequence { node : self . optimized_with ( & node , new_inputs ) ? , skip : 0 , } ) ; } } // This node has multiple inputs; optimize each input as a new chain let new_inputs = node . inputs . iter ( ) . map ( | input | { let mut input_chain = vec ! [ ] ; let source_sequence = self . optimize_branch_cached ( input . source_node . clone ( ) , & mut input_chain ) ? ; assert ! ( source_sequence skip 0 " " ) ; Ok ( Input { source_node : source_sequence . node , output_index : input . output_index , } ) } ) . collect :: < Result < Vec < Input > , OptimizerError > > ( ) ? ; Ok ( Sequence { node : self . optimized_with ( & node , new_inputs ) ? , skip : 0 , } ) }
Optimize a branch of a graph.
Optimize a branch of a graph .
&mut self
node
Arc<Node<'model>>
chain
&mut Vec<(String, Arc<Node<'model>>)>
&mut self
node
Arc<Node<'model>>
chain
&mut Vec<(String, Arc<Node<'model>>)>