xref: /aosp_15_r20/external/tensorflow/tensorflow/lite/python/analyzer_wrapper/model_analyzer.cc (revision b6fb3261f9314811a0f4371741dbb8839866f948)
1 /* Copyright 2021 The TensorFlow Authors. All Rights Reserved.
2 
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6 
7     http://www.apache.org/licenses/LICENSE-2.0
8 
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15 #include <algorithm>
16 #include <memory>
17 #include <sstream>
18 #include <string>
19 
20 #include "absl/strings/str_join.h"
21 #include "tensorflow/lite/core/api/error_reporter.h"
22 #include "tensorflow/lite/model_builder.h"
23 #include "tensorflow/lite/schema/schema_generated.h"
24 #include "tensorflow/lite/schema/schema_utils.h"
25 #include "tensorflow/lite/tools/versioning/gpu_compatibility.h"
26 #include "tensorflow/lite/version.h"
27 
28 namespace tflite {
29 
30 namespace {
31 
32 const float kThreshold_zero_buffer_ratio = 10.0f;
33 constexpr char kSectionSplitter[] =
34     "---------------------------------------------------------------\n";
35 const int kMaxContentDumpCnt = 5;
36 
37 // Returns string representation of the given tensor data up to 5 elements.
get_tensor_data_str(const tflite::Tensor * tensor,const tflite::Model * model)38 const std::string get_tensor_data_str(const tflite::Tensor* tensor,
39                                       const tflite::Model* model) {
40   std::stringstream ss;
41   auto buffer_idx = tensor->buffer();
42   if (buffer_idx != 0 && buffer_idx < model->buffers()->size()) {
43     auto* buffer = model->buffers()->Get(buffer_idx);
44     if (buffer->data() == nullptr) {
45       return "";
46     }
47     ss << "[";
48     if (buffer->data()->size() != 0) {
49       if (tensor->type() == tflite::TensorType_INT32) {
50         auto data = reinterpret_cast<const int32_t*>(buffer->data()->data());
51         int data_cnt = buffer->data()->size() / sizeof(int32_t);
52         for (int i = 0; i < std::min(kMaxContentDumpCnt, data_cnt); ++i) {
53           ss << data[i];
54           if (i != data_cnt - 1) {
55             ss << ", ";
56           }
57         }
58         if (data_cnt > kMaxContentDumpCnt) {
59           ss << "...";
60         }
61       }
62     }
63     ss << "]";
64   }
65   return ss.str();
66 }
67 
68 // Returns string representation of the given tensor of the subgraph.
tensor_str(const int tensor_idx,const int subgraph_idx,const tflite::Model * model=nullptr)69 const std::string tensor_str(const int tensor_idx, const int subgraph_idx,
70                              const tflite::Model* model = nullptr) {
71   std::stringstream ss;
72   if (subgraph_idx != 0 && tensor_idx != -1)
73     ss << "T#" << subgraph_idx << "_" << tensor_idx;
74   else
75     ss << "T#" << tensor_idx;
76   if (model && tensor_idx != -1) {
77     const SubGraph* subgraph = model->subgraphs()->Get(subgraph_idx);
78     if (subgraph) {
79       auto tensor = subgraph->tensors()->Get(tensor_idx);
80       if (tensor) {
81         ss << get_tensor_data_str(tensor, model);
82       }
83     }
84   }
85   return ss.str();
86 }
87 
88 // Returns string representation of the given subgraph.
subgraph_str(const int subgraph_idx)89 const std::string subgraph_str(const int subgraph_idx) {
90   std::stringstream ss;
91   ss << "Subgraph#" << subgraph_idx;
92   return ss.str();
93 }
94 
95 struct ModelStats {
96   // FlatBuffer buffer usage (in bytes) per subgraph.
97   std::vector<size_t> buffer_usage;
98 };
99 
100 // Dump details of the given tensor.
dump_tensor_detail(std::stringstream & out_stream,const tflite::Tensor * tensor,const int tensor_idx,const int subgraph_idx,const tflite::Model * model,ModelStats * stats)101 void dump_tensor_detail(std::stringstream& out_stream,
102                         const tflite::Tensor* tensor, const int tensor_idx,
103                         const int subgraph_idx, const tflite::Model* model,
104                         ModelStats* stats) {
105   out_stream << tensor_str(tensor_idx, subgraph_idx);
106   out_stream << "(" << tensor->name()->str() << ") ";
107   // Prints `shape_signature` instead of `shape` if it's available since it
108   // supports dynamic shapes.
109   if (tensor->shape_signature()) {
110     out_stream << "shape_signature:[";
111     for (int i = 0; i < tensor->shape_signature()->Length(); ++i) {
112       const int j = tensor->shape_signature()->Get(i);
113       out_stream << j;
114       if (i != tensor->shape_signature()->Length() - 1) {
115         out_stream << ", ";
116       }
117     }
118     out_stream << "]";
119   } else if (tensor->shape()) {
120     out_stream << "shape:[";
121     for (int i = 0; i < tensor->shape()->Length(); ++i) {
122       const int j = tensor->shape()->Get(i);
123       out_stream << j;
124       if (i != tensor->shape()->Length() - 1) {
125         out_stream << ", ";
126       }
127     }
128     out_stream << "]";
129   } else {
130     out_stream << "shape:n/a";
131   }
132   out_stream << ", type:" << EnumNameTensorType(tensor->type());
133 
134   // Dump buffer size of constant tensors.
135   auto buffer_idx = tensor->buffer();
136   if (buffer_idx != 0 && buffer_idx < model->buffers()->Length()) {
137     auto* buffer = model->buffers()->Get(buffer_idx);
138     if (buffer->data() && buffer->data()->size() != 0) {
139       out_stream << " RO " << buffer->data()->size() << " bytes";
140       out_stream << ", data:" << get_tensor_data_str(tensor, model);
141       stats->buffer_usage[subgraph_idx] += buffer->data()->size();
142     }
143   }
144   out_stream << "\n";
145 }
146 
147 // Dump list of input or output tensors.
dump_tensor_list(std::stringstream & out_stream,const flatbuffers::Vector<int32_t> * tensors,const int subgraph_idx,const tflite::Model * model=nullptr,bool verbose=false)148 void dump_tensor_list(std::stringstream& out_stream,
149                       const flatbuffers::Vector<int32_t>* tensors,
150                       const int subgraph_idx,
151                       const tflite::Model* model = nullptr,
152                       bool verbose = false) {
153   if (tensors == nullptr) {
154     return;
155   }
156   for (int i = 0; i < tensors->Length(); ++i) {
157     const int tensor_idx = tensors->Get(i);
158     if (verbose) {
159       out_stream << "tensor #" << tensor_idx;
160     } else {
161       out_stream << tensor_str(tensor_idx, subgraph_idx, model);
162     }
163     if (i != tensors->Length() - 1) {
164       if (verbose) {
165         out_stream << " and ";
166       } else {
167         out_stream << ", ";
168       }
169     }
170   }
171 }
172 
173 // Returns the string representation of the given OperatorCode.
get_op_name(const OperatorCode * op_code)174 const std::string get_op_name(const OperatorCode* op_code) {
175   auto builtin_code = GetBuiltinCode(op_code);
176   if (builtin_code != BuiltinOperator_CUSTOM) {
177     return EnumNameBuiltinOperator(builtin_code);
178   } else {
179     return op_code->custom_code()->str();
180   }
181 }
182 
183 // Dump the given Operator node.
dump_node(std::stringstream & out_stream,const int node_no,const OperatorCode * op_code,const Operator * op,const int subgraph_index,const::tflite::Model * model)184 void dump_node(std::stringstream& out_stream, const int node_no,
185                const OperatorCode* op_code, const Operator* op,
186                const int subgraph_index, const ::tflite::Model* model) {
187   out_stream << "Op#" << node_no << " " << get_op_name(op_code);
188   out_stream << "(";
189   dump_tensor_list(out_stream, op->inputs(), subgraph_index, model);
190   if (GetBuiltinCode(op_code) == BuiltinOperator_CALL_ONCE) {
191     out_stream << subgraph_str(
192         op->builtin_options_as_CallOnceOptions()->init_subgraph_index());
193   } else if (GetBuiltinCode(op_code) == BuiltinOperator_IF) {
194     out_stream << ", Then: "
195                << subgraph_str(op->builtin_options_as_IfOptions()
196                                    ->then_subgraph_index());
197     out_stream << ", Else: "
198                << subgraph_str(op->builtin_options_as_IfOptions()
199                                    ->else_subgraph_index());
200   } else if (GetBuiltinCode(op_code) == BuiltinOperator_WHILE) {
201     out_stream << ", Cond: "
202                << subgraph_str(op->builtin_options_as_WhileOptions()
203                                    ->cond_subgraph_index());
204     out_stream << ", Body: "
205                << subgraph_str(op->builtin_options_as_WhileOptions()
206                                    ->body_subgraph_index());
207   }
208   out_stream << ") -> [";
209   dump_tensor_list(out_stream, op->outputs(), subgraph_index);
210   out_stream << "]\n";
211 }
212 
213 // Dump the summary of the given TFLite flatbuffer model. It's printed at the
214 // beginning of the analyzer output.
dump_model_summary(std::stringstream & out_stream,const::tflite::Model * model)215 void dump_model_summary(std::stringstream& out_stream,
216                         const ::tflite::Model* model) {
217   auto* subgraphs = model->subgraphs();
218   out_stream
219       << "Your TFLite model has '" << subgraphs->Length()
220       << "' subgraph(s). In the subgraph description below,\nT# represents the "
221          "Tensor numbers. ";
222   if (subgraphs->Length() > 0 && subgraphs->Get(0)->operators()->Length() > 0) {
223     const Operator* first_op = subgraphs->Get(0)->operators()->Get(0);
224     const OperatorCode* first_op_code =
225         model->operator_codes()->Get(first_op->opcode_index());
226     out_stream << "For example, in " << subgraph_str(0) << ", the "
227                << get_op_name(first_op_code) << " op takes\n";
228     dump_tensor_list(out_stream, first_op->inputs(), 0, nullptr,
229                      /*verbose=*/true);
230     out_stream << " as input and produces ";
231     dump_tensor_list(out_stream, first_op->outputs(), 0, nullptr,
232                      /*verbose=*/true);
233     out_stream << " as output.\n\n";
234   }
235 }
236 
237 // Dump the signature definitions of the given TFLite flatbuffer model.
dump_model_signature_defs(std::stringstream & out_stream,const::tflite::Model * model)238 void dump_model_signature_defs(std::stringstream& out_stream,
239                                const ::tflite::Model* model) {
240   auto* signatures = model->signature_defs();
241   if (signatures == nullptr || signatures->Length() == 0) {
242     return;
243   }
244   out_stream << kSectionSplitter;
245   out_stream << "Your TFLite model has '" << signatures->Length()
246              << "' signature_def(s).\n\n";
247   for (int i = 0; i < signatures->Length(); ++i) {
248     auto* signature_def = signatures->Get(i);
249     out_stream << "Signature#" << i << " key: '"
250                << signature_def->signature_key()->str() << "'\n";
251     out_stream << "- Subgraph: "
252                << subgraph_str(signature_def->subgraph_index()) << "\n";
253     out_stream << "- Inputs: \n";
254     for (int j = 0; j < signature_def->inputs()->Length(); ++j) {
255       auto* input = signature_def->inputs()->Get(j);
256       out_stream << "    '" << input->name()->str() << "' : "
257                  << tensor_str(input->tensor_index(),
258                                signature_def->subgraph_index())
259                  << "\n";
260     }
261     out_stream << "- Outputs: \n";
262     for (int j = 0; j < signature_def->outputs()->Length(); ++j) {
263       auto* output = signature_def->outputs()->Get(j);
264       out_stream << "    '" << output->name()->str() << "' : "
265                  << tensor_str(output->tensor_index(),
266                                signature_def->subgraph_index())
267                  << "\n";
268     }
269     out_stream << "\n";
270   }
271 }
272 
273 // Dump the statistics of the given TFLite flatbuffer model. It's printed at the
274 // end of the analyzer output.
dump_model_stats(std::stringstream & out_stream,const::tflite::Model * model,size_t model_size,ModelStats * stats)275 void dump_model_stats(std::stringstream& out_stream,
276                       const ::tflite::Model* model, size_t model_size,
277                       ModelStats* stats) {
278   size_t total_buffer_size = 0;
279   size_t total_zero_buffer_size = 0;
280   auto* buffers = model->buffers();
281   for (int i = 0; i < buffers->size(); ++i) {
282     const tflite::Buffer* buffer = buffers->Get(i);
283     if (buffer->data() == nullptr) {
284       continue;
285     }
286     bool is_all_zeros = true;
287     const unsigned char* data = buffer->data()->data();
288     for (int j = 0; j < buffer->data()->size(); ++j) {
289       if (data[j] != 0) {
290         is_all_zeros = false;
291         break;
292       }
293     }
294     if (is_all_zeros) {
295       total_zero_buffer_size += buffer->data()->size();
296     }
297     total_buffer_size += buffer->data()->size();
298   }
299 
300   out_stream << kSectionSplitter;
301   char temp[2048];
302   snprintf(temp, sizeof(temp), "%24s: %10zu bytes\n", "Model size", model_size);
303   out_stream << temp;
304   snprintf(
305       temp, sizeof(temp), "%24s: %10zu bytes (%05.2f %%)\n",
306       "Non-data buffer size", model_size - total_buffer_size,
307       (static_cast<float>(model_size - total_buffer_size) / model_size * 100));
308   out_stream << temp;
309   snprintf(temp, sizeof(temp), "%24s: %10zu bytes (%05.2f %%)\n",
310            "Total data buffer size", total_buffer_size,
311            (static_cast<float>(total_buffer_size) / model_size * 100));
312   out_stream << temp;
313   if (model->subgraphs()->Length() > 1) {
314     for (int i = 0; i < model->subgraphs()->Length(); ++i) {
315       float subgraph_buffer_ratio =
316           static_cast<float>(stats->buffer_usage[i]) / model_size * 100;
317       snprintf(temp, sizeof(temp),
318                "          - %-12s: %10zu bytes (%05.2f %%)\n",
319                subgraph_str(i).c_str(), stats->buffer_usage[i],
320                subgraph_buffer_ratio);
321       out_stream << temp;
322     }
323   }
324   float zero_buffer_ratio =
325       static_cast<float>(total_zero_buffer_size) / model_size * 100;
326   snprintf(temp, sizeof(temp), "%24s: %10zu bytes (%05.2f %%)\n",
327            "(Zero value buffers)", total_zero_buffer_size, zero_buffer_ratio);
328   out_stream << temp;
329 
330   out_stream
331       << "\n"
332       << "* Buffers of TFLite model are mostly used for constant tensors.\n";
333   out_stream << "  And zero value buffers are buffers filled with zeros.\n";
334   if (zero_buffer_ratio > kThreshold_zero_buffer_ratio) {
335     out_stream << "  (Consider use "
336                   "`converter._experimental_unfold_large_splat_constant` "
337                   "to save the model size.)\n";
338   }
339   out_stream << "  Non-data buffers area are used to store operators, "
340                 "subgraphs and etc.\n";
341   out_stream << "  You can find more details from "
342                 "https://github.com/tensorflow/tensorflow/blob/master/"
343                 "tensorflow/lite/schema/schema.fbs\n";
344 }
345 
346 }  // namespace
347 
348 class StreamErrorReporter : public ErrorReporter {
349  public:
StreamErrorReporter(std::stringstream * out_stream)350   explicit StreamErrorReporter(std::stringstream* out_stream)
351       : out_stream_(out_stream) {}
Report(const char * format,va_list args)352   int Report(const char* format, va_list args) override {
353     char buffer[1024];
354     int size = vsnprintf(buffer, sizeof(buffer), format, args);
355     *out_stream_ << buffer;
356     return size;
357   }
358 
359  private:
360   std::stringstream* out_stream_;
361 };
362 
model_analyzer(const std::string & model_file_or_buffer,bool input_is_filepath,bool check_gpu_compatibility)363 std::string model_analyzer(const std::string& model_file_or_buffer,
364                            bool input_is_filepath,
365                            bool check_gpu_compatibility) {
366   std::stringstream out_stream;
367   StreamErrorReporter error_reporter(&out_stream);
368   std::unique_ptr<FlatBufferModel> fb_model;
369   if (input_is_filepath) {
370     fb_model = FlatBufferModel::BuildFromFile(model_file_or_buffer.c_str(),
371                                               &error_reporter);
372     if (!fb_model) {
373       out_stream << "Failed to mmap model " << model_file_or_buffer;
374       return out_stream.str();
375     }
376   } else {
377     fb_model = FlatBufferModel::BuildFromBuffer(model_file_or_buffer.c_str(),
378                                                 model_file_or_buffer.size(),
379                                                 &error_reporter);
380     if (!fb_model) {
381       out_stream << "Failed to mmap the given model buffer.";
382       return out_stream.str();
383     }
384   }
385   const ::tflite::Model* model = fb_model->GetModel();
386   auto* subgraphs = model->subgraphs();
387   ModelStats stats;
388   stats.buffer_usage.resize(subgraphs->Length());
389 
390   dump_model_summary(out_stream, model);
391 
392   bool model_is_gpu_compatibile = true;
393   for (int i = 0; i < subgraphs->Length(); ++i) {
394     std::vector<int> gpu_incompatibile_nodes;
395     const SubGraph* subgraph = subgraphs->Get(i);
396     out_stream << subgraph_str(i);
397     if (subgraph->name()) {
398       out_stream << " " << subgraph->name()->str();
399     }
400     out_stream << "(";
401     dump_tensor_list(out_stream, subgraph->inputs(), i);
402     out_stream << ") -> [";
403     dump_tensor_list(out_stream, subgraph->outputs(), i);
404     out_stream << "]\n";
405     for (int j = 0; j < subgraph->operators()->Length(); ++j) {
406       const Operator* op = subgraph->operators()->Get(j);
407       const OperatorCode* op_code =
408           model->operator_codes()->Get(op->opcode_index());
409       out_stream << "  ";  // indents for operators
410       dump_node(out_stream, /*node_no=*/j, op_code, op, i, model);
411       if (check_gpu_compatibility) {
412         auto status =
413             CheckGpuDelegateCompatibility(op_code, op, subgraph, model);
414         if (!status.ok()) {
415           gpu_incompatibile_nodes.push_back(j);
416           out_stream << "GPU COMPATIBILITY WARNING: " << status.message()
417                      << "\n";
418         }
419       }
420     }
421     if (!gpu_incompatibile_nodes.empty()) {
422       model_is_gpu_compatibile = false;
423       out_stream << "\nGPU COMPATIBILITY WARNING: Subgraph#" << i
424                  << " has GPU delegate compatibility issues at nodes "
425                  << absl::StrJoin(gpu_incompatibile_nodes, ", ")
426                  << " with TFLite runtime version " << TF_VERSION_STRING
427                  << "\n";
428     }
429 
430     // Dump Subgraph Tensors.
431     out_stream << "\nTensors of " << subgraph_str(i) << "\n";
432     auto tensors = subgraph->tensors();
433     for (int j = 0; j < tensors->Length(); ++j) {
434       auto tensor = tensors->Get(j);
435       out_stream << "  ";  // indents for tensors
436       dump_tensor_detail(out_stream, tensor, j, i, model, &stats);
437     }
438     out_stream << "\n";
439   }
440   if (check_gpu_compatibility && model_is_gpu_compatibile) {
441     out_stream
442         << "\nYour model looks compatibile with GPU delegate"
443         << " with TFLite runtime version " << TF_VERSION_STRING
444         << ".\nBut it doesn't guarantee that your model works well with GPU "
445            "delegate.\nThere could be some runtime incompatibililty happen.\n";
446   }
447 
448   dump_model_signature_defs(out_stream, model);
449   dump_model_stats(out_stream, model, fb_model->allocation()->bytes(), &stats);
450 
451   return out_stream.str();
452 }
453 
454 }  // namespace tflite
455