2f78e059e8e5829734dd2821f181154dd5e81e53
[platform/core/ml/nnfw.git] / compiler / luci / service / src / Validate.cpp
1 /*
2  * Copyright (c) 2020 Samsung Electronics Co., Ltd. All Rights Reserved
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "luci/Service/Validate.h"
18
19 #include <luci/IR/Nodes/CircleOutput.h>
20 #include <luci/IR/CircleNodeVisitor.h>
21 #include <luci/Log.h>
22 #include <luci/LogHelper.h>
23
24 #include <loco/IR/NodeShape.h>
25
26 #include <cassert>
27 #include <unordered_map>
28 #include <vector>
29 #include <iostream>
30
31 namespace
32 {
33
34 std::ostream &operator<<(std::ostream &os, const loco::TensorShape &tensor_shape)
35 {
36   os << "[";
37   for (uint32_t r = 0; r < tensor_shape.rank(); ++r)
38   {
39     if (r)
40       os << ",";
41
42     if (tensor_shape.dim(r).known())
43       os << tensor_shape.dim(r).value();
44     else
45       os << "?";
46   }
47   os << "]";
48   return os;
49 }
50
51 std::ostream &operator<<(std::ostream &os, const luci::CircleNode *circle_node)
52 {
53   os << "[";
54   for (uint32_t r = 0; r < circle_node->rank(); ++r)
55   {
56     if (r)
57       os << ",";
58
59     if (circle_node->dim(r).known())
60       os << circle_node->dim(r).value();
61     else
62       os << "?";
63   }
64   os << "]";
65   return os;
66 }
67
68 /**
69  * @brief  returns a node that is CircleOutput with index is out_index in nodes
70  */
71 luci::CircleOutput *find_node(std::vector<loco::Node *> nodes, loco::GraphOutputIndex out_index)
72 {
73   for (auto node : nodes)
74   {
75     auto circle_output = dynamic_cast<luci::CircleOutput *>(node);
76     if (circle_output != nullptr)
77     {
78       if (circle_output->indexed() && circle_output->index() == out_index)
79         return circle_output;
80     }
81   }
82   return nullptr;
83 }
84
85 bool validate_shape_dtype(loco::Graph *g)
86 {
87   LOGGER(l);
88
89   auto output_nodes = loco::output_nodes(g);
90
91   auto count = g->outputs()->size();
92   for (uint32_t out = 0; out < count; ++out)
93   {
94     auto graph_out = g->outputs()->at(out);
95     auto out_index = graph_out->index();
96
97     auto circle_output = find_node(output_nodes, out_index);
98     assert(circle_output != nullptr);
99     assert(circle_output->from() != nullptr);
100     auto circle_node = loco::must_cast<luci::CircleNode *>(circle_output->from());
101
102     // Shape and dtype validation for CircleOutputExclude is not needed
103     if (dynamic_cast<luci::CircleOutputExclude *>(circle_node))
104       continue;
105
106     assert(circle_node->shape_status() != luci::ShapeStatus::UNDEFINED);
107
108     // check if output node shape is same as graph output shape
109     auto go_tensor_shape = graph_out->shape();
110     assert(go_tensor_shape);
111
112     // NOTE Even if shape of graph output is [] (which means "shape inference was impossible")
113     //      but shape of CircleNode is not, it can be valid case because shape inference
114     //      algorithm of CircleNode may be upgraded than before. The opposite is possible either.
115     //      If such cases are appeared, following validation code should be fixed.
116     bool is_shape_valid = (circle_node->rank() == go_tensor_shape->rank());
117     for (uint32_t i = 0; is_shape_valid && i < circle_node->rank(); ++i)
118     {
119       if (!circle_node->dim(i).known() || !go_tensor_shape->dim(i).known())
120       {
121         // If at least one of two dimensions is unknown,
122         // the unknown dimension can accept any value.
123         INFO(l) << "Unknown dimension is matched with known dimension" << std::endl;
124       }
125       else if (circle_node->dim(i).value() != go_tensor_shape->dim(i).value())
126       {
127         is_shape_valid = false;
128       }
129     }
130
131     if (is_shape_valid == false)
132     {
133       INFO(l) << "[luci] Shape for output #" << out_index << " not same " << std::endl;
134       INFO(l) << "[luci]    " << circle_node->name() << " " << circle_node << " vs "
135               << *go_tensor_shape << std::endl;
136       return false;
137     }
138
139     // check if data type match
140     assert(circle_node->dtype() != loco::DataType::Unknown);
141     if (graph_out->dtype() != circle_node->dtype())
142     {
143       INFO(l) << "[luci] Type for output #" << out_index << " not same " << std::endl;
144       return false;
145     }
146   }
147
148   return true;
149 }
150
151 class MultiOutNodeValidate final : public luci::CircleNodeVisitor<bool>
152 {
153 public:
154   MultiOutNodeValidate() {}
155
156 private:
157   template <class T> bool check(const luci::CircleNode *node)
158   {
159     auto succs = loco::succs(node);
160     if (succs.size() < 1)
161       return false;
162     for (const auto &cnode : succs)
163     {
164       auto const child = dynamic_cast<const T *>(cnode);
165       if (child == nullptr)
166         return false;
167     }
168     return true;
169   }
170
171 public:
172   bool visit(const luci::CircleBidirectionalSequenceLSTM *node) final
173   {
174     return check<luci::CircleBidirectionalSequenceLSTMOut>(node);
175   }
176   bool visit(const luci::CircleCustom *node) final { return check<luci::CircleCustomOut>(node); }
177   bool visit(const luci::CircleIf *node) final { return check<luci::CircleIfOut>(node); }
178   bool visit(const luci::CircleNonMaxSuppressionV4 *node) final
179   {
180     return check<luci::CircleNonMaxSuppressionV4Out>(node);
181   }
182   bool visit(const luci::CircleNonMaxSuppressionV5 *node) final
183   {
184     return check<luci::CircleNonMaxSuppressionV5Out>(node);
185   }
186   bool visit(const luci::CircleSplit *node) final { return check<luci::CircleSplitOut>(node); }
187   bool visit(const luci::CircleSplitV *node) final { return check<luci::CircleSplitVOut>(node); }
188   bool visit(const luci::CircleTopKV2 *node) final { return check<luci::CircleTopKV2Out>(node); }
189   bool visit(const luci::CircleUnique *node) final { return check<luci::CircleUniqueOut>(node); }
190   bool visit(const luci::CircleUnpack *node) final { return check<luci::CircleUnpackOut>(node); }
191   bool visit(const luci::CircleWhile *node) final { return check<luci::CircleWhileOut>(node); }
192
193   // default true for other nodes
194   bool visit(const luci::CircleNode *) final { return true; }
195 };
196
197 /**
198  * @brief Validate sequence of multi-output nodes are followed for specific
199  *        IRs such as CircleIfOut.
200  */
201 bool validate_multi_outs(loco::Graph *g)
202 {
203   LOGGER(l);
204
205   for (auto node : loco::active_nodes(loco::output_nodes(g)))
206   {
207     auto const cnode = loco::must_cast<luci::CircleNode *>(node);
208
209     MultiOutNodeValidate d;
210     if (cnode->accept(&d))
211       continue;
212
213     auto const name = cnode->name();
214     INFO(l) << "Node: " << name << ", " << (uint32_t)(cnode->opcode()) << " has invalid successor."
215             << std::endl;
216
217     return false;
218   }
219
220   return true;
221 }
222
223 class VirtualNodeDetector final : public luci::CircleNodeVisitor<bool>
224 {
225 public:
226   VirtualNodeDetector() {}
227
228 public:
229   bool visit(const luci::CircleBidirectionalSequenceLSTMOut *) final { return true; }
230   bool visit(const luci::CircleCustomOut *) final { return true; }
231   bool visit(const luci::CircleIfOut *) final { return true; }
232   bool visit(const luci::CircleNonMaxSuppressionV4Out *) final { return true; }
233   bool visit(const luci::CircleNonMaxSuppressionV5Out *) final { return true; }
234   bool visit(const luci::CircleSplitOut *) final { return true; }
235   bool visit(const luci::CircleSplitVOut *) final { return true; }
236   bool visit(const luci::CircleTopKV2Out *) final { return true; }
237   bool visit(const luci::CircleUnpackOut *) final { return true; }
238   bool visit(const luci::CircleUniqueOut *) final { return true; }
239   bool visit(const luci::CircleWhileOut *) final { return true; }
240   bool visit(const luci::CircleOutputDummy *) final { return true; }
241   bool visit(const luci::CircleOutputExclude *) final { return true; }
242
243   // Return false by default
244   bool visit(const luci::CircleNode *) final { return false; }
245 };
246
247 } // namespace
248
249 namespace luci
250 {
251
252 bool validate(loco::Graph *g)
253 {
254   if (!loco::valid(g))
255     return false;
256
257   if (!validate_shape_dtype(g))
258     return false;
259
260   if (!validate_multi_outs(g))
261     return false;
262
263   // TODO add more validation
264
265   return true;
266 }
267
268 bool validate_name(loco::Graph *g)
269 {
270   auto nodes = g->nodes();
271   for (uint32_t n = 0; n < nodes->size(); ++n)
272   {
273     auto node = loco::must_cast<luci::CircleNode *>(nodes->at(n));
274     // skip virtual nodes
275     VirtualNodeDetector d;
276     if (node->accept(&d))
277       continue;
278
279     auto name = node->name();
280     if (name.empty())
281       return false;
282   }
283
284   return true;
285 }
286
287 bool validate_unique_name(luci::Module *m)
288 {
289   LOGGER(l);
290
291   std::unordered_map<std::string, bool> names_col;
292
293   for (size_t g = 0; g < m->size(); ++g)
294   {
295     auto graph = m->graph(g);
296     auto nodes = graph->nodes();
297     for (uint32_t n = 0; n < nodes->size(); ++n)
298     {
299       auto node = loco::must_cast<luci::CircleNode *>(nodes->at(n));
300       // skip CircleOutput as it may have same name with from() node
301       auto output = dynamic_cast<luci::CircleOutput *>(node);
302       if (output != nullptr)
303         continue;
304       // skip virtual nodes
305       VirtualNodeDetector d;
306       if (node->accept(&d))
307         continue;
308
309       auto name = node->name();
310       INFO(l) << "Node: " << name << ", " << (uint32_t)(node->opcode()) << std::endl;
311       auto it = names_col.find(name);
312       if (it != names_col.end())
313       {
314         INFO(l) << "validate_unique_name: found duplicate " << name << ", " << graph->name()
315                 << std::endl;
316         return false;
317       }
318
319       names_col[name] = true;
320     }
321     // There can exist same tensor name between different subgraphs.
322     names_col.clear();
323   }
324
325   return true;
326 }
327
328 bool validate(luci::Module *module)
329 {
330   LOGGER(l);
331
332   INFO(l) << "--- validate Module -----------------------------------";
333
334   for (size_t g = 0; g < module->size(); ++g)
335   {
336     auto graph = module->graph(g);
337
338     INFO(l) << luci::fmt(graph) << std::endl;
339
340     if (!validate(graph))
341     {
342       std::cerr << "ERROR: Invalid circle model" << std::endl;
343       return false;
344     }
345     if (!validate_name(graph))
346     {
347       std::cerr << "ERROR: circle model has empty name" << std::endl;
348       return false;
349     }
350   }
351
352   if (!validate_unique_name(module))
353   {
354     std::cerr << "ERROR: circle model has duplicate names" << std::endl;
355     return false;
356   }
357
358   return true;
359 }
360
361 } // namespace luci