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