Skip to main content

tract_hir/infer/
model.rs

1use std::collections::HashMap;
2
3use tract_core::ops::konst::Const;
4
5use super::factoid::Factoid;
6use super::{InferenceFact, InferenceModel, InferenceNode, InferenceOp};
7use crate::internal::*;
8use crate::prelude::TVec;
9
10pub trait InferenceModelExt {
11    /// Analyse all nodes of the graph.
12    ///
13    /// Will stop on first error unless `obstinate` is `true`.
14    fn analyse(&mut self, obstinate: bool) -> TractResult<bool>;
15
16    /// Perform early transformation before going typed.
17    fn incorporate(self) -> TractResult<InferenceModel>;
18
19    /// List OutletId with incomplete type information.
20    ///
21    /// Will stop on first error unless `obstinate` is `true`.
22    fn missing_type_shape(&self) -> TractResult<Vec<OutletId>>;
23
24    /// Eliminate seemingly dead branches of the graph.
25    ///
26    /// This may break stateful networks.
27    fn eliminate_dead_branches(self) -> TractResult<InferenceModel>;
28
29    /// Attempt full analyse and conversion to TypedModel.
30    fn into_typed(self) -> TractResult<TypedModel>;
31
32    /// Attempt full analyse, decluttering and mapping to optimized operations.
33    ///
34    /// This will work even if the network can not be normalized.
35    fn into_optimized(self) -> TractResult<TypedModel>;
36}
37
38impl InferenceModelExt for InferenceModel {
39    /// Analyse all nodes of the graph.
40    ///
41    /// Will stop on first error unless `obstinate` is `true`.
42    fn analyse(&mut self, obstinate: bool) -> TractResult<bool> {
43        super::analyser::Analyser::new(self).analyse_obstinate(obstinate)
44    }
45
46    /// Perform early transformation before going typed.
47    fn incorporate(self) -> TractResult<InferenceModel> {
48        let mut model = self;
49        loop {
50            let mut done_something = false;
51            for p in crate::infer::optim::incorporate() {
52                done_something = done_something || p.pass(&mut model)?;
53                if cfg!(debug_assertions) {
54                    model.check_edges()?;
55                }
56            }
57            if !done_something {
58                break;
59            }
60        }
61        model = model.into_compact()?;
62        model.analyse(false)?;
63        Ok(model)
64    }
65
66    /// List OutletId with incomplete type information.
67    ///
68    /// Will stop on first error unless `obstinate` is `true`.
69    fn missing_type_shape(&self) -> TractResult<Vec<OutletId>> {
70        Ok(self
71            .eval_order()?
72            .iter()
73            .flat_map(|&node| {
74                self.nodes()[node]
75                    .outputs
76                    .iter()
77                    .enumerate()
78                    .map(move |(ix, outlet)| (OutletId::new(node, ix), outlet))
79            })
80            .filter(|(_, o)| !o.fact.datum_type.is_concrete() || !o.fact.shape.is_concrete())
81            .map(|(id, _)| id)
82            .collect())
83    }
84
85    /// Eliminate seemingly dead branches of the graph.
86    ///
87    /// This may break stateful networks.
88    fn eliminate_dead_branches(self) -> TractResult<InferenceModel> {
89        self.into_compact()
90    }
91
92    /// Attempt full analyse and conversion to TypedModel.
93    fn into_typed(mut self) -> TractResult<TypedModel> {
94        use tract_core::internal::translator::Translate;
95
96        self.analyse(false)?;
97        let m = self.incorporate()?;
98
99        #[derive(Debug)]
100        struct ToTypedTranslator;
101        impl Translate<InferenceFact, Box<dyn InferenceOp>, TypedFact, Box<dyn TypedOp>>
102            for ToTypedTranslator
103        {
104            fn translate_node(
105                &self,
106                source: &InferenceModel,
107                node: &InferenceNode,
108                target: &mut TypedModel,
109                mapping: &HashMap<OutletId, OutletId>,
110            ) -> TractResult<TVec<OutletId>> {
111                // Two kinds of node must survive translation even when inference
112                // knows their value: a source, which is the model's input, and one
113                // whose state would be dropped along with it.
114                if !InferenceModel::is_source(&node.op)
115                    && node.op.state(&EvalContext::out_of_plan())?.is_none()
116                    && source.node_output_facts(node.id)?.iter().all(|f| f.value.is_concrete())
117                {
118                    (0..node.outputs.len())
119                        .map(|ix| {
120                            target.add_const(
121                                format!("{}.{}", node.name, ix),
122                                node.outputs[ix].fact.value.concretize().unwrap(),
123                            )
124                        })
125                        .collect()
126                } else {
127                    let outputs = node.op.to_typed(source, node, target, mapping)?;
128                    for output in &outputs {
129                        let fact = target.outlet_fact(*output)?;
130                        fact.consistent().with_context(|| {
131                            format!(
132                                "Checking oulet fact consistency for {:?}: {:?} after translating {:?}",
133                                output,
134                                fact, node.op,
135                            )
136                        })?;
137                    }
138                    Ok(outputs)
139                }
140            }
141        }
142
143        ToTypedTranslator.translate_model(&m)
144    }
145
146    /// Attempt full analyse, decluttering and mapping to optimized operations.
147    ///
148    /// This is meant for "simple" networks, where no special model
149    /// transformation needs to happen. Aternaltively, use to_typed() and
150    /// manipulate the TypedModel for more control.
151    fn into_optimized(self) -> TractResult<TypedModel> {
152        self.into_typed()?.into_optimized()
153    }
154}
155
156impl SpecialOps<InferenceFact, Box<dyn InferenceOp>> for InferenceModel {
157    fn is_source(op: &Box<dyn InferenceOp>) -> bool {
158        op.as_op().downcast_ref::<crate::ops::source::Source>().is_some()
159    }
160
161    fn create_dummy(&self) -> Box<dyn InferenceOp> {
162        Box::new(tract_core::ops::dummy::Dummy::new())
163    }
164
165    fn create_source(&self, _fact: InferenceFact) -> Box<dyn InferenceOp> {
166        Box::new(crate::ops::source::Source::new())
167    }
168
169    fn wire_node(
170        &mut self,
171        name: impl Into<String>,
172        op: impl Into<Box<dyn InferenceOp>>,
173        inputs: &[OutletId],
174    ) -> TractResult<TVec<OutletId>> {
175        let op = op.into();
176        let output_facts: TVec<InferenceFact> =
177            (0..op.nboutputs()?).map(|_| InferenceFact::default()).collect();
178        let id = self.add_node(name, op, output_facts)?;
179        inputs
180            .iter()
181            .enumerate()
182            .try_for_each(|(ix, i)| self.add_edge(*i, InletId::new(id, ix)))?;
183        Ok(self.node(id).outputs.iter().enumerate().map(|(ix, _)| OutletId::new(id, ix)).collect())
184    }
185
186    fn add_const(
187        &mut self,
188        name: impl Into<String>,
189        v: impl IntoArcTensor,
190    ) -> TractResult<OutletId> {
191        let v = v.into_arc_tensor();
192        for node in &self.nodes {
193            if let Some(op) = node.op_as::<Const>() {
194                if op.val() == &v {
195                    return Ok(node.id.into());
196                }
197            }
198        }
199        let name = name.into();
200        let fact = TypedFact::try_from(v.clone())?;
201        self.add_node(name, crate::ops::konst::Const::new(v)?, tvec!(fact.into()))
202            .map(|id| id.into())
203    }
204}
205
206#[cfg(test)]
207mod test {
208    use super::*;
209
210    #[test]
211    fn test() {
212        fn is_sync<T: Sync>() {}
213        is_sync::<InferenceModel>();
214    }
215}