chore: import upstream snapshot with attribution
cffconvert / validate (push) Has been skipped
License Check / license-check (push) Failing after 2s

This commit is contained in:
wehub-resource-sync
2026-07-13 12:14:16 +08:00
commit 8a852e4b4e
36502 changed files with 9277225 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
load("@rules_cc//cc:cc_library.bzl", "cc_library")
load("@rules_python//python:proto.bzl", "py_proto_library")
load("@xla//third_party/rules_python/python:py_library.bzl", "py_library")
load(
"//tensorflow:tensorflow.bzl",
"py_test",
"tf_cc_test",
)
load("//tensorflow:tensorflow.default.bzl", "get_compatible_with_portable")
load(
"//tensorflow/core/platform:build_config.bzl",
"tf_proto_library",
)
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//visibility:public"],
licenses = ["notice"],
)
tf_proto_library(
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"],
compatible_with = get_compatible_with_portable(),
visibility = ["//visibility:public"],
deps = [
":toco_conversion_log_proto_cc",
"//tensorflow/core:protos_all_cc",
"//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_cc",
"//tensorflow/lite/toco:model",
"//tensorflow/lite/toco:model_flags_proto_cc",
"@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",
],
strict_deps = True,
visibility = ["//visibility:public"],
deps = [
":toco_conversion_log_proto_py",
"//tensorflow/python/lib/io:file_io",
"//tensorflow/python/platform:resource_loader",
],
)
py_test(
name = "gen_html_test",
srcs = ["gen_html_test.py"],
data = [
":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",
],
strict_deps = True,
deps = [
":gen_html",
":toco_conversion_log_proto_py",
#internal proto upb dep
"//tensorflow/python/framework:test_lib",
"//tensorflow/python/lib/io:file_io",
"//tensorflow/python/platform:client_testlib",
"//tensorflow/python/platform:resource_loader",
],
)
# copybara:uncomment_begin(google-only)
# py_proto_library(
# name = "toco_conversion_log_proto_py",
# visibility = ["//visibility:public"],
# deps = [":toco_conversion_log_proto"],
# )
# copybara:uncomment_end
@@ -0,0 +1,272 @@
/* 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 <string>
#ifdef __linux__
#include <sys/utsname.h>
#endif
#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 {
std::string TryGetOperatorName(const Operator& op) {
std::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;
}
std::string GetOSVersion() {
std::string os_info;
#ifdef __linux__
utsname info;
if (uname(&info)) {
// Failed
LOG(ERROR) << "Cannot get OS info.";
return "";
}
os_info =
std::string(info.sysname) + ";OSVer=" + std::string(info.release) + ";";
#endif
return os_info;
}
std::string ShapeToStringNoSpace(const Shape& shape) {
if (shape.dimensions_count() == 0) {
return "[]";
}
return absl::StrCat("[", absl::StrJoin(shape.dims(), ","), "]");
}
std::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:
std::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<std::string> GetOperatorNames(const Model& model) {
std::vector<std::string> op_names;
op_names.reserve(model.operators.size());
for (const auto& op : model.operators) {
op_names.push_back(TryGetOperatorName(*op));
}
return op_names;
}
void CountOperatorsByType(const Model& model,
std::map<std::string, int>* built_in_ops,
std::map<std::string, int>* custom_ops,
std::map<std::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 std::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<std::string>* input_types,
TFLITE_PROTO_NS::RepeatedPtrField<std::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));
}
}
std::string GetTfLiteVersion() { return TFLITE_VERSION_STRING; }
std::string GetCachedOSVersion() {
static std::string* version = new std::string(GetOSVersion());
return *version;
}
void GetOpSignatures(
const Model& model,
TFLITE_PROTO_NS::RepeatedPtrField<std::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));
}
}
std::string GetModelHash(const Model& model) {
// TODO(b/123519920): Implement the hash function for Model.
// Need to consider different implementations for public/private models.
return "";
}
// This function scans through the error message string, extracts the part about
// missing ops and prunes away all other information in the error info.
std::string SanitizeErrorMessage(absl::string_view error_message) {
const std::string s1 = "Ops that can be supported by the flex runtime";
const std::string s2 = "Ops that need custom implementation";
std::string pruned_message;
size_t pos = error_message.find(s1);
if (pos != std::string::npos) {
// Find the terminate point for flex op list.
auto end = error_message.find('.', pos);
pruned_message.append(error_message.substr(pos, end - pos + 1));
}
pos = error_message.find(s2);
if (pos != std::string::npos) {
// Find the terminate point for custom op list.
auto end = error_message.find('.', pos);
pruned_message.append(error_message.substr(pos, end - pos + 1));
}
return pruned_message;
}
void PopulateConversionLog(const Model& model, TocoConversionLog* log) {
// Get the list of ops after conversion.
const std::vector<std::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<std::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<std::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<std::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
@@ -0,0 +1,66 @@
/* 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 <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "tensorflow/lite/toco/logging/toco_conversion_log.pb.h"
#include "tensorflow/lite/toco/model.h"
namespace toco {
// This function scans through the error message string, extracts the part about
// missing ops and prunes away all other information in the error info.
std::string SanitizeErrorMessage(absl::string_view error_message);
// 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<std::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 occurrences in the model.
void CountOperatorsByType(const Model& model,
std::map<std::string, int>* built_in_ops,
std::map<std::string, int>* custom_ops,
std::map<std::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<std::string>* input_types,
TFLITE_PROTO_NS::RepeatedPtrField<std::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<std::string>* op_signatures);
// TODO(b/123519920): Implement this.
// Calculates a unique hash for the model.
std::string GetModelHash(const Model& model);
} // namespace toco
#endif // TENSORFLOW_LITE_TOCO_LOGGING_CONVERSION_LOG_UTIL_H_
@@ -0,0 +1,254 @@
/* 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 <string>
#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(std::make_unique<ConvOperator>());
model.operators.push_back(std::make_unique<MeanOperator>());
model.operators.push_back(std::make_unique<NegOperator>());
// Flex ops.
auto avg_pool_3d = std::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 = std::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 std::string conv1_input_name = "conv_input1";
const std::string conv1_filter_name = "conv_filter1";
const std::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::make_unique<Array>();
array_map[conv1_filter_name] = std::make_unique<Array>();
array_map[conv1_output_name] = std::make_unique<Array>();
// 2nd Conv operator.
std::unique_ptr<ConvOperator> conv2(new ConvOperator());
const std::string conv2_input_name = "conv_input2";
const std::string conv2_filter_name = "conv_filter2";
const std::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::make_unique<Array>();
array_map[conv2_filter_name] = std::make_unique<Array>();
array_map[conv2_output_name] = std::make_unique<Array>();
// Mean operator.
std::unique_ptr<MeanOperator> mean(new MeanOperator());
const std::string mean_input_name = "mean_input";
mean->inputs.push_back(mean_input_name);
array_map[mean_input_name] = std::make_unique<Array>();
// 1st flex operator 'AvgPool3D'.
auto avg_pool_3d = std::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 = std::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 = std::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<std::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<std::string, int>("Conv", 2),
std::pair<std::string, int>("Mean", 1)));
EXPECT_THAT(select_ops,
UnorderedElementsAre(std::pair<std::string, int>("AvgPool3D", 1),
std::pair<std::string, int>("EluGrad", 1)));
EXPECT_THAT(custom_ops, UnorderedElementsAre(std::pair<std::string, int>(
"MyAwesomeCustomOp", 1)));
}
TEST(ConversionLogUtilTest, TestGetInputAndOutputTypes) {
Model model;
auto& array_map = model.GetMutableArrayMap();
const std::string input1 = "conv_input";
const std::string input2 = "conv_filter";
const std::string input3 = "feature";
const std::string output = "softmax";
array_map[input1] = std::make_unique<Array>();
array_map[input1]->data_type = ArrayDataType::kFloat;
array_map[input2] = std::make_unique<Array>();
array_map[input2]->data_type = ArrayDataType::kFloat;
array_map[input3] = std::make_unique<Array>();
array_map[input3]->data_type = ArrayDataType::kInt16;
array_map[output] = std::make_unique<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<std::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 std::string conv_input_name = "conv_input";
const std::string conv_filter_name = "conv_filter";
const std::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::make_unique<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::make_unique<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::make_unique<Array>();
array_map[conv_output_name]->data_type = ArrayDataType::kFloat;
array_map[conv_output_name]->copy_shape({4, 4, 2});
const std::string mean_input_name = "mean_input";
const std::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::make_unique<Array>();
array_map[mean_output_name] = std::make_unique<Array>();
const std::string avg_pool_3d_output_name = "avg_pool_output";
auto avg_pool_3d = std::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::make_unique<Array>();
array_map[avg_pool_3d_output_name]->data_type = ArrayDataType::kInt32;
array_map[avg_pool_3d_output_name]->copy_shape({2, 2});
const std::string custom_op_output_name = "custom_op_output";
auto my_custom_op = std::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::make_unique<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<std::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"));
}
TEST(ConversionLogUtilTest, TestSanitizeErrorMessage) {
const std::string error =
"error: failed while converting: 'main': Ops that can be supported by "
"the flex runtime (enabled via setting the -emit-select-tf-ops flag): "
"ResizeNearestNeighbor,ResizeNearestNeighbor. Ops that need custom "
"implementation (enabled via setting the -emit-custom-ops flag): "
"CombinedNonMaxSuppression.\nTraceback (most recent call last): File "
"/usr/local/bin/toco_from_protos, line 8, in <module>";
const std::string pruned_error =
"Ops that can be supported by "
"the flex runtime (enabled via setting the -emit-select-tf-ops flag): "
"ResizeNearestNeighbor,ResizeNearestNeighbor.Ops that need custom "
"implementation (enabled via setting the -emit-custom-ops flag): "
"CombinedNonMaxSuppression.";
EXPECT_EQ(SanitizeErrorMessage(error), pruned_error);
}
TEST(ConversionLogUtilTest, TestSanitizeErrorMessageNoMatching) {
const std::string error =
"error: failed while converting: 'main': Traceback (most recent call "
"last): File "
"/usr/local/bin/toco_from_protos, line 8, in <module>";
EXPECT_EQ(SanitizeErrorMessage(error), "");
}
} // namespace
} // namespace toco
+265
View File
@@ -0,0 +1,265 @@
# 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."""
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 = {
"&": "&amp;",
'"': "&quot;",
"'": "&apos;",
">": "&gt;",
"<": "&lt;",
}
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:
"""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="",
tflite_graph_path=""):
"""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 anonymized (any kind of
user data should be eliminated).
tflite_graph_path: A string, the filepath to the converted TFLite model.
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
if toco_err_log:
html_dict["<!--TOCO_INFO_LOG-->"] = html_escape(toco_err_log)
else:
success_info = ("TFLite graph conversion successful. You can preview the "
"converted model at: ") + tflite_graph_path
html_dict["<!--TOCO_INFO_LOG-->"] = html_escape(success_info)
# 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,
tflite_graph_path):
"""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.
tflite_graph_path: A string, the filepath to the converted TFLite model.
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,
tflite_graph_path)
@@ -0,0 +1,113 @@
# 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."""
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}", "",
"/path/to/flatbuffer")
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, "/path/to/flatbuffer")
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()
+181
View File
@@ -0,0 +1,181 @@
<!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&apos;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>
<a href="#conversionlog">Jump to conversion log</a>
<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="panel-group">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#collapse1"><b>Pre-conversion Op Profile</b></a>
</h4>
</div>
<div id="collapse1" class="panel-collapse collapse in">
<div class="panel-body">
<div class="flex-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></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>
</div>
</div>
</div>
<div class="panel-group">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#collapse2"><b>Post-conversion Op Profile</b></a>
</h4>
</div>
<div id="collapse2" class="panel-collapse collapse in">
<div class="panel-body">
<div class="flex-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></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>
</div>
</div>
</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>
<a name="conversionlog"></a>
<h2>Conversion Log</h2>
<div>
<pre class="pre-scrollable">
<code><!--TOCO_INFO_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>
+7
View File
@@ -0,0 +1,7 @@
package(
# copybara:uncomment default_applicable_licenses = ["//tensorflow:LICENSE"],
default_visibility = ["//tensorflow:internal"],
licenses = ["notice"],
)
exports_files(glob(["*.html"]) + glob(["*.pb"]) + glob(["*.dot"]))
+181
View File
@@ -0,0 +1,181 @@
<!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&apos;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>
<a href="#conversionlog">Jump to conversion log</a>
<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="panel-group">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#collapse1"><b>Pre-conversion Op Profile</b></a>
</h4>
</div>
<div id="collapse1" class="panel-collapse collapse in">
<div class="panel-body">
<div class="flex-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></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>
</div>
</div>
</div>
<div class="panel-group">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a data-toggle="collapse" href="#collapse2"><b>Post-conversion Op Profile</b></a>
</h4>
</div>
<div id="collapse2" class="panel-collapse collapse in">
<div class="panel-body">
<div class="flex-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></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>
</div>
</div>
</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>
<a name="conversionlog"></a>
<h2>Conversion Log</h2>
<div>
<pre class="pre-scrollable">
<code>TFLite graph conversion successful. You can preview the converted model at: /path/to/flatbuffer</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
View File
@@ -0,0 +1,14 @@
Conv1
Conv2
Dense
Dense
CustomOp
AvgPool3D
Softmax
Softmax
Conv2
Conv1
Dense
CustomOp"
AvgPool3DH
@@ -0,0 +1,10 @@
Conv1
Conv2
Identity
Reshape
Dense
Dense
CustomOp
AvgPool3D
SoftmaxH
@@ -0,0 +1 @@
digraph {a -> b}
@@ -0,0 +1 @@
digraph {a -> b}
@@ -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;
}