*Add flag 'conversion_summary_dir' to tflite_converter. When user passes this flag and uses the new MLIR converter(via command-line), it will generate conversion logs under the specified folder.
PiperOrigin-RevId: 278743450 Change-Id: Ic840a56642629514816582390a267b037b0bbb24
This commit is contained in:
parent
473223155e
commit
6b77079914
@ -155,3 +155,10 @@ graph transformations:
|
|||||||
completed.
|
completed.
|
||||||
* `--dump_graphviz_video`. Type: boolean. Outputs GraphViz after every graph
|
* `--dump_graphviz_video`. Type: boolean. Outputs GraphViz after every graph
|
||||||
transformation. Requires `--dump_graphviz_dir` to be specified.
|
transformation. Requires `--dump_graphviz_dir` to be specified.
|
||||||
|
|
||||||
|
The following flag controls generating the conversion logs. The conversion log
|
||||||
|
includes a protocol buffer of analytics collected during conversion, and an HTML
|
||||||
|
file where user can preview the conversion summary.
|
||||||
|
|
||||||
|
* `--conversion_summary_dir`. Type: string. Specifies the full path of the
|
||||||
|
directory to output conversion logs.
|
||||||
|
@ -71,6 +71,8 @@ py_library(
|
|||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
":lite",
|
":lite",
|
||||||
|
"//tensorflow/lite/toco/logging:gen_html",
|
||||||
|
"//tensorflow/lite/toco/logging:toco_conversion_log_proto_py",
|
||||||
"@six_archive//:six",
|
"@six_archive//:six",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -230,7 +230,8 @@ def build_toco_convert_protos(input_tensors,
|
|||||||
dump_graphviz_video=False,
|
dump_graphviz_video=False,
|
||||||
target_ops=None,
|
target_ops=None,
|
||||||
allow_nonexistent_arrays=False,
|
allow_nonexistent_arrays=False,
|
||||||
debug_info=None):
|
debug_info=None,
|
||||||
|
conversion_summary_dir=None):
|
||||||
"""Builds protocol buffers describing a conversion of a model using TOCO.
|
"""Builds protocol buffers describing a conversion of a model using TOCO.
|
||||||
|
|
||||||
Typically this is to convert from TensorFlow GraphDef to TFLite, in which
|
Typically this is to convert from TensorFlow GraphDef to TFLite, in which
|
||||||
@ -294,6 +295,7 @@ def build_toco_convert_protos(input_tensors,
|
|||||||
or are unused in the final graph. (default False)
|
or are unused in the final graph. (default False)
|
||||||
debug_info: `GraphDebugInfo` proto containing the stack traces for the
|
debug_info: `GraphDebugInfo` proto containing the stack traces for the
|
||||||
original nodes referred by the converted graph.
|
original nodes referred by the converted graph.
|
||||||
|
conversion_summary_dir: A string, the path to the generated conversion logs.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
model_flags, toco_flags, debug_info: three protocol buffers describing the
|
model_flags, toco_flags, debug_info: three protocol buffers describing the
|
||||||
@ -326,6 +328,8 @@ def build_toco_convert_protos(input_tensors,
|
|||||||
if dump_graphviz_dir:
|
if dump_graphviz_dir:
|
||||||
toco.dump_graphviz_dir = dump_graphviz_dir
|
toco.dump_graphviz_dir = dump_graphviz_dir
|
||||||
toco.dump_graphviz_include_video = dump_graphviz_video
|
toco.dump_graphviz_include_video = dump_graphviz_video
|
||||||
|
if conversion_summary_dir:
|
||||||
|
toco.conversion_summary_dir = conversion_summary_dir
|
||||||
if target_ops:
|
if target_ops:
|
||||||
if set(target_ops) == set([OpsSet.TFLITE_BUILTINS, OpsSet.SELECT_TF_OPS]):
|
if set(target_ops) == set([OpsSet.TFLITE_BUILTINS, OpsSet.SELECT_TF_OPS]):
|
||||||
toco.enable_select_tf_ops = True
|
toco.enable_select_tf_ops = True
|
||||||
|
@ -537,6 +537,8 @@ class TFLiteConverter(TFLiteConverterBase):
|
|||||||
output file. (default None)
|
output file. (default None)
|
||||||
dump_graphviz_video: Boolean indicating whether to dump the graph after
|
dump_graphviz_video: Boolean indicating whether to dump the graph after
|
||||||
every graph transformation. (default False)
|
every graph transformation. (default False)
|
||||||
|
conversion_summary_dir: A string indicating the path to the generated
|
||||||
|
conversion logs.
|
||||||
target_ops: Deprecated. Please specify `target_spec.supported_ops` instead.
|
target_ops: Deprecated. Please specify `target_spec.supported_ops` instead.
|
||||||
Set of OpsSet options indicating which converter to use.
|
Set of OpsSet options indicating which converter to use.
|
||||||
(default set([OpsSet.TFLITE_BUILTINS]))
|
(default set([OpsSet.TFLITE_BUILTINS]))
|
||||||
@ -621,6 +623,7 @@ class TFLiteConverter(TFLiteConverterBase):
|
|||||||
self._post_training_quantize = False
|
self._post_training_quantize = False
|
||||||
self.dump_graphviz_dir = None
|
self.dump_graphviz_dir = None
|
||||||
self.dump_graphviz_video = False
|
self.dump_graphviz_video = False
|
||||||
|
self.conversion_summary_dir = None
|
||||||
self._debug_info_func = experimental_debug_info_func
|
self._debug_info_func = experimental_debug_info_func
|
||||||
|
|
||||||
# Attributes are used by models that cannot be loaded into TensorFlow.
|
# Attributes are used by models that cannot be loaded into TensorFlow.
|
||||||
@ -991,7 +994,8 @@ class TFLiteConverter(TFLiteConverterBase):
|
|||||||
"reorder_across_fake_quant": self.reorder_across_fake_quant,
|
"reorder_across_fake_quant": self.reorder_across_fake_quant,
|
||||||
"change_concat_input_ranges": self.change_concat_input_ranges,
|
"change_concat_input_ranges": self.change_concat_input_ranges,
|
||||||
"dump_graphviz_dir": self.dump_graphviz_dir,
|
"dump_graphviz_dir": self.dump_graphviz_dir,
|
||||||
"dump_graphviz_video": self.dump_graphviz_video
|
"dump_graphviz_video": self.dump_graphviz_video,
|
||||||
|
"conversion_summary_dir": self.conversion_summary_dir
|
||||||
})
|
})
|
||||||
|
|
||||||
# Converts model.
|
# Converts model.
|
||||||
|
@ -502,6 +502,44 @@ class FromSessionTest(TestModels, parameterized.TestCase):
|
|||||||
num_items_graphviz_video = len(os.listdir(graphviz_dir))
|
num_items_graphviz_video = len(os.listdir(graphviz_dir))
|
||||||
self.assertGreater(num_items_graphviz_video, num_items_graphviz)
|
self.assertGreater(num_items_graphviz_video, num_items_graphviz)
|
||||||
|
|
||||||
|
def testDumpConversionSummary(self):
|
||||||
|
with ops.Graph().as_default():
|
||||||
|
in_tensor = array_ops.placeholder(
|
||||||
|
shape=[1, 16, 16, 3], dtype=dtypes.float32)
|
||||||
|
out_tensor = in_tensor + in_tensor
|
||||||
|
sess = session.Session()
|
||||||
|
|
||||||
|
# Convert model and ensure model is not None.
|
||||||
|
converter = lite.TFLiteConverter.from_session(sess, [in_tensor],
|
||||||
|
[out_tensor])
|
||||||
|
log_dir = self.get_temp_dir()
|
||||||
|
converter.conversion_summary_dir = log_dir
|
||||||
|
# Conversion logs will only be generated when the mlir converter is enabled.
|
||||||
|
converter.experimental_new_converter = True
|
||||||
|
tflite_model = converter.convert()
|
||||||
|
self.assertTrue(tflite_model)
|
||||||
|
|
||||||
|
num_items_conversion_summary = len(os.listdir(log_dir))
|
||||||
|
self.assertTrue(num_items_conversion_summary)
|
||||||
|
|
||||||
|
def testDumpConversionSummaryWithOldConverter(self):
|
||||||
|
with ops.Graph().as_default():
|
||||||
|
in_tensor = array_ops.placeholder(
|
||||||
|
shape=[1, 16, 16, 3], dtype=dtypes.float32)
|
||||||
|
out_tensor = in_tensor + in_tensor
|
||||||
|
sess = session.Session()
|
||||||
|
|
||||||
|
# Convert model and ensure model is not None.
|
||||||
|
converter = lite.TFLiteConverter.from_session(sess, [in_tensor],
|
||||||
|
[out_tensor])
|
||||||
|
log_dir = self.get_temp_dir()
|
||||||
|
converter.conversion_summary_dir = log_dir
|
||||||
|
tflite_model = converter.convert()
|
||||||
|
self.assertTrue(tflite_model)
|
||||||
|
# Check nothing is generated under the conversion summary path.
|
||||||
|
num_items_conversion_summary = len(os.listdir(log_dir))
|
||||||
|
self.assertEqual(num_items_conversion_summary, 0)
|
||||||
|
|
||||||
@parameterized.named_parameters(
|
@parameterized.named_parameters(
|
||||||
('EnableMlirConverter', True), # enable mlir
|
('EnableMlirConverter', True), # enable mlir
|
||||||
('DisableMlirConverter', False)) # disable mlir
|
('DisableMlirConverter', False)) # disable mlir
|
||||||
|
@ -22,6 +22,7 @@ from __future__ import print_function
|
|||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
import warnings
|
||||||
|
|
||||||
import six
|
import six
|
||||||
from six.moves import zip
|
from six.moves import zip
|
||||||
@ -29,6 +30,7 @@ from six.moves import zip
|
|||||||
from tensorflow.lite.python import lite
|
from tensorflow.lite.python import lite
|
||||||
from tensorflow.lite.python import lite_constants
|
from tensorflow.lite.python import lite_constants
|
||||||
from tensorflow.lite.toco import toco_flags_pb2 as _toco_flags_pb2
|
from tensorflow.lite.toco import toco_flags_pb2 as _toco_flags_pb2
|
||||||
|
from tensorflow.lite.toco.logging import gen_html
|
||||||
from tensorflow.python import keras
|
from tensorflow.python import keras
|
||||||
from tensorflow.python import tf2
|
from tensorflow.python import tf2
|
||||||
from tensorflow.python.platform import app
|
from tensorflow.python.platform import app
|
||||||
@ -198,6 +200,8 @@ def _convert_tf1_model(flags):
|
|||||||
converter.dump_graphviz_dir = flags.dump_graphviz_dir
|
converter.dump_graphviz_dir = flags.dump_graphviz_dir
|
||||||
if flags.dump_graphviz_video:
|
if flags.dump_graphviz_video:
|
||||||
converter.dump_graphviz_vode = flags.dump_graphviz_video
|
converter.dump_graphviz_vode = flags.dump_graphviz_video
|
||||||
|
if flags.conversion_summary_dir:
|
||||||
|
converter.conversion_summary_dir = flags.conversion_summary_dir
|
||||||
|
|
||||||
if flags.experimental_new_converter:
|
if flags.experimental_new_converter:
|
||||||
converter.experimental_new_converter = True
|
converter.experimental_new_converter = True
|
||||||
@ -479,6 +483,13 @@ def _get_tf1_flags(parser):
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help=("Boolean indicating whether to dump the graph after every graph "
|
help=("Boolean indicating whether to dump the graph after every graph "
|
||||||
"transformation"))
|
"transformation"))
|
||||||
|
parser.add_argument(
|
||||||
|
"--conversion_summary_dir",
|
||||||
|
type=str,
|
||||||
|
help=("Full filepath to store the conversion logs, which inclues graphviz"
|
||||||
|
" of the model before/after the conversion, an HTML report and the "
|
||||||
|
"conversion proto buffers. This will only be generated when passing"
|
||||||
|
" --experimental_new_converter"))
|
||||||
|
|
||||||
|
|
||||||
def _get_tf2_flags(parser):
|
def _get_tf2_flags(parser):
|
||||||
@ -565,7 +576,17 @@ def run_main(_):
|
|||||||
if use_v2_converter:
|
if use_v2_converter:
|
||||||
_convert_tf2_model(tflite_flags)
|
_convert_tf2_model(tflite_flags)
|
||||||
else:
|
else:
|
||||||
_convert_tf1_model(tflite_flags)
|
try:
|
||||||
|
_convert_tf1_model(tflite_flags)
|
||||||
|
finally:
|
||||||
|
if tflite_flags.conversion_summary_dir:
|
||||||
|
if tflite_flags.experimental_new_converter:
|
||||||
|
gen_html.gen_conversion_log_html(tflite_flags.conversion_summary_dir,
|
||||||
|
tflite_flags.post_training_quantize)
|
||||||
|
else:
|
||||||
|
warnings.warn(
|
||||||
|
"Conversion summary will only be generated when enabling"
|
||||||
|
" the new converter via --experimental_new_converter. ")
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
@ -147,6 +147,30 @@ class TfLiteConvertV1Test(TestModels):
|
|||||||
self._run(flags_str, should_succeed=True)
|
self._run(flags_str, should_succeed=True)
|
||||||
os.remove(keras_file)
|
os.remove(keras_file)
|
||||||
|
|
||||||
|
def testConversionSummary(self):
|
||||||
|
keras_file = self._getKerasModelFile()
|
||||||
|
log_dir = self.get_temp_dir()
|
||||||
|
|
||||||
|
flags_str = ('--keras_model_file={} --experimental_new_converter '
|
||||||
|
'--conversion_summary_dir={}'.format(keras_file, log_dir))
|
||||||
|
self._run(flags_str, should_succeed=True)
|
||||||
|
os.remove(keras_file)
|
||||||
|
|
||||||
|
num_items_conversion_summary = len(os.listdir(log_dir))
|
||||||
|
self.assertTrue(num_items_conversion_summary)
|
||||||
|
|
||||||
|
def testConversionSummaryWithOldConverter(self):
|
||||||
|
keras_file = self._getKerasModelFile()
|
||||||
|
log_dir = self.get_temp_dir()
|
||||||
|
|
||||||
|
flags_str = ('--keras_model_file={} '
|
||||||
|
'--conversion_summary_dir={}'.format(keras_file, log_dir))
|
||||||
|
self._run(flags_str, should_succeed=True)
|
||||||
|
os.remove(keras_file)
|
||||||
|
|
||||||
|
num_items_conversion_summary = len(os.listdir(log_dir))
|
||||||
|
self.assertEqual(num_items_conversion_summary, 0)
|
||||||
|
|
||||||
|
|
||||||
class TfLiteConvertV2Test(TestModels):
|
class TfLiteConvertV2Test(TestModels):
|
||||||
|
|
||||||
|
@ -146,6 +146,7 @@ struct ParsedModelFlags {
|
|||||||
Arg<string> graphviz_last_array;
|
Arg<string> graphviz_last_array;
|
||||||
Arg<string> dump_graphviz;
|
Arg<string> dump_graphviz;
|
||||||
Arg<bool> dump_graphviz_video = Arg<bool>(false);
|
Arg<bool> dump_graphviz_video = Arg<bool>(false);
|
||||||
|
Arg<string> conversion_summary_dir;
|
||||||
Arg<bool> allow_nonexistent_arrays = Arg<bool>(false);
|
Arg<bool> allow_nonexistent_arrays = Arg<bool>(false);
|
||||||
Arg<bool> allow_nonascii_arrays = Arg<bool>(false);
|
Arg<bool> allow_nonascii_arrays = Arg<bool>(false);
|
||||||
Arg<string> arrays_extra_info_file;
|
Arg<string> arrays_extra_info_file;
|
||||||
|
97
tensorflow/lite/toco/logging/BUILD
Normal file
97
tensorflow/lite/toco/logging/BUILD
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
load(
|
||||||
|
"//tensorflow/core/platform:default/build_config.bzl",
|
||||||
|
"tf_proto_library_cc",
|
||||||
|
"tf_proto_library_py",
|
||||||
|
)
|
||||||
|
load(
|
||||||
|
"//tensorflow:tensorflow.bzl",
|
||||||
|
"tf_cc_test",
|
||||||
|
)
|
||||||
|
|
||||||
|
package(
|
||||||
|
default_visibility = ["//visibility:public"],
|
||||||
|
licenses = ["notice"], # Apache 2.0
|
||||||
|
)
|
||||||
|
|
||||||
|
tf_proto_library_cc(
|
||||||
|
name = "toco_conversion_log_proto",
|
||||||
|
srcs = ["toco_conversion_log.proto"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
tf_proto_library_py(
|
||||||
|
name = "toco_conversion_log_proto",
|
||||||
|
srcs = ["toco_conversion_log.proto"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
cc_library(
|
||||||
|
name = "conversion_log_util",
|
||||||
|
srcs = ["conversion_log_util.cc"],
|
||||||
|
hdrs = ["conversion_log_util.h"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":toco_conversion_log_proto_cc",
|
||||||
|
"//tensorflow/core:protos_all",
|
||||||
|
"//tensorflow/lite:version",
|
||||||
|
"//tensorflow/lite/toco:model",
|
||||||
|
"//tensorflow/lite/toco:tooling_util",
|
||||||
|
"//tensorflow/lite/toco/tflite:export",
|
||||||
|
"//tensorflow/lite/toco/tflite:operator",
|
||||||
|
"@com_google_absl//absl/strings",
|
||||||
|
"@com_google_absl//absl/time",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
tf_cc_test(
|
||||||
|
name = "conversion_log_util_test",
|
||||||
|
srcs = ["conversion_log_util_test.cc"],
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
deps = [
|
||||||
|
":conversion_log_util",
|
||||||
|
"//tensorflow/core:ops",
|
||||||
|
"//tensorflow/core:protos_all",
|
||||||
|
"//tensorflow/lite/toco:model",
|
||||||
|
"//tensorflow/lite/toco:model_flags_proto",
|
||||||
|
"@com_google_absl//absl/memory",
|
||||||
|
"@com_google_googletest//:gtest",
|
||||||
|
"@com_google_googletest//:gtest_main",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
filegroup(
|
||||||
|
name = "html_template",
|
||||||
|
srcs = [
|
||||||
|
"template.html",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
py_library(
|
||||||
|
name = "gen_html",
|
||||||
|
srcs = ["gen_html.py"],
|
||||||
|
data = [
|
||||||
|
"html_template",
|
||||||
|
],
|
||||||
|
srcs_version = "PY2AND3",
|
||||||
|
visibility = ["//visibility:public"],
|
||||||
|
)
|
||||||
|
|
||||||
|
py_test(
|
||||||
|
name = "gen_html_test",
|
||||||
|
srcs = ["gen_html_test.py"],
|
||||||
|
data = [
|
||||||
|
"//tensorflow/lite/toco/logging:template.html",
|
||||||
|
"//tensorflow/lite/toco/logging/testdata:generated.html",
|
||||||
|
"//tensorflow/lite/toco/logging/testdata:toco_log_after.pb",
|
||||||
|
"//tensorflow/lite/toco/logging/testdata:toco_log_before.pb",
|
||||||
|
"//tensorflow/lite/toco/logging/testdata:toco_tf_graph.dot",
|
||||||
|
"//tensorflow/lite/toco/logging/testdata:toco_tflite_graph.dot",
|
||||||
|
],
|
||||||
|
python_version = "PY2",
|
||||||
|
srcs_version = "PY2AND3",
|
||||||
|
deps = [
|
||||||
|
":gen_html",
|
||||||
|
":toco_conversion_log_proto_py",
|
||||||
|
"//tensorflow/python:client_testlib",
|
||||||
|
],
|
||||||
|
)
|
241
tensorflow/lite/toco/logging/conversion_log_util.cc
Normal file
241
tensorflow/lite/toco/logging/conversion_log_util.cc
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==============================================================================*/
|
||||||
|
#include "tensorflow/lite/toco/logging/conversion_log_util.h"
|
||||||
|
|
||||||
|
#include <sys/utsname.h>
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "absl/strings/str_cat.h"
|
||||||
|
#include "absl/time/clock.h"
|
||||||
|
#include "absl/time/time.h"
|
||||||
|
#include "tensorflow/core/framework/node_def.pb.h"
|
||||||
|
#include "tensorflow/lite/toco/model.h"
|
||||||
|
#include "tensorflow/lite/toco/tflite/export.h"
|
||||||
|
#include "tensorflow/lite/toco/tflite/operator.h"
|
||||||
|
#include "tensorflow/lite/toco/tooling_util.h"
|
||||||
|
#include "tensorflow/lite/version.h"
|
||||||
|
|
||||||
|
namespace toco {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
string TryGetOperatorName(const Operator& op) {
|
||||||
|
string op_name;
|
||||||
|
if (!op.tensorflow_node_def.empty()) {
|
||||||
|
// Parse op name from serialized NodeDef.
|
||||||
|
tensorflow::NodeDef node_def;
|
||||||
|
if (!node_def.ParseFromString(op.tensorflow_node_def)) {
|
||||||
|
LOG(ERROR) << "Failed to parse Tensorflow NodeDef";
|
||||||
|
} else {
|
||||||
|
op_name = node_def.op();
|
||||||
|
if (!op_name.empty()) return op_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (op.type == OperatorType::kUnsupported) {
|
||||||
|
// If we failed to get op name from serialized NodeDef (either because
|
||||||
|
// the tensorflow_node_def is an empty string, or we failed to parse
|
||||||
|
// from it), fall back to use 'tensorflow_op' field if this op is a
|
||||||
|
// TensorflowUnsupportedOperator.
|
||||||
|
const TensorFlowUnsupportedOperator& unsupported_op =
|
||||||
|
static_cast<const TensorFlowUnsupportedOperator&>(op);
|
||||||
|
if (!unsupported_op.tensorflow_op.empty()) {
|
||||||
|
op_name = unsupported_op.tensorflow_op;
|
||||||
|
return op_name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If this is a built-in op.
|
||||||
|
op_name = OperatorTypeName(op.type);
|
||||||
|
return op_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetOSVersion() {
|
||||||
|
utsname info;
|
||||||
|
if (uname(&info)) {
|
||||||
|
// Failed
|
||||||
|
LOG(ERROR) << "Cannot get OS info.";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
string os_info =
|
||||||
|
string(info.sysname) + ";OSVer=" + string(info.release) + ";";
|
||||||
|
return os_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
string ShapeToStringNoSpace(const Shape& shape) {
|
||||||
|
if (shape.dimensions_count() == 0) {
|
||||||
|
return "[]";
|
||||||
|
}
|
||||||
|
|
||||||
|
return absl::StrCat("[", absl::StrJoin(shape.dims(), ","), "]");
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetOperatorSignature(
|
||||||
|
const Model& model, const Operator& op,
|
||||||
|
const std::map<OperatorType, std::unique_ptr<tflite::BaseOperator>>&
|
||||||
|
op_types_map) {
|
||||||
|
// The signature of an op has the following schema:
|
||||||
|
// INPUT:SHAPE::TYPE::OUTPUT:SHAPE::TYPE::NAME:VERSION:
|
||||||
|
string op_signature;
|
||||||
|
constexpr char delimiter[] = "::";
|
||||||
|
|
||||||
|
// Get input shapes and types.
|
||||||
|
op_signature.append("INPUT:");
|
||||||
|
for (const auto& input : op.inputs) {
|
||||||
|
const auto& array = model.GetArray(input);
|
||||||
|
if (array.has_shape()) {
|
||||||
|
op_signature.append(ShapeToStringNoSpace(array.shape()));
|
||||||
|
} else {
|
||||||
|
op_signature.append("None");
|
||||||
|
}
|
||||||
|
op_signature.append(delimiter);
|
||||||
|
op_signature.append(ArrayDataTypeName(array.data_type) + delimiter);
|
||||||
|
}
|
||||||
|
// Get output shapes and types.
|
||||||
|
op_signature.append("OUTPUT:");
|
||||||
|
for (const auto& output : op.outputs) {
|
||||||
|
const auto& array = model.GetArray(output);
|
||||||
|
if (array.has_shape()) {
|
||||||
|
op_signature.append(ShapeToStringNoSpace(array.shape()));
|
||||||
|
} else {
|
||||||
|
op_signature.append("None");
|
||||||
|
}
|
||||||
|
op_signature.append(delimiter);
|
||||||
|
op_signature.append(ArrayDataTypeName(array.data_type) + delimiter);
|
||||||
|
}
|
||||||
|
// Append Op name.
|
||||||
|
op_signature.append("NAME:");
|
||||||
|
op_signature.append(TryGetOperatorName(op) + delimiter);
|
||||||
|
// Append Op version.
|
||||||
|
op_signature.append("VERSION:");
|
||||||
|
OperatorSignature toco_op_signature;
|
||||||
|
toco_op_signature.op = &op;
|
||||||
|
toco_op_signature.model = &model;
|
||||||
|
if (op_types_map.find(op.type) != op_types_map.end()) {
|
||||||
|
const int version = op_types_map.at(op.type)->GetVersion(toco_op_signature);
|
||||||
|
op_signature.append(std::to_string(version));
|
||||||
|
} else {
|
||||||
|
op_signature.append("None");
|
||||||
|
}
|
||||||
|
return op_signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::vector<string> GetOperatorNames(const Model& model) {
|
||||||
|
std::vector<string> op_names;
|
||||||
|
for (const auto& op : model.operators) {
|
||||||
|
op_names.push_back(TryGetOperatorName(*op));
|
||||||
|
}
|
||||||
|
return op_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CountOperatorsByType(const Model& model,
|
||||||
|
std::map<string, int>* built_in_ops,
|
||||||
|
std::map<string, int>* custom_ops,
|
||||||
|
std::map<string, int>* select_ops) {
|
||||||
|
for (const auto& op : model.operators) {
|
||||||
|
OperatorSignature op_signature = {op.get(), &model};
|
||||||
|
const auto ops_by_type =
|
||||||
|
tflite::BuildOperatorByTypeMap(true /*enable_select_tf_ops*/);
|
||||||
|
tflite::details::OperatorKey op_key(op_signature, ops_by_type,
|
||||||
|
true /*enable_select_tf_ops*/);
|
||||||
|
|
||||||
|
const string op_name = TryGetOperatorName(*op);
|
||||||
|
if (op_key.is_custom_op()) {
|
||||||
|
(*custom_ops)[op_name]++;
|
||||||
|
} else if (op_key.is_flex_op()) {
|
||||||
|
(*select_ops)[op_name]++;
|
||||||
|
} else {
|
||||||
|
(*built_in_ops)[op_name]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetInputAndOutputTypes(
|
||||||
|
const Model& model, TFLITE_PROTO_NS::RepeatedPtrField<string>* input_types,
|
||||||
|
TFLITE_PROTO_NS::RepeatedPtrField<string>* output_types) {
|
||||||
|
for (const auto& input_array : model.flags.input_arrays()) {
|
||||||
|
const Array& array = model.GetArray(input_array.name());
|
||||||
|
input_types->Add(ArrayDataTypeName(array.data_type));
|
||||||
|
}
|
||||||
|
for (const auto& output_array : model.flags.output_arrays()) {
|
||||||
|
const Array& array = model.GetArray(output_array);
|
||||||
|
output_types->Add(ArrayDataTypeName(array.data_type));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetTfLiteVersion() { return TFLITE_VERSION_STRING; }
|
||||||
|
|
||||||
|
string GetCachedOSVersion() {
|
||||||
|
static string* version = new string(GetOSVersion());
|
||||||
|
return *version;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GetOpSignatures(const Model& model,
|
||||||
|
TFLITE_PROTO_NS::RepeatedPtrField<string>* op_signatures) {
|
||||||
|
const auto& op_types_map =
|
||||||
|
tflite::BuildOperatorByTypeMap(true /*enable_select_tf_ops*/);
|
||||||
|
for (const auto& op : model.operators) {
|
||||||
|
op_signatures->Add(GetOperatorSignature(model, *op, op_types_map));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string GetModelHash(const Model& model) {
|
||||||
|
// TODO(b/123519920): Implement the hash function for Model.
|
||||||
|
// Need to consider different implementations for public/private models.
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
void PopulateConversionLog(const Model& model, TocoConversionLog* log) {
|
||||||
|
// Get the list of ops after conversion.
|
||||||
|
const std::vector<string> op_names = GetOperatorNames(model);
|
||||||
|
for (const auto& op_name : op_names) {
|
||||||
|
log->add_op_list(op_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get op signatures.
|
||||||
|
TFLITE_PROTO_NS::RepeatedPtrField<string> op_signatures;
|
||||||
|
GetOpSignatures(model, &op_signatures);
|
||||||
|
log->mutable_op_signatures()->CopyFrom(op_signatures);
|
||||||
|
|
||||||
|
// Get op counts by category: custom, built-in or select.
|
||||||
|
std::map<string, int> custom_ops, select_ops, built_in_ops;
|
||||||
|
CountOperatorsByType(model, &built_in_ops, &custom_ops, &select_ops);
|
||||||
|
log->mutable_custom_ops()->insert(custom_ops.cbegin(), custom_ops.cend());
|
||||||
|
log->mutable_built_in_ops()->insert(built_in_ops.cbegin(),
|
||||||
|
built_in_ops.cend());
|
||||||
|
log->mutable_select_ops()->insert(select_ops.cbegin(), select_ops.cend());
|
||||||
|
|
||||||
|
// Get the model's input and output types.
|
||||||
|
TFLITE_PROTO_NS::RepeatedPtrField<string> input_types, output_types;
|
||||||
|
GetInputAndOutputTypes(model, &input_types, &output_types);
|
||||||
|
log->mutable_input_tensor_types()->CopyFrom(input_types);
|
||||||
|
log->mutable_output_tensor_types()->CopyFrom(output_types);
|
||||||
|
|
||||||
|
log->set_log_generation_ts(absl::ToUnixMicros(absl::Now()));
|
||||||
|
|
||||||
|
log->set_model_size(model.operators.size());
|
||||||
|
log->set_tf_lite_version(GetTfLiteVersion());
|
||||||
|
log->set_os_version(GetCachedOSVersion());
|
||||||
|
log->set_model_hash(GetModelHash(model));
|
||||||
|
// TODO(b/123519920): Populate TOCO error logs.
|
||||||
|
// Currently we will focus on external installation of TOCO via pip, where
|
||||||
|
// the C++ TOCO binary is invoked via subprocess command, this will make our
|
||||||
|
// life easier collecting the error logs emitted by TOCO. However, note that
|
||||||
|
// if a user directly invokes the C++ TOCO binary, this log might not be
|
||||||
|
// available.
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace toco
|
58
tensorflow/lite/toco/logging/conversion_log_util.h
Normal file
58
tensorflow/lite/toco/logging/conversion_log_util.h
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==============================================================================*/
|
||||||
|
#ifndef TENSORFLOW_LITE_TOCO_LOGGING_CONVERSION_LOG_UTIL_H_
|
||||||
|
#define TENSORFLOW_LITE_TOCO_LOGGING_CONVERSION_LOG_UTIL_H_
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "tensorflow/lite/toco/logging/toco_conversion_log.pb.h"
|
||||||
|
#include "tensorflow/lite/toco/model.h"
|
||||||
|
|
||||||
|
namespace toco {
|
||||||
|
|
||||||
|
// Populates the TocoConversionLog proto after analyzing the model.
|
||||||
|
void PopulateConversionLog(const Model& model, TocoConversionLog* log);
|
||||||
|
|
||||||
|
// Returns the names of the operators in the model.
|
||||||
|
std::vector<string> GetOperatorNames(const Model& model);
|
||||||
|
|
||||||
|
// Counts the number of different types of operators in the model:
|
||||||
|
// Built-in ops, custom ops and select ops.
|
||||||
|
// Each map is mapping from the name of the operator (such as 'Conv') to its
|
||||||
|
// total number of occurences in the model.
|
||||||
|
void CountOperatorsByType(const Model& model,
|
||||||
|
std::map<string, int>* built_in_ops,
|
||||||
|
std::map<string, int>* custom_ops,
|
||||||
|
std::map<string, int>* select_ops);
|
||||||
|
|
||||||
|
// Gets the input and output types of the model. The input and output is
|
||||||
|
// specified by model.flags.input_arrays and model.flags.output_arrays.
|
||||||
|
void GetInputAndOutputTypes(
|
||||||
|
const Model& model, TFLITE_PROTO_NS::RepeatedPtrField<string>* input_types,
|
||||||
|
TFLITE_PROTO_NS::RepeatedPtrField<string>* output_types);
|
||||||
|
|
||||||
|
// Calculates signatures for all the ops in the model. An op signature is
|
||||||
|
// defined by its input/output shapes and types, op name and its version.
|
||||||
|
void GetOpSignatures(const Model& model,
|
||||||
|
TFLITE_PROTO_NS::RepeatedPtrField<string>* op_signatures);
|
||||||
|
|
||||||
|
// TODO(b/123519920): Implement this.
|
||||||
|
// Calculates a unique hash for the model.
|
||||||
|
string GetModelHash(const Model& model);
|
||||||
|
|
||||||
|
} // namespace toco
|
||||||
|
|
||||||
|
#endif // TENSORFLOW_LITE_TOCO_LOGGING_CONVERSION_LOG_UTIL_H_
|
228
tensorflow/lite/toco/logging/conversion_log_util_test.cc
Normal file
228
tensorflow/lite/toco/logging/conversion_log_util_test.cc
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
/* Copyright 2019 The TensorFlow Authors. All Rights Reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
==============================================================================*/
|
||||||
|
#include "tensorflow/lite/toco/logging/conversion_log_util.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <gmock/gmock.h>
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include "absl/memory/memory.h"
|
||||||
|
#include "tensorflow/core/framework/node_def.pb.h"
|
||||||
|
#include "tensorflow/lite/toco/model.h"
|
||||||
|
#include "tensorflow/lite/toco/model_flags.pb.h"
|
||||||
|
|
||||||
|
namespace toco {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using ::testing::ElementsAre;
|
||||||
|
using ::testing::UnorderedElementsAre;
|
||||||
|
|
||||||
|
TEST(ConversionLogUtilTest, TestGetOperatorNames) {
|
||||||
|
Model model;
|
||||||
|
// Built-in ops.
|
||||||
|
model.operators.push_back(absl::make_unique<ConvOperator>());
|
||||||
|
model.operators.push_back(absl::make_unique<MeanOperator>());
|
||||||
|
model.operators.push_back(absl::make_unique<NegOperator>());
|
||||||
|
// Flex ops.
|
||||||
|
auto avg_pool_3d = absl::make_unique<TensorFlowUnsupportedOperator>();
|
||||||
|
avg_pool_3d->tensorflow_op = "AvgPool3D";
|
||||||
|
tensorflow::NodeDef node_def;
|
||||||
|
node_def.set_op("AvgPool3D");
|
||||||
|
node_def.SerializeToString(&avg_pool_3d->tensorflow_node_def);
|
||||||
|
model.operators.push_back(std::move(avg_pool_3d));
|
||||||
|
// Custom ops.
|
||||||
|
auto my_custom_op = absl::make_unique<TensorFlowUnsupportedOperator>();
|
||||||
|
my_custom_op->tensorflow_op = "MyAwesomeCustomOp";
|
||||||
|
model.operators.push_back(std::move(my_custom_op));
|
||||||
|
|
||||||
|
const auto& output = GetOperatorNames(model);
|
||||||
|
EXPECT_THAT(output, ElementsAre("Conv", "Mean", "Neg", "AvgPool3D",
|
||||||
|
"MyAwesomeCustomOp"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ConversionLogUtilTest, TestCountOperatorsByType) {
|
||||||
|
Model model;
|
||||||
|
// 1st Conv operator.
|
||||||
|
std::unique_ptr<ConvOperator> conv1(new ConvOperator());
|
||||||
|
const string conv1_input_name = "conv_input1";
|
||||||
|
const string conv1_filter_name = "conv_filter1";
|
||||||
|
const string conv1_output_name = "conv_output1";
|
||||||
|
conv1->inputs.push_back(conv1_input_name);
|
||||||
|
conv1->inputs.push_back(conv1_filter_name);
|
||||||
|
conv1->outputs.push_back(conv1_output_name);
|
||||||
|
auto& array_map = model.GetMutableArrayMap();
|
||||||
|
array_map[conv1_input_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[conv1_filter_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[conv1_output_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
|
||||||
|
// 2nd Conv operator.
|
||||||
|
std::unique_ptr<ConvOperator> conv2(new ConvOperator());
|
||||||
|
const string conv2_input_name = "conv_input2";
|
||||||
|
const string conv2_filter_name = "conv_filter2";
|
||||||
|
const string conv2_output_name = "conv_output2";
|
||||||
|
conv2->inputs.push_back(conv2_input_name);
|
||||||
|
conv2->inputs.push_back(conv2_filter_name);
|
||||||
|
conv2->outputs.push_back(conv2_output_name);
|
||||||
|
array_map[conv2_input_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[conv2_filter_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[conv2_output_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
|
||||||
|
// Mean operator.
|
||||||
|
std::unique_ptr<MeanOperator> mean(new MeanOperator());
|
||||||
|
const string mean_input_name = "mean_input";
|
||||||
|
mean->inputs.push_back(mean_input_name);
|
||||||
|
array_map[mean_input_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
|
||||||
|
// 1st flex operator 'AvgPool3D'.
|
||||||
|
auto avg_pool_3d = absl::make_unique<TensorFlowUnsupportedOperator>();
|
||||||
|
avg_pool_3d->tensorflow_op = "AvgPool3D";
|
||||||
|
tensorflow::NodeDef node_def;
|
||||||
|
node_def.set_op("AvgPool3D");
|
||||||
|
node_def.SerializeToString(&avg_pool_3d->tensorflow_node_def);
|
||||||
|
|
||||||
|
// 2nd flex operator 'EluGrad'.
|
||||||
|
auto elu_grad = absl::make_unique<TensorFlowUnsupportedOperator>();
|
||||||
|
elu_grad->tensorflow_op = "EluGrad";
|
||||||
|
node_def.set_op("EluGrad");
|
||||||
|
node_def.SerializeToString(&elu_grad->tensorflow_node_def);
|
||||||
|
|
||||||
|
// 1st custom operator 'MyAwesomeCustomOp'.
|
||||||
|
auto my_custom_op = absl::make_unique<TensorFlowUnsupportedOperator>();
|
||||||
|
my_custom_op->tensorflow_op = "MyAwesomeCustomOp";
|
||||||
|
|
||||||
|
model.operators.push_back(std::move(conv1));
|
||||||
|
model.operators.push_back(std::move(conv2));
|
||||||
|
model.operators.push_back(std::move(mean));
|
||||||
|
model.operators.push_back(std::move(avg_pool_3d));
|
||||||
|
model.operators.push_back(std::move(elu_grad));
|
||||||
|
model.operators.push_back(std::move(my_custom_op));
|
||||||
|
|
||||||
|
std::map<string, int> built_in_ops, select_ops, custom_ops;
|
||||||
|
CountOperatorsByType(model, &built_in_ops, &custom_ops, &select_ops);
|
||||||
|
|
||||||
|
EXPECT_THAT(built_in_ops,
|
||||||
|
UnorderedElementsAre(std::pair<string, int>("Conv", 2),
|
||||||
|
std::pair<string, int>("Mean", 1)));
|
||||||
|
EXPECT_THAT(select_ops,
|
||||||
|
UnorderedElementsAre(std::pair<string, int>("AvgPool3D", 1),
|
||||||
|
std::pair<string, int>("EluGrad", 1)));
|
||||||
|
EXPECT_THAT(custom_ops, UnorderedElementsAre(
|
||||||
|
std::pair<string, int>("MyAwesomeCustomOp", 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ConversionLogUtilTest, TestGetInputAndOutputTypes) {
|
||||||
|
Model model;
|
||||||
|
auto& array_map = model.GetMutableArrayMap();
|
||||||
|
const string input1 = "conv_input";
|
||||||
|
const string input2 = "conv_filter";
|
||||||
|
const string input3 = "feature";
|
||||||
|
const string output = "softmax";
|
||||||
|
array_map[input1] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[input1]->data_type = ArrayDataType::kFloat;
|
||||||
|
array_map[input2] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[input2]->data_type = ArrayDataType::kFloat;
|
||||||
|
array_map[input3] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[input3]->data_type = ArrayDataType::kInt16;
|
||||||
|
array_map[output] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[output]->data_type = ArrayDataType::kFloat;
|
||||||
|
|
||||||
|
InputArray input_arrays[3];
|
||||||
|
input_arrays[0].set_name(input1);
|
||||||
|
input_arrays[1].set_name(input2);
|
||||||
|
input_arrays[2].set_name(input3);
|
||||||
|
*model.flags.add_input_arrays() = input_arrays[0];
|
||||||
|
*model.flags.add_input_arrays() = input_arrays[1];
|
||||||
|
*model.flags.add_input_arrays() = input_arrays[2];
|
||||||
|
model.flags.add_output_arrays(output);
|
||||||
|
|
||||||
|
TFLITE_PROTO_NS::RepeatedPtrField<string> input_types, output_types;
|
||||||
|
GetInputAndOutputTypes(model, &input_types, &output_types);
|
||||||
|
|
||||||
|
EXPECT_THAT(input_types, ElementsAre("float", "float", "int16"));
|
||||||
|
EXPECT_THAT(output_types, ElementsAre("float"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ConversionLogUtilTest, TestGetOpSignatures) {
|
||||||
|
Model model;
|
||||||
|
auto& array_map = model.GetMutableArrayMap();
|
||||||
|
|
||||||
|
std::unique_ptr<ConvOperator> conv(new ConvOperator());
|
||||||
|
const string conv_input_name = "conv_input";
|
||||||
|
const string conv_filter_name = "conv_filter";
|
||||||
|
const string conv_output_name = "conv_output";
|
||||||
|
conv->inputs.push_back(conv_input_name);
|
||||||
|
conv->inputs.push_back(conv_filter_name);
|
||||||
|
conv->outputs.push_back(conv_output_name);
|
||||||
|
array_map[conv_input_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[conv_input_name]->data_type = ArrayDataType::kFloat;
|
||||||
|
array_map[conv_input_name]->copy_shape({4, 4, 3});
|
||||||
|
array_map[conv_filter_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[conv_filter_name]->data_type = ArrayDataType::kFloat;
|
||||||
|
array_map[conv_filter_name]->copy_shape({2, 2});
|
||||||
|
array_map[conv_output_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[conv_output_name]->data_type = ArrayDataType::kFloat;
|
||||||
|
array_map[conv_output_name]->copy_shape({4, 4, 2});
|
||||||
|
|
||||||
|
const string mean_input_name = "mean_input";
|
||||||
|
const string mean_output_name = "mean_output";
|
||||||
|
std::unique_ptr<MeanOperator> mean(new MeanOperator());
|
||||||
|
mean->inputs.push_back(mean_input_name);
|
||||||
|
mean->outputs.push_back(mean_output_name);
|
||||||
|
array_map[mean_input_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[mean_output_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
|
||||||
|
const string avg_pool_3d_output_name = "avg_pool_output";
|
||||||
|
auto avg_pool_3d = absl::make_unique<TensorFlowUnsupportedOperator>();
|
||||||
|
avg_pool_3d->tensorflow_op = "AvgPool3D";
|
||||||
|
tensorflow::NodeDef node_def;
|
||||||
|
node_def.set_op("AvgPool3D");
|
||||||
|
node_def.SerializeToString(&avg_pool_3d->tensorflow_node_def);
|
||||||
|
avg_pool_3d->inputs.push_back(conv_output_name);
|
||||||
|
avg_pool_3d->outputs.push_back(avg_pool_3d_output_name);
|
||||||
|
array_map[avg_pool_3d_output_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[avg_pool_3d_output_name]->data_type = ArrayDataType::kInt32;
|
||||||
|
array_map[avg_pool_3d_output_name]->copy_shape({2, 2});
|
||||||
|
|
||||||
|
const string custom_op_output_name = "custom_op_output";
|
||||||
|
auto my_custom_op = absl::make_unique<TensorFlowUnsupportedOperator>();
|
||||||
|
my_custom_op->tensorflow_op = "MyAwesomeCustomOp";
|
||||||
|
my_custom_op->inputs.push_back(avg_pool_3d_output_name);
|
||||||
|
my_custom_op->outputs.push_back(custom_op_output_name);
|
||||||
|
array_map[custom_op_output_name] = std::unique_ptr<Array>(new Array);
|
||||||
|
array_map[custom_op_output_name]->data_type = ArrayDataType::kFloat;
|
||||||
|
array_map[custom_op_output_name]->copy_shape({3});
|
||||||
|
|
||||||
|
model.operators.push_back(std::move(conv));
|
||||||
|
model.operators.push_back(std::move(mean));
|
||||||
|
model.operators.push_back(std::move(avg_pool_3d));
|
||||||
|
model.operators.push_back(std::move(my_custom_op));
|
||||||
|
|
||||||
|
TFLITE_PROTO_NS::RepeatedPtrField<string> op_signatures;
|
||||||
|
GetOpSignatures(model, &op_signatures);
|
||||||
|
EXPECT_THAT(op_signatures,
|
||||||
|
UnorderedElementsAre(
|
||||||
|
"INPUT:[4,4,3]::float::[2,2]::float::OUTPUT:[4,4,2]::float::"
|
||||||
|
"NAME:Conv::VERSION:1",
|
||||||
|
"INPUT:None::None::OUTPUT:None::None::NAME:Mean::VERSION:1",
|
||||||
|
"INPUT:[4,4,2]::float::OUTPUT:[2,2]::int32::NAME:AvgPool3D::"
|
||||||
|
"VERSION:1",
|
||||||
|
"INPUT:[2,2]::int32::OUTPUT:[3]::float::NAME:"
|
||||||
|
"MyAwesomeCustomOp::VERSION:1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace toco
|
259
tensorflow/lite/toco/logging/gen_html.py
Normal file
259
tensorflow/lite/toco/logging/gen_html.py
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
# Copyright 2019 The TensorFlow Authors. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
# ==============================================================================
|
||||||
|
"""A utility class to generate the report HTML based on a common template."""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import io
|
||||||
|
import os
|
||||||
|
|
||||||
|
from tensorflow.lite.toco.logging import toco_conversion_log_pb2 as _toco_conversion_log_pb2
|
||||||
|
from tensorflow.python.lib.io import file_io as _file_io
|
||||||
|
from tensorflow.python.platform import resource_loader as _resource_loader
|
||||||
|
|
||||||
|
html_escape_table = {
|
||||||
|
"&": "&",
|
||||||
|
'"': """,
|
||||||
|
"'": "'",
|
||||||
|
">": ">",
|
||||||
|
"<": "<",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def html_escape(text):
|
||||||
|
return "".join(html_escape_table.get(c, c) for c in text)
|
||||||
|
|
||||||
|
|
||||||
|
def get_input_type_from_signature(op_signature):
|
||||||
|
"""Parses op_signature and returns a string denoting the input tensor type.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
op_signature: a string specifying the signature of a particular operator.
|
||||||
|
The signature of an operator contains the input tensor's shape and type,
|
||||||
|
output tensor's shape and type, operator's name and its version. It has
|
||||||
|
the following schema:
|
||||||
|
INPUT:input_1_shape::input_1_type::input_2_shape::input_2_type::..
|
||||||
|
::OUTPUT:output_1_shape::output_1_type::output_2_shape::output_2_type::
|
||||||
|
..::NAME:operator_name ::VERSION:operator_version
|
||||||
|
An example of an operator signature is:
|
||||||
|
INPUT:[1,73,73,160]::float::[64,1,1,160]::float::[64]::float::
|
||||||
|
OUTPUT:[1,73,73,64]::float::NAME:Conv::VERSION:1
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
A string denoting the input tensors' type. In the form of shape/type
|
||||||
|
separated
|
||||||
|
by comma. For example:
|
||||||
|
shape:[1,73,73,160],type:float,shape:[64,1,1,160],type:float,shape:[64],
|
||||||
|
type:float
|
||||||
|
"""
|
||||||
|
start = op_signature.find(":")
|
||||||
|
end = op_signature.find("::OUTPUT")
|
||||||
|
inputs = op_signature[start + 1:end]
|
||||||
|
lst = inputs.split("::")
|
||||||
|
out_str = ""
|
||||||
|
for i in range(len(lst)):
|
||||||
|
if i % 2 == 0:
|
||||||
|
out_str += "shape:"
|
||||||
|
else:
|
||||||
|
out_str += "type:"
|
||||||
|
out_str += lst[i]
|
||||||
|
out_str += ","
|
||||||
|
return out_str[:-1]
|
||||||
|
|
||||||
|
|
||||||
|
def get_operator_type(op_name, conversion_log):
|
||||||
|
if op_name in conversion_log.built_in_ops:
|
||||||
|
return "BUILT-IN"
|
||||||
|
elif op_name in conversion_log.custom_ops:
|
||||||
|
return "CUSTOM OP"
|
||||||
|
else:
|
||||||
|
return "SELECT OP"
|
||||||
|
|
||||||
|
|
||||||
|
class HTMLGenerator(object):
|
||||||
|
"""Utility class to generate an HTML report."""
|
||||||
|
|
||||||
|
def __init__(self, html_template_path, export_report_path):
|
||||||
|
"""Reads the HTML template content.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
html_template_path: A string, path to the template HTML file.
|
||||||
|
export_report_path: A string, path to the generated HTML report. This path
|
||||||
|
should point to a '.html' file with date and time in its name.
|
||||||
|
e.g. 2019-01-01-10:05.toco_report.html.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
IOError: File doesn't exist.
|
||||||
|
"""
|
||||||
|
# Load the template HTML.
|
||||||
|
if not _file_io.file_exists(html_template_path):
|
||||||
|
raise IOError("File '{0}' does not exist.".format(html_template_path))
|
||||||
|
with _file_io.FileIO(html_template_path, "r") as f:
|
||||||
|
self.html_template = f.read()
|
||||||
|
|
||||||
|
_file_io.recursive_create_dir(os.path.dirname(export_report_path))
|
||||||
|
self.export_report_path = export_report_path
|
||||||
|
|
||||||
|
def generate(self,
|
||||||
|
toco_conversion_log_before,
|
||||||
|
toco_conversion_log_after,
|
||||||
|
post_training_quant_enabled,
|
||||||
|
dot_before,
|
||||||
|
dot_after,
|
||||||
|
toco_err_log=""):
|
||||||
|
"""Generates the HTML report and writes it to local directory.
|
||||||
|
|
||||||
|
This function uses the fields in `toco_conversion_log_before` and
|
||||||
|
`toco_conversion_log_after` to populate the HTML content. Certain markers
|
||||||
|
(placeholders) in the HTML template are then substituted with the fields
|
||||||
|
from the protos. Once finished it will write the HTML file to the specified
|
||||||
|
local file path.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
toco_conversion_log_before: A `TocoConversionLog` protobuf generated
|
||||||
|
before the model is converted by TOCO.
|
||||||
|
toco_conversion_log_after: A `TocoConversionLog` protobuf generated after
|
||||||
|
the model is converted by TOCO.
|
||||||
|
post_training_quant_enabled: A boolean, whether post-training quantization
|
||||||
|
is enabled.
|
||||||
|
dot_before: A string, the dot representation of the model
|
||||||
|
before the conversion.
|
||||||
|
dot_after: A string, the dot representation of the model after
|
||||||
|
the conversion.
|
||||||
|
toco_err_log: A string, the logs emitted by TOCO during conversion. Caller
|
||||||
|
need to ensure that this string is properly anoynimized (any kind of
|
||||||
|
user data should be eliminated).
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
RuntimeError: When error occurs while generating the template.
|
||||||
|
"""
|
||||||
|
html_dict = {}
|
||||||
|
html_dict["<!--CONVERSION_STATUS-->"] = (
|
||||||
|
r'<span class="label label-danger">Fail</span>'
|
||||||
|
) if toco_err_log else r'<span class="label label-success">Success</span>'
|
||||||
|
html_dict["<!--TOTAL_OPS_BEFORE_CONVERT-->"] = str(
|
||||||
|
toco_conversion_log_before.model_size)
|
||||||
|
html_dict["<!--TOTAL_OPS_AFTER_CONVERT-->"] = str(
|
||||||
|
toco_conversion_log_after.model_size)
|
||||||
|
html_dict["<!--BUILT_IN_OPS_COUNT-->"] = str(
|
||||||
|
sum(toco_conversion_log_after.built_in_ops.values()))
|
||||||
|
html_dict["<!--SELECT_OPS_COUNT-->"] = str(
|
||||||
|
sum(toco_conversion_log_after.select_ops.values()))
|
||||||
|
html_dict["<!--CUSTOM_OPS_COUNT-->"] = str(
|
||||||
|
sum(toco_conversion_log_after.custom_ops.values()))
|
||||||
|
html_dict["<!--POST_TRAINING_QUANT_ENABLED-->"] = (
|
||||||
|
"is" if post_training_quant_enabled else "isn't")
|
||||||
|
|
||||||
|
pre_op_profile = ""
|
||||||
|
post_op_profile = ""
|
||||||
|
|
||||||
|
# Generate pre-conversion op profiles as a list of HTML table rows.
|
||||||
|
for i in range(len(toco_conversion_log_before.op_list)):
|
||||||
|
# Append operator name column.
|
||||||
|
pre_op_profile += "<tr><td>" + toco_conversion_log_before.op_list[
|
||||||
|
i] + "</td>"
|
||||||
|
# Append input type column.
|
||||||
|
if i < len(toco_conversion_log_before.op_signatures):
|
||||||
|
pre_op_profile += "<td>" + get_input_type_from_signature(
|
||||||
|
toco_conversion_log_before.op_signatures[i]) + "</td></tr>"
|
||||||
|
else:
|
||||||
|
pre_op_profile += "<td></td></tr>"
|
||||||
|
|
||||||
|
# Generate post-conversion op profiles as a list of HTML table rows.
|
||||||
|
for op in toco_conversion_log_after.op_list:
|
||||||
|
supported_type = get_operator_type(op, toco_conversion_log_after)
|
||||||
|
post_op_profile += ("<tr><td>" + op + "</td><td>" + supported_type +
|
||||||
|
"</td></tr>")
|
||||||
|
|
||||||
|
html_dict["<!--REPEAT_TABLE1_ROWS-->"] = pre_op_profile
|
||||||
|
html_dict["<!--REPEAT_TABLE2_ROWS-->"] = post_op_profile
|
||||||
|
html_dict["<!--DOT_BEFORE_CONVERT-->"] = dot_before
|
||||||
|
html_dict["<!--DOT_AFTER_CONVERT-->"] = dot_after
|
||||||
|
html_dict["<!--TOCO_ERROR_LOG-->"] = html_escape(toco_err_log)
|
||||||
|
|
||||||
|
# Replace each marker (as keys of html_dict) with the actual text (as values
|
||||||
|
# of html_dict) in the HTML template string.
|
||||||
|
template = self.html_template
|
||||||
|
for marker in html_dict:
|
||||||
|
template = template.replace(marker, html_dict[marker], 1)
|
||||||
|
# Check that the marker text is replaced.
|
||||||
|
if template.find(marker) != -1:
|
||||||
|
raise RuntimeError("Could not populate marker text %r" % marker)
|
||||||
|
|
||||||
|
with _file_io.FileIO(self.export_report_path, "w") as f:
|
||||||
|
f.write(template)
|
||||||
|
|
||||||
|
|
||||||
|
def gen_conversion_log_html(conversion_log_dir, quantization_enabled):
|
||||||
|
"""Generates an HTML report about the conversion process.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
conversion_log_dir: A string specifying the file directory of the conversion
|
||||||
|
logs. It's required that before calling this function, the
|
||||||
|
`conversion_log_dir`
|
||||||
|
already contains the following files: `toco_log_before.pb`,
|
||||||
|
`toco_log_after.pb`, `toco_tf_graph.dot`,
|
||||||
|
`toco_tflite_graph.dot`.
|
||||||
|
quantization_enabled: A boolean, passed from the tflite converter to
|
||||||
|
indicate whether post-training quantization is enabled during conversion.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
IOError: When any of the required files doesn't exist.
|
||||||
|
"""
|
||||||
|
template_filename = _resource_loader.get_path_to_datafile("template.html")
|
||||||
|
if not os.path.exists(template_filename):
|
||||||
|
raise IOError("Failed to generate HTML: file '{0}' doesn't exist.".format(
|
||||||
|
template_filename))
|
||||||
|
|
||||||
|
toco_log_before_path = os.path.join(conversion_log_dir, "toco_log_before.pb")
|
||||||
|
toco_log_after_path = os.path.join(conversion_log_dir, "toco_log_after.pb")
|
||||||
|
dot_before_path = os.path.join(conversion_log_dir, "toco_tf_graph.dot")
|
||||||
|
dot_after_path = os.path.join(conversion_log_dir, "toco_tflite_graph.dot")
|
||||||
|
if not os.path.exists(toco_log_before_path):
|
||||||
|
raise IOError("Failed to generate HTML: file '{0}' doesn't exist.".format(
|
||||||
|
toco_log_before_path))
|
||||||
|
if not os.path.exists(toco_log_after_path):
|
||||||
|
raise IOError("Failed to generate HTML: file '{0}' doesn't exist.".format(
|
||||||
|
toco_log_after_path))
|
||||||
|
if not os.path.exists(dot_before_path):
|
||||||
|
raise IOError("Failed to generate HTML: file '{0}' doesn't exist.".format(
|
||||||
|
dot_before_path))
|
||||||
|
if not os.path.exists(dot_after_path):
|
||||||
|
raise IOError("Failed to generate HTML: file '{0}' doesn't exist.".format(
|
||||||
|
dot_after_path))
|
||||||
|
|
||||||
|
html_generator = HTMLGenerator(
|
||||||
|
template_filename,
|
||||||
|
os.path.join(conversion_log_dir, "toco_conversion_summary.html"))
|
||||||
|
|
||||||
|
# Parse the generated `TocoConversionLog`.
|
||||||
|
toco_conversion_log_before = _toco_conversion_log_pb2.TocoConversionLog()
|
||||||
|
toco_conversion_log_after = _toco_conversion_log_pb2.TocoConversionLog()
|
||||||
|
with open(toco_log_before_path, "rb") as f:
|
||||||
|
toco_conversion_log_before.ParseFromString(f.read())
|
||||||
|
with open(toco_log_after_path, "rb") as f:
|
||||||
|
toco_conversion_log_after.ParseFromString(f.read())
|
||||||
|
|
||||||
|
# Read the dot file before/after the conversion.
|
||||||
|
with io.open(dot_before_path, "r", encoding="utf-8") as f:
|
||||||
|
dot_before = f.read().rstrip()
|
||||||
|
with io.open(dot_after_path, "r", encoding="utf-8") as f:
|
||||||
|
dot_after = f.read().rstrip()
|
||||||
|
|
||||||
|
html_generator.generate(toco_conversion_log_before, toco_conversion_log_after,
|
||||||
|
quantization_enabled, dot_before, dot_after,
|
||||||
|
toco_conversion_log_after.toco_err_logs)
|
116
tensorflow/lite/toco/logging/gen_html_test.py
Normal file
116
tensorflow/lite/toco/logging/gen_html_test.py
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
# Copyright 2018 The TensorFlow Authors. All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
# ==============================================================================
|
||||||
|
"""Tests for gen_html.py."""
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from __future__ import division
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
|
||||||
|
from tensorflow.lite.toco.logging import gen_html
|
||||||
|
from tensorflow.lite.toco.logging import toco_conversion_log_pb2 as _toco_conversion_log_pb2
|
||||||
|
from tensorflow.python.framework import test_util
|
||||||
|
from tensorflow.python.lib.io import file_io as _file_io
|
||||||
|
from tensorflow.python.platform import resource_loader
|
||||||
|
from tensorflow.python.platform import test
|
||||||
|
|
||||||
|
|
||||||
|
class GenHtmlTest(test_util.TensorFlowTestCase):
|
||||||
|
|
||||||
|
def test_generate_html(self):
|
||||||
|
toco_conversion_log_before = _toco_conversion_log_pb2.TocoConversionLog()
|
||||||
|
toco_conversion_log_after = _toco_conversion_log_pb2.TocoConversionLog()
|
||||||
|
|
||||||
|
toco_conversion_log_before.op_list.extend([
|
||||||
|
"Conv1", "Conv2", "Identity", "Reshape", "Dense", "Dense", "CustomOp",
|
||||||
|
"AvgPool3D", "Softmax"
|
||||||
|
])
|
||||||
|
toco_conversion_log_before.model_size = 9
|
||||||
|
|
||||||
|
toco_conversion_log_after.op_list.extend([
|
||||||
|
"Conv1", "Conv2", "Dense", "Dense", "CustomOp", "AvgPool3D", "Softmax"
|
||||||
|
])
|
||||||
|
toco_conversion_log_after.built_in_ops["Conv1"] = 1
|
||||||
|
toco_conversion_log_after.built_in_ops["Conv2"] = 1
|
||||||
|
toco_conversion_log_after.built_in_ops["Dense"] = 2
|
||||||
|
toco_conversion_log_after.built_in_ops["Softmax"] = 1
|
||||||
|
toco_conversion_log_after.custom_ops["CustomOp"] = 1
|
||||||
|
toco_conversion_log_after.select_ops["AvgPool3D"] = 1
|
||||||
|
toco_conversion_log_after.model_size = 7
|
||||||
|
|
||||||
|
export_path = os.path.join(self.get_temp_dir(), "generated.html")
|
||||||
|
html_generator = gen_html.HTMLGenerator(
|
||||||
|
html_template_path=resource_loader.get_path_to_datafile(
|
||||||
|
"template.html"),
|
||||||
|
export_report_path=export_path)
|
||||||
|
|
||||||
|
html_generator.generate(toco_conversion_log_before,
|
||||||
|
toco_conversion_log_after, True,
|
||||||
|
"digraph {a -> b}", "digraph {a -> b}")
|
||||||
|
|
||||||
|
with _file_io.FileIO(export_path, "r") as f_export, _file_io.FileIO(
|
||||||
|
resource_loader.get_path_to_datafile("testdata/generated.html"),
|
||||||
|
"r") as f_expect:
|
||||||
|
expected = f_expect.read()
|
||||||
|
exported = f_export.read()
|
||||||
|
self.assertEqual(exported, expected)
|
||||||
|
|
||||||
|
def test_gen_conversion_log_html(self):
|
||||||
|
# Copies all required data files into a temporary folder for testing.
|
||||||
|
export_path = self.get_temp_dir()
|
||||||
|
toco_log_before_path = resource_loader.get_path_to_datafile(
|
||||||
|
"testdata/toco_log_before.pb")
|
||||||
|
toco_log_after_path = resource_loader.get_path_to_datafile(
|
||||||
|
"testdata/toco_log_after.pb")
|
||||||
|
dot_before = resource_loader.get_path_to_datafile(
|
||||||
|
"testdata/toco_tf_graph.dot")
|
||||||
|
dot_after = resource_loader.get_path_to_datafile(
|
||||||
|
"testdata/toco_tflite_graph.dot")
|
||||||
|
shutil.copy(toco_log_before_path, export_path)
|
||||||
|
shutil.copy(toco_log_after_path, export_path)
|
||||||
|
shutil.copy(dot_before, export_path)
|
||||||
|
shutil.copy(dot_after, export_path)
|
||||||
|
|
||||||
|
# Generate HTML content based on files in the test folder.
|
||||||
|
gen_html.gen_conversion_log_html(export_path, True)
|
||||||
|
|
||||||
|
result_html = os.path.join(export_path, "toco_conversion_summary.html")
|
||||||
|
|
||||||
|
with _file_io.FileIO(result_html, "r") as f_export, _file_io.FileIO(
|
||||||
|
resource_loader.get_path_to_datafile("testdata/generated.html"),
|
||||||
|
"r") as f_expect:
|
||||||
|
expected = f_expect.read()
|
||||||
|
exported = f_export.read()
|
||||||
|
self.assertEqual(exported, expected)
|
||||||
|
|
||||||
|
def test_get_input_type_from_signature(self):
|
||||||
|
op_signatures = [
|
||||||
|
("INPUT:[1,73,73,160]::float::[64,1,1,160]::float::[64]::float::"
|
||||||
|
"OUTPUT:[1,73,73,64]::float::NAME:Conv::VERSION:1")
|
||||||
|
]
|
||||||
|
expect_input_types = [
|
||||||
|
("shape:[1,73,73,160],type:float,shape:[64,1,1,160],type:float,"
|
||||||
|
"shape:[64],type:float")
|
||||||
|
]
|
||||||
|
for i in range(len(op_signatures)):
|
||||||
|
self.assertEqual(
|
||||||
|
gen_html.get_input_type_from_signature(op_signatures[i]),
|
||||||
|
expect_input_types[i])
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test.main()
|
163
tensorflow/lite/toco/logging/template.html
Normal file
163
tensorflow/lite/toco/logging/template.html
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
|
<!-- If IE use the latest rendering engine -->
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
|
||||||
|
<!-- Set the page to the width of the device and set the zoon level -->
|
||||||
|
<meta name="viewport" content="width = device-width, initial-scale = 1">
|
||||||
|
<title>Tensorflow Conversion Tooling & Logging</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.jumbotron{
|
||||||
|
background-color:#2E2D88;
|
||||||
|
color:white;
|
||||||
|
}
|
||||||
|
/* Adds borders for tabs */
|
||||||
|
.tab-content {
|
||||||
|
border-left: 1px solid #ddd;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.nav-tabs {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- CONTAINERS -->
|
||||||
|
<!-- container puts padding around itself while container-fluid fills the whole screen. Bootstap grids require a container. -->
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- page-header adds space aroundtext and enlarges it. It also adds an underline at the end -->
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Tensorflow Conversion Tooling & Logging</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- jumbotron enlarges fonts and puts everything in a gray box with rounded corners. If jumbotron is outside a container it fills the total width. You can change the styles by placing the changes after the Bootstrap CDN link -->
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="input-group mb-3" style="display: none;">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" aria-label="Checkbox for following text input"> I've reviewed the <a href="https://www.tensorflow.org/">data collection policy</a> and agree to submit this data to Google.
|
||||||
|
<button type="button" class="btn btn-primary">Submit Conversion Log</button>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<h2>Conversion Summary
|
||||||
|
<!--CONVERSION_STATUS-->
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">Total ops used before conversion: <!--TOTAL_OPS_BEFORE_CONVERT--></li>
|
||||||
|
<li class="list-group-item">Total ops used post conversion: <!--TOTAL_OPS_AFTER_CONVERT-->
|
||||||
|
<span class="badge badge-pill badge-primary"><!--BUILT_IN_OPS_COUNT--> Built-in Ops</span>
|
||||||
|
<span class="badge badge-pill badge-primary"><!--SELECT_OPS_COUNT--> Select Ops</span>
|
||||||
|
<span class="badge badge-pill badge-primary"><!--CUSTOM_OPS_COUNT--> Custom Ops</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">Post-training quantization <!--POST_TRAINING_QUANT_ENABLED--> applied.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Operator Profile</h2>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<!-- You can constrain the columns if you want -->
|
||||||
|
<div class="row">
|
||||||
|
<!-- The table class adds nice spacing and the other classes add additional style -->
|
||||||
|
<table class="table table-bordered table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4"><b>Pre-conversion Op Profile</b></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<!-- Use text alignment like text-center or text-right -->
|
||||||
|
<th class="text-center">Operations Used</td>
|
||||||
|
<th class="text-center">Input Types</td>
|
||||||
|
</tr>
|
||||||
|
<!--REPEAT_TABLE1_ROWS-->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<!-- The table class adds nice spacing and the other classes add additional style -->
|
||||||
|
<table class="table table-bordered table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4"><b>Post-conversion Op Profile</b></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<!-- Use text alignment like text-center or text-right -->
|
||||||
|
<th class="text-center">Operations Used</td>
|
||||||
|
<th class="text-center">Supported</td>
|
||||||
|
</tr>
|
||||||
|
<!--REPEAT_TABLE2_ROWS-->
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Graph Visualization</h2>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<script src="https://d3js.org/d3.v4.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/viz.js@1.8.1/viz.js" type="javascript/worker"></script>
|
||||||
|
<script src="https://unpkg.com/d3-graphviz@2.6.1/build/d3-graphviz.js"></script>
|
||||||
|
<!-- The table class adds nice spacing and the other classes add additional style -->
|
||||||
|
<table class="table table-bordered table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4"><b>Compare the model before/after conversion</b></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<!-- Use text alignment like text-center or text-right -->
|
||||||
|
<th class="text-center">Before Conversion <button class="btn btn-default" type="button">Open</button></td>
|
||||||
|
<th class="text-center">After Conversion <button class="btn btn-default" type="button">Open</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><div id="graph1" style="text-align: center;"></div></td>
|
||||||
|
<td><div id="graph2" style="text-align: center;"></div></td>
|
||||||
|
<script>d3.select("#graph1").graphviz().fade(false).width(500).height(600).zoomScaleExtent([0.1,100]).fit(true).renderDot(`<!--DOT_BEFORE_CONVERT-->`);</script>
|
||||||
|
<script>d3.select("#graph2").graphviz().fade(false).width(500).height(600).zoomScaleExtent([0.1,100]).fit(true).renderDot(`<!--DOT_AFTER_CONVERT-->`);</script>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Conversion Log</h2>
|
||||||
|
<div>
|
||||||
|
<pre class="pre-scrollable">
|
||||||
|
<code><!--TOCO_ERROR_LOG--></code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
6
tensorflow/lite/toco/logging/testdata/BUILD
vendored
Normal file
6
tensorflow/lite/toco/logging/testdata/BUILD
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package(
|
||||||
|
default_visibility = ["//tensorflow:internal"],
|
||||||
|
licenses = ["notice"], # Apache 2.0
|
||||||
|
)
|
||||||
|
|
||||||
|
exports_files(glob(["*.html"]) + glob(["*.pb"]) + glob(["*.dot"]))
|
163
tensorflow/lite/toco/logging/testdata/generated.html
vendored
Normal file
163
tensorflow/lite/toco/logging/testdata/generated.html
vendored
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
|
||||||
|
<!-- If IE use the latest rendering engine -->
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
|
||||||
|
<!-- Set the page to the width of the device and set the zoon level -->
|
||||||
|
<meta name="viewport" content="width = device-width, initial-scale = 1">
|
||||||
|
<title>Tensorflow Conversion Tooling & Logging</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.jumbotron{
|
||||||
|
background-color:#2E2D88;
|
||||||
|
color:white;
|
||||||
|
}
|
||||||
|
/* Adds borders for tabs */
|
||||||
|
.tab-content {
|
||||||
|
border-left: 1px solid #ddd;
|
||||||
|
border-right: 1px solid #ddd;
|
||||||
|
border-bottom: 1px solid #ddd;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.nav-tabs {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<!-- CONTAINERS -->
|
||||||
|
<!-- container puts padding around itself while container-fluid fills the whole screen. Bootstap grids require a container. -->
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<!-- page-header adds space aroundtext and enlarges it. It also adds an underline at the end -->
|
||||||
|
<div class="page-header">
|
||||||
|
<h1>Tensorflow Conversion Tooling & Logging</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- jumbotron enlarges fonts and puts everything in a gray box with rounded corners. If jumbotron is outside a container it fills the total width. You can change the styles by placing the changes after the Bootstrap CDN link -->
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div class="input-group mb-3" style="display: none;">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" aria-label="Checkbox for following text input"> I've reviewed the <a href="https://www.tensorflow.org/">data collection policy</a> and agree to submit this data to Google.
|
||||||
|
<button type="button" class="btn btn-primary">Submit Conversion Log</button>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<h2>Conversion Summary
|
||||||
|
<span class="label label-success">Success</span>
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<ul class="list-group">
|
||||||
|
<li class="list-group-item">Total ops used before conversion: 9</li>
|
||||||
|
<li class="list-group-item">Total ops used post conversion: 7
|
||||||
|
<span class="badge badge-pill badge-primary">5 Built-in Ops</span>
|
||||||
|
<span class="badge badge-pill badge-primary">1 Select Ops</span>
|
||||||
|
<span class="badge badge-pill badge-primary">1 Custom Ops</span>
|
||||||
|
</li>
|
||||||
|
<li class="list-group-item">Post-training quantization is applied.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Operator Profile</h2>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<!-- You can constrain the columns if you want -->
|
||||||
|
<div class="row">
|
||||||
|
<!-- The table class adds nice spacing and the other classes add additional style -->
|
||||||
|
<table class="table table-bordered table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4"><b>Pre-conversion Op Profile</b></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<!-- Use text alignment like text-center or text-right -->
|
||||||
|
<th class="text-center">Operations Used</td>
|
||||||
|
<th class="text-center">Input Types</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td>Conv1</td><td></td></tr><tr><td>Conv2</td><td></td></tr><tr><td>Identity</td><td></td></tr><tr><td>Reshape</td><td></td></tr><tr><td>Dense</td><td></td></tr><tr><td>Dense</td><td></td></tr><tr><td>CustomOp</td><td></td></tr><tr><td>AvgPool3D</td><td></td></tr><tr><td>Softmax</td><td></td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<!-- The table class adds nice spacing and the other classes add additional style -->
|
||||||
|
<table class="table table-bordered table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4"><b>Post-conversion Op Profile</b></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<!-- Use text alignment like text-center or text-right -->
|
||||||
|
<th class="text-center">Operations Used</td>
|
||||||
|
<th class="text-center">Supported</td>
|
||||||
|
</tr>
|
||||||
|
<tr><td>Conv1</td><td>BUILT-IN</td></tr><tr><td>Conv2</td><td>BUILT-IN</td></tr><tr><td>Dense</td><td>BUILT-IN</td></tr><tr><td>Dense</td><td>BUILT-IN</td></tr><tr><td>CustomOp</td><td>CUSTOM OP</td></tr><tr><td>AvgPool3D</td><td>SELECT OP</td></tr><tr><td>Softmax</td><td>BUILT-IN</td></tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Graph Visualization</h2>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<div>
|
||||||
|
<script src="https://d3js.org/d3.v4.min.js"></script>
|
||||||
|
<script src="https://unpkg.com/viz.js@1.8.1/viz.js" type="javascript/worker"></script>
|
||||||
|
<script src="https://unpkg.com/d3-graphviz@2.6.1/build/d3-graphviz.js"></script>
|
||||||
|
<!-- The table class adds nice spacing and the other classes add additional style -->
|
||||||
|
<table class="table table-bordered table-striped table-hover">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4"><b>Compare the model before/after conversion</b></td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
|
||||||
|
<!-- Use text alignment like text-center or text-right -->
|
||||||
|
<th class="text-center">Before Conversion <button class="btn btn-default" type="button">Open</button></td>
|
||||||
|
<th class="text-center">After Conversion <button class="btn btn-default" type="button">Open</button></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><div id="graph1" style="text-align: center;"></div></td>
|
||||||
|
<td><div id="graph2" style="text-align: center;"></div></td>
|
||||||
|
<script>d3.select("#graph1").graphviz().fade(false).width(500).height(600).zoomScaleExtent([0.1,100]).fit(true).renderDot(`digraph {a -> b}`);</script>
|
||||||
|
<script>d3.select("#graph2").graphviz().fade(false).width(500).height(600).zoomScaleExtent([0.1,100]).fit(true).renderDot(`digraph {a -> b}`);</script>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Conversion Log</h2>
|
||||||
|
<div>
|
||||||
|
<pre class="pre-scrollable">
|
||||||
|
<code></code>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
|
||||||
|
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
14
tensorflow/lite/toco/logging/testdata/toco_log_after.pb
vendored
Normal file
14
tensorflow/lite/toco/logging/testdata/toco_log_after.pb
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
Conv1
|
||||||
|
Conv2
|
||||||
|
Dense
|
||||||
|
Dense
|
||||||
|
CustomOp
|
||||||
|
AvgPool3D
|
||||||
|
Softmax
|
||||||
|
Softmax
|
||||||
|
Conv2
|
||||||
|
Conv1
|
||||||
|
Dense
|
||||||
|
CustomOp"
|
||||||
|
AvgPool3DH
|
10
tensorflow/lite/toco/logging/testdata/toco_log_before.pb
vendored
Normal file
10
tensorflow/lite/toco/logging/testdata/toco_log_before.pb
vendored
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
Conv1
|
||||||
|
Conv2
|
||||||
|
Identity
|
||||||
|
Reshape
|
||||||
|
Dense
|
||||||
|
Dense
|
||||||
|
CustomOp
|
||||||
|
AvgPool3D
|
||||||
|
SoftmaxH
|
1
tensorflow/lite/toco/logging/testdata/toco_tf_graph.dot
vendored
Normal file
1
tensorflow/lite/toco/logging/testdata/toco_tf_graph.dot
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
digraph {a -> b}
|
1
tensorflow/lite/toco/logging/testdata/toco_tflite_graph.dot
vendored
Normal file
1
tensorflow/lite/toco/logging/testdata/toco_tflite_graph.dot
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
digraph {a -> b}
|
50
tensorflow/lite/toco/logging/toco_conversion_log.proto
Normal file
50
tensorflow/lite/toco/logging/toco_conversion_log.proto
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
// Copyright 2019 The TensorFlow Authors. All Rights Reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
syntax = "proto2";
|
||||||
|
|
||||||
|
package toco;
|
||||||
|
|
||||||
|
// TocoConversionLog contains the analytics to be gathered when user converts
|
||||||
|
// a model to TF Lite using TOCO.
|
||||||
|
// Next ID to USE: 14.
|
||||||
|
message TocoConversionLog {
|
||||||
|
// Total ops listed by name.
|
||||||
|
repeated string op_list = 1;
|
||||||
|
// Counts of built-in ops.
|
||||||
|
// Key is op name and value is the count.
|
||||||
|
map<string, int32> built_in_ops = 2;
|
||||||
|
// Counts of custom ops.
|
||||||
|
map<string, int32> custom_ops = 3;
|
||||||
|
// Counts of select ops.
|
||||||
|
map<string, int32> select_ops = 4;
|
||||||
|
// The signature of operators. Including ops input/output types and shapes,
|
||||||
|
// op name and version.
|
||||||
|
repeated string op_signatures = 5;
|
||||||
|
// Input tensor types.
|
||||||
|
repeated string input_tensor_types = 6;
|
||||||
|
// Output tensor types.
|
||||||
|
repeated string output_tensor_types = 7;
|
||||||
|
// Log generation time in micro-seconds.
|
||||||
|
optional int64 log_generation_ts = 8;
|
||||||
|
// Total number of ops in the model.
|
||||||
|
optional int32 model_size = 9;
|
||||||
|
// Tensorflow Lite runtime version.
|
||||||
|
optional string tf_lite_version = 10;
|
||||||
|
// Operating System info.
|
||||||
|
optional string os_version = 11;
|
||||||
|
// Model hash string.
|
||||||
|
optional string model_hash = 12;
|
||||||
|
// Error messages emitted by TOCO during conversion.
|
||||||
|
optional string toco_err_logs = 13;
|
||||||
|
}
|
@ -132,6 +132,9 @@ bool ParseModelFlagsFromCommandLineFlags(
|
|||||||
parsed_flags.dump_graphviz_video.default_value(),
|
parsed_flags.dump_graphviz_video.default_value(),
|
||||||
"If true, will dump graphviz at each "
|
"If true, will dump graphviz at each "
|
||||||
"graph transformation, which may be used to generate a video."),
|
"graph transformation, which may be used to generate a video."),
|
||||||
|
Flag("conversion_summary_dir", parsed_flags.conversion_summary_dir.bind(),
|
||||||
|
parsed_flags.conversion_summary_dir.default_value(),
|
||||||
|
"Local file directory to store the conversion logs."),
|
||||||
Flag("allow_nonexistent_arrays",
|
Flag("allow_nonexistent_arrays",
|
||||||
parsed_flags.allow_nonexistent_arrays.bind(),
|
parsed_flags.allow_nonexistent_arrays.bind(),
|
||||||
parsed_flags.allow_nonexistent_arrays.default_value(),
|
parsed_flags.allow_nonexistent_arrays.default_value(),
|
||||||
|
@ -34,15 +34,19 @@ cc_library(
|
|||||||
"//tensorflow/python:__subpackages__",
|
"//tensorflow/python:__subpackages__",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
|
"@com_google_protobuf//:protobuf_headers",
|
||||||
"//third_party/python_runtime:headers",
|
"//third_party/python_runtime:headers",
|
||||||
"//tensorflow/core:lib",
|
"//tensorflow/core:lib",
|
||||||
"//tensorflow/lite/python/interpreter_wrapper:python_utils",
|
"//tensorflow/lite/python/interpreter_wrapper:python_utils",
|
||||||
|
"//tensorflow/lite/toco/logging:conversion_log_util",
|
||||||
"//tensorflow/lite/toco:model_flags_proto",
|
"//tensorflow/lite/toco:model_flags_proto",
|
||||||
"//tensorflow/lite/toco:toco_convert",
|
"//tensorflow/lite/toco:toco_convert",
|
||||||
|
"//tensorflow/lite/toco/logging:toco_conversion_log_proto_cc",
|
||||||
"//tensorflow/lite/toco:toco_flags_proto",
|
"//tensorflow/lite/toco:toco_flags_proto",
|
||||||
"//tensorflow/lite/toco:toco_graphviz_dump_options",
|
"//tensorflow/lite/toco:toco_graphviz_dump_options",
|
||||||
"//tensorflow/lite/toco:toco_port",
|
"//tensorflow/lite/toco:toco_port",
|
||||||
"//tensorflow/lite/toco:toco_tooling",
|
"//tensorflow/lite/toco:toco_tooling",
|
||||||
|
"//tensorflow/lite/toco:tooling_util",
|
||||||
"//tensorflow/core:protos_all",
|
"//tensorflow/core:protos_all",
|
||||||
"//tensorflow/compiler/mlir/lite/python:graphdef_to_tfl_flatbuffer",
|
"//tensorflow/compiler/mlir/lite/python:graphdef_to_tfl_flatbuffer",
|
||||||
] + select({
|
] + select({
|
||||||
|
@ -14,14 +14,18 @@ limitations under the License.
|
|||||||
==============================================================================*/
|
==============================================================================*/
|
||||||
#include "tensorflow/lite/toco/python/toco_python_api.h"
|
#include "tensorflow/lite/toco/python/toco_python_api.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "google/protobuf/text_format.h"
|
||||||
#include "tensorflow/compiler/mlir/lite/python/graphdef_to_tfl_flatbuffer.h"
|
#include "tensorflow/compiler/mlir/lite/python/graphdef_to_tfl_flatbuffer.h"
|
||||||
#include "tensorflow/core/platform/logging.h"
|
#include "tensorflow/core/platform/logging.h"
|
||||||
#include "tensorflow/lite/python/interpreter_wrapper/python_utils.h"
|
#include "tensorflow/lite/python/interpreter_wrapper/python_utils.h"
|
||||||
#include "tensorflow/lite/toco/import_tensorflow.h"
|
#include "tensorflow/lite/toco/import_tensorflow.h"
|
||||||
|
#include "tensorflow/lite/toco/logging/conversion_log_util.h"
|
||||||
|
#include "tensorflow/lite/toco/logging/toco_conversion_log.pb.h"
|
||||||
#include "tensorflow/lite/toco/model_flags.pb.h"
|
#include "tensorflow/lite/toco/model_flags.pb.h"
|
||||||
#include "tensorflow/lite/toco/toco_convert.h"
|
#include "tensorflow/lite/toco/toco_convert.h"
|
||||||
#include "tensorflow/lite/toco/toco_flags.pb.h"
|
#include "tensorflow/lite/toco/toco_flags.pb.h"
|
||||||
@ -29,9 +33,48 @@ limitations under the License.
|
|||||||
#include "tensorflow/lite/toco/toco_port.h"
|
#include "tensorflow/lite/toco/toco_port.h"
|
||||||
#include "tensorflow/lite/toco/toco_tooling.h"
|
#include "tensorflow/lite/toco/toco_tooling.h"
|
||||||
#include "tensorflow/lite/toco/toco_types.h"
|
#include "tensorflow/lite/toco/toco_types.h"
|
||||||
|
#include "tensorflow/lite/toco/tooling_util.h"
|
||||||
|
|
||||||
namespace toco {
|
namespace toco {
|
||||||
|
|
||||||
|
void PopulateConversionLogHelper(const toco::ModelFlags& model_flags,
|
||||||
|
toco::TocoFlags* toco_flags,
|
||||||
|
const string& input_contents_txt,
|
||||||
|
const string& output_file_contents_txt,
|
||||||
|
const string& error_message,
|
||||||
|
GraphVizDumpOptions* dump_options) {
|
||||||
|
// Make sure the graphviz file will be dumped under the same folder.
|
||||||
|
dump_options->dump_graphviz = toco_flags->conversion_summary_dir();
|
||||||
|
// Here we construct the `toco::Model` class based on the input graph def,
|
||||||
|
// it will then be used to populate the conversion log.
|
||||||
|
// TODO(haoliang): Don't depend on `toco::Model`.
|
||||||
|
std::unique_ptr<toco::Model> imported_model =
|
||||||
|
toco::Import(*toco_flags, model_flags, input_contents_txt);
|
||||||
|
// Dump pre-conversion toco logs.
|
||||||
|
TocoConversionLog toco_log_before;
|
||||||
|
PopulateConversionLog(*imported_model, &toco_log_before);
|
||||||
|
std::ofstream osstream_before(toco_flags->conversion_summary_dir() +
|
||||||
|
"/toco_log_before.pb");
|
||||||
|
toco_log_before.SerializeToOstream(&osstream_before);
|
||||||
|
osstream_before.close();
|
||||||
|
toco::LogDump(toco::kLogLevelModelChanged, "tf_graph", *imported_model);
|
||||||
|
|
||||||
|
// Populate the post-conversion log, for convenient initiate the
|
||||||
|
// `toco::Model` class from the generated flatbuffer.
|
||||||
|
toco_flags->set_input_format(toco::FileFormat::TFLITE);
|
||||||
|
std::unique_ptr<toco::Model> flatbuffer_model =
|
||||||
|
toco::Import(*toco_flags, model_flags, output_file_contents_txt);
|
||||||
|
// Dump post-conversion toco logs.
|
||||||
|
TocoConversionLog toco_log_after;
|
||||||
|
PopulateConversionLog(*flatbuffer_model, &toco_log_after);
|
||||||
|
toco_log_after.set_toco_err_logs(error_message);
|
||||||
|
std::ofstream ostream_after(toco_flags->conversion_summary_dir() +
|
||||||
|
"/toco_log_after.pb");
|
||||||
|
toco_log_after.SerializeToOstream(&ostream_after);
|
||||||
|
ostream_after.close();
|
||||||
|
toco::LogDump(toco::kLogLevelModelChanged, "tflite_graph", *flatbuffer_model);
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE(aselle): We are using raw PyObject's here because we want to make
|
// NOTE(aselle): We are using raw PyObject's here because we want to make
|
||||||
// sure we input and output bytes rather than unicode strings for Python3.
|
// sure we input and output bytes rather than unicode strings for Python3.
|
||||||
PyObject* TocoConvert(PyObject* model_flags_proto_txt_raw,
|
PyObject* TocoConvert(PyObject* model_flags_proto_txt_raw,
|
||||||
@ -124,6 +167,11 @@ PyObject* TocoConvert(PyObject* model_flags_proto_txt_raw,
|
|||||||
status = tensorflow::ConvertGraphDefToTFLiteFlatBuffer(
|
status = tensorflow::ConvertGraphDefToTFLiteFlatBuffer(
|
||||||
model_flags, toco_flags, debug_info, graph_def,
|
model_flags, toco_flags, debug_info, graph_def,
|
||||||
&output_file_contents_txt);
|
&output_file_contents_txt);
|
||||||
|
if (!toco_flags.conversion_summary_dir().empty()) {
|
||||||
|
PopulateConversionLogHelper(model_flags, &toco_flags, input_contents_txt,
|
||||||
|
output_file_contents_txt,
|
||||||
|
status.error_message(), &dump_options);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
status = Convert(input_contents_txt, toco_flags, model_flags,
|
status = Convert(input_contents_txt, toco_flags, model_flags,
|
||||||
&output_file_contents_txt, &arithmetic_ops_count);
|
&output_file_contents_txt, &arithmetic_ops_count);
|
||||||
|
@ -38,7 +38,7 @@ enum FileFormat {
|
|||||||
// of as properties of models, instead describing how models are to be
|
// of as properties of models, instead describing how models are to be
|
||||||
// processed in the context of the present tooling job.
|
// processed in the context of the present tooling job.
|
||||||
//
|
//
|
||||||
// Next ID to use: 31.
|
// Next ID to use: 32.
|
||||||
message TocoFlags {
|
message TocoFlags {
|
||||||
// Input file format
|
// Input file format
|
||||||
optional FileFormat input_format = 1;
|
optional FileFormat input_format = 1;
|
||||||
@ -218,4 +218,8 @@ message TocoFlags {
|
|||||||
// runtime memory offsets for activation Tensors (with 128 bits alignment)
|
// runtime memory offsets for activation Tensors (with 128 bits alignment)
|
||||||
// and error out on models with undetermined Tensor shape. (Default: True)
|
// and error out on models with undetermined Tensor shape. (Default: True)
|
||||||
optional bool allow_dynamic_tensors = 30 [default = true];
|
optional bool allow_dynamic_tensors = 30 [default = true];
|
||||||
|
|
||||||
|
// Full filepath of the folder to dump conversion logs. This includes a global
|
||||||
|
// view of the conversion process, and user can choose to submit those logs.
|
||||||
|
optional string conversion_summary_dir = 31;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user