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